Skip to content

Commit 60238f1

Browse files
committed
Merge branch 'dev-elarroba' into develop
# Conflicts: # django_ledger/__init__.py # django_ledger/templatetags/django_ledger.py # django_ledger/urls/item.py # django_ledger/utils.py # django_ledger/views/item.py
2 parents 7ce4d64 + b0fdcee commit 60238f1

30 files changed

+933
-248
lines changed

django_ledger/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
1010

1111
"""Django Ledger"""
12-
__version__ = '0.4.0.3'
12+
__version__ = '0.4.0.4'
1313
__license__ = 'GPLv3 License'
1414

1515
__author__ = 'Miguel Sanda'

django_ledger/forms/bill.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
from django.forms import ModelForm, DateInput, TextInput, Select, EmailInput, URLInput, CheckboxInput
1+
from django.forms import ModelForm, DateInput, TextInput, Select, CheckboxInput, BaseModelFormSet, modelformset_factory
22
from django.utils.translation import gettext_lazy as _
33

44
from django_ledger.io.roles import ASSET_CA_CASH, ASSET_CA_RECEIVABLES, LIABILITY_CL_ACC_PAYABLE, GROUP_EXPENSES
5+
from django_ledger.models import ItemModel
56
from django_ledger.models.accounts import AccountModel
6-
from django_ledger.models.bill import BillModel
7+
from django_ledger.models.bill import BillModel, BillModelItemsThroughModel
78
from django_ledger.models.vendor import VendorModel
89
from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
910

@@ -119,3 +120,51 @@ class Meta:
119120
'progressible': CheckboxInput(attrs={'type': 'checkbox'}),
120121
'paid': CheckboxInput(attrs={'type': 'checkbox'}),
121122
}
123+
124+
125+
class BillItemForm(ModelForm):
126+
class Meta:
127+
model = BillModelItemsThroughModel
128+
fields = [
129+
'item_model',
130+
'unit_cost',
131+
'quantity'
132+
]
133+
widgets = {
134+
'item_model': Select(attrs={
135+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES,
136+
}),
137+
'unit_cost': TextInput(attrs={
138+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES,
139+
}),
140+
'quantity': TextInput(attrs={
141+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES,
142+
})
143+
}
144+
145+
146+
class BaseInvoiceItemFormset(BaseModelFormSet):
147+
148+
def __init__(self, *args, entity_slug, bill_pk, user_model, **kwargs):
149+
super().__init__(*args, **kwargs)
150+
self.USER_MODEL = user_model
151+
self.BILL_PK = bill_pk
152+
self.ENTITY_SLUG = entity_slug
153+
154+
items_qs = ItemModel.objects.expenses(
155+
entity_slug=self.ENTITY_SLUG,
156+
user_model=self.USER_MODEL
157+
)
158+
159+
self.LN = len(items_qs) # evaluate the QS and cache results...
160+
for form in self.forms:
161+
form.fields['item_model'].queryset = items_qs
162+
163+
164+
BillItemFormset = modelformset_factory(
165+
model=BillModelItemsThroughModel,
166+
form=BillItemForm,
167+
formset=BaseInvoiceItemFormset,
168+
can_delete=True,
169+
extra=5
170+
)

django_ledger/forms/invoice.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,9 @@ class Meta:
136136
}),
137137
'unit_cost': TextInput(attrs={
138138
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES,
139-
'onchange': 'djLedger.onChangeItem(this)'
140139
}),
141140
'quantity': TextInput(attrs={
142141
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES,
143-
'onchange': 'djLedger.onChangeItem(this)'
144142
})
145143
}
146144

@@ -153,7 +151,7 @@ def __init__(self, *args, entity_slug, invoice_pk, user_model, **kwargs):
153151
self.INVOICE_PK = invoice_pk
154152
self.ENTITY_SLUG = entity_slug
155153

156-
items_qs = ItemModel.objects.for_entity_active(
154+
items_qs = ItemModel.objects.products_and_services(
157155
entity_slug=self.ENTITY_SLUG,
158156
user_model=self.USER_MODEL
159157
)

django_ledger/forms/item.py

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.forms import ModelForm, TextInput, Select, HiddenInput
22

3-
from django_ledger.io.roles import INCOME_SALES
3+
from django_ledger.io.roles import GROUP_INCOME, GROUP_EXPENSES
44
from django_ledger.models import AccountModel, ItemModel, UnitOfMeasureModel
55
from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
66

@@ -33,30 +33,24 @@ class UnitOfMeasureModelUpdateForm(UnitOfMeasureModelCreateForm):
3333
pass
3434

3535

36-
class ProductOrServiceCreateForm(ModelForm):
37-
# PRODUCT_ACCOUNT_ROLES: list = [COGS, ASSET_CA_INVENTORY, INCOME_SALES]
36+
class ProductOrServiceUpdateForm(ModelForm):
3837

3938
def __init__(self, entity_slug: str, user_model, *args, **kwargs):
4039
self.ENTITY_SLUG = entity_slug
4140
self.USER_MODEL = user_model
4241
super().__init__(*args, **kwargs)
4342

4443
accounts_qs = AccountModel.on_coa.with_roles(
45-
roles=[INCOME_SALES],
44+
roles=GROUP_INCOME,
4645
entity_slug=self.ENTITY_SLUG,
4746
user_model=self.USER_MODEL)
47+
self.fields['earnings_account'].queryset = accounts_qs.filter(role__in=GROUP_INCOME)
4848

4949
uom_qs = UnitOfMeasureModel.objects.for_entity(
5050
entity_slug=self.ENTITY_SLUG,
5151
user_model=self.USER_MODEL
5252
)
53-
54-
self.fields['earnings_account'].queryset = accounts_qs.filter(role__iexact=INCOME_SALES)
5553
self.fields['uom'].queryset = uom_qs
56-
self.fields['is_product_or_service'].initial = True
57-
58-
def clean_is_product_or_service(self):
59-
return True
6054

6155
class Meta:
6256
model = ItemModel
@@ -68,7 +62,53 @@ class Meta:
6862
'uom',
6963
'default_amount',
7064
'earnings_account',
71-
'is_product_or_service',
65+
]
66+
widgets = {
67+
'name': TextInput(attrs={
68+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
69+
}),
70+
'uom': Select(attrs={
71+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
72+
}),
73+
'earnings_account': Select(attrs={
74+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
75+
}),
76+
'sku': TextInput(attrs={
77+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
78+
}),
79+
'upc': TextInput(attrs={
80+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
81+
}),
82+
'item_id': TextInput(attrs={
83+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
84+
}),
85+
'default_amount': TextInput(attrs={
86+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
87+
}),
88+
}
89+
90+
91+
class ProductOrServiceCreateForm(ProductOrServiceUpdateForm):
92+
93+
def __init__(self, entity_slug: str, user_model, *args, **kwargs):
94+
super().__init__(entity_slug=entity_slug, user_model=user_model, *args, **kwargs)
95+
uom_qs = UnitOfMeasureModel.objects.for_entity_active(
96+
entity_slug=self.ENTITY_SLUG,
97+
user_model=self.USER_MODEL
98+
)
99+
self.fields['uom'].queryset = uom_qs
100+
self.fields['is_product_or_service'].initial = True
101+
102+
class Meta(ProductOrServiceUpdateForm.Meta):
103+
fields = [
104+
'name',
105+
'sku',
106+
'upc',
107+
'item_id',
108+
'uom',
109+
'default_amount',
110+
'earnings_account',
111+
'is_product_or_service'
72112
]
73113
widgets = {
74114
'name': TextInput(attrs={
@@ -98,5 +138,63 @@ class Meta:
98138
}
99139

100140

101-
class ProductOrServiceUpdateForm(ProductOrServiceCreateForm):
102-
pass
141+
class ExpenseItemUpdateForm(ModelForm):
142+
def __init__(self, entity_slug: str, user_model, *args, **kwargs):
143+
self.ENTITY_SLUG = entity_slug
144+
self.USER_MODEL = user_model
145+
super().__init__(*args, **kwargs)
146+
147+
accounts_qs = AccountModel.on_coa.with_roles(
148+
roles=GROUP_EXPENSES,
149+
entity_slug=self.ENTITY_SLUG,
150+
user_model=self.USER_MODEL)
151+
self.fields['expense_account'].queryset = accounts_qs.filter(role__in=GROUP_EXPENSES)
152+
153+
uom_qs = UnitOfMeasureModel.objects.for_entity(
154+
entity_slug=self.ENTITY_SLUG,
155+
user_model=self.USER_MODEL
156+
)
157+
self.fields['uom'].queryset = uom_qs
158+
159+
class Meta:
160+
model = ItemModel
161+
fields = [
162+
'name',
163+
'uom',
164+
'default_amount',
165+
'expense_account',
166+
]
167+
widgets = {
168+
'name': TextInput(attrs={
169+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
170+
}),
171+
'uom': Select(attrs={
172+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
173+
}),
174+
'expense_account': Select(attrs={
175+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
176+
}),
177+
'sku': TextInput(attrs={
178+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
179+
}),
180+
'upc': TextInput(attrs={
181+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
182+
}),
183+
'item_id': TextInput(attrs={
184+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
185+
}),
186+
'default_amount': TextInput(attrs={
187+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
188+
}),
189+
}
190+
191+
192+
class ExpenseItemCreateForm(ExpenseItemUpdateForm):
193+
194+
def __init__(self, entity_slug: str, user_model, *args, **kwargs):
195+
super().__init__(entity_slug=entity_slug, user_model=user_model, *args, **kwargs)
196+
uom_qs = UnitOfMeasureModel.objects.for_entity_active(
197+
entity_slug=self.ENTITY_SLUG,
198+
user_model=self.USER_MODEL
199+
)
200+
self.fields['uom'].queryset = uom_qs

django_ledger/models/bill.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from uuid import uuid4
1212

1313
from django.db import models
14-
from django.db.models import Q
14+
from django.db.models import Q, Sum, Count
1515
from django.db.models.signals import post_delete
1616
from django.urls import reverse
1717
from django.utils.translation import gettext_lazy as _
@@ -151,6 +151,19 @@ def get_mark_paid_url(self, entity_slug):
151151
'bill_pk': self.uuid
152152
})
153153

154+
def get_bill_item_data(self, queryset=None) -> tuple:
155+
if not queryset:
156+
queryset = self.billmodelitemsthroughmodel_set.all()
157+
return queryset, queryset.aggregate(
158+
amount_due=Sum('total_amount'),
159+
total_items=Count('uuid')
160+
)
161+
162+
def update_amount_due(self, queryset=None) -> tuple:
163+
queryset, item_data = self.get_bill_item_data(queryset=queryset)
164+
self.amount_due = item_data['amount_due']
165+
return queryset, item_data
166+
154167
def clean(self):
155168
if not self.bill_number:
156169
self.bill_number = generate_bill_number()
@@ -171,7 +184,6 @@ def billmodel_predelete(instance: BillModel, **kwargs):
171184

172185

173186
class BillModelItemsThroughModelAbstract(ItemTotalCostMixIn, CreateUpdateMixIn):
174-
175187
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
176188
bill_model = models.ForeignKey('django_ledger.BillModel',
177189
on_delete=models.CASCADE,

django_ledger/models/invoice.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ def get_migrate_state_desc(self):
146146
"""
147147
Must be implemented.
148148
:return:
149-
:return:
150149
"""
151150
return f'Invoice {self.invoice_number} account adjustment.'
152151

@@ -158,10 +157,10 @@ def get_invoice_item_data(self, queryset=None) -> tuple:
158157
total_items=Count('uuid')
159158
)
160159

161-
def update_amount_due(self, queryset=None) -> dict:
160+
def update_amount_due(self, queryset=None) -> tuple:
162161
queryset, item_data = self.get_invoice_item_data(queryset=queryset)
163162
self.amount_due = item_data['amount_due']
164-
return item_data
163+
return queryset, item_data
165164

166165
def clean(self):
167166
if not self.invoice_number:

django_ledger/models/items.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ def for_entity(self, entity_slug: str, user_model):
3232
)
3333
)
3434

35+
def for_entity_active(self, entity_slug: str, user_model):
36+
qs = self.for_entity(entity_slug=entity_slug, user_model=user_model)
37+
return qs.filter(is_active=True)
38+
3539

3640
class UnitOfMeasureModelAbstract(CreateUpdateMixIn):
3741
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
@@ -58,8 +62,23 @@ def __str__(self):
5862
return f'{self.name} ({self.unit_abbr})'
5963

6064

65+
class UnitOfMeasureModel(UnitOfMeasureModelAbstract):
66+
"""
67+
Base Unit of Measure Model from Abstract.
68+
"""
69+
70+
71+
class ItemModelMQuerySet(models.QuerySet):
72+
73+
def active(self):
74+
return self.filter(active=True)
75+
76+
6177
class ItemModelManager(models.Manager):
6278

79+
def get_queryset(self):
80+
return ItemModelMQuerySet(self.model, using=self._db)
81+
6382
def for_entity(self, entity_slug: str, user_model):
6483
qs = self.get_queryset()
6584
return qs.filter(
@@ -74,6 +93,17 @@ def for_entity_active(self, entity_slug: str, user_model):
7493
qs = self.for_entity(entity_slug=entity_slug, user_model=user_model)
7594
return qs.filter(is_active=True)
7695

96+
def products_and_services(self, entity_slug: str, user_model):
97+
qs = self.for_entity(entity_slug=entity_slug, user_model=user_model)
98+
return qs.filter(is_product_or_service=True)
99+
100+
def expenses(self, entity_slug: str, user_model):
101+
qs = self.for_entity(entity_slug=entity_slug, user_model=user_model)
102+
return qs.filter(
103+
is_product_or_service=False,
104+
for_inventory=False
105+
)
106+
77107

78108
class ItemModelAbstract(CreateUpdateMixIn):
79109
REL_NAME_PREFIX = 'item'
@@ -194,12 +224,6 @@ def clean(self):
194224
self.cogs_account = None
195225

196226

197-
class UnitOfMeasureModel(UnitOfMeasureModelAbstract):
198-
"""
199-
Base Unit of Measure Model from Abstract.
200-
"""
201-
202-
203227
class ItemModel(ItemModelAbstract):
204228
"""
205229
Base Item Model from Abstract.

django_ledger/models/mixins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ class Meta:
481481
abstract = True
482482

483483
def update_total_amount(self):
484-
self.total_amount = self.quantity * self.unit_cost
484+
self.total_amount = round(self.quantity * self.unit_cost, 2)
485485

486486
def clean(self):
487487
self.update_total_amount()

0 commit comments

Comments
 (0)