|
@@ -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(),
|