Andrew Wallo il y a 10 mois
Parent
révision
dc0fc6f1d1

+ 46
- 0
app/Casts/DocumentMoneyCast.php Voir le fichier

@@ -0,0 +1,46 @@
1
+<?php
2
+
3
+namespace App\Casts;
4
+
5
+use App\Utilities\Currency\CurrencyAccessor;
6
+use App\Utilities\Currency\CurrencyConverter;
7
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
8
+use Illuminate\Database\Eloquent\Model;
9
+use UnexpectedValueException;
10
+
11
+class DocumentMoneyCast implements CastsAttributes
12
+{
13
+    /**
14
+     * Cast the given value.
15
+     *
16
+     * @param  array<string, mixed>  $attributes
17
+     */
18
+    public function get(Model $model, string $key, mixed $value, array $attributes): mixed
19
+    {
20
+        $currency_code = $attributes['currency_code'] ?? CurrencyAccessor::getDefaultCurrency();
21
+
22
+        if ($value !== null) {
23
+            return CurrencyConverter::convertCentsToFloat($value, $currency_code);
24
+        }
25
+
26
+        return 0.0;
27
+    }
28
+
29
+    /**
30
+     * Prepare the given value for storage.
31
+     *
32
+     * @param  array<string, mixed>  $attributes
33
+     */
34
+    public function set(Model $model, string $key, mixed $value, array $attributes): mixed
35
+    {
36
+        $currency_code = $attributes['currency_code'] ?? CurrencyAccessor::getDefaultCurrency();
37
+
38
+        if (is_numeric($value)) {
39
+            $value = (string) $value;
40
+        } elseif (! is_string($value)) {
41
+            throw new UnexpectedValueException('Expected string or numeric value for money cast');
42
+        }
43
+
44
+        return CurrencyConverter::prepareForAccessor($value, $currency_code);
45
+    }
46
+}

+ 60
- 0
app/Enums/Accounting/DocumentStatus.php Voir le fichier

@@ -0,0 +1,60 @@
1
+<?php
2
+
3
+namespace App\Enums\Accounting;
4
+
5
+use Filament\Support\Contracts\HasColor;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum DocumentStatus: string implements HasColor, HasLabel
9
+{
10
+    case Draft = 'draft';
11
+    case Sent = 'sent';
12
+
13
+    case Partial = 'partial';
14
+
15
+    case Paid = 'paid';
16
+
17
+    case Overdue = 'overdue';
18
+
19
+    case Void = 'void';
20
+
21
+    case Unpaid = 'unpaid';
22
+
23
+    public function getInvoiceStatuses(): array
24
+    {
25
+        return [
26
+            self::Draft,
27
+            self::Sent,
28
+            self::Partial,
29
+            self::Paid,
30
+            self::Overdue,
31
+            self::Void,
32
+        ];
33
+    }
34
+
35
+    public function getBillStatuses(): array
36
+    {
37
+        return [
38
+            self::Partial,
39
+            self::Paid,
40
+            self::Unpaid,
41
+            self::Void,
42
+        ];
43
+    }
44
+
45
+    public function getLabel(): ?string
46
+    {
47
+        return $this->name;
48
+    }
49
+
50
+    public function getColor(): string | array | null
51
+    {
52
+        return match ($this) {
53
+            self::Draft, self::Void => 'gray',
54
+            self::Sent => 'primary',
55
+            self::Partial, self::Unpaid => 'warning',
56
+            self::Paid => 'success',
57
+            self::Overdue => 'danger',
58
+        };
59
+    }
60
+}

+ 139
- 41
app/Filament/Company/Resources/Accounting/DocumentResource.php Voir le fichier

@@ -6,10 +6,12 @@ use App\Enums\Accounting\AdjustmentCategory;
6 6
 use App\Filament\Company\Resources\Accounting\DocumentResource\Pages;
7 7
 use App\Models\Accounting\Adjustment;
8 8
 use App\Models\Accounting\Document;
9
+use App\Models\Accounting\DocumentLineItem;
9 10
 use App\Models\Common\Offering;
10 11
 use App\Utilities\Currency\CurrencyAccessor;
11 12
 use Awcodes\TableRepeater\Components\TableRepeater;
12 13
 use Awcodes\TableRepeater\Header;
14
+use Carbon\CarbonInterface;
13 15
 use Filament\Forms;
14 16
 use Filament\Forms\Components\FileUpload;
15 17
 use Filament\Forms\Form;
@@ -17,6 +19,8 @@ use Filament\Resources\Resource;
17 19
 use Filament\Support\Enums\MaxWidth;
18 20
 use Filament\Tables;
19 21
 use Filament\Tables\Table;
22
+use Illuminate\Database\Eloquent\Model;
23
+use Illuminate\Support\Carbon;
20 24
 use Illuminate\Support\Facades\Auth;
21 25
 use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
22 26
 
@@ -88,7 +92,7 @@ class DocumentResource extends Resource
88 92
                             Forms\Components\Group::make([
89 93
                                 Forms\Components\TextInput::make('document_number')
90 94
                                     ->label('Invoice Number')
91
-                                    ->default(fn () => $company->defaultInvoice->getNumberNext()),
95
+                                    ->default(fn () => Document::getNextDocumentNumber()),
92 96
                                 Forms\Components\TextInput::make('order_number')
93 97
                                     ->label('P.O/S.O Number'),
94 98
                                 Forms\Components\DatePicker::make('date')
@@ -103,28 +107,96 @@ class DocumentResource extends Resource
103 107
                         ])->from('md'),
104 108
                         TableRepeater::make('lineItems')
105 109
                             ->relationship()
106
-                            ->saveRelationshipsUsing(function (Document $document, array $state) {
107
-                                $document->lineItems()->delete();
108
-
109
-                                collect($state)->map(function ($lineItemData) use ($document) {
110
-                                    $lineItem = $document->lineItems()->create([
111
-                                        'offering_id' => $lineItemData['offering_id'],
112
-                                        'description' => $lineItemData['description'],
113
-                                        'quantity' => $lineItemData['quantity'],
114
-                                        'unit_price' => $lineItemData['unit_price'],
115
-                                        'tax_total' => collect($lineItemData['salesTaxes'] ?? [])->sum(function ($taxId) use ($lineItemData) {
116
-                                            $tax = Adjustment::find($taxId);
117
-
118
-                                            return $tax ? ($lineItemData['quantity'] * $lineItemData['unit_price']) * ($tax->rate / 100) : 0;
119
-                                        }),
120
-                                    ]);
110
+                            ->saveRelationshipsUsing(function (TableRepeater $component, Forms\Contracts\HasForms $livewire, ?array $state) {
111
+                                if (! is_array($state)) {
112
+                                    $state = [];
113
+                                }
114
+
115
+                                $relationship = $component->getRelationship();
116
+
117
+                                $existingRecords = $component->getCachedExistingRecords();
118
+
119
+                                $recordsToDelete = [];
120
+
121
+                                foreach ($existingRecords->pluck($relationship->getRelated()->getKeyName()) as $keyToCheckForDeletion) {
122
+                                    if (array_key_exists("record-{$keyToCheckForDeletion}", $state)) {
123
+                                        continue;
124
+                                    }
125
+
126
+                                    $recordsToDelete[] = $keyToCheckForDeletion;
127
+                                    $existingRecords->forget("record-{$keyToCheckForDeletion}");
128
+                                }
129
+
130
+                                $relationship
131
+                                    ->whereKey($recordsToDelete)
132
+                                    ->get()
133
+                                    ->each(static fn (Model $record) => $record->delete());
134
+
135
+                                $childComponentContainers = $component->getChildComponentContainers(
136
+                                    withHidden: $component->shouldSaveRelationshipsWhenHidden(),
137
+                                );
138
+
139
+                                $itemOrder = 1;
140
+                                $orderColumn = $component->getOrderColumn();
141
+
142
+                                $translatableContentDriver = $livewire->makeFilamentTranslatableContentDriver();
143
+
144
+                                foreach ($childComponentContainers as $itemKey => $item) {
145
+                                    $itemData = $item->getState(shouldCallHooksBefore: false);
146
+
147
+                                    if ($orderColumn) {
148
+                                        $itemData[$orderColumn] = $itemOrder;
149
+
150
+                                        $itemOrder++;
151
+                                    }
152
+
153
+                                    if ($record = ($existingRecords[$itemKey] ?? null)) {
154
+                                        $itemData = $component->mutateRelationshipDataBeforeSave($itemData, record: $record);
155
+
156
+                                        if ($itemData === null) {
157
+                                            continue;
158
+                                        }
159
+
160
+                                        $translatableContentDriver ?
161
+                                            $translatableContentDriver->updateRecord($record, $itemData) :
162
+                                            $record->fill($itemData)->save();
163
+
164
+                                        continue;
165
+                                    }
166
+
167
+                                    $relatedModel = $component->getRelatedModel();
121 168
 
122
-                                    $lineItem->taxes()->sync($lineItemData['salesTaxes'] ?? []);
169
+                                    $itemData = $component->mutateRelationshipDataBeforeCreate($itemData);
123 170
 
124
-                                    return $lineItem;
171
+                                    if ($itemData === null) {
172
+                                        continue;
173
+                                    }
174
+
175
+                                    if ($translatableContentDriver) {
176
+                                        $record = $translatableContentDriver->makeRecord($relatedModel, $itemData);
177
+                                    } else {
178
+                                        $record = new $relatedModel;
179
+                                        $record->fill($itemData);
180
+                                    }
181
+
182
+                                    $record = $relationship->save($record);
183
+                                    $item->model($record)->saveRelationships();
184
+                                    $existingRecords->push($record);
185
+                                }
186
+
187
+                                $component->getRecord()->setRelation($component->getRelationshipName(), $existingRecords);
188
+
189
+                                /** @var Document $document */
190
+                                $document = $component->getRecord();
191
+
192
+                                // Recalculate totals for line items
193
+                                $document->lineItems()->each(function (DocumentLineItem $lineItem) {
194
+                                    $lineItem->updateQuietly([
195
+                                        'tax_total' => $lineItem->calculateTaxTotal()->getAmount(),
196
+                                        'discount_total' => $lineItem->calculateDiscountTotal()->getAmount(),
197
+                                    ]);
125 198
                                 });
126 199
 
127
-                                $document->refresh();
128 200
                                 $subtotal = $document->lineItems()->sum('subtotal') / 100;
129 201
                                 $taxTotal = $document->lineItems()->sum('tax_total') / 100;
130 202
                                 $discountTotal = $document->lineItems()->sum('discount_total') / 100;
@@ -138,11 +210,12 @@ class DocumentResource extends Resource
138 210
                                 ]);
139 211
                             })
140 212
                             ->headers([
141
-                                Header::make('Items')->width('20%'),
142
-                                Header::make('Description')->width('30%'),
213
+                                Header::make('Items')->width('15%'),
214
+                                Header::make('Description')->width('25%'),
143 215
                                 Header::make('Quantity')->width('10%'),
144 216
                                 Header::make('Price')->width('10%'),
145
-                                Header::make('Taxes')->width('20%'),
217
+                                Header::make('Taxes')->width('15%'),
218
+                                Header::make('Discounts')->width('15%'),
146 219
                                 Header::make('Amount')->width('10%')->align('right'),
147 220
                             ])
148 221
                             ->live()
@@ -160,13 +233,7 @@ class DocumentResource extends Resource
160 233
                                             $set('description', $offeringRecord->description);
161 234
                                             $set('unit_price', $offeringRecord->price);
162 235
                                             $set('salesTaxes', $offeringRecord->salesTaxes->pluck('id')->toArray());
163
-
164
-                                            $quantity = $get('quantity');
165
-                                            $total = $quantity * $offeringRecord->price;
166
-
167
-                                            // Calculate taxes and update total
168
-                                            $taxAmount = $offeringRecord->salesTaxes->sum(fn ($tax) => $total * ($tax->rate / 100));
169
-                                            $set('total', $total + $taxAmount);
236
+                                            $set('salesDiscounts', $offeringRecord->salesDiscounts->pluck('id')->toArray());
170 237
                                         }
171 238
                                     }),
172 239
                                 Forms\Components\TextInput::make('description'),
@@ -183,27 +250,39 @@ class DocumentResource extends Resource
183 250
                                     ->preload()
184 251
                                     ->multiple()
185 252
                                     ->searchable(),
253
+                                Forms\Components\Select::make('salesDiscounts')
254
+                                    ->relationship('salesDiscounts', 'name')
255
+                                    ->preload()
256
+                                    ->multiple()
257
+                                    ->searchable(),
186 258
                                 Forms\Components\Placeholder::make('total')
187 259
                                     ->hiddenLabel()
188 260
                                     ->content(function (Forms\Get $get) {
189 261
                                         $quantity = $get('quantity') ?? 0;
190 262
                                         $unitPrice = $get('unit_price') ?? 0;
191 263
                                         $salesTaxes = $get('salesTaxes') ?? [];
264
+                                        $salesDiscounts = $get('salesDiscounts') ?? [];
192 265
 
193
-                                        $total = $quantity * $unitPrice;
266
+                                        // Base total (subtotal)
267
+                                        $subtotal = $quantity * $unitPrice;
194 268
 
269
+                                        // Calculate tax amount based on subtotal
270
+                                        $taxAmount = 0;
195 271
                                         if (! empty($salesTaxes)) {
196 272
                                             $taxRates = Adjustment::whereIn('id', $salesTaxes)->pluck('rate');
273
+                                            $taxAmount = collect($taxRates)->sum(fn ($rate) => $subtotal * ($rate / 100));
274
+                                        }
197 275
 
198
-                                            $taxAmount = $taxRates->sum(function ($rate) use ($total) {
199
-                                                return $total * ($rate / 100);
200
-                                            });
201
-
202
-                                            $total += $taxAmount;
203
-
204
-                                            return money($total, CurrencyAccessor::getDefaultCurrency(), true)->format();
276
+                                        // Calculate discount amount based on subtotal
277
+                                        $discountAmount = 0;
278
+                                        if (! empty($salesDiscounts)) {
279
+                                            $discountRates = Adjustment::whereIn('id', $salesDiscounts)->pluck('rate');
280
+                                            $discountAmount = collect($discountRates)->sum(fn ($rate) => $subtotal * ($rate / 100));
205 281
                                         }
206 282
 
283
+                                        // Final total
284
+                                        $total = $subtotal + ($taxAmount - $discountAmount);
285
+
207 286
                                         return money($total, CurrencyAccessor::getDefaultCurrency(), true)->format();
208 287
                                     }),
209 288
                             ]),
@@ -316,9 +395,26 @@ class DocumentResource extends Resource
316 395
         return $table
317 396
             ->columns([
318 397
                 Tables\Columns\TextColumn::make('status')
398
+                    ->badge()
319 399
                     ->searchable(),
320 400
                 Tables\Columns\TextColumn::make('due_date')
321
-                    ->date()
401
+                    ->label('Due')
402
+                    ->formatStateUsing(function (Tables\Columns\TextColumn $column, mixed $state) {
403
+                        if (blank($state)) {
404
+                            return null;
405
+                        }
406
+
407
+                        $date = Carbon::parse($state)
408
+                            ->setTimezone($timezone ?? $column->getTimezone());
409
+
410
+                        if ($date->isToday()) {
411
+                            return 'Today';
412
+                        }
413
+
414
+                        return $date->diffForHumans([
415
+                            'options' => CarbonInterface::ONE_DAY_WORDS,
416
+                        ]);
417
+                    })
322 418
                     ->sortable(),
323 419
                 Tables\Columns\TextColumn::make('date')
324 420
                     ->date()
@@ -330,11 +426,13 @@ class DocumentResource extends Resource
330 426
                     ->numeric()
331 427
                     ->sortable(),
332 428
                 Tables\Columns\TextColumn::make('total')
333
-                    ->money(),
429
+                    ->currency(),
334 430
                 Tables\Columns\TextColumn::make('amount_paid')
335
-                    ->money(),
431
+                    ->label('Amount Paid')
432
+                    ->currency(),
336 433
                 Tables\Columns\TextColumn::make('amount_due')
337
-                    ->money(),
434
+                    ->label('Amount Due')
435
+                    ->currency(),
338 436
             ])
339 437
             ->filters([
340 438
                 //

+ 3
- 5
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/EditInvoice.php Voir le fichier

@@ -5,7 +5,7 @@ namespace App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
5 5
 use App\Filament\Company\Resources\Sales\InvoiceResource;
6 6
 use Filament\Actions;
7 7
 use Filament\Resources\Pages\EditRecord;
8
-use Illuminate\Database\Eloquent\Model;
8
+use Filament\Support\Enums\MaxWidth;
9 9
 
10 10
 class EditInvoice extends EditRecord
11 11
 {
@@ -18,10 +18,8 @@ class EditInvoice extends EditRecord
18 18
         ];
19 19
     }
20 20
 
21
-    protected function handleRecordUpdate(Model $record, array $data): Model
21
+    public function getMaxContentWidth(): MaxWidth | string | null
22 22
     {
23
-        ray($data);
24
-
25
-        return parent::handleRecordUpdate($record, $data); // TODO: Change the autogenerated stub
23
+        return MaxWidth::Full;
26 24
     }
27 25
 }

+ 36
- 0
app/Models/Accounting/Document.php Voir le fichier

@@ -5,6 +5,7 @@ namespace App\Models\Accounting;
5 5
 use App\Casts\MoneyCast;
6 6
 use App\Concerns\Blamable;
7 7
 use App\Concerns\CompanyOwned;
8
+use App\Enums\Accounting\DocumentStatus;
8 9
 use App\Enums\Accounting\DocumentType;
9 10
 use App\Models\Banking\Payment;
10 11
 use App\Models\Common\Client;
@@ -54,6 +55,7 @@ class Document extends Model
54 55
         'type' => DocumentType::class,
55 56
         'date' => 'date',
56 57
         'due_date' => 'date',
58
+        'status' => DocumentStatus::class,
57 59
         'subtotal' => MoneyCast::class,
58 60
         'tax_total' => MoneyCast::class,
59 61
         'discount_total' => MoneyCast::class,
@@ -81,4 +83,38 @@ class Document extends Model
81 83
     {
82 84
         return $this->hasMany(Payment::class);
83 85
     }
86
+
87
+    public static function getNextDocumentNumber(DocumentType $documentType = DocumentType::Invoice): string
88
+    {
89
+        $company = auth()->user()->currentCompany;
90
+
91
+        if (! $company) {
92
+            throw new \RuntimeException('No current company is set for the user.');
93
+        }
94
+
95
+        $defaultInvoiceSettings = $company->defaultInvoice;
96
+
97
+        $numberPrefix = $defaultInvoiceSettings->number_prefix;
98
+        $numberDigits = $defaultInvoiceSettings->number_digits;
99
+
100
+        $latestDocument = static::query()
101
+            ->whereNotNull('document_number')
102
+            ->where('type', $documentType)
103
+            ->latest('document_number')
104
+            ->first();
105
+
106
+        $lastNumberNumericPart = $latestDocument
107
+            ? (int) substr($latestDocument->document_number, strlen($numberPrefix))
108
+            : 0;
109
+
110
+        $numberNext = $lastNumberNumericPart + 1;
111
+
112
+        return $defaultInvoiceSettings->getNumberNext(
113
+            padded: true,
114
+            format: true,
115
+            prefix: $numberPrefix,
116
+            digits: $numberDigits,
117
+            next: $numberNext
118
+        );
119
+    }
84 120
 }

+ 24
- 1
app/Models/Accounting/DocumentLineItem.php Voir le fichier

@@ -2,6 +2,8 @@
2 2
 
3 3
 namespace App\Models\Accounting;
4 4
 
5
+use Akaunting\Money\Money;
6
+use App\Casts\DocumentMoneyCast;
5 7
 use App\Casts\MoneyCast;
6 8
 use App\Concerns\Blamable;
7 9
 use App\Concerns\CompanyOwned;
@@ -9,6 +11,7 @@ use App\Enums\Accounting\AdjustmentCategory;
9 11
 use App\Enums\Accounting\AdjustmentType;
10 12
 use App\Models\Common\Offering;
11 13
 use App\Observers\DocumentLineItemObserver;
14
+use App\Utilities\Currency\CurrencyAccessor;
12 15
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
13 16
 use Illuminate\Database\Eloquent\Factories\HasFactory;
14 17
 use Illuminate\Database\Eloquent\Model;
@@ -39,7 +42,7 @@ class DocumentLineItem extends Model
39 42
 
40 43
     protected $casts = [
41 44
         'unit_price' => MoneyCast::class,
42
-        'subtotal' => MoneyCast::class,
45
+        'subtotal' => DocumentMoneyCast::class,
43 46
         'tax_total' => MoneyCast::class,
44 47
         'discount_total' => MoneyCast::class,
45 48
         'total' => MoneyCast::class,
@@ -79,4 +82,24 @@ class DocumentLineItem extends Model
79 82
     {
80 83
         return $this->adjustments()->where('category', AdjustmentCategory::Discount);
81 84
     }
85
+
86
+    public function calculateTaxTotal(): Money
87
+    {
88
+        $subtotal = money($this->subtotal, CurrencyAccessor::getDefaultCurrency());
89
+
90
+        return $this->taxes->reduce(
91
+            fn (Money $carry, Adjustment $tax) => $carry->add($subtotal->multiply($tax->rate / 100)),
92
+            money(0, CurrencyAccessor::getDefaultCurrency())
93
+        );
94
+    }
95
+
96
+    public function calculateDiscountTotal(): Money
97
+    {
98
+        $subtotal = money($this->subtotal, CurrencyAccessor::getDefaultCurrency());
99
+
100
+        return $this->discounts->reduce(
101
+            fn (Money $carry, Adjustment $discount) => $carry->add($subtotal->multiply($discount->rate / 100)),
102
+            money(0, CurrencyAccessor::getDefaultCurrency())
103
+        );
104
+    }
82 105
 }

+ 1
- 1
app/Observers/DocumentLineItemObserver.php Voir le fichier

@@ -27,7 +27,7 @@ class DocumentLineItemObserver
27 27
      */
28 28
     public function deleted(DocumentLineItem $documentLineItem): void
29 29
     {
30
-        //
30
+        $documentLineItem->adjustments()->detach();
31 31
     }
32 32
 
33 33
     /**

+ 3
- 0
app/Providers/MacroServiceProvider.php Voir le fichier

@@ -81,6 +81,9 @@ class MacroServiceProvider extends ServiceProvider
81 81
         });
82 82
 
83 83
         TextColumn::macro('currency', function (string | Closure | null $currency = null, ?bool $convert = null): static {
84
+            $currency ??= CurrencyAccessor::getDefaultCurrency();
85
+            $convert ??= true;
86
+
84 87
             $this->formatStateUsing(static function (TextColumn $column, $state) use ($currency, $convert): ?string {
85 88
                 if (blank($state)) {
86 89
                     return null;

+ 7
- 0
app/Utilities/Currency/CurrencyConverter.php Voir le fichier

@@ -56,6 +56,13 @@ class CurrencyConverter
56 56
         return money($amount, $currency, true)->format();
57 57
     }
58 58
 
59
+    public static function convertCentsToFloat(int $amount, ?string $currency = null): float
60
+    {
61
+        $currency ??= CurrencyAccessor::getDefaultCurrency();
62
+
63
+        return money($amount, $currency)->getValue();
64
+    }
65
+
59 66
     public static function handleCurrencyChange(Set $set, $state): void
60 67
     {
61 68
         $currency = currency($state);

+ 16
- 1
app/View/Models/InvoiceTotalViewModel.php Voir le fichier

@@ -32,15 +32,30 @@ class InvoiceTotalViewModel
32 32
             return $carry + $taxAmount;
33 33
         }, 0);
34 34
 
35
-        $grandTotal = $subtotal + $taxTotal;
35
+        $discountTotal = $lineItems->reduce(function ($carry, $item) {
36
+            $quantity = $item['quantity'] ?? 0;
37
+            $unitPrice = $item['unit_price'] ?? 0;
38
+            $salesDiscounts = $item['salesDiscounts'] ?? [];
39
+            $lineTotal = $quantity * $unitPrice;
40
+
41
+            $discountAmount = Adjustment::whereIn('id', $salesDiscounts)
42
+                ->pluck('rate')
43
+                ->sum(fn ($rate) => $lineTotal * ($rate / 100));
44
+
45
+            return $carry + $discountAmount;
46
+        }, 0);
47
+
48
+        $grandTotal = $subtotal + ($taxTotal - $discountTotal);
36 49
 
37 50
         $subTotalFormatted = CurrencyConverter::formatToMoney($subtotal);
38 51
         $taxTotalFormatted = CurrencyConverter::formatToMoney($taxTotal);
52
+        $discountTotalFormatted = CurrencyConverter::formatToMoney($discountTotal);
39 53
         $grandTotalFormatted = CurrencyConverter::formatToMoney($grandTotal);
40 54
 
41 55
         return [
42 56
             'subtotal' => $subTotalFormatted,
43 57
             'taxTotal' => $taxTotalFormatted,
58
+            'discountTotal' => $discountTotalFormatted,
44 59
             'grandTotal' => $grandTotalFormatted,
45 60
         ];
46 61
     }

+ 79
- 75
composer.lock Voir le fichier

@@ -497,16 +497,16 @@
497 497
         },
498 498
         {
499 499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.330.1",
500
+            "version": "3.330.2",
501 501
             "source": {
502 502
                 "type": "git",
503 503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "136749f15d1dbff07064ef5ba1c2f08b96cf78ff"
504
+                "reference": "4ac43cc8356fb16a494c3631c8f39f6e7555f00a"
505 505
             },
506 506
             "dist": {
507 507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/136749f15d1dbff07064ef5ba1c2f08b96cf78ff",
509
-                "reference": "136749f15d1dbff07064ef5ba1c2f08b96cf78ff",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4ac43cc8356fb16a494c3631c8f39f6e7555f00a",
509
+                "reference": "4ac43cc8356fb16a494c3631c8f39f6e7555f00a",
510 510
                 "shasum": ""
511 511
             },
512 512
             "require": {
@@ -589,9 +589,9 @@
589 589
             "support": {
590 590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
591 591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
592
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.330.1"
592
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.330.2"
593 593
             },
594
-            "time": "2024-11-25T19:20:00+00:00"
594
+            "time": "2024-11-26T19:07:56+00:00"
595 595
         },
596 596
         {
597 597
             "name": "aws/aws-sdk-php-laravel",
@@ -2852,16 +2852,16 @@
2852 2852
         },
2853 2853
         {
2854 2854
             "name": "kirschbaum-development/eloquent-power-joins",
2855
-            "version": "4.0.0",
2855
+            "version": "4.0.1",
2856 2856
             "source": {
2857 2857
                 "type": "git",
2858 2858
                 "url": "https://github.com/kirschbaum-development/eloquent-power-joins.git",
2859
-                "reference": "c6c42a52c5a097cc11761e72782b2d0215692caf"
2859
+                "reference": "3c1af9b86b02f1e39219849c1d2fee7cf77e8638"
2860 2860
             },
2861 2861
             "dist": {
2862 2862
                 "type": "zip",
2863
-                "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/c6c42a52c5a097cc11761e72782b2d0215692caf",
2864
-                "reference": "c6c42a52c5a097cc11761e72782b2d0215692caf",
2863
+                "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/3c1af9b86b02f1e39219849c1d2fee7cf77e8638",
2864
+                "reference": "3c1af9b86b02f1e39219849c1d2fee7cf77e8638",
2865 2865
                 "shasum": ""
2866 2866
             },
2867 2867
             "require": {
@@ -2909,9 +2909,9 @@
2909 2909
             ],
2910 2910
             "support": {
2911 2911
                 "issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues",
2912
-                "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.0.0"
2912
+                "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.0.1"
2913 2913
             },
2914
-            "time": "2024-10-06T12:28:14+00:00"
2914
+            "time": "2024-11-26T13:22:08+00:00"
2915 2915
         },
2916 2916
         {
2917 2917
             "name": "knplabs/knp-snappy",
@@ -2982,23 +2982,23 @@
2982 2982
         },
2983 2983
         {
2984 2984
             "name": "laravel/framework",
2985
-            "version": "v11.33.2",
2985
+            "version": "v11.34.1",
2986 2986
             "source": {
2987 2987
                 "type": "git",
2988 2988
                 "url": "https://github.com/laravel/framework.git",
2989
-                "reference": "6b9832751cf8eed18b3c73df5071f78f0682aa5d"
2989
+                "reference": "ed07324892c87277b7d37ba76b1a6f93a37401b5"
2990 2990
             },
2991 2991
             "dist": {
2992 2992
                 "type": "zip",
2993
-                "url": "https://api.github.com/repos/laravel/framework/zipball/6b9832751cf8eed18b3c73df5071f78f0682aa5d",
2994
-                "reference": "6b9832751cf8eed18b3c73df5071f78f0682aa5d",
2993
+                "url": "https://api.github.com/repos/laravel/framework/zipball/ed07324892c87277b7d37ba76b1a6f93a37401b5",
2994
+                "reference": "ed07324892c87277b7d37ba76b1a6f93a37401b5",
2995 2995
                 "shasum": ""
2996 2996
             },
2997 2997
             "require": {
2998 2998
                 "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12",
2999 2999
                 "composer-runtime-api": "^2.2",
3000 3000
                 "doctrine/inflector": "^2.0.5",
3001
-                "dragonmantank/cron-expression": "^3.3.2",
3001
+                "dragonmantank/cron-expression": "^3.4",
3002 3002
                 "egulias/email-validator": "^3.2.1|^4.0",
3003 3003
                 "ext-ctype": "*",
3004 3004
                 "ext-filter": "*",
@@ -3008,35 +3008,36 @@
3008 3008
                 "ext-session": "*",
3009 3009
                 "ext-tokenizer": "*",
3010 3010
                 "fruitcake/php-cors": "^1.3",
3011
-                "guzzlehttp/guzzle": "^7.8",
3011
+                "guzzlehttp/guzzle": "^7.8.2",
3012 3012
                 "guzzlehttp/uri-template": "^1.0",
3013 3013
                 "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
3014 3014
                 "laravel/serializable-closure": "^1.3|^2.0",
3015 3015
                 "league/commonmark": "^2.2.1",
3016
-                "league/flysystem": "^3.8.0",
3016
+                "league/flysystem": "^3.25.1",
3017
+                "league/flysystem-local": "^3.25.1",
3017 3018
                 "monolog/monolog": "^3.0",
3018
-                "nesbot/carbon": "^2.72.2|^3.0",
3019
+                "nesbot/carbon": "^2.72.2|^3.4",
3019 3020
                 "nunomaduro/termwind": "^2.0",
3020 3021
                 "php": "^8.2",
3021 3022
                 "psr/container": "^1.1.1|^2.0.1",
3022 3023
                 "psr/log": "^1.0|^2.0|^3.0",
3023 3024
                 "psr/simple-cache": "^1.0|^2.0|^3.0",
3024 3025
                 "ramsey/uuid": "^4.7",
3025
-                "symfony/console": "^7.0",
3026
-                "symfony/error-handler": "^7.0",
3027
-                "symfony/finder": "^7.0",
3028
-                "symfony/http-foundation": "^7.0",
3029
-                "symfony/http-kernel": "^7.0",
3030
-                "symfony/mailer": "^7.0",
3031
-                "symfony/mime": "^7.0",
3032
-                "symfony/polyfill-php83": "^1.28",
3033
-                "symfony/process": "^7.0",
3034
-                "symfony/routing": "^7.0",
3035
-                "symfony/uid": "^7.0",
3036
-                "symfony/var-dumper": "^7.0",
3026
+                "symfony/console": "^7.0.3",
3027
+                "symfony/error-handler": "^7.0.3",
3028
+                "symfony/finder": "^7.0.3",
3029
+                "symfony/http-foundation": "^7.0.3",
3030
+                "symfony/http-kernel": "^7.0.3",
3031
+                "symfony/mailer": "^7.0.3",
3032
+                "symfony/mime": "^7.0.3",
3033
+                "symfony/polyfill-php83": "^1.31",
3034
+                "symfony/process": "^7.0.3",
3035
+                "symfony/routing": "^7.0.3",
3036
+                "symfony/uid": "^7.0.3",
3037
+                "symfony/var-dumper": "^7.0.3",
3037 3038
                 "tijsverkoyen/css-to-inline-styles": "^2.2.5",
3038
-                "vlucas/phpdotenv": "^5.4.1",
3039
-                "voku/portable-ascii": "^2.0"
3039
+                "vlucas/phpdotenv": "^5.6.1",
3040
+                "voku/portable-ascii": "^2.0.2"
3040 3041
             },
3041 3042
             "conflict": {
3042 3043
                 "mockery/mockery": "1.6.8",
@@ -3086,29 +3087,32 @@
3086 3087
             },
3087 3088
             "require-dev": {
3088 3089
                 "ably/ably-php": "^1.0",
3089
-                "aws/aws-sdk-php": "^3.235.5",
3090
+                "aws/aws-sdk-php": "^3.322.9",
3090 3091
                 "ext-gmp": "*",
3091
-                "fakerphp/faker": "^1.23",
3092
-                "league/flysystem-aws-s3-v3": "^3.0",
3093
-                "league/flysystem-ftp": "^3.0",
3094
-                "league/flysystem-path-prefixing": "^3.3",
3095
-                "league/flysystem-read-only": "^3.3",
3096
-                "league/flysystem-sftp-v3": "^3.0",
3092
+                "fakerphp/faker": "^1.24",
3093
+                "guzzlehttp/promises": "^2.0.3",
3094
+                "guzzlehttp/psr7": "^2.4",
3095
+                "league/flysystem-aws-s3-v3": "^3.25.1",
3096
+                "league/flysystem-ftp": "^3.25.1",
3097
+                "league/flysystem-path-prefixing": "^3.25.1",
3098
+                "league/flysystem-read-only": "^3.25.1",
3099
+                "league/flysystem-sftp-v3": "^3.25.1",
3097 3100
                 "mockery/mockery": "^1.6.10",
3098 3101
                 "nyholm/psr7": "^1.2",
3099 3102
                 "orchestra/testbench-core": "^9.6",
3100
-                "pda/pheanstalk": "^5.0",
3103
+                "pda/pheanstalk": "^5.0.6",
3101 3104
                 "phpstan/phpstan": "^1.11.5",
3102
-                "phpunit/phpunit": "^10.5|^11.0",
3103
-                "predis/predis": "^2.0.2",
3105
+                "phpunit/phpunit": "^10.5.35|^11.3.6",
3106
+                "predis/predis": "^2.3",
3104 3107
                 "resend/resend-php": "^0.10.0",
3105
-                "symfony/cache": "^7.0",
3106
-                "symfony/http-client": "^7.0",
3107
-                "symfony/psr-http-message-bridge": "^7.0"
3108
+                "symfony/cache": "^7.0.3",
3109
+                "symfony/http-client": "^7.0.3",
3110
+                "symfony/psr-http-message-bridge": "^7.0.3",
3111
+                "symfony/translation": "^7.0.3"
3108 3112
             },
3109 3113
             "suggest": {
3110 3114
                 "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
3111
-                "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).",
3115
+                "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).",
3112 3116
                 "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).",
3113 3117
                 "ext-apcu": "Required to use the APC cache driver.",
3114 3118
                 "ext-fileinfo": "Required to use the Filesystem class.",
@@ -3122,16 +3126,16 @@
3122 3126
                 "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).",
3123 3127
                 "filp/whoops": "Required for friendly error pages in development (^2.14.3).",
3124 3128
                 "laravel/tinker": "Required to use the tinker console command (^2.0).",
3125
-                "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).",
3126
-                "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).",
3127
-                "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).",
3128
-                "league/flysystem-read-only": "Required to use read-only disks (^3.3)",
3129
-                "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).",
3129
+                "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).",
3130
+                "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).",
3131
+                "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).",
3132
+                "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)",
3133
+                "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).",
3130 3134
                 "mockery/mockery": "Required to use mocking (^1.6).",
3131 3135
                 "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).",
3132 3136
                 "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).",
3133 3137
                 "phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).",
3134
-                "predis/predis": "Required to use the predis connector (^2.0.2).",
3138
+                "predis/predis": "Required to use the predis connector (^2.3).",
3135 3139
                 "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
3136 3140
                 "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",
3137 3141
                 "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).",
@@ -3187,7 +3191,7 @@
3187 3191
                 "issues": "https://github.com/laravel/framework/issues",
3188 3192
                 "source": "https://github.com/laravel/framework"
3189 3193
             },
3190
-            "time": "2024-11-19T22:47:13+00:00"
3194
+            "time": "2024-11-26T21:32:01+00:00"
3191 3195
         },
3192 3196
         {
3193 3197
             "name": "laravel/prompts",
@@ -3250,16 +3254,16 @@
3250 3254
         },
3251 3255
         {
3252 3256
             "name": "laravel/sanctum",
3253
-            "version": "v4.0.4",
3257
+            "version": "v4.0.5",
3254 3258
             "source": {
3255 3259
                 "type": "git",
3256 3260
                 "url": "https://github.com/laravel/sanctum.git",
3257
-                "reference": "819782c75aaf2b08da1765503893bd2b8023d3b3"
3261
+                "reference": "fe361b9a63407a228f884eb78d7217f680b50140"
3258 3262
             },
3259 3263
             "dist": {
3260 3264
                 "type": "zip",
3261
-                "url": "https://api.github.com/repos/laravel/sanctum/zipball/819782c75aaf2b08da1765503893bd2b8023d3b3",
3262
-                "reference": "819782c75aaf2b08da1765503893bd2b8023d3b3",
3265
+                "url": "https://api.github.com/repos/laravel/sanctum/zipball/fe361b9a63407a228f884eb78d7217f680b50140",
3266
+                "reference": "fe361b9a63407a228f884eb78d7217f680b50140",
3263 3267
                 "shasum": ""
3264 3268
             },
3265 3269
             "require": {
@@ -3310,7 +3314,7 @@
3310 3314
                 "issues": "https://github.com/laravel/sanctum/issues",
3311 3315
                 "source": "https://github.com/laravel/sanctum"
3312 3316
             },
3313
-            "time": "2024-11-15T14:47:23+00:00"
3317
+            "time": "2024-11-26T14:36:23+00:00"
3314 3318
         },
3315 3319
         {
3316 3320
             "name": "laravel/serializable-closure",
@@ -9632,16 +9636,16 @@
9632 9636
         },
9633 9637
         {
9634 9638
             "name": "laravel/pint",
9635
-            "version": "v1.18.2",
9639
+            "version": "v1.18.3",
9636 9640
             "source": {
9637 9641
                 "type": "git",
9638 9642
                 "url": "https://github.com/laravel/pint.git",
9639
-                "reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64"
9643
+                "reference": "cef51821608239040ab841ad6e1c6ae502ae3026"
9640 9644
             },
9641 9645
             "dist": {
9642 9646
                 "type": "zip",
9643
-                "url": "https://api.github.com/repos/laravel/pint/zipball/f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64",
9644
-                "reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64",
9647
+                "url": "https://api.github.com/repos/laravel/pint/zipball/cef51821608239040ab841ad6e1c6ae502ae3026",
9648
+                "reference": "cef51821608239040ab841ad6e1c6ae502ae3026",
9645 9649
                 "shasum": ""
9646 9650
             },
9647 9651
             "require": {
@@ -9652,13 +9656,13 @@
9652 9656
                 "php": "^8.1.0"
9653 9657
             },
9654 9658
             "require-dev": {
9655
-                "friendsofphp/php-cs-fixer": "^3.64.0",
9656
-                "illuminate/view": "^10.48.20",
9657
-                "larastan/larastan": "^2.9.8",
9659
+                "friendsofphp/php-cs-fixer": "^3.65.0",
9660
+                "illuminate/view": "^10.48.24",
9661
+                "larastan/larastan": "^2.9.11",
9658 9662
                 "laravel-zero/framework": "^10.4.0",
9659 9663
                 "mockery/mockery": "^1.6.12",
9660
-                "nunomaduro/termwind": "^1.15.1",
9661
-                "pestphp/pest": "^2.35.1"
9664
+                "nunomaduro/termwind": "^1.17.0",
9665
+                "pestphp/pest": "^2.36.0"
9662 9666
             },
9663 9667
             "bin": [
9664 9668
                 "builds/pint"
@@ -9694,20 +9698,20 @@
9694 9698
                 "issues": "https://github.com/laravel/pint/issues",
9695 9699
                 "source": "https://github.com/laravel/pint"
9696 9700
             },
9697
-            "time": "2024-11-20T09:33:46+00:00"
9701
+            "time": "2024-11-26T15:34:00+00:00"
9698 9702
         },
9699 9703
         {
9700 9704
             "name": "laravel/sail",
9701
-            "version": "v1.38.0",
9705
+            "version": "v1.39.0",
9702 9706
             "source": {
9703 9707
                 "type": "git",
9704 9708
                 "url": "https://github.com/laravel/sail.git",
9705
-                "reference": "d17abae06661dd6c46d13627b1683a2924259145"
9709
+                "reference": "be9d67a11133535811f9ec4ab5c176a2f47250fc"
9706 9710
             },
9707 9711
             "dist": {
9708 9712
                 "type": "zip",
9709
-                "url": "https://api.github.com/repos/laravel/sail/zipball/d17abae06661dd6c46d13627b1683a2924259145",
9710
-                "reference": "d17abae06661dd6c46d13627b1683a2924259145",
9713
+                "url": "https://api.github.com/repos/laravel/sail/zipball/be9d67a11133535811f9ec4ab5c176a2f47250fc",
9714
+                "reference": "be9d67a11133535811f9ec4ab5c176a2f47250fc",
9711 9715
                 "shasum": ""
9712 9716
             },
9713 9717
             "require": {
@@ -9757,7 +9761,7 @@
9757 9761
                 "issues": "https://github.com/laravel/sail/issues",
9758 9762
                 "source": "https://github.com/laravel/sail"
9759 9763
             },
9760
-            "time": "2024-11-11T20:16:51+00:00"
9764
+            "time": "2024-11-25T23:48:26+00:00"
9761 9765
         },
9762 9766
         {
9763 9767
             "name": "mockery/mockery",

+ 3
- 3
package-lock.json Voir le fichier

@@ -1707,9 +1707,9 @@
1707 1707
             }
1708 1708
         },
1709 1709
         "node_modules/nanoid": {
1710
-            "version": "3.3.7",
1711
-            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
1712
-            "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
1710
+            "version": "3.3.8",
1711
+            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
1712
+            "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
1713 1713
             "dev": true,
1714 1714
             "funding": [
1715 1715
                 {

+ 4
- 0
resources/views/filament/forms/components/invoice-totals.blade.php Voir le fichier

@@ -17,6 +17,10 @@
17 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 18
                 <td class="w-1/3 text-sm pl-4 py-2 leading-6">{{ $taxTotal }}</td>
19 19
             </tr>
20
+            <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>
23
+            </tr>
20 24
             <tr class="font-semibold">
21 25
                 <td class="w-2/3 text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Total:</td>
22 26
                 <td class="w-1/3 text-sm pl-4 py-2 leading-6">{{ $grandTotal }}</td>

Chargement…
Annuler
Enregistrer