66Miguel Sanda <msanda@arrobalytics.com>
77"""
88from django .forms import (ModelForm , DateInput , TextInput , Select , BaseModelFormSet ,
9- modelformset_factory , Textarea , BooleanField , HiddenInput )
9+ modelformset_factory , Textarea , BooleanField , ValidationError )
1010from django .utils .translation import gettext_lazy as _
1111
1212from 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
86112class 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
118159class 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+ )
0 commit comments