1010use Illuminate \Database \Eloquent \Model ;
1111use Illuminate \Database \Eloquent \Relations \BelongsTo ;
1212use Illuminate \Database \Eloquent \Relations \HasMany ;
13+ use Illuminate \Database \Eloquent \Relations \HasOne ;
1314use Illuminate \Database \Eloquent \SoftDeletes ;
1415
1516class SalesOrder extends Model
@@ -22,7 +23,8 @@ class SalesOrder extends Model
2223
2324 protected $ fillable = [
2425 'tenant_id ' , 'contact_id ' , 'warehouse_id ' , 'invoice_id ' , 'number ' ,
25- 'order_date ' , 'expected_date ' , 'status ' , 'notes ' , 'created_by ' ,
26+ 'reference ' , 'order_date ' , 'expected_date ' , 'status ' , 'notes ' , 'created_by ' ,
27+ 'currency_code ' , 'exchange_rate ' ,
2628 ];
2729
2830 protected $ casts = [
@@ -36,8 +38,9 @@ protected function getTransitions(): array
3638 {
3739 return [
3840 'draft ' => ['confirmed ' , 'cancelled ' ],
39- 'confirmed ' => ['fulfilled ' , 'cancelled ' ],
40- 'fulfilled ' => [],
41+ 'confirmed ' => ['fulfilled ' , 'invoiced ' , 'cancelled ' ],
42+ 'fulfilled ' => ['invoiced ' ],
43+ 'invoiced ' => [],
4144 'cancelled ' => [],
4245 ];
4346 }
@@ -57,6 +60,11 @@ public function invoice(): BelongsTo
5760 return $ this ->belongsTo (Invoice::class);
5861 }
5962
63+ public function generatedInvoice (): HasOne
64+ {
65+ return $ this ->hasOne (Invoice::class, 'sales_order_id ' );
66+ }
67+
6068 public function items (): HasMany
6169 {
6270 return $ this ->hasMany (SalesOrderItem::class);
@@ -67,6 +75,50 @@ public function creator(): BelongsTo
6775 return $ this ->belongsTo (User::class, 'created_by ' );
6876 }
6977
78+ /** Convert this confirmed SO to a new Invoice and mark SO as invoiced. */
79+ public function convertToInvoice (): Invoice
80+ {
81+ abort_unless ($ this ->status === 'confirmed ' , 422 , 'Only confirmed orders can be invoiced. ' );
82+ $ this ->load ('items ' );
83+
84+ $ ref = $ this ->reference ?? $ this ->number ;
85+ $ invoiceRef = $ ref ? 'INV- ' . preg_replace ('/^SO-/ ' , '' , $ ref ) : null ;
86+
87+ $ invoice = Invoice::create ([
88+ 'tenant_id ' => $ this ->tenant_id ,
89+ 'sales_order_id ' => $ this ->id ,
90+ 'contact_id ' => $ this ->contact_id ,
91+ 'issue_date ' => now ()->toDateString (),
92+ 'due_date ' => now ()->addDays (30 )->toDateString (),
93+ 'status ' => 'draft ' ,
94+ 'notes ' => $ this ->notes ,
95+ 'created_by ' => auth ()->id (),
96+ 'currency_code ' => $ this ->currency_code ?? 'USD ' ,
97+ 'exchange_rate ' => $ this ->exchange_rate ?? 1 ,
98+ ]);
99+
100+ if ($ invoiceRef ) {
101+ $ invoice ->update (['number ' => $ invoiceRef ]);
102+ } else {
103+ $ invoice ->update ([
104+ 'number ' => 'INV- ' . now ()->format ('Y ' ) . '- ' . str_pad ((string ) $ invoice ->id , 5 , '0 ' , STR_PAD_LEFT ),
105+ ]);
106+ }
107+
108+ foreach ($ this ->items as $ item ) {
109+ $ invoice ->items ()->create ([
110+ 'description ' => $ item ->description ,
111+ 'quantity ' => $ item ->quantity ,
112+ 'unit_price ' => $ item ->unit_price ,
113+ 'tax_rate ' => $ item ->tax_rate ,
114+ ]);
115+ }
116+
117+ $ this ->update (['status ' => 'invoiced ' , 'invoice_id ' => $ invoice ->id ]);
118+
119+ return $ invoice ;
120+ }
121+
70122 public function fulfill (): void
71123 {
72124 if (! $ this ->canTransitionTo ('fulfilled ' )) {
0 commit comments