Skip to content

Commit 7ce0c8f

Browse files
committed
v0.4.3.2
Create a Bill directly from PO Markdown PO Notes & MixIn PO Create/Update Validation Removed vendor from PO model. Removed for_inventory field of PO Model. Pipfile update Migrations reset
1 parent 6456764 commit 7ce0c8f

28 files changed

+480
-329
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ django = ">=3.2"
88
django-mptt = ">=0.11.0"
99
ofxtools = ">=0.8.21"
1010
jsonschema = ">=3.2.0"
11+
markdown = ">=3.3.4"
1112

1213
[dev-packages]
1314
sphinx = "~=3.4.1"

Pipfile.lock

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ output __(help needed!)__.
4747
* Client proposals & estimates.
4848
* User preferences and settings & account creation views.
4949
* Update package and code documentation.
50-
### Version 0.9
5150

51+
### Version 0.9
5252
* Enable Hierarchical Entity structures via MPTT.
5353
* Consolidated financial statements.
5454
* Intercompany transactions.

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.3.1'
12+
__version__ = '0.4.3.2'
1313
__license__ = 'GPLv3 License'
1414

1515
__author__ = 'Miguel Sanda'

django_ledger/forms/bill.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -173,26 +173,13 @@ def __init__(self, *args, entity_slug, bill_pk, user_model, **kwargs):
173173
self.BILL_PK = bill_pk
174174
self.ENTITY_SLUG = entity_slug
175175

176-
items_qs_exp = ItemModel.objects.expenses(
177-
entity_slug=self.ENTITY_SLUG,
178-
user_model=self.USER_MODEL
179-
)
180-
181-
items_qs_inv = ItemModel.objects.inventory(
176+
items_qs = ItemModel.objects.for_bill(
182177
entity_slug=self.ENTITY_SLUG,
183178
user_model=self.USER_MODEL
184179
)
185180

186181
for form in self.forms:
187-
po_model = form.instance.po_model
188-
if po_model:
189-
if po_model.for_inventory:
190-
form.fields['item_model'].queryset = items_qs_inv
191-
else:
192-
form.fields['item_model'].queryset = items_qs_exp
193-
else:
194-
form.fields['item_model'].queryset = items_qs_exp
195-
182+
form.fields['item_model'].queryset = items_qs
196183

197184

198185
BillItemFormset = modelformset_factory(

django_ledger/forms/purchase_order.py

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Miguel Sanda <msanda@arrobalytics.com>
77
"""
88
from django.forms import (ModelForm, DateInput, TextInput, Select, BaseModelFormSet,
9-
modelformset_factory, Textarea, BooleanField, HiddenInput)
9+
modelformset_factory, Textarea, BooleanField, ValidationError)
1010
from django.utils.translation import gettext_lazy as _
1111

1212
from django_ledger.models import (ItemModel, PurchaseOrderModel, ItemThroughModel)
@@ -24,7 +24,6 @@ class Meta:
2424
fields = [
2525
'po_date',
2626
'po_title',
27-
'for_inventory'
2827
]
2928
widgets = {
3029
'po_date': DateInput(attrs={
@@ -47,14 +46,18 @@ def __init__(self, *args, entity_slug, user_model, **kwargs):
4746
self.ENTITY_SLUG = entity_slug
4847
self.USER_MODEL = user_model
4948

49+
if self.initial['po_status'] == PurchaseOrderModel.PO_STATUS_APPROVED:
50+
self.fields['po_status'].disabled = True
51+
else:
52+
self.fields['fulfilled'].disabled = True
53+
5054
class Meta:
5155
model = PurchaseOrderModel
5256
fields = [
5357
'po_date',
5458
'po_title',
5559
'po_status',
56-
'po_notes',
57-
'vendor',
60+
'markdown_notes',
5861
'fulfillment_date',
5962
'fulfilled'
6063
]
@@ -73,15 +76,38 @@ class Meta:
7376
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES,
7477
'placeholder': _('PO Date (YYYY-MM-DD)...')
7578
}),
76-
'po_notes': Textarea(attrs={
79+
'markdown_notes': Textarea(attrs={
7780
'class': 'textarea'
7881
})
7982
}
8083
labels = {
8184
'po_status': _('PO Status'),
82-
'fulfilled': _('Is Fulfilled?')
85+
'fulfilled': _('Mark as Fulfilled'),
86+
'markdown_notes': _('PO Notes')
8387
}
8488

89+
def clean(self):
90+
is_fulfilled = self.cleaned_data['fulfilled']
91+
new_status = self.cleaned_data['po_status']
92+
93+
if 'fulfilled' in self.changed_data:
94+
if not is_fulfilled:
95+
raise ValidationError(
96+
message=f'Cannot change fulfilled status to False after being fulfilled once.'
97+
)
98+
if new_status != PurchaseOrderModel.PO_STATUS_APPROVED:
99+
raise ValidationError(
100+
message=f'Cannot fulfill a PO that has not been approved.'
101+
)
102+
103+
if 'po_status' in self.changed_data:
104+
if new_status != PurchaseOrderModel.PO_STATUS_APPROVED:
105+
initial_status = self.initial['po_status']
106+
raise ValidationError(
107+
message=f'Cannot change form status to {new_status} '
108+
f'from {initial_status}'
109+
)
110+
85111

86112
class PurchaseOrderItemForm(ModelForm):
87113
create_bill = BooleanField(required=False)
@@ -114,6 +140,21 @@ class Meta:
114140
})
115141
}
116142

143+
def clean_po_item_status(self):
144+
po_item_status = self.cleaned_data['po_item_status']
145+
if 'po_item_status' in self.changed_data:
146+
po_model: PurchaseOrderModel = getattr(self, 'PO_MODEL')
147+
if po_model.po_status == po_model.PO_STATUS_APPROVED:
148+
if not po_item_status:
149+
raise ValidationError('Cannot assign null status to approved PO.')
150+
if all([
151+
self.instance.bill_model_id,
152+
po_item_status == ItemThroughModel.STATUS_NOT_ORDERED
153+
]):
154+
raise ValidationError('Cannot assign not ordered status to a billed item. '
155+
'Void or delete bill first')
156+
return po_item_status
157+
117158

118159
class BasePurchaseOrderItemFormset(BaseModelFormSet):
119160

@@ -123,30 +164,40 @@ def __init__(self, *args, entity_slug, user_model, po_model: PurchaseOrderModel,
123164
self.ENTITY_SLUG = entity_slug
124165
self.PO_MODEL = po_model
125166

126-
if self.PO_MODEL.for_inventory:
127-
items_qs = ItemModel.objects.inventory(
128-
entity_slug=self.ENTITY_SLUG,
129-
user_model=self.USER_MODEL
130-
)
131-
else:
132-
items_qs = ItemModel.objects.expenses(
133-
entity_slug=self.ENTITY_SLUG,
134-
user_model=self.USER_MODEL
135-
)
167+
items_qs = ItemModel.objects.for_po(
168+
entity_slug=self.ENTITY_SLUG,
169+
user_model=self.USER_MODEL
170+
)
136171

137-
self.LN = len(items_qs) # evaluate the QS and cache results...
138172
for form in self.forms:
173+
form.PO_MODEL = self.PO_MODEL
139174
form.fields['item_model'].queryset = items_qs
140175
if self.PO_MODEL.po_status != PurchaseOrderModel.PO_STATUS_APPROVED:
141-
form.fields['po_item_status'].widget.attrs['disabled'] = True
176+
form.fields['po_item_status'].disabled = True
142177
else:
143178
form.fields['po_item_status'].widget.attrs['class'] += form.instance.get_status_css_class()
144-
145-
146-
PurchaseOrderItemFormset = modelformset_factory(
147-
model=ItemThroughModel,
148-
form=PurchaseOrderItemForm,
149-
formset=BasePurchaseOrderItemFormset,
150-
can_delete=True,
151-
extra=5
152-
)
179+
if form.instance.po_item_status == ItemThroughModel.STATUS_RECEIVED:
180+
form.fields['po_item_status'].disabled = True
181+
182+
form.fields['unit_cost'].disabled = True
183+
form.fields['quantity'].disabled = True
184+
form.fields['entity_unit'].disabled = True
185+
form.fields['item_model'].disabled = True
186+
187+
188+
def get_po_item_formset(po_model: PurchaseOrderModel):
189+
if po_model.po_status != PurchaseOrderModel.PO_STATUS_APPROVED:
190+
return modelformset_factory(
191+
model=ItemThroughModel,
192+
form=PurchaseOrderItemForm,
193+
formset=BasePurchaseOrderItemFormset,
194+
can_delete=True,
195+
extra=5
196+
)
197+
return modelformset_factory(
198+
model=ItemThroughModel,
199+
form=PurchaseOrderItemForm,
200+
formset=BasePurchaseOrderItemFormset,
201+
can_delete=False,
202+
extra=0
203+
)

django_ledger/migrations/0001_initial.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 3.2.3 on 2021-06-08 11:56
1+
# Generated by Django 3.2.4 on 2021-06-18 03:34
22

33
from django.conf import settings
44
import django.core.validators
@@ -378,20 +378,18 @@ class Migration(migrations.Migration):
378378
fields=[
379379
('created', models.DateTimeField(auto_now_add=True)),
380380
('updated', models.DateTimeField(auto_now=True, null=True)),
381+
('markdown_notes', models.TextField(blank=True, null=True, verbose_name='Markdown Notes')),
381382
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
382383
('po_number', models.SlugField(max_length=20, unique=True, verbose_name='Purchase Order Number')),
383384
('po_date', models.DateField(verbose_name='Purchase Order Date')),
384385
('po_title', models.CharField(max_length=250, verbose_name='Purchase Order Title')),
385-
('po_notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
386386
('po_status', models.CharField(choices=[('draft', 'Draft'), ('in_review', 'In Review'), ('approved', 'Approved')], default='draft', max_length=10)),
387387
('po_amount', models.DecimalField(decimal_places=2, default=0, max_digits=20, verbose_name='Purchase Order Amount')),
388388
('po_amount_received', models.DecimalField(decimal_places=2, default=0, max_digits=20, verbose_name='Received Amount')),
389-
('for_inventory', models.BooleanField(verbose_name='Inventory Purchase?')),
390389
('fulfilled', models.BooleanField(default=False, verbose_name='Is Fulfilled')),
391390
('fulfillment_date', models.DateField(blank=True, null=True, verbose_name='Fulfillment Date')),
392391
('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_ledger.entitymodel', verbose_name='Entity')),
393392
('po_items', models.ManyToManyField(through='django_ledger.ItemThroughModel', to='django_ledger.ItemModel', verbose_name='Purchase Order Items')),
394-
('vendor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='django_ledger.vendormodel')),
395393
],
396394
options={
397395
'abstract': False,

django_ledger/models/bill.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class BillModelAbstract(AccruableItemMixIn, CreateUpdateMixIn):
7070
IS_DEBIT_BALANCE = False
7171
ALLOW_MIGRATE = True
7272

73+
# todo: add markdown mixin...
7374
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
7475
bill_number = models.SlugField(max_length=20, unique=True, verbose_name=_('Bill Number'))
7576
xref = models.SlugField(null=True, blank=True, verbose_name=_('External Reference Number'))

django_ledger/models/invoice.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class InvoiceModelAbstract(AccruableItemMixIn, CreateUpdateMixIn):
6969
IS_DEBIT_BALANCE = True
7070
REL_NAME_PREFIX = 'invoice'
7171

72+
# todo: add markdown mixin...
7273
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
7374
invoice_number = models.SlugField(max_length=20, unique=True, verbose_name=_('Invoice Number'))
7475
customer = models.ForeignKey('django_ledger.CustomerModel',

0 commit comments

Comments
 (0)