forked from bartaelterman/invoicing-app
-
Notifications
You must be signed in to change notification settings - Fork 0
/
models.py
152 lines (126 loc) · 6.51 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import pandas as pd
from datetime import date
from django.contrib.auth.models import User
from django.db import models
class Client(models.Model):
"""
Contact and invoicing details of the client
"""
address_line_1 = models.CharField(max_length=100)
address_line_2 = models.CharField(max_length=100)
name = models.CharField(max_length=200, unique=True)
VAT_number = models.CharField(max_length=20, unique=True)
def __str__(self):
return self.name
class Profile(models.Model):
"""
Additional attributes that belong to the user of the application. This includes
details of him/her that should also be mentioned on the invoice. It includes a one-to-one
relationship with the User model, a built-in model from django's authentication.
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
invoice_name = models.CharField(max_length=200)
address_line_1 = models.CharField(max_length=100)
address_line_2 = models.CharField(max_length=100)
bank_account = models.CharField(max_length=20, unique=True)
bic_account_code = models.CharField(max_length=20, null=True, blank=True)
phone = models.CharField(max_length=20, unique=True)
email = models.EmailField()
VAT_number = models.CharField(max_length=20, unique=True)
def __str__(self):
return self.invoice_name
class Project(models.Model):
"""
A user can create one or many projects for a given client. Note that:
- rate: is the rate agreed for this project between the user and the client. While not explicitly
stated here, other parts of the application will assume that this is for 8 hours, VAT excluded.
- togglId: the id of the project in toggl.
"""
client = models.ForeignKey(Client, on_delete=models.CASCADE)
user = models.ForeignKey(Profile)
name = models.CharField(max_length=200)
rate = models.DecimalField(max_digits=5, decimal_places=2) # daily
togglId = models.CharField(max_length=20, unique=True)
timesheet_template = models.CharField(max_length=50, null=True, blank=True)
invoice_template = models.CharField(max_length=50, null=True, blank=True)
credit_template = models.CharField(max_length=50, null=True, blank=True)
default_invoice_description = models.CharField(max_length=500, null=True, blank=True)
vat_rate = models.DecimalField(max_digits=5, decimal_places=2) # daily
def __str__(self):
return self.name
class TimeEntryDFManager(models.Manager):
"""
Helper manager to get time entries returned as a pandas DataFrame
"""
def get_queryset_df(self, *args, **kwargs):
df = pd.DataFrame(list(super(TimeEntryDFManager, self).get_queryset().filter(*args, **kwargs).values()))
return df
class TimeEntry(models.Model):
"""
An amount of time worked on a project. Can be billable or not (although at the current state, the invoicing
logic does not take this into account yet)
"""
billable = models.BooleanField(default=True) # todo: make sure time entries with billable = False are not included in time sheets and invoices
project = models.ForeignKey(Project, on_delete=models.CASCADE)
start = models.DateTimeField()
duration = models.FloatField()
duration_unit = models.CharField(choices=(('days', 'days'), ('hours', 'hours'), ('minutes', 'minutes')), default='hours', max_length=10) # todo: current reports can only handle 'hours'
togglId = models.CharField(max_length=20, unique=True)
objects = TimeEntryDFManager()
def __str__(self):
return '{0} : {1}'.format(self.project, self.start.isoformat())
def to_dict(self):
return {
'start': self.start,
'duration': self.duration,
'duration_unit': self.duration_unit
}
class Invoice(models.Model):
"""
A model to store invoice data.
"""
number = models.IntegerField(unique=True, blank=True, null=True, help_text='autofilled, only fill in yourself if you want to override the default') # will be autofilled, but is editable
project = models.ForeignKey(Project, on_delete=models.CASCADE)
vat_rate = models.DecimalField(max_digits=5, decimal_places=2, default=0.21) # daily
date = models.DateField(editable=False)
start = models.DateField()
end = models.DateField()
delivery_date = models.DateField(blank=True, null=True)
days = models.DecimalField(max_digits=5, decimal_places=2)
paid = models.BooleanField(default=False)
description = models.CharField(max_length=500, null=True, blank=True)
is_credit_invoice = models.BooleanField(default=False)
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.id:
if not self.number:
invoice_highest_number = Invoice.objects.all().order_by('-number').first()
latest_number = invoice_highest_number.number
self.number = latest_number + 1
self.date = date.today()
return super(Invoice, self).save(*args, **kwargs)
def __str__(self):
return '{} - {}: {}'.format(self.date, self.number, self.project)
class CreditNote(models.Model):
number = models.IntegerField(unique=True, blank=True, null=True, help_text='autofilled, only fill in yourself if you want to override the default') # will be autofilled, but is editable
project = models.ForeignKey(Project, on_delete=models.CASCADE)
vat_rate = models.DecimalField(max_digits=5, decimal_places=2, default=0.21) # daily
date = models.DateField(editable=False)
amount = models.DecimalField(max_digits=8, decimal_places=2)
description = models.CharField(max_length=500, null=True, blank=True)
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.id:
if not self.number:
credit_note_highest_number = CreditNote.objects.all().order_by('-number').first()
latest_number = credit_note_highest_number.number
self.number = latest_number + 1
self.date = date.today()
return super(CreditNote, self).save(*args, **kwargs)
def __str__(self):
return 'Creditnote {} - {}: {}'.format(self.date, self.number, self.project)
class InvoiceItem(models.Model):
invoice = models.ForeignKey(Invoice)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=7, decimal_places=2)
vat_rate = models.DecimalField(max_digits=7, decimal_places=2, help_text='If the VAT% is not equal to the overall VAT rate', null=True, blank=True)