lincolnloop / django-beancounter

Django-beancounter is a simple app I built to track my income and expenses.

This URL has Read+Write access

9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 1 import datetime
1c4c5ca6 » lincolnloop 2009-01-04 some helper functions for p... 2 import decimal
9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 3
ca5ea477 » lincolnloop 2008-08-04 initial commit 4 from django.db import models
b13309e1 » lincolnloop 2008-12-09 Django 1.0 updates 5 from django.contrib.localflavor.us.models import PhoneNumberField
9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 6 from tagging.fields import TagField
ca5ea477 » lincolnloop 2008-08-04 initial commit 7
8 TYPE_CHOICES = (
9 ('INC', 'Income'),
10 ('EXP', 'Expense'),
11 ('COGS', 'Cost of Goods Sold'),
12 )
13 class Category(models.Model):
4b8ad771 » lincolnloop 2008-08-04 correct max_length 14 type = models.CharField(max_length=4,choices=TYPE_CHOICES)
15 name = models.CharField(max_length=50)
ca5ea477 » lincolnloop 2008-08-04 initial commit 16 income = models.ForeignKey('self', null=True, blank=True, limit_choices_to = {'type__exact':'INC'}, help_text='Use this to enable tracking your costs of goods vs. income')
17
18 def __str__(self):
19 return "%s: %s" % (self.type,self.name)
20 class Meta:
21 verbose_name_plural = 'Categories'
22 ordering = ['type','name']
23
24 class Admin:
25 fields = (
26 (None, {
27 'fields': ('type','name')
28 }),
29 ('Associate Costs of Goods Sold to an Income Category', {
30 'classes': 'collapse',
31 'fields' : ('income',)
32 }),
33 )
34
35 class BankAccount(models.Model):
4b8ad771 » lincolnloop 2008-08-04 correct max_length 36 type = models.CharField(max_length=50,help_text='Checking, Savings, Credit, etc.')
37 name = models.CharField(max_length=50)
bf69f37a » lincolnloop 2008-08-19 code cleanup and replacemen... 38 initial_balance = models.DecimalField(max_digits=10, decimal_places=2, null=True,blank=True)
ca5ea477 » lincolnloop 2008-08-04 initial commit 39 track_balance = models.BooleanField(help_text='Generate reports of the balance of this account over time.')
40
41 def __str__(self):
42 return "%s (%s)" % (self.name,self.type)
43 class Admin:
44 list_display = ('name','type')
45 ordering = ['-track_balance','name','type']
46
47 class AccountTransfer(models.Model):
48 date = models.DateField()
49 from_account = models.ForeignKey(BankAccount,related_name='transferred_from')
50 to_account = models.ForeignKey(BankAccount,related_name='transferred_to')
bf69f37a » lincolnloop 2008-08-19 code cleanup and replacemen... 51 amount = models.DecimalField(max_digits=8, decimal_places=2)
4b8ad771 » lincolnloop 2008-08-04 correct max_length 52 memo = models.CharField(max_length=100,null=True,blank=True)
ca5ea477 » lincolnloop 2008-08-04 initial commit 53 def __str__(self):
54 return "$%.2f from %s to %s" % (self.amount,self.from_account,self.to_account)
55
56 class Admin:
57 list_display = ('date','amount','from_account','to_account')
58 list_filter = ('to_account','from_account')
59 date_hierarchy = 'date'
60
61
62 class Person(models.Model):
4b8ad771 » lincolnloop 2008-08-04 correct max_length 63 name = models.CharField(max_length=100)
64 contact = models.CharField(max_length=100,null=True,blank=True)
b13309e1 » lincolnloop 2008-12-09 Django 1.0 updates 65 phone = PhoneNumberField(null=True,blank=True)
ca5ea477 » lincolnloop 2008-08-04 initial commit 66 website = models.URLField(null=True,blank=True)
67 email = models.EmailField(null=True,blank=True)
4b8ad771 » lincolnloop 2008-08-04 correct max_length 68 notes = models.CharField(max_length=100,null=True,blank=True)
ca5ea477 » lincolnloop 2008-08-04 initial commit 69
9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 70 def __unicode__(self):
71 return self.name
ca5ea477 » lincolnloop 2008-08-04 initial commit 72
73 class Meta:
74 verbose_name_plural = 'People'
75 ordering = ['name']
76
9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 77
78 class Employee(Person):
79 PAYMENT_CHOICES = (
80 ('paypal', 'PayPal'),
81 ('check', 'Mail Check'),
82 ('wire', 'Wire Transfer'),
83 ('elance', 'Elance'),
84 ('other', 'Other'),
85 )
e4ceba0f » lincolnloop 2009-01-04 remove bid field, add invoi... 86 gmt_offset = models.DecimalField(max_digits=3, decimal_places=1, null=True, blank=True)
9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 87 skills = TagField()
88 payment_preference = models.CharField(blank=True, max_length=100, choices=PAYMENT_CHOICES)
89 payment_notes = models.TextField(blank=True)
90 contract = models.DateField(blank=True, null=True, help_text="Date contractor contract was signed and received.")
91 hourly_rate = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True, help_text="If rate varies, enter average and note below.")
92 currency = models.CharField(default="USD", max_length=3)
93 rate_notes = models.TextField(blank=True, help_text="Additional notes regarding contractor rates.")
94
95
96 def timezone(self):
97 if self.gmt_offset == int(self.gmt_offset):
98 gmt_offset = int(self.gmt_offset)
99 else:
100 gmt_offset = self.gmt_offset
101 if gmt_offset > 0:
102 gmt_offset = '+%s' % gmt_offset
103 return 'GMT%s' % gmt_offset
104
105 def under_contract(self):
106 if self.contract:
107 return True
108 return False
109 under_contract.boolean = True
110
111 def rate(self):
112 return "%s %s" % (self.hourly_rate, self.currency)
113
114 class Project(models.Model):
115 name = models.CharField(max_length=100)
116 employees = models.ManyToManyField(Employee)
117 active = models.BooleanField(default=True)
118
119 def __unicode__(self):
120 return self.name
1c4c5ca6 » lincolnloop 2009-01-04 some helper functions for p... 121
122 def total_invoiced(self):
123 #TODO aggregate support
124 total = decimal.Decimal("0.00")
125 for invoice in self.projectinvoice_set.all():
126 total += invoice.amount
127 return total
128
129 def total_cost(self):
130 #TODO aggregate support
131 total = decimal.Decimal("0.00")
132 for time in self.projecttime_set.all():
133 total += time.cost_converted
134 return total
135
136 def profit(self):
137 return self.total_invoiced() - self.total_cost()
138
e4ceba0f » lincolnloop 2009-01-04 remove bid field, add invoi... 139
140 class ProjectInvoice(models.Model):
141 """
142 Invoice sent for project
143
144 """
145 project = models.ForeignKey(Project)
146 date = models.DateField(default=datetime.date.today())
147 amount = models.DecimalField(max_digits=8, decimal_places=2)
148
149 def __unicode__(self):
150 return "$%s for %s on %s" % (self.amount, self.project, self.date)
9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 151
152
153 class ProjectTime(models.Model):
154 """
155 Hours spent by an employee on a project
ca5ea477 » lincolnloop 2008-08-04 initial commit 156
9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 157 """
158
159 employee = models.ForeignKey(Employee)
160 project = models.ForeignKey(Project)
161 start_date = models.DateField(default=datetime.date.today())
162 end_date = models.DateField(default=datetime.date.today())
163 hours = models.DecimalField(max_digits=6, decimal_places=3)
164 cost = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True, help_text="Leave blank to automatically calculate")
165 cost_converted = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True, help_text="Cost converted to local currency")
166
167 def __unicode__(self):
168 return "%s on %s (%s-%s)" % (self.employee, self.project, self.start_date, self.end_date)
f9ffe2cd » lincolnloop 2009-01-05 auto-populate cost 169
170 def save(self, force_insert=False, force_update=False):
171 if not self.cost:
172 self.cost = self.hours * self.employee.hourly_rate
173 if not self.cost_converted and self.employee.currency == "USD":
174 self.cost_converted = self.cost
175 super(ProjectTime, self).save(force_insert, force_update)
9d6640ef » lincolnloop 2009-01-04 added project/employee trac... 176
ca5ea477 » lincolnloop 2008-08-04 initial commit 177
178 class Entry(models.Model):
179 category = models.ForeignKey(Category)
180 date = models.DateField()
181 name = models.ForeignKey(Person)
82d729a8 » lincolnloop 2008-08-05 added flot, fixed history view 182 amount = models.DecimalField(max_digits=8, decimal_places=2)
ca5ea477 » lincolnloop 2008-08-04 initial commit 183 bank_account = models.ForeignKey(BankAccount,related_name='paid_from',null=True,blank=True)
4b8ad771 » lincolnloop 2008-08-04 correct max_length 184 memo = models.CharField(max_length=100,null=True,blank=True)
ca5ea477 » lincolnloop 2008-08-04 initial commit 185
186 def __str__(self):
187 return "$%.2f | %s | %s" % (self.amount,self.name,self.date)
188 class Meta:
189 verbose_name_plural = 'Entries'
190
191 class Admin:
192 list_display = ('date', 'name', 'category', 'amount')
193 date_hierarchy = 'date'
194 search_fields = ('name','memo')
195 list_filter = ('category','name','bank_account')