efd7ea085bdfda0cbfaec94109d9a7d8e126c8f7
[fsij-members-webapp.git] / accounting / models.py
1 # -*- coding: utf-8-*-
2 from django.db import models
3 import datetime
4 #
5 # Business: FSIJ has(had) three kinds of business:
6 #           general, seminar, goods-selling
7 #
8 class Business(models.Model):
9     name = models.CharField(max_length=30)
10     def __unicode__(self):
11         return self.name
12
13 # 0: Equity 
14 # 1: Asset
15 # 2: Liability
16 # 3: Income
17 # 4: Expense
18 class AccountType(models.Model):
19     name = models.CharField(max_length=30)
20     value = models.IntegerField()
21     def __unicode__(self):
22         return self.name
23     def is_credit(self):
24         if self.value == 0 or self.value == 2 or self.value == 3:
25             return True
26         else:
27             return False
28
29 # Account -- "Kanjo-Kamoku" in Japanese
30 class Account(models.Model):
31     type = models.ForeignKey(AccountType)
32     name = models.CharField(max_length=30)
33     is_cash = models.BooleanField()
34     def __unicode__(self):
35         return self.name
36
37 class Transaction(models.Model):
38     date = models.DateField()
39     business = models.ForeignKey(Business)
40     memo = models.CharField(max_length=50, blank=True)
41     memo_detail = models.CharField(max_length=100, blank=True)
42     class Meta:
43         permissions = (("can_manage", "Can manage accounting"),)
44     def __unicode__(self):
45         return self.business.__unicode__() + "/" + self.date.__str__()
46     n = 0
47     credit = None
48     debit = None
49     def num_line(self):
50         self.credit = self.ledgerentry_set.filter(is_credit=True)
51         self.debit = self.ledgerentry_set.filter(is_credit=False)
52         self.n = 0
53         return range(0,max(self.credit.count(), self.debit.count()))
54     def get_credit(self):
55         try:
56             return self.credit[self.n]
57         except:
58             return None
59     def get_debit(self):
60         try:
61             return self.debit[self.n]
62         except:
63             return None
64     def next(self):
65         self.n += 1
66         return "%d" % self.n
67     def credit_total(self):
68         ct = 0
69         for i in range(0,self.credit.count()):
70             ct += self.credit[i].amount
71         return ct
72     def debit_total(self):
73         dt = 0
74         for i in range(0,self.debit.count()):
75             dt += self.debit[i].amount
76         return dt
77
78 class LedgerEntry(models.Model):
79     t = models.ForeignKey(Transaction)
80     is_credit = models.BooleanField() # Debit or Credit, True if Credit 
81     account = models.ForeignKey(Account)
82     amount  = models.IntegerField()
83     class Meta:
84         permissions = (("can_view_sheets", "Can view sheets"),)
85     def __unicode__(self):
86         return self.t.__unicode__() + "/" + self.account.name
87
88 class FinancialYear(object):
89     date_beginning = None
90     date_ending = None
91
92     def __init__(self, year):
93         self.date_beginning = datetime.date(year, 6, 1)
94         self.date_ending = datetime.date(year+1, 5, 31)
95
96     def begin(self):
97         return self.date_beginning
98
99     def end(self):
100         return self.date_ending
101
102 class GeneralLedgerEntry(object):
103     fy = None
104     gles = None
105     transactions_over_terms = None
106     def __init__(self, year):
107         self.fy = FinancialYear(year)
108         le_list = LedgerEntry.objects.filter(t__date__range=(self.fy.begin(),self.fy.end())).order_by('account')
109         ac_dict = {}
110         self.transactions_over_terms = set()
111         for le in le_list:
112             if le.account.name == u'繰越':
113                 self.transactions_over_terms.add(le.t)
114                 continue
115             if le.account.type.is_credit():
116                 is_total_credit = True
117             else:
118                 is_total_credit = False
119             if (le.is_credit and is_total_credit) or ((not le.is_credit) and (not is_total_credit)):
120                 total = le.amount
121             else:
122                 total = -le.amount
123             try:
124                 cores_le = le.t.ledgerentry_set.get(account__is_cash=True)
125                 if le.account.is_cash or (cores_le.is_credit == le.is_credit):
126                     cash_total = 0
127                 else:
128                     cash_total = cores_le.amount
129             except:
130                 cash_total = 0
131             try:
132                 a = ac_dict[le.account.name]
133                 a['cash_total'] = a['cash_total'] + cash_total
134                 a['cash_total_less_than_zero'] = (a['cash_total'] < 0)
135                 if a['cash_total'] < 0:
136                     a['abs_cash_total'] = -a['cash_total']
137                 else:
138                     a['abs_cash_total'] = a['cash_total']
139                 a['ledgerentries'].append((le.is_credit,le.amount,le.t))
140                 a['total'] = a['total'] + total
141             except:
142                 if cash_total < 0:
143                     abs_cash_total = -cash_total
144                 else:
145                     abs_cash_total = cash_total
146                 ac_dict[le.account.name] = {'name': le.account.name,
147                                'type': le.account.type,
148                                'is_cash': le.account.is_cash,
149                                'cash_total': cash_total, 
150                                'cash_total_less_than_zero': (cash_total < 0), 
151                                'abs_cash_total': abs_cash_total,
152                                'ledgerentries':
153                                [(le.is_credit,le.amount,le.t)],
154                                'total': total, }
155         self.gles = ac_dict.values()
156         self.gles.sort(cmp = lambda a,b: a['type'].value - b['type'].value)
157
158     def get_gles(self):
159         return self.gles
160
161     def get_pl_total(self):
162         pl = 0
163         for e in self.gles:
164             if e['type'].value == 3:
165                 pl += e['total']
166             if e['type'].value == 4:
167                 pl -= e['total']
168         return pl
169
170     def get_cash_total(self):
171         cash = 0
172         for e in self.gles:
173             if e['type'].is_credit():
174                 cash += e['cash_total']
175             else:
176                 cash -= e['cash_total']
177         return cash
178
179     def get_initial_value(self,account):
180         for t in self.transactions_over_terms:
181             if t.date != self.fy.begin():
182                 continue
183             try:
184                 le = t.ledgerentry_set.get(account=account)
185                 return le
186             except:
187                 continue
188         return None
189 #
190 # Transactions, example
191 #
192 # In 2007, a member send 14000 (by misunderstanding) by wire
193 # transfer, but ask to use the remainder 4000 for next year.
194 #
195 # In 2008, the member payed 10000 (by misunderstanding again)
196 # in cash, asking to use another 7000 as next year members fee.
197 #
198 # Advance Payment Received
199 #
200 # -----------------------------------------------------------
201 #   Date             Debit                    Credit
202 #             Account        Amount    Account         Amount
203 # -----------------------------------------------------------
204 # 2007-01-03  Deposit        14000     Admission         3000
205 #                                      Fee2006           7000
206 #                                      Advance Payment   4000
207 #
208 # 2008-05-04  Cache          10000     Fee2007           7000
209 #                                      Advance Payment   3000
210 #
211 # 2008-06-01  Advance Payment 7000     Fee2008           7000
212 # -----------------------------------------------------------
213 #
214 #
215 #
216 # -----------------------------------------------------------
217 #   Date             Debit                    Credit
218 #             Account        Amount    Account         Amount
219 # -----------------------------------------------------------
220 # 2007-06-03  Deposit          9750    Admission         3000
221 #             Transfer fee      250    Fee2007           7000
222 # -----------------------------------------------------------
223 #
224 # A member tried to send 10000 to FSIJ, and FSIJ received 9750
225 # because bank took 250 for a fee of wire transfer.
226 # FSIJ secretariat decided to treat this 250 as FSIJ's expense.
227 #
228 #
229 # This could be handled by three transactions:
230 #
231 # -----------------------------------------------------------
232 #   Date             Debit                    Credit
233 #             Account        Amount    Account         Amount
234 # -----------------------------------------------------------
235 # 2007-06-03  Deposit          9750    Admission         3000
236 #                                      Advance Payment   6750
237 # -----------------------------------------------------------
238 #
239 # -----------------------------------------------------------
240 #   Date             Debit                    Credit
241 #             Account        Amount    Account         Amount
242 # -----------------------------------------------------------
243 # 2007-06-03  Transfer fee      250    Advance Payment    250
244 # -----------------------------------------------------------
245 #
246 # -----------------------------------------------------------
247 #   Date             Debit                    Credit
248 #             Account        Amount    Account         Amount
249 # -----------------------------------------------------------
250 # 2007-06-03  Advance Payment  7000    Fee2007           7000
251 # -----------------------------------------------------------
252 #