├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── my │ └── generalledger │ ├── Application.java │ ├── controller │ ├── AccountController.java │ ├── LedgerController.java │ ├── TransactionController.java │ ├── TrialBalanceController.java │ └── Utils.java │ ├── dao │ └── ledger │ │ ├── LedgerAccountDAO.java │ │ ├── LedgerAccountDAOImpl.java │ │ ├── TransactionDAO.java │ │ └── TransactionDAOImpl.java │ ├── domain │ └── ledger │ │ ├── LedgerAccount.java │ │ └── Transaction.java │ └── service │ └── ledger │ ├── LedgerAccountService.java │ ├── LedgerAccountServiceImpl.java │ ├── TransactionService.java │ ├── TransactionServiceImpl.java │ └── TrialBalance.java ├── resources ├── application.properties ├── static │ └── css │ │ └── styles.css └── templates │ ├── index.html │ └── ledger │ ├── account.html │ ├── addaccount.html │ ├── deleteaccount.html │ ├── ledger.html │ └── updatetransaction.html └── webapp └── WEB-INF └── web.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings/ 3 | .classpath 4 | .project 5 | webapp/ 6 | logs/ 7 | *.csv* 8 | .idea 9 | *.iml 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # General Ledger 2 | 3 | A general ledger is a company's set of numbered accounts for its accounting records. The ledger provides 4 | a complete record of financial transactions over the life of the company. The ledger holds account 5 | information that is needed to prepare financial statements and includes accounts for assets, liabilities, 6 | owners' equity, revenues and expenses. (Source: http://www.investopedia.com/terms/g/generalledger.asp) 7 | 8 | This project is a general ledger web application. Users can add accounts and transactions to the 9 | general ledger. Transactions are added in accordance to the double-entry bookkeeping system, 10 | where every transaction has to be recorded twice, once as a debit entry and once as a credit entry. 11 | 12 | Accounts can be deleted if they have no transactions. Transactions can be deleted or updated. When a transaction is deleted or updated then both corresponding entries are modified as well. 13 | 14 | A user can view the trial balance, which is a statement of all credits and debits in the general ledger. 15 | The trial balance and general ledger can be viewed in the application and they can be exported to a .csv file. 16 | 17 | ## Technologies Used 18 | - Java, HTML, CSS 19 | - Spring(Spring Boot, Spring MVC) 20 | - JPA/Hibernate 21 | - MySQL 22 | - SLF4J 23 | - Thymeleaf 24 | - Bootstrap 25 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | my 7 | generalledger 8 | 0.2-a2-SNAPSHOT 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 1.4.0.RELEASE 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-thymeleaf 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-test 24 | test 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-data-jpa 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-devtools 37 | true 38 | 39 | 40 | org.hibernate 41 | hibernate-validator 42 | 43 | 44 | mysql 45 | mysql-connector-java 46 | 5.1.6 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-maven-plugin 55 | 56 | 57 | 58 | 59 | 60 | 1.8 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/Application.java: -------------------------------------------------------------------------------- 1 | package my.generalledger; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.web.support.SpringBootServletInitializer; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean; 8 | 9 | @SpringBootApplication 10 | public class Application extends SpringBootServletInitializer { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | @Bean 17 | public HibernateJpaSessionFactoryBean sessionFactory() { 18 | return new HibernateJpaSessionFactoryBean(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/controller/AccountController.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | 14 | import my.generalledger.domain.ledger.LedgerAccount; 15 | import my.generalledger.domain.ledger.LedgerAccount.Type; 16 | import my.generalledger.service.ledger.LedgerAccountService; 17 | 18 | @Controller 19 | public class AccountController { 20 | 21 | private final static Logger logger = LoggerFactory.getLogger(AccountController.class); 22 | 23 | @Autowired 24 | private LedgerAccountService ledgerAccountService; 25 | 26 | @RequestMapping(value = "/ledger/accounts", method = RequestMethod.GET) 27 | public String getAccount(@RequestParam(value = "id") int id, Model model) { 28 | List accounts = ledgerAccountService.getAccounts(); 29 | model.addAttribute("ledgerAccounts", accounts); 30 | 31 | LedgerAccount account = ledgerAccountService.getAccountById(id); 32 | model.addAttribute("ledgerAccount", account); 33 | model.addAttribute("debitEntries", account.getDebitEntries()); 34 | model.addAttribute("creditEntries", account.getCreditEntries()); 35 | logger.info("retrieving view for displaying account info and entries"); 36 | return "ledger/account"; 37 | } 38 | 39 | @RequestMapping(value = "/ledger/accounts/add", method = RequestMethod.GET) 40 | public String getAddAccount() { 41 | logger.info("retrieving view for adding new ledger accounts"); 42 | return "ledger/addaccount"; 43 | } 44 | 45 | @RequestMapping(value = "/ledger/accounts/delete", method = RequestMethod.GET) 46 | public String getDeleteAccount(Model model) { 47 | List accounts = ledgerAccountService.getAccounts(); 48 | model.addAttribute("ledgerAccounts", accounts); 49 | logger.info("retrieving view for deleting ledger accounts"); 50 | return "ledger/deleteaccount"; 51 | } 52 | 53 | @RequestMapping(value = "/ledger/accounts/add", method = RequestMethod.POST) 54 | public String addAccount(@RequestParam(value = "name") String name, 55 | @RequestParam(value = "type") Type type, 56 | @RequestParam(value = "number") String number) { 57 | logger.debug("creating a new ledger account"); 58 | ledgerAccountService.saveAccount(name, type, number); 59 | return "redirect:/ledger"; 60 | } 61 | 62 | @RequestMapping(value = "/ledger/accounts/delete", method = RequestMethod.DELETE) 63 | public String deleteAccount(@RequestParam(value="id") int id) { 64 | LedgerAccount la = ledgerAccountService.getAccountById(id); 65 | logger.debug("calling service to delete ledger account: " + id); 66 | ledgerAccountService.deleteAccount(la); 67 | return "redirect:/ledger"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/controller/LedgerController.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | 13 | import my.generalledger.domain.ledger.LedgerAccount; 14 | import my.generalledger.service.ledger.LedgerAccountService; 15 | 16 | @Controller 17 | public class LedgerController { 18 | 19 | private final static Logger logger = LoggerFactory.getLogger(LedgerController.class); 20 | 21 | @Autowired 22 | private LedgerAccountService ledgerAccountService; 23 | 24 | @RequestMapping(value = "/ledger", method = RequestMethod.GET) 25 | public String getLedger(Model model) { 26 | List accounts = ledgerAccountService.getAccounts(); 27 | model.addAttribute("ledgerAccounts", accounts); 28 | logger.info("retrieving view for displaying ledger accounts"); 29 | return "ledger/ledger"; 30 | } 31 | 32 | @RequestMapping(value = "/", method = RequestMethod.GET) 33 | public String getLanding() { 34 | return "index"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/controller/TransactionController.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.controller; 2 | 3 | import java.util.Calendar; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 15 | 16 | import my.generalledger.domain.ledger.LedgerAccount; 17 | import my.generalledger.domain.ledger.Transaction; 18 | import my.generalledger.service.ledger.LedgerAccountService; 19 | import my.generalledger.service.ledger.TransactionService; 20 | 21 | @Controller 22 | public class TransactionController { 23 | 24 | private final static Logger logger = LoggerFactory.getLogger(TransactionController.class); 25 | 26 | @Autowired 27 | private LedgerAccountService ledgerAccountService; 28 | @Autowired 29 | private TransactionService transactionService; 30 | 31 | @RequestMapping(value = "/ledger/accounts/{accountId}/transactions/add", method = RequestMethod.POST) 32 | public String addTransaction(RedirectAttributes redir, 33 | @RequestParam(value = "accountId") String accountId, 34 | @RequestParam(value = "date") String date, 35 | @RequestParam(value = "description") String description, 36 | @RequestParam(value = "amount") String amountStr, 37 | @RequestParam(value = "creditId") int creditId, 38 | @RequestParam(value = "debitId") int debitId) { 39 | Calendar cal = Utils.stringToCalendar(date); 40 | LedgerAccount creditAccount = ledgerAccountService.getAccountById(creditId); 41 | LedgerAccount debitAccount = ledgerAccountService.getAccountById(debitId); 42 | int amount = Utils.CurrencyStringToInt(amountStr); 43 | logger.debug("creating a new transaction"); 44 | transactionService.saveTransaction(cal, description, amount, creditAccount, debitAccount); 45 | int id = Integer.valueOf(accountId); 46 | redir.addAttribute("id", id); 47 | logger.debug("redirecting to account page"); 48 | return "redirect:/ledger/accounts"; 49 | } 50 | 51 | @RequestMapping(value = "/ledger/accounts/{redirAccountId}/transactions/update", 52 | method = RequestMethod.GET) 53 | public String updateTransaction(Model model, 54 | @RequestParam(value = "redirAccountId") int redirAccountId, 55 | @RequestParam(value = "transactionId") int transactionId, 56 | @RequestParam(value = "creditId") int creditId, 57 | @RequestParam(value = "debitId") int debitId) { 58 | List accounts = ledgerAccountService.getAccounts(); 59 | model.addAttribute("ledgerAccounts", accounts); 60 | LedgerAccount redirAccount = ledgerAccountService.getAccountById(redirAccountId); 61 | model.addAttribute("redirAccount", redirAccount); 62 | Transaction transaction = transactionService.getTransactionById(transactionId); 63 | model.addAttribute("transaction", transaction); 64 | LedgerAccount creditAccount = ledgerAccountService.getAccountById(creditId); 65 | model.addAttribute("creditAccount", creditAccount); 66 | LedgerAccount debitAccount = ledgerAccountService.getAccountById(debitId); 67 | model.addAttribute("debitAccount", debitAccount); 68 | logger.info("retrieving view for updating transactions"); 69 | return "ledger/updatetransaction"; 70 | } 71 | 72 | @RequestMapping(value = "/ledger/accounts/{accountId}/transactions/update", method = RequestMethod.PUT) 73 | public String updateTransaction(RedirectAttributes redir, 74 | @RequestParam(value = "accountId") int accountId, 75 | @RequestParam(value = "transactionId") int transactionId, 76 | @RequestParam(value = "date") String date, 77 | @RequestParam(value = "description") String description, 78 | @RequestParam(value = "amount") String amountStr, 79 | @RequestParam(value = "creditId") int creditId, 80 | @RequestParam(value = "debitId") int debitId) { 81 | 82 | Calendar cal = Utils.stringToCalendar(date); 83 | int amount = Utils.CurrencyStringToInt(amountStr); 84 | LedgerAccount creditAccount = ledgerAccountService.getAccountById(creditId); 85 | LedgerAccount debitAccount = ledgerAccountService.getAccountById(debitId); 86 | logger.debug("calling service to update transaction: " + transactionId); 87 | transactionService.updateTransaction(transactionId, cal, description, 88 | amount, creditAccount, debitAccount); 89 | 90 | redir.addAttribute("id", accountId); 91 | //redirect to the account page from where transaction update was initiated 92 | return "redirect:/ledger/accounts"; 93 | } 94 | 95 | @RequestMapping(value = "/ledger/accounts/{accountId}/transactions/delete", method = RequestMethod.DELETE) 96 | public String deleteTransaction(RedirectAttributes redir, 97 | @RequestParam(value = "accountId") int accountId, 98 | @RequestParam(value = "transactionId") int transactionId) { 99 | logger.debug("calling service to delete transaction: " + transactionId); 100 | transactionService.deleteTransaction(transactionId); 101 | redir.addAttribute("id", accountId); 102 | return "redirect:/ledger/accounts"; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/controller/TrialBalanceController.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.controller; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | 14 | import my.generalledger.domain.ledger.Transaction; 15 | import my.generalledger.service.ledger.TransactionService; 16 | import my.generalledger.service.ledger.TrialBalance; 17 | 18 | @Controller 19 | public class TrialBalanceController { 20 | 21 | private final static Logger logger = LoggerFactory.getLogger(TrialBalanceController.class); 22 | 23 | @Autowired 24 | private TransactionService transactionService; 25 | 26 | @Autowired 27 | private TrialBalance trialBalanceService; 28 | 29 | @RequestMapping(value = "/ledger/trialbalance", method = RequestMethod.GET) 30 | public String getTrialBalance(Model model) { 31 | List transactions = transactionService.getTransactions(); 32 | int balance = trialBalanceService.calculateBalance(transactions); 33 | Set debitTransactions = 34 | trialBalanceService.getDebitTransactions(transactions); 35 | Set creditTransactions = 36 | trialBalanceService.getCreditTransactions(transactions); 37 | model.addAttribute("balance", balance); 38 | model.addAttribute("debitTransactions", debitTransactions); 39 | model.addAttribute("creditTransactions", creditTransactions); 40 | logger.info("retrieving view for displaying trial balance"); 41 | return "ledger/trialbalance"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/controller/Utils.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.controller; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | 7 | public final class Utils { 8 | 9 | public static Calendar stringToCalendar(String calendarStr) { 10 | Calendar cal = Calendar.getInstance(); 11 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 12 | try { 13 | cal.setTime(sdf.parse(calendarStr)); 14 | } catch (ParseException e) { 15 | e.printStackTrace(); 16 | } 17 | return cal; 18 | } 19 | 20 | /* remove everything that isn't a numeric value from a String and return an Integer, 21 | only supports positive values */ 22 | public static int CurrencyStringToInt(String currencyStr) { 23 | currencyStr = currencyStr.replaceAll("[^0-9]", ""); 24 | return Integer.valueOf(currencyStr); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/dao/ledger/LedgerAccountDAO.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.dao.ledger; 2 | 3 | import java.util.List; 4 | 5 | import my.generalledger.domain.ledger.LedgerAccount; 6 | 7 | public interface LedgerAccountDAO { 8 | 9 | void saveAccount(LedgerAccount ledgerAccount); 10 | LedgerAccount getAccountById(int id); 11 | LedgerAccount getAccountByNumber(String number); 12 | List getAccounts(); 13 | void updateAccount(LedgerAccount ledgerAccount); 14 | void deleteAccount(LedgerAccount ledgerAccount); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/dao/ledger/LedgerAccountDAOImpl.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.dao.ledger; 2 | 3 | import my.generalledger.domain.ledger.LedgerAccount; 4 | import org.hibernate.Session; 5 | import org.hibernate.SessionFactory; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import javax.transaction.Transactional; 12 | import java.util.List; 13 | 14 | @Repository 15 | @Transactional 16 | public class LedgerAccountDAOImpl implements LedgerAccountDAO { 17 | 18 | private final static Logger logger = LoggerFactory.getLogger(LedgerAccountDAOImpl.class); 19 | 20 | private final SessionFactory sessionFactory; 21 | 22 | @Autowired 23 | public LedgerAccountDAOImpl(SessionFactory sessionFactory) { 24 | this.sessionFactory = sessionFactory; 25 | } 26 | 27 | @Override 28 | public void saveAccount(LedgerAccount ledgerAccount) { 29 | Session session = sessionFactory.getCurrentSession(); 30 | logger.debug("added ledger account: " + ledgerAccount.getId()); 31 | session.persist(ledgerAccount); 32 | } 33 | 34 | @Override 35 | public LedgerAccount getAccountById(int id) { 36 | Session session = sessionFactory.getCurrentSession(); 37 | logger.debug("retrieving ledger account by id: " + id); 38 | return session.get(LedgerAccount.class, id); 39 | } 40 | 41 | @Override 42 | public LedgerAccount getAccountByNumber(String number) { 43 | Session session = sessionFactory.getCurrentSession(); 44 | logger.debug("retrieving ledger account by number: " + number); 45 | return session.get(LedgerAccount.class, number); 46 | } 47 | 48 | @SuppressWarnings("unchecked") 49 | @Override 50 | public List getAccounts() { 51 | Session session = sessionFactory.getCurrentSession(); 52 | logger.debug("retrieving list of ledger accounts"); 53 | return session.createSQLQuery("SELECT * FROM ledger_account") 54 | .addEntity(LedgerAccount.class).list(); 55 | } 56 | 57 | @Override 58 | public void updateAccount(LedgerAccount ledgerAccount) { 59 | Session session = sessionFactory.getCurrentSession(); 60 | logger.debug("updating ledger account: " + ledgerAccount.getId()); 61 | session.update(ledgerAccount); 62 | } 63 | 64 | @Override 65 | public void deleteAccount(LedgerAccount ledgerAccount) { 66 | Session session = sessionFactory.getCurrentSession(); 67 | int id = ledgerAccount.getId(); 68 | logger.debug("deleting ledger account: " + id); 69 | ledgerAccount = session.get(LedgerAccount.class, id); 70 | if (ledgerAccount != null) { 71 | session.delete(ledgerAccount); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/dao/ledger/TransactionDAO.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.dao.ledger; 2 | 3 | import java.util.List; 4 | 5 | import my.generalledger.domain.ledger.Transaction; 6 | 7 | public interface TransactionDAO { 8 | 9 | void saveTransaction(Transaction transaction); 10 | Transaction getTransactionById(int id); 11 | List getTransactions(); 12 | void updateTransaction(Transaction transaction); 13 | void deleteTransaction(Transaction transaction); 14 | } -------------------------------------------------------------------------------- /src/main/java/my/generalledger/dao/ledger/TransactionDAOImpl.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.dao.ledger; 2 | 3 | import my.generalledger.domain.ledger.Transaction; 4 | import org.hibernate.Session; 5 | import org.hibernate.SessionFactory; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import javax.transaction.Transactional; 12 | import java.util.List; 13 | 14 | @Repository 15 | @Transactional 16 | public class TransactionDAOImpl implements TransactionDAO { 17 | 18 | private final static Logger logger = LoggerFactory.getLogger(TransactionDAOImpl.class); 19 | 20 | private final SessionFactory sessionFactory; 21 | 22 | @Autowired 23 | public TransactionDAOImpl(SessionFactory sessionFactory) { 24 | this.sessionFactory = sessionFactory; 25 | } 26 | 27 | @Override 28 | public void saveTransaction(Transaction transaction) { 29 | Session session = sessionFactory.getCurrentSession(); 30 | logger.debug("adding transaction: " + transaction.getId()); 31 | session.persist(transaction); 32 | } 33 | 34 | @Override 35 | public Transaction getTransactionById(int id) { 36 | Session session = sessionFactory.getCurrentSession(); 37 | logger.debug("retrieving transaction by id: " + id); 38 | return session.get(Transaction.class, id); 39 | } 40 | 41 | @Override 42 | @SuppressWarnings("unchecked") 43 | public List getTransactions() { 44 | Session session = sessionFactory.getCurrentSession(); 45 | logger.debug("retrieving list of transactions"); 46 | return session.createSQLQuery("SELECT * FROM ledger_entry") 47 | .addEntity(Transaction.class).list(); 48 | } 49 | 50 | @Override 51 | public void updateTransaction(Transaction transaction) { 52 | Session session = sessionFactory.getCurrentSession(); 53 | logger.debug("updating transaction: " + transaction.getId()); 54 | session.update(transaction); 55 | } 56 | 57 | @Override 58 | public void deleteTransaction(Transaction transaction) { 59 | Session session = sessionFactory.getCurrentSession(); 60 | int id = transaction.getId(); 61 | logger.debug("deleting transaction: " + id); 62 | transaction = session.get(Transaction.class, id); 63 | if (transaction != null) { 64 | session.delete(transaction); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/domain/ledger/LedgerAccount.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.domain.ledger; 2 | 3 | 4 | import java.util.List; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.EnumType; 9 | import javax.persistence.Enumerated; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.GenerationType; 12 | import javax.persistence.Id; 13 | import javax.persistence.OneToMany; 14 | import javax.validation.constraints.NotNull; 15 | import javax.validation.constraints.Size; 16 | 17 | import org.hibernate.validator.constraints.NotEmpty; 18 | 19 | @Entity 20 | public class LedgerAccount { 21 | 22 | public enum Type { 23 | CREDIT, 24 | DEBIT, 25 | } 26 | 27 | @Id 28 | @GeneratedValue(strategy=GenerationType.AUTO) 29 | private int id; 30 | 31 | @NotNull 32 | @NotEmpty 33 | @Size(max = 64) 34 | private String name; 35 | 36 | @NotNull 37 | @Enumerated(EnumType.STRING) 38 | private Type type; 39 | 40 | @NotNull 41 | @NotEmpty 42 | @Size(max = 16) 43 | @Column(unique=true) 44 | private String number; 45 | 46 | @OneToMany(mappedBy="creditAccount") 47 | private List creditEntries; 48 | 49 | @OneToMany(mappedBy="debitAccount") 50 | private List debitEntries; 51 | 52 | protected LedgerAccount() {} 53 | 54 | public LedgerAccount(String name, Type type, String number) { 55 | this.name = name; 56 | this.type = type; 57 | this.number = number; 58 | } 59 | 60 | public int getId() { 61 | return id; 62 | } 63 | 64 | public String getName() { 65 | return name; 66 | } 67 | 68 | public void setName(String name) { 69 | this.name = name; 70 | } 71 | 72 | public Type getType() { 73 | return type; 74 | } 75 | 76 | public String getNumber() { 77 | return number; 78 | } 79 | 80 | public void setNumber(String number) { 81 | this.number = number; 82 | } 83 | 84 | public List getDebitEntries() { 85 | return debitEntries; 86 | } 87 | 88 | public List getCreditEntries() { 89 | return creditEntries; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return "LedgerAccount [id=" + id + ", name=" + name + ", type=" + type + ", number=" + number + "]"; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/domain/ledger/Transaction.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.domain.ledger; 2 | 3 | import org.springframework.format.annotation.DateTimeFormat; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.NotNull; 7 | import javax.validation.constraints.Size; 8 | import java.text.NumberFormat; 9 | import java.util.Calendar; 10 | 11 | @Entity 12 | public class Transaction { 13 | 14 | @Id 15 | @GeneratedValue(strategy=GenerationType.AUTO) 16 | private int id; 17 | 18 | @NotNull 19 | @DateTimeFormat(pattern = "yyyy-MM-dd") 20 | private Calendar date; 21 | 22 | @NotNull 23 | @Size(max = 128) 24 | private String description; 25 | 26 | @NotNull 27 | private int amount; 28 | 29 | //the credit entry for this transaction 30 | @ManyToOne 31 | @JoinColumn(name = "credit_account_id") 32 | private LedgerAccount creditAccount; 33 | 34 | //the debit entry for this transaction 35 | @ManyToOne 36 | @JoinColumn(name = "debit_account_id") 37 | private LedgerAccount debitAccount; 38 | 39 | protected Transaction() {} 40 | 41 | public Transaction(Calendar date, String description, int amount, 42 | LedgerAccount creditAccount, LedgerAccount debitAccount) { 43 | this.date = date; 44 | this.description = description; 45 | this.amount = amount; 46 | this.creditAccount = creditAccount; 47 | this.debitAccount = debitAccount; 48 | } 49 | 50 | public int getId() { 51 | return id; 52 | } 53 | 54 | public Calendar getDate() { 55 | return date; 56 | } 57 | 58 | public void setDate(Calendar date) { 59 | this.date = date; 60 | } 61 | 62 | public String getDescription() { 63 | return description; 64 | } 65 | 66 | public void setDescription(String description) { 67 | this.description = description; 68 | } 69 | 70 | public int getAmount() { 71 | return amount; 72 | } 73 | 74 | public void setAmount(int amount) { 75 | this.amount = amount; 76 | } 77 | 78 | public LedgerAccount getCreditAccount() { 79 | return creditAccount; 80 | } 81 | 82 | public void setCreditAccount(LedgerAccount creditAccount) { 83 | this.creditAccount = creditAccount; 84 | } 85 | 86 | public LedgerAccount getDebitAccount() { 87 | return debitAccount; 88 | } 89 | 90 | public void setDebitAccount(LedgerAccount debitAccount) { 91 | this.debitAccount = debitAccount; 92 | } 93 | 94 | public String amountToString() { 95 | NumberFormat n = NumberFormat.getCurrencyInstance(); 96 | return n.format(amount / 100.0); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/service/ledger/LedgerAccountService.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.service.ledger; 2 | 3 | import java.util.List; 4 | 5 | import my.generalledger.domain.ledger.LedgerAccount; 6 | import my.generalledger.domain.ledger.LedgerAccount.Type; 7 | 8 | public interface LedgerAccountService { 9 | 10 | void saveAccount(LedgerAccount ledgerAccount); 11 | void saveAccount(String name, Type type, String number); 12 | LedgerAccount getAccountById(int id); 13 | LedgerAccount getAccountByNumber(String number); 14 | List getAccounts(); 15 | void updateAccount(LedgerAccount ledgerAccount); 16 | void deleteAccount(LedgerAccount ledgerAccount); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/service/ledger/LedgerAccountServiceImpl.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.service.ledger; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import my.generalledger.dao.ledger.LedgerAccountDAO; 11 | import my.generalledger.domain.ledger.LedgerAccount; 12 | import my.generalledger.domain.ledger.LedgerAccount.Type; 13 | 14 | @Service 15 | public class LedgerAccountServiceImpl implements LedgerAccountService { 16 | 17 | private final static Logger logger = LoggerFactory.getLogger(LedgerAccountServiceImpl.class); 18 | 19 | private final LedgerAccountDAO dao; 20 | 21 | @Autowired 22 | public LedgerAccountServiceImpl(LedgerAccountDAO dao) { 23 | this.dao = dao; 24 | } 25 | 26 | @Override 27 | public void saveAccount(LedgerAccount ledgerAccount) { 28 | dao.saveAccount(ledgerAccount); 29 | } 30 | 31 | @Override 32 | public void saveAccount(String name, Type type, String number) { 33 | LedgerAccount ledgerAccount = new LedgerAccount(name, type, number); 34 | logger.debug("created ledger account: " + ledgerAccount.getId()); 35 | saveAccount(ledgerAccount); 36 | } 37 | 38 | @Override 39 | public LedgerAccount getAccountById(int id) { 40 | return dao.getAccountById(id); 41 | } 42 | 43 | @Override 44 | public LedgerAccount getAccountByNumber(String number) { 45 | return dao.getAccountByNumber(number); 46 | } 47 | 48 | @Override 49 | public List getAccounts() { 50 | return dao.getAccounts(); 51 | } 52 | 53 | @Override 54 | public void updateAccount(LedgerAccount ledgerAccount) { 55 | dao.updateAccount(ledgerAccount); 56 | } 57 | 58 | @Override 59 | public void deleteAccount(LedgerAccount ledgerAccount) { 60 | dao.deleteAccount(ledgerAccount); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/service/ledger/TransactionService.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.service.ledger; 2 | 3 | import java.util.Calendar; 4 | import java.util.List; 5 | 6 | import my.generalledger.domain.ledger.LedgerAccount; 7 | import my.generalledger.domain.ledger.Transaction; 8 | 9 | public interface TransactionService { 10 | void saveTransaction(Transaction transaction); 11 | void saveTransaction(Calendar date, String description, int amount, 12 | LedgerAccount creditAccount, LedgerAccount debitAccount); 13 | 14 | Transaction getTransactionById(int id); 15 | List getTransactions(); 16 | 17 | void updateTransaction(Transaction transaction); 18 | void updateTransaction(int transactionId, Calendar date, String description, int amount, 19 | LedgerAccount creditAccount, LedgerAccount debitAccount); 20 | 21 | void deleteTransaction(Transaction transaction); 22 | void deleteTransaction(int id); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/service/ledger/TransactionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.service.ledger; 2 | 3 | import my.generalledger.dao.ledger.TransactionDAO; 4 | import my.generalledger.domain.ledger.LedgerAccount; 5 | import my.generalledger.domain.ledger.Transaction; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Calendar; 12 | import java.util.List; 13 | 14 | @Service 15 | public class TransactionServiceImpl implements TransactionService { 16 | 17 | private final static Logger logger = LoggerFactory.getLogger(TransactionServiceImpl.class); 18 | 19 | private final TransactionDAO dao; 20 | 21 | @Autowired 22 | public TransactionServiceImpl(TransactionDAO dao) { 23 | this.dao = dao; 24 | } 25 | 26 | @Override 27 | public void saveTransaction(Transaction transaction) { 28 | dao.saveTransaction(transaction); 29 | } 30 | 31 | @Override 32 | public void saveTransaction(Calendar date, String description, int amount, 33 | LedgerAccount creditAccount, LedgerAccount debitAccount) { 34 | Transaction transaction = new Transaction(date, description, amount, creditAccount, debitAccount); 35 | logger.debug("created transaction: " + transaction.getId()); 36 | saveTransaction(transaction); 37 | } 38 | 39 | @Override 40 | public Transaction getTransactionById(int id) { 41 | return dao.getTransactionById(id); 42 | } 43 | 44 | @Override 45 | public List getTransactions() { 46 | return dao.getTransactions(); 47 | } 48 | 49 | @Override 50 | public void updateTransaction(Transaction transaction) { 51 | dao.updateTransaction(transaction); 52 | } 53 | 54 | @Override 55 | public void updateTransaction(int transactionId, Calendar date, String description, int amount, 56 | LedgerAccount creditAccount, LedgerAccount debitAccount) { 57 | logger.debug("updating transaction: " + transactionId); 58 | Transaction transaction = getTransactionById(transactionId); 59 | transaction.setDate(date); 60 | transaction.setDescription(description); 61 | transaction.setAmount(amount); 62 | transaction.setCreditAccount(creditAccount); 63 | transaction.setDebitAccount(debitAccount); 64 | updateTransaction(transaction); 65 | } 66 | 67 | @Override 68 | public void deleteTransaction(Transaction transaction) { 69 | dao.deleteTransaction(transaction); 70 | } 71 | 72 | @Override 73 | public void deleteTransaction(int id) { 74 | logger.debug("deleting transaction: " + id); 75 | Transaction transaction = dao.getTransactionById(id); 76 | deleteTransaction(transaction); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/my/generalledger/service/ledger/TrialBalance.java: -------------------------------------------------------------------------------- 1 | package my.generalledger.service.ledger; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | import org.springframework.stereotype.Service; 8 | 9 | import my.generalledger.domain.ledger.Transaction; 10 | 11 | @Service 12 | public class TrialBalance { 13 | 14 | public int calculateBalance(List transactions) { 15 | int debitBalance = 0; 16 | int creditBalance = 0; 17 | 18 | for (Transaction transaction : transactions) { 19 | if (transaction.getDebitAccount() != null) { 20 | debitBalance += transaction.getAmount(); 21 | } 22 | if (transaction.getCreditAccount() != null) { 23 | creditBalance += transaction.getAmount(); 24 | } 25 | } 26 | return debitBalance - creditBalance; 27 | } 28 | 29 | //in a balanced ledger the set of debit transactions and credit transactions 30 | //will be equivalent 31 | public Set getDebitTransactions(List transactions) { 32 | Set debitTransactions = new HashSet(); 33 | for (Transaction transaction : transactions) { 34 | if (transaction.getDebitAccount() != null) { 35 | debitTransactions.add(transaction); 36 | } 37 | } 38 | return debitTransactions; 39 | } 40 | 41 | public Set getCreditTransactions(List transactions) { 42 | Set creditTransactions = new HashSet(); 43 | for (Transaction transaction : transactions) { 44 | if (transaction.getCreditAccount() != null) { 45 | creditTransactions.add(transaction); 46 | } 47 | } 48 | return creditTransactions; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext 2 | 3 | spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect 4 | 5 | spring.datasource.url=jdbc:mysql://localhost:3306/test 6 | spring.datasource.username=root 7 | spring.datasource.password=root 8 | 9 | spring.jpa.hibernate.ddl-auto=update 10 | 11 | logging.level.my.generalledger=DEBUG 12 | logging.level.org.springframework.web=INFO 13 | logging.level.org.hibernate=ERROR 14 | logging.file=logs/general-ledger.log -------------------------------------------------------------------------------- /src/main/resources/static/css/styles.css: -------------------------------------------------------------------------------- 1 | .table.table-striped .minimal_cell { 2 | background: white none; 3 | border-width: 0; 4 | } 5 | 6 | .dl-horizontal dt{ 7 | text-align: left; 8 | width: auto; 9 | padding-right: 1em; 10 | } 11 | 12 | .dl-horizontal dd{ 13 | margin-left: 0; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | General Ledger 5 | 6 | 7 | 8 | 18 |
19 |

Welcome to your general ledger.

20 |

21 | You can add accounts and transactions to the general ledger. Transactions 22 | are added in accordance to the double-entry bookkeeping system, where every 23 | transaction has to be recorded twice, once as a debit entry and once as a 24 | credit entry. 25 |

26 |

27 | Accounts can be deleted if they have no transactions. Transactions can be 28 | deleted or updated. When a transaction is deleted or updated then both 29 | corresponding entries are modified as well. 30 |

31 |

32 | You can also view the trial balance, which is a statement of all credits and 33 | debits in the general ledger. You can view the general ledger and trial 34 | balance in the application and you can export them to a .csv file. 35 |

36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/templates/ledger/account.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | General Ledger 5 | 6 | 7 | 8 | 9 | 19 |
20 |

Account

21 |
22 |
Number:
23 |
number
24 | 25 |
Name:
26 |
name
27 | 28 |
Type:
29 |
type
30 |
31 |
32 |
33 |

Entries

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 66 | 67 | 68 | 71 | 72 | 91 | 92 |
DateDescriptionDebitCredit
44 | 45 | - 48 |
49 | 50 | 51 | 52 |
53 | 54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 |
62 | 63 |
64 |
65 |
69 | 70 | - 73 |
74 | 75 | 76 | 77 |
78 | 79 |
80 |
81 |
82 | 83 | 84 | 85 | 86 |
87 | 88 |
89 |
90 |
93 |
94 |
95 |

New Transaction

96 |
97 |
98 | 99 | 100 |
101 |
102 | 103 | 104 |
105 |
106 | 107 | 109 |
110 |
111 | 112 | 117 |
118 |
119 | 120 | 125 |
126 | 127 | 128 |
129 |
130 | 131 | 132 | -------------------------------------------------------------------------------- /src/main/resources/templates/ledger/addaccount.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Add Account 5 | 6 | 7 | 8 | 18 |
19 |

Create Account

20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 35 |
36 | 37 |
38 |
39 | 40 | -------------------------------------------------------------------------------- /src/main/resources/templates/ledger/deleteaccount.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Delete Account 5 | 6 | 7 | 8 | 18 |
19 |

Delete Acccount

20 |

An account can only be deleted if it has no entries

21 |
22 |
23 | 24 | 29 |
30 | 31 | 32 |
33 |
34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/templates/ledger/ledger.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | General Ledger 5 | 6 | 7 | 8 | 18 |
19 |

Accounts

20 | 21 | 22 | 23 | 26 |
24 | 25 |
27 |
28 |
29 |
30 | Add Account 31 | Delete Account 32 | 33 |
34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/templates/ledger/updatetransaction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | General Ledger 5 | 6 | 7 | 8 | 18 |
19 |

New Transaction

20 |
21 |
22 | 23 | 25 |
26 |
27 | 28 | 30 |
31 |
32 | 33 | 35 |
36 |
37 | 38 | 45 |
46 |
47 | 48 | 55 |
56 | 57 | 58 | 59 | 60 |
61 |
62 | 63 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Archetype Created Web Application 7 | 8 | --------------------------------------------------------------------------------