Andrew Wallo 10 个月前
父节点
当前提交
6cec455b7e

+ 13
- 7
app/Concerns/ManagesLineItems.php 查看文件

@@ -4,14 +4,15 @@ namespace App\Concerns;
4 4
 
5 5
 use App\Enums\Accounting\AdjustmentComputation;
6 6
 use App\Enums\Accounting\DocumentDiscountMethod;
7
+use App\Models\Accounting\Bill;
7 8
 use App\Models\Accounting\DocumentLineItem;
8
-use App\Models\Accounting\Invoice;
9 9
 use App\Utilities\Currency\CurrencyConverter;
10
+use Illuminate\Database\Eloquent\Model;
10 11
 use Illuminate\Support\Collection;
11 12
 
12 13
 trait ManagesLineItems
13 14
 {
14
-    protected function handleLineItems(Invoice $record, Collection $lineItems): void
15
+    protected function handleLineItems(Model $record, Collection $lineItems): void
15 16
     {
16 17
         foreach ($lineItems as $itemData) {
17 18
             $lineItem = isset($itemData['id'])
@@ -36,7 +37,7 @@ trait ManagesLineItems
36 37
         }
37 38
     }
38 39
 
39
-    protected function deleteRemovedLineItems(Invoice $record, Collection $lineItems): void
40
+    protected function deleteRemovedLineItems(Model $record, Collection $lineItems): void
40 41
     {
41 42
         $existingLineItemIds = $record->lineItems->pluck('id');
42 43
         $updatedLineItemIds = $lineItems->pluck('id')->filter();
@@ -52,8 +53,13 @@ trait ManagesLineItems
52 53
 
53 54
     protected function handleLineItemAdjustments(DocumentLineItem $lineItem, array $itemData, DocumentDiscountMethod $discountMethod): void
54 55
     {
55
-        $adjustmentIds = collect($itemData['salesTaxes'] ?? [])
56
-            ->merge($discountMethod->isPerLineItem() ? ($itemData['salesDiscounts'] ?? []) : [])
56
+        $isBill = $lineItem->documentable instanceof Bill;
57
+
58
+        $taxType = $isBill ? 'purchaseTaxes' : 'salesTaxes';
59
+        $discountType = $isBill ? 'purchaseDiscounts' : 'salesDiscounts';
60
+
61
+        $adjustmentIds = collect($itemData[$taxType] ?? [])
62
+            ->merge($discountMethod->isPerLineItem() ? ($itemData[$discountType] ?? []) : [])
57 63
             ->filter()
58 64
             ->unique();
59 65
 
@@ -71,7 +77,7 @@ trait ManagesLineItems
71 77
         ]);
72 78
     }
73 79
 
74
-    protected function updateInvoiceTotals(Invoice $record, array $data): array
80
+    protected function updateDocumentTotals(Model $record, array $data): array
75 81
     {
76 82
         $subtotalCents = $record->lineItems()->sum('subtotal');
77 83
         $taxTotalCents = $record->lineItems()->sum('tax_total');
@@ -98,7 +104,7 @@ trait ManagesLineItems
98 104
         ?AdjustmentComputation $discountComputation,
99 105
         ?string $discountRate,
100 106
         int $subtotalCents,
101
-        Invoice $record
107
+        Model $record
102 108
     ): int {
103 109
         if ($discountMethod->isPerLineItem()) {
104 110
             return $record->lineItems()->sum('discount_total');

+ 52
- 125
app/Filament/Company/Resources/Purchases/BillResource.php 查看文件

@@ -3,13 +3,14 @@
3 3
 namespace App\Filament\Company\Resources\Purchases;
4 4
 
5 5
 use App\Enums\Accounting\BillStatus;
6
+use App\Enums\Accounting\DocumentDiscountMethod;
6 7
 use App\Enums\Accounting\PaymentMethod;
7 8
 use App\Filament\Company\Resources\Purchases\BillResource\Pages;
9
+use App\Filament\Forms\Components\BillTotals;
8 10
 use App\Filament\Tables\Actions\ReplicateBulkAction;
9 11
 use App\Filament\Tables\Filters\DateRangeFilter;
10 12
 use App\Models\Accounting\Adjustment;
11 13
 use App\Models\Accounting\Bill;
12
-use App\Models\Accounting\DocumentLineItem;
13 14
 use App\Models\Banking\BankAccount;
14 15
 use App\Models\Common\Offering;
15 16
 use App\Utilities\Currency\CurrencyConverter;
@@ -26,7 +27,6 @@ use Filament\Tables;
26 27
 use Filament\Tables\Table;
27 28
 use Illuminate\Database\Eloquent\Builder;
28 29
 use Illuminate\Database\Eloquent\Collection;
29
-use Illuminate\Database\Eloquent\Model;
30 30
 use Illuminate\Support\Facades\Auth;
31 31
 
32 32
 class BillResource extends Resource
@@ -71,129 +71,44 @@ class BillResource extends Resource
71 71
                                         return now()->addDays($company->defaultBill->payment_terms->getDays());
72 72
                                     })
73 73
                                     ->required(),
74
+                                Forms\Components\Select::make('discount_method')
75
+                                    ->label('Discount Method')
76
+                                    ->options(DocumentDiscountMethod::class)
77
+                                    ->selectablePlaceholder(false)
78
+                                    ->default(DocumentDiscountMethod::PerLineItem)
79
+                                    ->afterStateUpdated(function ($state, Forms\Set $set) {
80
+                                        $discountMethod = DocumentDiscountMethod::parse($state);
81
+
82
+                                        if ($discountMethod->isPerDocument()) {
83
+                                            $set('lineItems.*.purchaseDiscounts', []);
84
+                                        }
85
+                                    })
86
+                                    ->live(),
74 87
                             ])->grow(true),
75 88
                         ])->from('md'),
76 89
                         TableRepeater::make('lineItems')
77 90
                             ->relationship()
78
-                            ->saveRelationshipsUsing(function (TableRepeater $component, Forms\Contracts\HasForms $livewire, ?array $state) {
79
-                                if (! is_array($state)) {
80
-                                    $state = [];
81
-                                }
82
-
83
-                                $relationship = $component->getRelationship();
84
-
85
-                                $existingRecords = $component->getCachedExistingRecords();
86
-
87
-                                $recordsToDelete = [];
88
-
89
-                                foreach ($existingRecords->pluck($relationship->getRelated()->getKeyName()) as $keyToCheckForDeletion) {
90
-                                    if (array_key_exists("record-{$keyToCheckForDeletion}", $state)) {
91
-                                        continue;
92
-                                    }
93
-
94
-                                    $recordsToDelete[] = $keyToCheckForDeletion;
95
-                                    $existingRecords->forget("record-{$keyToCheckForDeletion}");
91
+                            ->saveRelationshipsUsing(null)
92
+                            ->dehydrated(true)
93
+                            ->headers(function (Forms\Get $get) {
94
+                                $hasDiscounts = DocumentDiscountMethod::parse($get('discount_method'))->isPerLineItem();
95
+
96
+                                $headers = [
97
+                                    Header::make('Items')->width($hasDiscounts ? '15%' : '20%'),
98
+                                    Header::make('Description')->width($hasDiscounts ? '25%' : '30%'),  // Increase when no discounts
99
+                                    Header::make('Quantity')->width('10%'),
100
+                                    Header::make('Price')->width('10%'),
101
+                                    Header::make('Taxes')->width($hasDiscounts ? '15%' : '20%'),       // Increase when no discounts
102
+                                ];
103
+
104
+                                if ($hasDiscounts) {
105
+                                    $headers[] = Header::make('Discounts')->width('15%');
96 106
                                 }
97 107
 
98
-                                $relationship
99
-                                    ->whereKey($recordsToDelete)
100
-                                    ->get()
101
-                                    ->each(static fn (Model $record) => $record->delete());
102
-
103
-                                $childComponentContainers = $component->getChildComponentContainers(
104
-                                    withHidden: $component->shouldSaveRelationshipsWhenHidden(),
105
-                                );
106
-
107
-                                $itemOrder = 1;
108
-                                $orderColumn = $component->getOrderColumn();
109
-
110
-                                $translatableContentDriver = $livewire->makeFilamentTranslatableContentDriver();
111
-
112
-                                foreach ($childComponentContainers as $itemKey => $item) {
113
-                                    $itemData = $item->getState(shouldCallHooksBefore: false);
114
-
115
-                                    if ($orderColumn) {
116
-                                        $itemData[$orderColumn] = $itemOrder;
117
-
118
-                                        $itemOrder++;
119
-                                    }
120
-
121
-                                    if ($record = ($existingRecords[$itemKey] ?? null)) {
122
-                                        $itemData = $component->mutateRelationshipDataBeforeSave($itemData, record: $record);
123
-
124
-                                        if ($itemData === null) {
125
-                                            continue;
126
-                                        }
127
-
128
-                                        $translatableContentDriver ?
129
-                                            $translatableContentDriver->updateRecord($record, $itemData) :
130
-                                            $record->fill($itemData)->save();
131
-
132
-                                        continue;
133
-                                    }
134
-
135
-                                    $relatedModel = $component->getRelatedModel();
136
-
137
-                                    $itemData = $component->mutateRelationshipDataBeforeCreate($itemData);
138
-
139
-                                    if ($itemData === null) {
140
-                                        continue;
141
-                                    }
142
-
143
-                                    if ($translatableContentDriver) {
144
-                                        $record = $translatableContentDriver->makeRecord($relatedModel, $itemData);
145
-                                    } else {
146
-                                        $record = new $relatedModel;
147
-                                        $record->fill($itemData);
148
-                                    }
108
+                                $headers[] = Header::make('Amount')->width('10%')->align('right');
149 109
 
150
-                                    $record = $relationship->save($record);
151
-                                    $item->model($record)->saveRelationships();
152
-                                    $existingRecords->push($record);
153
-                                }
154
-
155
-                                $component->getRecord()->setRelation($component->getRelationshipName(), $existingRecords);
156
-
157
-                                /** @var Bill $bill */
158
-                                $bill = $component->getRecord();
159
-
160
-                                // Recalculate totals for line items
161
-                                $bill->lineItems()->each(function (DocumentLineItem $lineItem) {
162
-                                    $lineItem->updateQuietly([
163
-                                        'tax_total' => $lineItem->calculateTaxTotal()->getAmount(),
164
-                                        'discount_total' => $lineItem->calculateDiscountTotal()->getAmount(),
165
-                                    ]);
166
-                                });
167
-
168
-                                $subtotal = $bill->lineItems()->sum('subtotal') / 100;
169
-                                $taxTotal = $bill->lineItems()->sum('tax_total') / 100;
170
-                                $discountTotal = $bill->lineItems()->sum('discount_total') / 100;
171
-                                $grandTotal = $subtotal + $taxTotal - $discountTotal;
172
-
173
-                                $bill->updateQuietly([
174
-                                    'subtotal' => $subtotal,
175
-                                    'tax_total' => $taxTotal,
176
-                                    'discount_total' => $discountTotal,
177
-                                    'total' => $grandTotal,
178
-                                ]);
179
-
180
-                                $bill->refresh();
181
-
182
-                                if (! $bill->initialTransaction) {
183
-                                    $bill->createInitialTransaction();
184
-                                } else {
185
-                                    $bill->updateInitialTransaction();
186
-                                }
110
+                                return $headers;
187 111
                             })
188
-                            ->headers([
189
-                                Header::make('Items')->width('15%'),
190
-                                Header::make('Description')->width('25%'),
191
-                                Header::make('Quantity')->width('10%'),
192
-                                Header::make('Price')->width('10%'),
193
-                                Header::make('Taxes')->width('15%'),
194
-                                Header::make('Discounts')->width('15%'),
195
-                                Header::make('Amount')->width('10%')->align('right'),
196
-                            ])
197 112
                             ->schema([
198 113
                                 Forms\Components\Select::make('offering_id')
199 114
                                     ->relationship('purchasableOffering', 'name')
@@ -209,7 +124,11 @@ class BillResource extends Resource
209 124
                                             $set('description', $offeringRecord->description);
210 125
                                             $set('unit_price', $offeringRecord->price);
211 126
                                             $set('purchaseTaxes', $offeringRecord->purchaseTaxes->pluck('id')->toArray());
212
-                                            $set('purchaseDiscounts', $offeringRecord->purchaseDiscounts->pluck('id')->toArray());
127
+
128
+                                            $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));
129
+                                            if ($discountMethod->isPerLineItem()) {
130
+                                                $set('purchaseDiscounts', $offeringRecord->purchaseDiscounts->pluck('id')->toArray());
131
+                                            }
213 132
                                         }
214 133
                                     }),
215 134
                                 Forms\Components\TextInput::make('description'),
@@ -225,15 +144,24 @@ class BillResource extends Resource
225 144
                                     ->default(0),
226 145
                                 Forms\Components\Select::make('purchaseTaxes')
227 146
                                     ->relationship('purchaseTaxes', 'name')
147
+                                    ->saveRelationshipsUsing(null)
148
+                                    ->dehydrated(true)
228 149
                                     ->preload()
229 150
                                     ->multiple()
230 151
                                     ->live()
231 152
                                     ->searchable(),
232 153
                                 Forms\Components\Select::make('purchaseDiscounts')
233 154
                                     ->relationship('purchaseDiscounts', 'name')
155
+                                    ->saveRelationshipsUsing(null)
156
+                                    ->dehydrated(true)
234 157
                                     ->preload()
235 158
                                     ->multiple()
236 159
                                     ->live()
160
+                                    ->hidden(function (Forms\Get $get) {
161
+                                        $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));
162
+
163
+                                        return $discountMethod->isPerDocument();
164
+                                    })
237 165
                                     ->searchable(),
238 166
                                 Forms\Components\Placeholder::make('total')
239 167
                                     ->hiddenLabel()
@@ -265,13 +193,7 @@ class BillResource extends Resource
265 193
                                         return CurrencyConverter::formatToMoney($total);
266 194
                                     }),
267 195
                             ]),
268
-                        Forms\Components\Grid::make(6)
269
-                            ->schema([
270
-                                Forms\Components\ViewField::make('totals')
271
-                                    ->columnStart(5)
272
-                                    ->columnSpan(2)
273
-                                    ->view('filament.forms.components.bill-totals'),
274
-                            ]),
196
+                        BillTotals::make(),
275 197
                     ]),
276 198
             ]);
277 199
     }
@@ -281,6 +203,11 @@ class BillResource extends Resource
281 203
         return $table
282 204
             ->defaultSort('due_date')
283 205
             ->columns([
206
+                Tables\Columns\TextColumn::make('id')
207
+                    ->label('ID')
208
+                    ->sortable()
209
+                    ->toggleable(isToggledHiddenByDefault: true)
210
+                    ->searchable(),
284 211
                 Tables\Columns\TextColumn::make('status')
285 212
                     ->badge()
286 213
                     ->searchable(),

+ 22
- 0
app/Filament/Company/Resources/Purchases/BillResource/Pages/CreateBill.php 查看文件

@@ -2,13 +2,17 @@
2 2
 
3 3
 namespace App\Filament\Company\Resources\Purchases\BillResource\Pages;
4 4
 
5
+use App\Concerns\ManagesLineItems;
5 6
 use App\Concerns\RedirectToListPage;
6 7
 use App\Filament\Company\Resources\Purchases\BillResource;
8
+use App\Models\Accounting\Bill;
7 9
 use Filament\Resources\Pages\CreateRecord;
8 10
 use Filament\Support\Enums\MaxWidth;
11
+use Illuminate\Database\Eloquent\Model;
9 12
 
10 13
 class CreateBill extends CreateRecord
11 14
 {
15
+    use ManagesLineItems;
12 16
     use RedirectToListPage;
13 17
 
14 18
     protected static string $resource = BillResource::class;
@@ -17,4 +21,22 @@ class CreateBill extends CreateRecord
17 21
     {
18 22
         return MaxWidth::Full;
19 23
     }
24
+
25
+    protected function handleRecordCreation(array $data): Model
26
+    {
27
+        /** @var Bill $record */
28
+        $record = parent::handleRecordCreation($data);
29
+
30
+        $this->handleLineItems($record, collect($data['lineItems'] ?? []));
31
+
32
+        $totals = $this->updateDocumentTotals($record, $data);
33
+
34
+        $record->updateQuietly($totals);
35
+
36
+        if (! $record->initialTransaction) {
37
+            $record->createInitialTransaction();
38
+        }
39
+
40
+        return $record;
41
+    }
20 42
 }

+ 28
- 0
app/Filament/Company/Resources/Purchases/BillResource/Pages/EditBill.php 查看文件

@@ -2,14 +2,18 @@
2 2
 
3 3
 namespace App\Filament\Company\Resources\Purchases\BillResource\Pages;
4 4
 
5
+use App\Concerns\ManagesLineItems;
5 6
 use App\Concerns\RedirectToListPage;
6 7
 use App\Filament\Company\Resources\Purchases\BillResource;
8
+use App\Models\Accounting\Bill;
7 9
 use Filament\Actions;
8 10
 use Filament\Resources\Pages\EditRecord;
9 11
 use Filament\Support\Enums\MaxWidth;
12
+use Illuminate\Database\Eloquent\Model;
10 13
 
11 14
 class EditBill extends EditRecord
12 15
 {
16
+    use ManagesLineItems;
13 17
     use RedirectToListPage;
14 18
 
15 19
     protected static string $resource = BillResource::class;
@@ -25,4 +29,28 @@ class EditBill extends EditRecord
25 29
     {
26 30
         return MaxWidth::Full;
27 31
     }
32
+
33
+    protected function handleRecordUpdate(Model $record, array $data): Model
34
+    {
35
+        /** @var Bill $record */
36
+        $lineItems = collect($data['lineItems'] ?? []);
37
+
38
+        $this->deleteRemovedLineItems($record, $lineItems);
39
+
40
+        $this->handleLineItems($record, $lineItems);
41
+
42
+        $totals = $this->updateDocumentTotals($record, $data);
43
+
44
+        $data = array_merge($data, $totals);
45
+
46
+        $record = parent::handleRecordUpdate($record, $data);
47
+
48
+        if (! $record->initialTransaction) {
49
+            $record->createInitialTransaction();
50
+        } else {
51
+            $record->updateInitialTransaction();
52
+        }
53
+
54
+        return $record;
55
+    }
28 56
 }

+ 1
- 1
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/CreateInvoice.php 查看文件

@@ -29,7 +29,7 @@ class CreateInvoice extends CreateRecord
29 29
 
30 30
         $this->handleLineItems($record, collect($data['lineItems'] ?? []));
31 31
 
32
-        $totals = $this->updateInvoiceTotals($record, $data);
32
+        $totals = $this->updateDocumentTotals($record, $data);
33 33
 
34 34
         $record->updateQuietly($totals);
35 35
 

+ 1
- 1
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/EditInvoice.php 查看文件

@@ -39,7 +39,7 @@ class EditInvoice extends EditRecord
39 39
 
40 40
         $this->handleLineItems($record, $lineItems);
41 41
 
42
-        $totals = $this->updateInvoiceTotals($record, $data);
42
+        $totals = $this->updateDocumentTotals($record, $data);
43 43
 
44 44
         $data = array_merge($data, $totals);
45 45
 

+ 37
- 0
app/Filament/Forms/Components/BillTotals.php 查看文件

@@ -0,0 +1,37 @@
1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use App\Enums\Accounting\AdjustmentComputation;
6
+use Filament\Forms\Components\Grid;
7
+use Filament\Forms\Components\Select;
8
+use Filament\Forms\Components\TextInput;
9
+use Filament\Forms\Get;
10
+
11
+class BillTotals extends Grid
12
+{
13
+    protected string $view = 'filament.forms.components.bill-totals';
14
+
15
+    protected function setUp(): void
16
+    {
17
+        parent::setUp();
18
+
19
+        $this->schema([
20
+            TextInput::make('discount_rate')
21
+                ->label('Discount Rate')
22
+                ->hiddenLabel()
23
+                ->live()
24
+                ->rate(computation: static fn (Get $get) => $get('discount_computation'), showAffix: false),
25
+            Select::make('discount_computation')
26
+                ->label('Discount Computation')
27
+                ->hiddenLabel()
28
+                ->options([
29
+                    'percentage' => '%',
30
+                    'fixed' => '$',
31
+                ])
32
+                ->default(AdjustmentComputation::Percentage)
33
+                ->selectablePlaceholder(false)
34
+                ->live(),
35
+        ]);
36
+    }
37
+}

+ 5
- 0
app/Models/Accounting/Account.php 查看文件

@@ -138,6 +138,11 @@ class Account extends Model
138 138
         return self::where('name', 'Sales Discount')->firstOrFail();
139 139
     }
140 140
 
141
+    public static function getPurchaseDiscountAccount(): self
142
+    {
143
+        return self::where('name', 'Purchase Discount')->firstOrFail();
144
+    }
145
+
141 146
     protected static function newFactory(): Factory
142 147
     {
143 148
         return AccountFactory::new();

+ 29
- 1
app/Models/Accounting/Bill.php 查看文件

@@ -14,6 +14,7 @@ use App\Enums\Accounting\TransactionType;
14 14
 use App\Filament\Company\Resources\Purchases\BillResource;
15 15
 use App\Models\Common\Vendor;
16 16
 use App\Observers\BillObserver;
17
+use App\Utilities\Currency\CurrencyConverter;
17 18
 use Filament\Actions\MountableAction;
18 19
 use Filament\Actions\ReplicateAction;
19 20
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
@@ -224,7 +225,11 @@ class Bill extends Model
224 225
             'description' => $baseDescription,
225 226
         ]);
226 227
 
227
-        foreach ($this->lineItems as $lineItem) {
228
+        $totalLineItemSubtotal = (int) $this->lineItems()->sum('subtotal');
229
+        $billDiscountTotalCents = (int) $this->getRawOriginal('discount_total');
230
+        $remainingDiscountCents = $billDiscountTotalCents;
231
+
232
+        foreach ($this->lineItems as $index => $lineItem) {
228 233
             $lineItemDescription = "{$baseDescription} › {$lineItem->offering->name}";
229 234
 
230 235
             $transaction->journalEntries()->create([
@@ -254,6 +259,29 @@ class Bill extends Model
254 259
                     ]);
255 260
                 }
256 261
             }
262
+
263
+            if ($this->discount_method->isPerDocument() && $totalLineItemSubtotal > 0) {
264
+                $lineItemSubtotalCents = (int) $lineItem->getRawOriginal('subtotal');
265
+
266
+                if ($index === $this->lineItems->count() - 1) {
267
+                    $lineItemDiscount = $remainingDiscountCents;
268
+                } else {
269
+                    $lineItemDiscount = (int) round(
270
+                        ($lineItemSubtotalCents / $totalLineItemSubtotal) * $billDiscountTotalCents
271
+                    );
272
+                    $remainingDiscountCents -= $lineItemDiscount;
273
+                }
274
+
275
+                if ($lineItemDiscount > 0) {
276
+                    $transaction->journalEntries()->create([
277
+                        'company_id' => $this->company_id,
278
+                        'type' => JournalEntryType::Credit,
279
+                        'account_id' => Account::getPurchaseDiscountAccount()->id,
280
+                        'amount' => CurrencyConverter::convertCentsToFormatSimple($lineItemDiscount),
281
+                        'description' => "{$lineItemDescription} (Proportional Discount)",
282
+                    ]);
283
+                }
284
+            }
257 285
         }
258 286
     }
259 287
 

+ 27
- 11
app/View/Models/BillTotalViewModel.php 查看文件

@@ -2,6 +2,8 @@
2 2
 
3 3
 namespace App\View\Models;
4 4
 
5
+use App\Enums\Accounting\AdjustmentComputation;
6
+use App\Enums\Accounting\DocumentDiscountMethod;
5 7
 use App\Models\Accounting\Adjustment;
6 8
 use App\Models\Accounting\Bill;
7 9
 use App\Utilities\Currency\CurrencyConverter;
@@ -17,7 +19,7 @@ class BillTotalViewModel
17 19
     {
18 20
         $lineItems = collect($this->data['lineItems'] ?? []);
19 21
 
20
-        $subtotal = $lineItems->sum(function ($item) {
22
+        $subtotal = $lineItems->sum(static function ($item) {
21 23
             $quantity = max((float) ($item['quantity'] ?? 0), 0);
22 24
             $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
23 25
 
@@ -37,18 +39,32 @@ class BillTotalViewModel
37 39
             return $carry + $taxAmount;
38 40
         }, 0);
39 41
 
40
-        $discountTotal = $lineItems->reduce(function ($carry, $item) {
41
-            $quantity = max((float) ($item['quantity'] ?? 0), 0);
42
-            $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
43
-            $purchaseDiscounts = $item['purchaseDiscounts'] ?? [];
44
-            $lineTotal = $quantity * $unitPrice;
42
+        // Calculate discount based on method
43
+        $discountMethod = DocumentDiscountMethod::parse($this->data['discount_method']) ?? DocumentDiscountMethod::PerLineItem;
45 44
 
46
-            $discountAmount = Adjustment::whereIn('id', $purchaseDiscounts)
47
-                ->pluck('rate')
48
-                ->sum(fn ($rate) => $lineTotal * ($rate / 100));
45
+        if ($discountMethod->isPerLineItem()) {
46
+            $discountTotal = $lineItems->reduce(function ($carry, $item) {
47
+                $quantity = max((float) ($item['quantity'] ?? 0), 0);
48
+                $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
49
+                $purchaseDiscounts = $item['purchaseDiscounts'] ?? [];
50
+                $lineTotal = $quantity * $unitPrice;
49 51
 
50
-            return $carry + $discountAmount;
51
-        }, 0);
52
+                $discountAmount = Adjustment::whereIn('id', $purchaseDiscounts)
53
+                    ->pluck('rate')
54
+                    ->sum(fn ($rate) => $lineTotal * ($rate / 100));
55
+
56
+                return $carry + $discountAmount;
57
+            }, 0);
58
+        } else {
59
+            $discountComputation = AdjustmentComputation::parse($this->data['discount_computation']) ?? AdjustmentComputation::Percentage;
60
+            $discountRate = (float) ($this->data['discount_rate'] ?? 0);
61
+
62
+            if ($discountComputation->isPercentage()) {
63
+                $discountTotal = $subtotal * ($discountRate / 100);
64
+            } else {
65
+                $discountTotal = $discountRate;
66
+            }
67
+        }
52 68
 
53 69
         $grandTotal = $subtotal + ($taxTotal - $discountTotal);
54 70
 

+ 45
- 18
resources/views/filament/forms/components/bill-totals.blade.php 查看文件

@@ -1,34 +1,61 @@
1
-@use('App\Utilities\Currency\CurrencyAccessor')
2
-
3 1
 @php
2
+    use App\Enums\Accounting\DocumentDiscountMethod;
3
+    use App\Utilities\Currency\CurrencyAccessor;
4
+    use App\View\Models\BillTotalViewModel;
5
+
4 6
     $data = $this->form->getRawState();
5
-    $viewModel = new \App\View\Models\BillTotalViewModel($this->record, $data);
6
-    extract($viewModel->buildViewData(), \EXTR_SKIP);
7
+    $viewModel = new BillTotalViewModel($this->record, $data);
8
+    extract($viewModel->buildViewData(), EXTR_SKIP);
9
+
10
+    $discountMethod = DocumentDiscountMethod::parse($data['discount_method']);
11
+    $isPerDocumentDiscount = $discountMethod->isPerDocument();
7 12
 @endphp
8 13
 
9 14
 <div class="totals-summary w-full pr-14">
10 15
     <table class="w-full text-right table-fixed">
16
+        <colgroup>
17
+            <col class="w-[20%]"> {{-- Items --}}
18
+            <col class="w-[30%]"> {{-- Description --}}
19
+            <col class="w-[10%]"> {{-- Quantity --}}
20
+            <col class="w-[10%]"> {{-- Price --}}
21
+            <col class="w-[20%]"> {{-- Taxes --}}
22
+            <col class="w-[10%]"> {{-- Amount --}}
23
+        </colgroup>
11 24
         <tbody>
12 25
             <tr>
13
-                <td class="w-2/3 text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Subtotal:</td>
14
-                <td class="w-1/3 text-sm pl-4 py-2 leading-6">{{ $subtotal }}</td>
15
-            </tr>
16
-            <tr>
17
-                <td class="w-2/3 text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Taxes:</td>
18
-                <td class="w-1/3 text-sm pl-4 py-2 leading-6">{{ $taxTotal }}</td>
26
+                <td colspan="4"></td>
27
+                <td class="text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Subtotal:</td>
28
+                <td class="text-sm pl-4 py-2 leading-6">{{ $subtotal }}</td>
19 29
             </tr>
20 30
             <tr>
21
-                <td class="w-2/3 text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Discounts:</td>
22
-                <td class="w-1/3 text-sm pl-4 py-2 leading-6">({{ $discountTotal }})</td>
31
+                <td colspan="4"></td>
32
+                <td class="text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Taxes:</td>
33
+                <td class="text-sm pl-4 py-2 leading-6">{{ $taxTotal }}</td>
23 34
             </tr>
35
+            @if($isPerDocumentDiscount)
36
+                <tr>
37
+                    <td colspan="4" class="text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white text-right">Discount:</td>
38
+                    <td class="text-sm px-4 py-2">
39
+                        <div class="flex justify-between space-x-2">
40
+                            @foreach($getChildComponentContainer()->getComponents() as $component)
41
+                                <div class="flex-1">{{ $component }}</div>
42
+                            @endforeach
43
+                        </div>
44
+                    </td>
45
+                    <td class="text-sm pl-4 py-2 leading-6">({{ $discountTotal }})</td>
46
+                </tr>
47
+            @else
48
+                <tr>
49
+                    <td colspan="4"></td>
50
+                    <td class="text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Discounts:</td>
51
+                    <td class="text-sm pl-4 py-2 leading-6">({{ $discountTotal }})</td>
52
+                </tr>
53
+            @endif
24 54
             <tr class="font-semibold">
25
-                <td class="w-2/3 text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Total:</td>
26
-                <td class="w-1/3 text-sm pl-4 py-2 leading-6">{{ $grandTotal }}</td>
55
+                <td colspan="4"></td>
56
+                <td class="text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Total:</td>
57
+                <td class="text-sm pl-4 py-2 leading-6">{{ $grandTotal }}</td>
27 58
             </tr>
28 59
         </tbody>
29 60
     </table>
30 61
 </div>
31
-
32
-
33
-
34
-

正在加载...
取消
保存