org.angnysa.yaba.service.impl.DefaultTransactionService.java Source code

Java tutorial

Introduction

Here is the source code for org.angnysa.yaba.service.impl.DefaultTransactionService.java

Source

package org.angnysa.yaba.service.impl;

/*
 * #%L
 * Yet Another Budget Application
 * %%
 * Copyright (C) 2012 angnysa
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Comparator;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.Months;
import org.joda.time.ReadablePeriod;
import org.joda.time.Weeks;
import org.joda.time.Years;
import org.angnysa.yaba.dao.ReadException;
import org.angnysa.yaba.dao.TransactionDefinitionDao;
import org.angnysa.yaba.dao.WriteException;
import org.angnysa.yaba.dto.Transaction;
import org.angnysa.yaba.dto.SimulationDetail;
import org.angnysa.yaba.dto.TransactionDefinition;
import org.angnysa.yaba.service.TransactionService;

public class DefaultTransactionService implements TransactionService {
    public static final float MONTH_PER_YEAR = 12;
    public static final float DAY_PER_WEEK = 7;
    public static final float WORKDAY_PER_WEEK = 5;
    public static final float AVG_DAY_PER_YEAR = 365.2425f;
    public static final float AVG_DAY_PER_MONTH = AVG_DAY_PER_YEAR / MONTH_PER_YEAR;
    public static final float AVG_WEEK_PER_YEAR = AVG_DAY_PER_YEAR / DAY_PER_WEEK;
    public static final float AVG_WEEK_PER_MONTH = AVG_DAY_PER_MONTH / DAY_PER_WEEK;
    public static final float AVG_WORKDAY_PER_YEAR = AVG_WEEK_PER_YEAR * WORKDAY_PER_WEEK;
    public static final float AVG_WORKDAY_PER_MONTH = AVG_WEEK_PER_MONTH * WORKDAY_PER_WEEK;

    private TransactionDefinitionDao transactionDao;

    public DefaultTransactionService(TransactionDefinitionDao transactionDao) {
        this.transactionDao = transactionDao;
    }

    public void rollback() throws ReadException {
        transactionDao.rollback();
    }

    public void commit() throws WriteException {
        transactionDao.commit();
    }

    public int count() {
        return transactionDao.count();
    }

    public List<TransactionDefinition> getAll() {
        return transactionDao.getAll();
    }

    public TransactionDefinition get(int id) {
        return transactionDao.get(id);
    }

    public void save(TransactionDefinition td) {
        transactionDao.save(td);
    }

    public void delete(int id) {
        transactionDao.delete(id);
    }

    public double computeAvgYearlyBudget(TransactionDefinition td) {
        ReadablePeriod period = td.getPeriod();

        if (period instanceof Years) {
            return td.getAmount() / period.getValue(0);
        } else if (period instanceof Months) {
            return td.getAmount() * MONTH_PER_YEAR / period.getValue(0);
        } else if (period instanceof Weeks) {
            return td.getAmount() * AVG_WEEK_PER_YEAR / period.getValue(0);
        } else if (period instanceof Days) {
            return td.getAmount() * AVG_DAY_PER_YEAR / period.getValue(0);
        } else {
            return Double.NaN;
        }
    }

    public double computeAvgMonthlyBudget(TransactionDefinition td) {
        ReadablePeriod period = td.getPeriod();

        if (period instanceof Years) {
            return td.getAmount() / MONTH_PER_YEAR / period.getValue(0);
        } else if (period instanceof Months) {
            return td.getAmount() / period.getValue(0);
        } else if (period instanceof Weeks) {
            return td.getAmount() * AVG_WEEK_PER_MONTH / period.getValue(0);
        } else if (period instanceof Days) {
            return td.getAmount() * AVG_DAY_PER_MONTH / period.getValue(0);
        } else {
            return Double.NaN;
        }
    }

    public double computeAvgWeeklyBudget(TransactionDefinition td) {
        ReadablePeriod period = td.getPeriod();

        if (period instanceof Years) {
            return td.getAmount() / AVG_WEEK_PER_YEAR / period.getValue(0);
        } else if (period instanceof Months) {
            return td.getAmount() / AVG_WEEK_PER_MONTH / period.getValue(0);
        } else if (period instanceof Weeks) {
            return td.getAmount() / period.getValue(0);
        } else if (period instanceof Days) {
            return td.getAmount() * DAY_PER_WEEK / period.getValue(0);
        } else {
            return Double.NaN;
        }
    }

    public double computeAvgDaylyBudget(TransactionDefinition td) {
        ReadablePeriod period = td.getPeriod();

        if (period instanceof Years) {
            return td.getAmount() / AVG_DAY_PER_YEAR / period.getValue(0);
        } else if (period instanceof Months) {
            return td.getAmount() / AVG_DAY_PER_MONTH / period.getValue(0);
        } else if (period instanceof Weeks) {
            return td.getAmount() / DAY_PER_WEEK / period.getValue(0);
        } else if (period instanceof Days) {
            return td.getAmount() / period.getValue(0);
        } else {
            return Double.NaN;
        }
    }

    @Override
    public List<Transaction> computeTransactions(TransactionDefinition td, LocalDate start, LocalDate end) {

        if (start.isAfter(end)) {
            throw new IllegalArgumentException("start is after end");
        }

        List<Transaction> result = new ArrayList<Transaction>();

        if (td.getPeriod() == null) {
            // non repeating
            if ((td.getDate().isAfter(start) || td.getDate().isEqual(start))
                    && (td.getDate().isBefore(end) || td.getDate().isEqual(end))) {

                Transaction at = new Transaction();
                at.setTransactionId(td.getId());
                at.setDate(td.getDate());
                at.setAmount(td.getAmount());
                result.add(at);
            }
        } else {
            // repeating

            // get first valid date
            LocalDate current = td.getDate();
            while (current.isBefore(start)) {
                current = current.plus(td.getPeriod());
            }

            // get true last limit
            if (td.getEnd() != null && td.getEnd().isBefore(end)) {
                end = td.getEnd();
            }

            // list occurrences
            while (current.isBefore(end) || current.isEqual(end)) {

                Transaction at = new Transaction();
                at.setTransactionId(td.getId());
                at.setDate(current);
                if (td.getReconciliation(current) == null) {
                    at.setAmount(td.getAmount());
                } else {
                    at.setAmount(td.getReconciliation(current));
                }
                result.add(at);

                current = current.plus(td.getPeriod());
            }
        }

        Collections.sort(result, new Comparator<Transaction>() {

            @Override
            public int compare(Transaction t1, Transaction t2) {
                return t1.getDate().compareTo(t2.getDate());
            }
        });

        return result;
    }

    @Override
    public List<SimulationDetail> simulate(double initial, LocalDate start, LocalDate end, ReadablePeriod period) {

        List<SimulationDetail> result = new ArrayList<SimulationDetail>();

        trs_loop: for (TransactionDefinition td : getAll()) {
            // current insertion point (CIP)
            int cip = 0;
            // the amount added/removed by the current actual transaction (AT)
            double delta = 0D;

            // init
            if (cip >= result.size()) {
                SimulationDetail detail = new SimulationDetail();
                detail.setDate(start);
                detail.setRemainingAmount(initial);
                result.add(detail);
            }

            for (Transaction t : computeTransactions(td, start, end)) {

                // if the AT occurs after the CIP,
                // update all CIP until we find the next valid CIP
                while (t.getDate().isAfter(result.get(cip).getDate())) {

                    result.get(cip).setRemainingAmount(result.get(cip).getRemainingAmount() + delta);

                    cip++;

                    // init
                    if (cip >= result.size()) {

                        LocalDate date = result.get(cip - 1).getDate().plus(period);
                        // if the end date doesn't end a full period, we can have some transactions that cannot be properly used.
                        // we stop here if it's the case.
                        if (date.isAfter(end)) {
                            continue trs_loop;
                        }

                        SimulationDetail detail = new SimulationDetail();
                        detail.setDate(date);
                        detail.setRemainingAmount(initial);
                        result.add(detail);
                    }
                }

                // store the AT in its CIP
                result.get(cip).getTransactions().add(t);

                // update the delta
                delta += t.getAmount();
            }

            // update all remaining points
            LocalDate date = result.get(cip).getDate();
            while (!date.isAfter(end)) {

                //init
                if (cip >= result.size()) {
                    SimulationDetail detail = new SimulationDetail();
                    detail.setDate(date);
                    detail.setRemainingAmount(initial);
                    result.add(detail);
                }

                result.get(cip).setRemainingAmount(result.get(cip).getRemainingAmount() + delta);

                date = date.plus(period);
                cip++;
            }
        }

        return result;
    }
}