Bläddra i källkod

wip multi-currency

3.x
Andrew Wallo 10 månader sedan
förälder
incheckning
194016dfb5

+ 16
- 28
app/Casts/RateCast.php Visa fil

2
 
2
 
3
 namespace App\Casts;
3
 namespace App\Casts;
4
 
4
 
5
-use App\Enums\Setting\NumberFormat;
6
-use App\Models\Setting\Localization;
5
+use App\Enums\Accounting\AdjustmentComputation;
7
 use App\Utilities\Currency\CurrencyAccessor;
6
 use App\Utilities\Currency\CurrencyAccessor;
7
+use App\Utilities\RateCalculator;
8
 use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
8
 use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
9
 use Illuminate\Database\Eloquent\Model;
9
 use Illuminate\Database\Eloquent\Model;
10
 
10
 
11
 class RateCast implements CastsAttributes
11
 class RateCast implements CastsAttributes
12
 {
12
 {
13
-    private const PRECISION = 4;
14
-
15
     public function get($model, string $key, $value, array $attributes): string
13
     public function get($model, string $key, $value, array $attributes): string
16
     {
14
     {
15
+        if (! $value) {
16
+            return '0';
17
+        }
18
+
17
         $currency_code = $this->getDefaultCurrencyCode();
19
         $currency_code = $this->getDefaultCurrencyCode();
18
-        $computation = $attributes['computation'] ?? null;
20
+        $computation = AdjustmentComputation::parse($attributes['computation'] ?? $attributes['discount_computation'] ?? null);
19
 
21
 
20
-        if ($computation === 'fixed') {
22
+        if ($computation?->isFixed()) {
21
             return money($value, $currency_code)->formatSimple();
23
             return money($value, $currency_code)->formatSimple();
22
         }
24
         }
23
 
25
 
24
-        $floatValue = $value / (10 ** self::PRECISION);
25
-
26
-        $format = Localization::firstOrFail()->number_format->value;
27
-        [$decimal_mark, $thousands_separator] = NumberFormat::from($format)->getFormattingParameters();
28
-
29
-        return $this->formatWithoutTrailingZeros($floatValue, $decimal_mark, $thousands_separator);
26
+        return RateCalculator::formatScaledRate($value);
30
     }
27
     }
31
 
28
 
32
     public function set(Model $model, string $key, mixed $value, array $attributes): int
29
     public function set(Model $model, string $key, mixed $value, array $attributes): int
33
     {
30
     {
31
+        if (! $value) {
32
+            return 0;
33
+        }
34
+
34
         if (is_int($value)) {
35
         if (is_int($value)) {
35
             return $value;
36
             return $value;
36
         }
37
         }
37
 
38
 
38
-        $computation = $attributes['computation'] ?? null;
39
+        $computation = AdjustmentComputation::parse($attributes['computation'] ?? $attributes['discount_computation'] ?? null);
39
 
40
 
40
         $currency_code = $this->getDefaultCurrencyCode();
41
         $currency_code = $this->getDefaultCurrencyCode();
41
 
42
 
42
-        if ($computation === 'fixed') {
43
+        if ($computation?->isFixed()) {
43
             return money($value, $currency_code, true)->getAmount();
44
             return money($value, $currency_code, true)->getAmount();
44
         }
45
         }
45
 
46
 
46
-        $format = Localization::firstOrFail()->number_format->value;
47
-        [$decimal_mark, $thousands_separator] = NumberFormat::from($format)->getFormattingParameters();
48
-
49
-        $intValue = str_replace([$thousands_separator, $decimal_mark], ['', '.'], $value);
50
-
51
-        return (int) round((float) $intValue * (10 ** self::PRECISION));
47
+        return RateCalculator::parseLocalizedRate($value);
52
     }
48
     }
53
 
49
 
54
     private function getDefaultCurrencyCode(): string
50
     private function getDefaultCurrencyCode(): string
55
     {
51
     {
56
         return CurrencyAccessor::getDefaultCurrency();
52
         return CurrencyAccessor::getDefaultCurrency();
57
     }
53
     }
58
-
59
-    private function formatWithoutTrailingZeros($floatValue, $decimal_mark, $thousands_separator): string
60
-    {
61
-        $formatted = number_format($floatValue, self::PRECISION, $decimal_mark, $thousands_separator);
62
-        $formatted = rtrim($formatted, '0');
63
-
64
-        return rtrim($formatted, $decimal_mark);
65
-    }
66
 }
54
 }

+ 15
- 8
app/Concerns/ManagesLineItems.php Visa fil

6
 use App\Enums\Accounting\DocumentDiscountMethod;
6
 use App\Enums\Accounting\DocumentDiscountMethod;
7
 use App\Models\Accounting\Bill;
7
 use App\Models\Accounting\Bill;
8
 use App\Models\Accounting\DocumentLineItem;
8
 use App\Models\Accounting\DocumentLineItem;
9
+use App\Utilities\Currency\CurrencyAccessor;
9
 use App\Utilities\Currency\CurrencyConverter;
10
 use App\Utilities\Currency\CurrencyConverter;
11
+use App\Utilities\RateCalculator;
10
 use Illuminate\Database\Eloquent\Model;
12
 use Illuminate\Database\Eloquent\Model;
11
 use Illuminate\Support\Collection;
13
 use Illuminate\Support\Collection;
12
 
14
 
79
 
81
 
80
     protected function updateDocumentTotals(Model $record, array $data): array
82
     protected function updateDocumentTotals(Model $record, array $data): array
81
     {
83
     {
84
+        $currencyCode = $data['currency_code'] ?? CurrencyAccessor::getDefaultCurrency();
82
         $subtotalCents = $record->lineItems()->sum('subtotal');
85
         $subtotalCents = $record->lineItems()->sum('subtotal');
83
         $taxTotalCents = $record->lineItems()->sum('tax_total');
86
         $taxTotalCents = $record->lineItems()->sum('tax_total');
84
         $discountTotalCents = $this->calculateDiscountTotal(
87
         $discountTotalCents = $this->calculateDiscountTotal(
86
             AdjustmentComputation::parse($data['discount_computation']),
89
             AdjustmentComputation::parse($data['discount_computation']),
87
             $data['discount_rate'] ?? null,
90
             $data['discount_rate'] ?? null,
88
             $subtotalCents,
91
             $subtotalCents,
89
-            $record
92
+            $record,
93
+            $currencyCode,
90
         );
94
         );
91
 
95
 
92
         $grandTotalCents = $subtotalCents + $taxTotalCents - $discountTotalCents;
96
         $grandTotalCents = $subtotalCents + $taxTotalCents - $discountTotalCents;
93
 
97
 
94
         return [
98
         return [
95
-            'subtotal' => CurrencyConverter::convertCentsToFormatSimple($subtotalCents),
96
-            'tax_total' => CurrencyConverter::convertCentsToFormatSimple($taxTotalCents),
97
-            'discount_total' => CurrencyConverter::convertCentsToFormatSimple($discountTotalCents),
98
-            'total' => CurrencyConverter::convertCentsToFormatSimple($grandTotalCents),
99
+            'subtotal' => CurrencyConverter::convertCentsToFormatSimple($subtotalCents, $currencyCode),
100
+            'tax_total' => CurrencyConverter::convertCentsToFormatSimple($taxTotalCents, $currencyCode),
101
+            'discount_total' => CurrencyConverter::convertCentsToFormatSimple($discountTotalCents, $currencyCode),
102
+            'total' => CurrencyConverter::convertCentsToFormatSimple($grandTotalCents, $currencyCode),
99
         ];
103
         ];
100
     }
104
     }
101
 
105
 
104
         ?AdjustmentComputation $discountComputation,
108
         ?AdjustmentComputation $discountComputation,
105
         ?string $discountRate,
109
         ?string $discountRate,
106
         int $subtotalCents,
110
         int $subtotalCents,
107
-        Model $record
111
+        Model $record,
112
+        string $currencyCode
108
     ): int {
113
     ): int {
109
         if ($discountMethod->isPerLineItem()) {
114
         if ($discountMethod->isPerLineItem()) {
110
             return $record->lineItems()->sum('discount_total');
115
             return $record->lineItems()->sum('discount_total');
111
         }
116
         }
112
 
117
 
113
         if ($discountComputation?->isPercentage()) {
118
         if ($discountComputation?->isPercentage()) {
114
-            return (int) ($subtotalCents * ((float) $discountRate / 100));
119
+            $scaledRate = RateCalculator::parseLocalizedRate($discountRate);
120
+
121
+            return RateCalculator::calculatePercentage($subtotalCents, $scaledRate);
115
         }
122
         }
116
 
123
 
117
-        return CurrencyConverter::convertToCents($discountRate);
124
+        return CurrencyConverter::convertToCents($discountRate, $currencyCode);
118
     }
125
     }
119
 }
126
 }

+ 1
- 1
app/Filament/Company/Pages/Accounting/Transactions.php Visa fil

305
                         }
305
                         }
306
                     )
306
                     )
307
                     ->sortable()
307
                     ->sortable()
308
-                    ->currency(static fn (Transaction $transaction) => $transaction->bankAccount?->account->currency_code ?? CurrencyAccessor::getDefaultCurrency(), true),
308
+                    ->currency(static fn (Transaction $transaction) => $transaction->bankAccount?->account->currency_code),
309
             ])
309
             ])
310
             ->recordClasses(static fn (Transaction $transaction) => $transaction->reviewed ? 'bg-primary-300/10' : null)
310
             ->recordClasses(static fn (Transaction $transaction) => $transaction->reviewed ? 'bg-primary-300/10' : null)
311
             ->defaultSort('posted_at', 'desc')
311
             ->defaultSort('posted_at', 'desc')

+ 1
- 2
app/Filament/Company/Resources/Banking/AccountResource.php Visa fil

82
                                     ->localizeLabel()
82
                                     ->localizeLabel()
83
                                     ->required(),
83
                                     ->required(),
84
                                 CreateCurrencySelect::make('currency_code')
84
                                 CreateCurrencySelect::make('currency_code')
85
-                                    ->disabledOn('edit')
86
-                                    ->relationship('currency', 'name'),
85
+                                    ->disabledOn('edit'),
87
                             ]),
86
                             ]),
88
                         Forms\Components\Group::make()
87
                         Forms\Components\Group::make()
89
                             ->columns()
88
                             ->columns()

+ 0
- 1
app/Filament/Company/Resources/Purchases/VendorResource.php Visa fil

42
                                     ->default(VendorType::Regular)
42
                                     ->default(VendorType::Regular)
43
                                     ->columnSpanFull(),
43
                                     ->columnSpanFull(),
44
                                 CreateCurrencySelect::make('currency_code')
44
                                 CreateCurrencySelect::make('currency_code')
45
-                                    ->relationship('currency', 'name')
46
                                     ->nullable()
45
                                     ->nullable()
47
                                     ->visible(fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Regular),
46
                                     ->visible(fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Regular),
48
                                 Forms\Components\Select::make('contractor_type')
47
                                 Forms\Components\Select::make('contractor_type')

+ 1
- 2
app/Filament/Company/Resources/Sales/ClientResource.php Visa fil

167
                     ])->columns(1),
167
                     ])->columns(1),
168
                 Forms\Components\Section::make('Billing')
168
                 Forms\Components\Section::make('Billing')
169
                     ->schema([
169
                     ->schema([
170
-                        CreateCurrencySelect::make('currency_code')
171
-                            ->relationship('currency', 'name'),
170
+                        CreateCurrencySelect::make('currency_code'),
172
                         CustomSection::make('Billing Address')
171
                         CustomSection::make('Billing Address')
173
                             ->relationship('billingAddress')
172
                             ->relationship('billingAddress')
174
                             ->contained(false)
173
                             ->contained(false)

+ 49
- 21
app/Filament/Company/Resources/Sales/InvoiceResource.php Visa fil

9
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
9
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
10
 use App\Filament\Company\Resources\Sales\InvoiceResource\RelationManagers;
10
 use App\Filament\Company\Resources\Sales\InvoiceResource\RelationManagers;
11
 use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
11
 use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
12
+use App\Filament\Forms\Components\CreateCurrencySelect;
12
 use App\Filament\Forms\Components\InvoiceTotals;
13
 use App\Filament\Forms\Components\InvoiceTotals;
13
 use App\Filament\Tables\Actions\ReplicateBulkAction;
14
 use App\Filament\Tables\Actions\ReplicateBulkAction;
14
 use App\Filament\Tables\Filters\DateRangeFilter;
15
 use App\Filament\Tables\Filters\DateRangeFilter;
15
 use App\Models\Accounting\Adjustment;
16
 use App\Models\Accounting\Adjustment;
16
 use App\Models\Accounting\Invoice;
17
 use App\Models\Accounting\Invoice;
17
 use App\Models\Banking\BankAccount;
18
 use App\Models\Banking\BankAccount;
19
+use App\Models\Common\Client;
18
 use App\Models\Common\Offering;
20
 use App\Models\Common\Offering;
21
+use App\Utilities\Currency\CurrencyAccessor;
19
 use App\Utilities\Currency\CurrencyConverter;
22
 use App\Utilities\Currency\CurrencyConverter;
23
+use App\Utilities\RateCalculator;
20
 use Awcodes\TableRepeater\Components\TableRepeater;
24
 use Awcodes\TableRepeater\Components\TableRepeater;
21
 use Awcodes\TableRepeater\Header;
25
 use Awcodes\TableRepeater\Header;
22
 use Closure;
26
 use Closure;
97
                                     ->relationship('client', 'name')
101
                                     ->relationship('client', 'name')
98
                                     ->preload()
102
                                     ->preload()
99
                                     ->searchable()
103
                                     ->searchable()
100
-                                    ->required(),
104
+                                    ->required()
105
+                                    ->live()
106
+                                    ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
107
+                                        if (! $state) {
108
+                                            return;
109
+                                        }
110
+
111
+                                        $currencyCode = Client::find($state)?->currency_code;
112
+
113
+                                        if ($currencyCode) {
114
+                                            $set('currency_code', $currencyCode);
115
+                                        }
116
+                                    }),
117
+                                CreateCurrencySelect::make('currency_code'),
101
                             ]),
118
                             ]),
102
                             Forms\Components\Group::make([
119
                             Forms\Components\Group::make([
103
                                 Forms\Components\TextInput::make('invoice_number')
120
                                 Forms\Components\TextInput::make('invoice_number')
227
                                         $unitPrice = max((float) ($get('unit_price') ?? 0), 0);
244
                                         $unitPrice = max((float) ($get('unit_price') ?? 0), 0);
228
                                         $salesTaxes = $get('salesTaxes') ?? [];
245
                                         $salesTaxes = $get('salesTaxes') ?? [];
229
                                         $salesDiscounts = $get('salesDiscounts') ?? [];
246
                                         $salesDiscounts = $get('salesDiscounts') ?? [];
247
+                                        $currencyCode = $get('../../currency_code') ?? CurrencyAccessor::getDefaultCurrency();
230
 
248
 
231
                                         $subtotal = $quantity * $unitPrice;
249
                                         $subtotal = $quantity * $unitPrice;
232
 
250
 
233
-                                        // Calculate tax amount based on subtotal
234
-                                        $taxAmount = 0;
235
-                                        if (! empty($salesTaxes)) {
236
-                                            $taxRates = Adjustment::whereIn('id', $salesTaxes)->pluck('rate');
237
-                                            $taxAmount = collect($taxRates)->sum(fn ($rate) => $subtotal * ($rate / 100));
238
-                                        }
239
-
240
-                                        // Calculate discount amount based on subtotal
241
-                                        $discountAmount = 0;
242
-                                        if (! empty($salesDiscounts)) {
243
-                                            $discountRates = Adjustment::whereIn('id', $salesDiscounts)->pluck('rate');
244
-                                            $discountAmount = collect($discountRates)->sum(fn ($rate) => $subtotal * ($rate / 100));
245
-                                        }
251
+                                        $subtotalInCents = CurrencyConverter::convertToCents($subtotal, $currencyCode);
252
+
253
+                                        $taxAmountInCents = Adjustment::whereIn('id', $salesTaxes)
254
+                                            ->get()
255
+                                            ->sum(function (Adjustment $adjustment) use ($subtotalInCents) {
256
+                                                if ($adjustment->computation->isPercentage()) {
257
+                                                    return RateCalculator::calculatePercentage($subtotalInCents, $adjustment->getRawOriginal('rate'));
258
+                                                } else {
259
+                                                    return $adjustment->getRawOriginal('rate');
260
+                                                }
261
+                                            });
262
+
263
+                                        $discountAmountInCents = Adjustment::whereIn('id', $salesDiscounts)
264
+                                            ->get()
265
+                                            ->sum(function (Adjustment $adjustment) use ($subtotalInCents) {
266
+                                                if ($adjustment->computation->isPercentage()) {
267
+                                                    return RateCalculator::calculatePercentage($subtotalInCents, $adjustment->getRawOriginal('rate'));
268
+                                                } else {
269
+                                                    return $adjustment->getRawOriginal('rate');
270
+                                                }
271
+                                            });
246
 
272
 
247
                                         // Final total
273
                                         // Final total
248
-                                        $total = $subtotal + ($taxAmount - $discountAmount);
274
+                                        $totalInCents = $subtotalInCents + ($taxAmountInCents - $discountAmountInCents);
249
 
275
 
250
-                                        return CurrencyConverter::formatToMoney($total);
276
+                                        return CurrencyConverter::formatCentsToMoney($totalInCents, $currencyCode);
251
                                     }),
277
                                     }),
252
                             ]),
278
                             ]),
253
                         InvoiceTotals::make(),
279
                         InvoiceTotals::make(),
291
                     ->sortable()
317
                     ->sortable()
292
                     ->searchable(),
318
                     ->searchable(),
293
                 Tables\Columns\TextColumn::make('total')
319
                 Tables\Columns\TextColumn::make('total')
294
-                    ->currency()
295
-                    ->sortable(),
320
+                    ->currencyWithConversion(static fn (Invoice $record) => $record->currency_code)
321
+                    ->sortable()
322
+                    ->toggleable(),
296
                 Tables\Columns\TextColumn::make('amount_paid')
323
                 Tables\Columns\TextColumn::make('amount_paid')
297
                     ->label('Amount Paid')
324
                     ->label('Amount Paid')
298
-                    ->currency()
299
-                    ->sortable(),
325
+                    ->currencyWithConversion(static fn (Invoice $record) => $record->currency_code)
326
+                    ->sortable()
327
+                    ->toggleable(),
300
                 Tables\Columns\TextColumn::make('amount_due')
328
                 Tables\Columns\TextColumn::make('amount_due')
301
                     ->label('Amount Due')
329
                     ->label('Amount Due')
302
-                    ->currency()
330
+                    ->currencyWithConversion(static fn (Invoice $record) => $record->currency_code)
303
                     ->sortable(),
331
                     ->sortable(),
304
             ])
332
             ])
305
             ->filters([
333
             ->filters([

+ 1
- 1
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/ViewInvoice.php Visa fil

72
                                     ->url(static fn (Invoice $record) => ClientResource::getUrl('edit', ['record' => $record->client_id])),
72
                                     ->url(static fn (Invoice $record) => ClientResource::getUrl('edit', ['record' => $record->client_id])),
73
                                 TextEntry::make('amount_due')
73
                                 TextEntry::make('amount_due')
74
                                     ->label('Amount Due')
74
                                     ->label('Amount Due')
75
-                                    ->money(),
75
+                                    ->currency(static fn (Invoice $record) => $record->currency_code),
76
                                 TextEntry::make('due_date')
76
                                 TextEntry::make('due_date')
77
                                     ->label('Due')
77
                                     ->label('Due')
78
                                     ->asRelativeDay(),
78
                                     ->asRelativeDay(),

+ 15
- 10
app/Filament/Forms/Components/CreateCurrencySelect.php Visa fil

26
             ->required()
26
             ->required()
27
             ->createOptionForm($this->createCurrencyForm())
27
             ->createOptionForm($this->createCurrencyForm())
28
             ->createOptionAction(fn (Action $action) => $this->createCurrencyAction($action));
28
             ->createOptionAction(fn (Action $action) => $this->createCurrencyAction($action));
29
+
30
+        $this->relationship('currency', 'name');
31
+
32
+        $this->createOptionUsing(static function (array $data) {
33
+            return DB::transaction(static function () use ($data) {
34
+                $currency = CreateCurrency::create(
35
+                    $data['code'],
36
+                    $data['name'],
37
+                    $data['rate']
38
+                );
39
+
40
+                return $currency->code;
41
+            });
42
+        });
29
     }
43
     }
30
 
44
 
31
     protected function createCurrencyForm(): array
45
     protected function createCurrencyForm(): array
56
         return $action
70
         return $action
57
             ->label('Add Currency')
71
             ->label('Add Currency')
58
             ->slideOver()
72
             ->slideOver()
59
-            ->modalWidth(MaxWidth::Medium)
60
-            ->action(static function (array $data) {
61
-                return DB::transaction(static function () use ($data) {
62
-                    $code = $data['code'];
63
-                    $name = $data['name'];
64
-                    $rate = $data['rate'];
65
-
66
-                    return CreateCurrency::create($code, $name, $rate);
67
-                });
68
-            });
73
+            ->modalWidth(MaxWidth::Medium);
69
     }
74
     }
70
 }
75
 }

+ 6
- 0
app/Models/Accounting/Bill.php Visa fil

13
 use App\Enums\Accounting\TransactionType;
13
 use App\Enums\Accounting\TransactionType;
14
 use App\Filament\Company\Resources\Purchases\BillResource;
14
 use App\Filament\Company\Resources\Purchases\BillResource;
15
 use App\Models\Common\Vendor;
15
 use App\Models\Common\Vendor;
16
+use App\Models\Setting\Currency;
16
 use App\Observers\BillObserver;
17
 use App\Observers\BillObserver;
17
 use App\Utilities\Currency\CurrencyConverter;
18
 use App\Utilities\Currency\CurrencyConverter;
18
 use Filament\Actions\MountableAction;
19
 use Filament\Actions\MountableAction;
75
         'amount_due' => MoneyCast::class,
76
         'amount_due' => MoneyCast::class,
76
     ];
77
     ];
77
 
78
 
79
+    public function currency(): BelongsTo
80
+    {
81
+        return $this->belongsTo(Currency::class, 'currency_code', 'code');
82
+    }
83
+
78
     public function vendor(): BelongsTo
84
     public function vendor(): BelongsTo
79
     {
85
     {
80
         return $this->belongsTo(Vendor::class);
86
         return $this->belongsTo(Vendor::class);

+ 44
- 0
app/Models/Accounting/DocumentLineItem.php Visa fil

12
 use App\Models\Common\Offering;
12
 use App\Models\Common\Offering;
13
 use App\Observers\DocumentLineItemObserver;
13
 use App\Observers\DocumentLineItemObserver;
14
 use App\Utilities\Currency\CurrencyAccessor;
14
 use App\Utilities\Currency\CurrencyAccessor;
15
+use App\Utilities\RateCalculator;
15
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
16
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
16
 use Illuminate\Database\Eloquent\Factories\HasFactory;
17
 use Illuminate\Database\Eloquent\Factories\HasFactory;
17
 use Illuminate\Database\Eloquent\Model;
18
 use Illuminate\Database\Eloquent\Model;
113
         );
114
         );
114
     }
115
     }
115
 
116
 
117
+    public function calculateTaxTotalAmount(): int
118
+    {
119
+        $subtotalInCents = $this->getRawOriginal('subtotal');
120
+
121
+        return $this->taxes->reduce(function (int $carry, Adjustment $tax) use ($subtotalInCents) {
122
+            if ($tax->computation->isPercentage()) {
123
+                $scaledRate = $tax->getRawOriginal('rate');
124
+
125
+                return $carry + RateCalculator::calculatePercentage($subtotalInCents, $scaledRate);
126
+            } else {
127
+                return $carry + $tax->getRawOriginal('rate');
128
+            }
129
+        }, 0);
130
+    }
131
+
116
     public function calculateDiscountTotal(): Money
132
     public function calculateDiscountTotal(): Money
117
     {
133
     {
118
         $subtotal = money($this->subtotal, CurrencyAccessor::getDefaultCurrency());
134
         $subtotal = money($this->subtotal, CurrencyAccessor::getDefaultCurrency());
123
         );
139
         );
124
     }
140
     }
125
 
141
 
142
+    public function calculateDiscountTotalAmount(): int
143
+    {
144
+        $subtotalInCents = $this->getRawOriginal('subtotal');
145
+
146
+        return $this->discounts->reduce(function (int $carry, Adjustment $discount) use ($subtotalInCents) {
147
+            if ($discount->computation->isPercentage()) {
148
+                $scaledRate = $discount->getRawOriginal('rate');
149
+
150
+                return $carry + RateCalculator::calculatePercentage($subtotalInCents, $scaledRate);
151
+            } else {
152
+                return $carry + $discount->getRawOriginal('rate');
153
+            }
154
+        }, 0);
155
+    }
156
+
126
     public function calculateAdjustmentTotal(Adjustment $adjustment): Money
157
     public function calculateAdjustmentTotal(Adjustment $adjustment): Money
127
     {
158
     {
128
         $subtotal = money($this->subtotal, CurrencyAccessor::getDefaultCurrency());
159
         $subtotal = money($this->subtotal, CurrencyAccessor::getDefaultCurrency());
129
 
160
 
130
         return $subtotal->multiply($adjustment->rate / 100);
161
         return $subtotal->multiply($adjustment->rate / 100);
131
     }
162
     }
163
+
164
+    public function calculateAdjustmentTotalAmount(Adjustment $adjustment): int
165
+    {
166
+        $subtotalInCents = $this->getRawOriginal('subtotal');
167
+
168
+        if ($adjustment->computation->isPercentage()) {
169
+            $scaledRate = $adjustment->getRawOriginal('rate');
170
+
171
+            return RateCalculator::calculatePercentage($subtotalInCents, $scaledRate);
172
+        } else {
173
+            return $adjustment->getRawOriginal('rate');
174
+        }
175
+    }
132
 }
176
 }

+ 41
- 9
app/Models/Accounting/Invoice.php Visa fil

14
 use App\Enums\Accounting\TransactionType;
14
 use App\Enums\Accounting\TransactionType;
15
 use App\Filament\Company\Resources\Sales\InvoiceResource;
15
 use App\Filament\Company\Resources\Sales\InvoiceResource;
16
 use App\Models\Common\Client;
16
 use App\Models\Common\Client;
17
+use App\Models\Setting\Currency;
17
 use App\Observers\InvoiceObserver;
18
 use App\Observers\InvoiceObserver;
19
+use App\Utilities\Currency\CurrencyAccessor;
18
 use App\Utilities\Currency\CurrencyConverter;
20
 use App\Utilities\Currency\CurrencyConverter;
19
 use Filament\Actions\Action;
21
 use Filament\Actions\Action;
20
 use Filament\Actions\MountableAction;
22
 use Filament\Actions\MountableAction;
92
         return $this->belongsTo(Client::class);
94
         return $this->belongsTo(Client::class);
93
     }
95
     }
94
 
96
 
97
+    public function currency(): BelongsTo
98
+    {
99
+        return $this->belongsTo(Currency::class, 'currency_code', 'code');
100
+    }
101
+
95
     public function lineItems(): MorphMany
102
     public function lineItems(): MorphMany
96
     {
103
     {
97
         return $this->morphMany(DocumentLineItem::class, 'documentable');
104
         return $this->morphMany(DocumentLineItem::class, 'documentable');
252
 
259
 
253
     public function createApprovalTransaction(): void
260
     public function createApprovalTransaction(): void
254
     {
261
     {
262
+        $total = $this->formatAmountToDefaultCurrency($this->getRawOriginal('total'));
263
+
255
         $transaction = $this->transactions()->create([
264
         $transaction = $this->transactions()->create([
256
             'company_id' => $this->company_id,
265
             'company_id' => $this->company_id,
257
             'type' => TransactionType::Journal,
266
             'type' => TransactionType::Journal,
258
             'posted_at' => $this->date,
267
             'posted_at' => $this->date,
259
-            'amount' => $this->total,
268
+            'amount' => $total,
260
             'description' => 'Invoice Approval for Invoice #' . $this->invoice_number,
269
             'description' => 'Invoice Approval for Invoice #' . $this->invoice_number,
261
         ]);
270
         ]);
262
 
271
 
266
             'company_id' => $this->company_id,
275
             'company_id' => $this->company_id,
267
             'type' => JournalEntryType::Debit,
276
             'type' => JournalEntryType::Debit,
268
             'account_id' => Account::getAccountsReceivableAccount()->id,
277
             'account_id' => Account::getAccountsReceivableAccount()->id,
269
-            'amount' => $this->total,
278
+            'amount' => $total,
270
             'description' => $baseDescription,
279
             'description' => $baseDescription,
271
         ]);
280
         ]);
272
 
281
 
273
-        $totalLineItemSubtotal = (int) $this->lineItems()->sum('subtotal');
274
-        $invoiceDiscountTotalCents = (int) $this->getRawOriginal('discount_total');
282
+        $totalLineItemSubtotalCents = $this->convertAmountToDefaultCurrency((int) $this->lineItems()->sum('subtotal'));
283
+        $invoiceDiscountTotalCents = $this->convertAmountToDefaultCurrency((int) $this->getRawOriginal('discount_total'));
275
         $remainingDiscountCents = $invoiceDiscountTotalCents;
284
         $remainingDiscountCents = $invoiceDiscountTotalCents;
276
 
285
 
277
         foreach ($this->lineItems as $index => $lineItem) {
286
         foreach ($this->lineItems as $index => $lineItem) {
278
             $lineItemDescription = "{$baseDescription} › {$lineItem->offering->name}";
287
             $lineItemDescription = "{$baseDescription} › {$lineItem->offering->name}";
279
 
288
 
289
+            $lineItemSubtotal = $this->formatAmountToDefaultCurrency($lineItem->getRawOriginal('subtotal'));
290
+
280
             $transaction->journalEntries()->create([
291
             $transaction->journalEntries()->create([
281
                 'company_id' => $this->company_id,
292
                 'company_id' => $this->company_id,
282
                 'type' => JournalEntryType::Credit,
293
                 'type' => JournalEntryType::Credit,
283
                 'account_id' => $lineItem->offering->income_account_id,
294
                 'account_id' => $lineItem->offering->income_account_id,
284
-                'amount' => $lineItem->subtotal,
295
+                'amount' => $lineItemSubtotal,
285
                 'description' => $lineItemDescription,
296
                 'description' => $lineItemDescription,
286
             ]);
297
             ]);
287
 
298
 
288
             foreach ($lineItem->adjustments as $adjustment) {
299
             foreach ($lineItem->adjustments as $adjustment) {
300
+                $adjustmentAmount = $this->formatAmountToDefaultCurrency($lineItem->calculateAdjustmentTotalAmount($adjustment));
301
+
289
                 $transaction->journalEntries()->create([
302
                 $transaction->journalEntries()->create([
290
                     'company_id' => $this->company_id,
303
                     'company_id' => $this->company_id,
291
                     'type' => $adjustment->category->isDiscount() ? JournalEntryType::Debit : JournalEntryType::Credit,
304
                     'type' => $adjustment->category->isDiscount() ? JournalEntryType::Debit : JournalEntryType::Credit,
292
                     'account_id' => $adjustment->account_id,
305
                     'account_id' => $adjustment->account_id,
293
-                    'amount' => $lineItem->calculateAdjustmentTotal($adjustment)->getAmount(),
306
+                    'amount' => $adjustmentAmount,
294
                     'description' => $lineItemDescription,
307
                     'description' => $lineItemDescription,
295
                 ]);
308
                 ]);
296
             }
309
             }
297
 
310
 
298
-            if ($this->discount_method->isPerDocument() && $totalLineItemSubtotal > 0) {
299
-                $lineItemSubtotalCents = (int) $lineItem->getRawOriginal('subtotal');
311
+            if ($this->discount_method->isPerDocument() && $totalLineItemSubtotalCents > 0) {
312
+                $lineItemSubtotalCents = $this->convertAmountToDefaultCurrency((int) $lineItem->getRawOriginal('subtotal'));
300
 
313
 
301
                 if ($index === $this->lineItems->count() - 1) {
314
                 if ($index === $this->lineItems->count() - 1) {
302
                     $lineItemDiscount = $remainingDiscountCents;
315
                     $lineItemDiscount = $remainingDiscountCents;
303
                 } else {
316
                 } else {
304
                     $lineItemDiscount = (int) round(
317
                     $lineItemDiscount = (int) round(
305
-                        ($lineItemSubtotalCents / $totalLineItemSubtotal) * $invoiceDiscountTotalCents
318
+                        ($lineItemSubtotalCents / $totalLineItemSubtotalCents) * $invoiceDiscountTotalCents
306
                     );
319
                     );
307
                     $remainingDiscountCents -= $lineItemDiscount;
320
                     $remainingDiscountCents -= $lineItemDiscount;
308
                 }
321
                 }
331
         $this->createApprovalTransaction();
344
         $this->createApprovalTransaction();
332
     }
345
     }
333
 
346
 
347
+    public function convertAmountToDefaultCurrency(int $amountCents): int
348
+    {
349
+        $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
350
+        $needsConversion = $this->currency_code !== $defaultCurrency;
351
+
352
+        if ($needsConversion) {
353
+            return CurrencyConverter::convertBalance($amountCents, $this->currency_code, $defaultCurrency);
354
+        }
355
+
356
+        return $amountCents;
357
+    }
358
+
359
+    public function formatAmountToDefaultCurrency(int $amountCents): string
360
+    {
361
+        $convertedCents = $this->convertAmountToDefaultCurrency($amountCents);
362
+
363
+        return CurrencyConverter::convertCentsToFormatSimple($convertedCents);
364
+    }
365
+
334
     public static function getApproveDraftAction(string $action = Action::class): MountableAction
366
     public static function getApproveDraftAction(string $action = Action::class): MountableAction
335
     {
367
     {
336
         return $action::make('approveDraft')
368
         return $action::make('approveDraft')

+ 88
- 0
app/Providers/MacroServiceProvider.php Visa fil

9
 use App\Models\Setting\Localization;
9
 use App\Models\Setting\Localization;
10
 use App\Utilities\Accounting\AccountCode;
10
 use App\Utilities\Accounting\AccountCode;
11
 use App\Utilities\Currency\CurrencyAccessor;
11
 use App\Utilities\Currency\CurrencyAccessor;
12
+use App\Utilities\Currency\CurrencyConverter;
12
 use BackedEnum;
13
 use BackedEnum;
13
 use Carbon\CarbonInterface;
14
 use Carbon\CarbonInterface;
14
 use Closure;
15
 use Closure;
102
             return $this;
103
             return $this;
103
         });
104
         });
104
 
105
 
106
+        TextEntry::macro('currency', function (string | Closure | null $currency = null, ?bool $convert = null): static {
107
+            $currency ??= CurrencyAccessor::getDefaultCurrency();
108
+            $convert ??= true;
109
+
110
+            $this->formatStateUsing(static function (TextEntry $entry, $state) use ($currency, $convert): ?string {
111
+                if (blank($state)) {
112
+                    return null;
113
+                }
114
+
115
+                $currency = $entry->evaluate($currency);
116
+                $convert = $entry->evaluate($convert);
117
+
118
+                return money($state, $currency, $convert)->format();
119
+            });
120
+
121
+            return $this;
122
+        });
123
+
124
+        TextColumn::macro('currencyWithConversion', function (string | Closure | null $currency = null): static {
125
+            $currency ??= CurrencyAccessor::getDefaultCurrency();
126
+
127
+            $this->formatStateUsing(static function (TextColumn $column, $state) use ($currency): ?string {
128
+                if (blank($state)) {
129
+                    return null;
130
+                }
131
+
132
+                $currency = $column->evaluate($currency);
133
+
134
+                return CurrencyConverter::formatToMoney($state, $currency);
135
+            });
136
+
137
+            $this->description(static function (TextColumn $column, $state) use ($currency): ?string {
138
+                if (blank($state)) {
139
+                    return null;
140
+                }
141
+
142
+                $oldCurrency = $column->evaluate($currency);
143
+                $newCurrency = CurrencyAccessor::getDefaultCurrency();
144
+
145
+                if ($oldCurrency === $newCurrency) {
146
+                    return null;
147
+                }
148
+
149
+                $balanceInCents = CurrencyConverter::convertToCents($state, $oldCurrency);
150
+
151
+                $convertedBalanceInCents = CurrencyConverter::convertBalance($balanceInCents, $oldCurrency, $newCurrency);
152
+
153
+                return CurrencyConverter::formatCentsToMoney($convertedBalanceInCents, $newCurrency, true);
154
+            });
155
+
156
+            return $this;
157
+        });
158
+
159
+        TextEntry::macro('currencyWithConversion', function (string | Closure | null $currency = null): static {
160
+            $currency ??= CurrencyAccessor::getDefaultCurrency();
161
+
162
+            $this->formatStateUsing(static function (TextEntry $entry, $state) use ($currency): ?string {
163
+                if (blank($state)) {
164
+                    return null;
165
+                }
166
+
167
+                $currency = $entry->evaluate($currency);
168
+
169
+                return CurrencyConverter::formatToMoney($state, $currency);
170
+            });
171
+
172
+            $this->helperText(static function (TextEntry $entry, $state) use ($currency): ?string {
173
+                if (blank($state)) {
174
+                    return null;
175
+                }
176
+
177
+                $oldCurrency = $entry->evaluate($currency);
178
+                $newCurrency = CurrencyAccessor::getDefaultCurrency();
179
+
180
+                if ($oldCurrency === $newCurrency) {
181
+                    return null;
182
+                }
183
+
184
+                $balanceInCents = CurrencyConverter::convertToCents($state, $oldCurrency);
185
+                $convertedBalanceInCents = CurrencyConverter::convertBalance($balanceInCents, $oldCurrency, $newCurrency);
186
+
187
+                return CurrencyConverter::formatCentsToMoney($convertedBalanceInCents, $newCurrency, true);
188
+            });
189
+
190
+            return $this;
191
+        });
192
+
105
         TextInput::macro('rate', function (string | Closure | null $computation = null, bool $showAffix = true): static {
193
         TextInput::macro('rate', function (string | Closure | null $computation = null, bool $showAffix = true): static {
106
             $this
194
             $this
107
                 ->when(
195
                 ->when(

+ 53
- 0
app/Utilities/RateCalculator.php Visa fil

1
+<?php
2
+
3
+namespace App\Utilities;
4
+
5
+use App\Enums\Setting\NumberFormat;
6
+use App\Models\Setting\Localization;
7
+
8
+class RateCalculator
9
+{
10
+    public const PRECISION = 4;
11
+
12
+    public const SCALING_FACTOR = 10 ** self::PRECISION;
13
+
14
+    public const PERCENTAGE_SCALING_FACTOR = self::SCALING_FACTOR * 100;
15
+
16
+    public static function calculatePercentage(int $value, int $scaledRate): int
17
+    {
18
+        return (int) round(($value * $scaledRate) / self::PERCENTAGE_SCALING_FACTOR);
19
+    }
20
+
21
+    public static function scaledRateToDecimal(int $scaledRate): float
22
+    {
23
+        return $scaledRate / self::PERCENTAGE_SCALING_FACTOR;
24
+    }
25
+
26
+    public static function decimalToScaledRate(float $decimalRate): int
27
+    {
28
+        return (int) round($decimalRate * self::PERCENTAGE_SCALING_FACTOR);
29
+    }
30
+
31
+    public static function parseLocalizedRate(string $value): int
32
+    {
33
+        $format = Localization::firstOrFail()->number_format->value;
34
+        [$decimalMark, $thousandsSeparator] = NumberFormat::from($format)->getFormattingParameters();
35
+
36
+        $floatValue = (float) str_replace([$thousandsSeparator, $decimalMark], ['', '.'], $value);
37
+
38
+        return (int) round($floatValue * self::SCALING_FACTOR);
39
+    }
40
+
41
+    public static function formatScaledRate(int $scaledRate): string
42
+    {
43
+        $format = Localization::firstOrFail()->number_format->value;
44
+        [$decimalMark, $thousandsSeparator] = NumberFormat::from($format)->getFormattingParameters();
45
+
46
+        $percentageValue = $scaledRate / self::SCALING_FACTOR;
47
+
48
+        $formatted = number_format($percentageValue, self::PRECISION, $decimalMark, $thousandsSeparator);
49
+        $formatted = rtrim($formatted, '0');
50
+
51
+        return rtrim($formatted, $decimalMark);
52
+    }
53
+}

+ 61
- 23
app/View/Models/InvoiceTotalViewModel.php Visa fil

6
 use App\Enums\Accounting\DocumentDiscountMethod;
6
 use App\Enums\Accounting\DocumentDiscountMethod;
7
 use App\Models\Accounting\Adjustment;
7
 use App\Models\Accounting\Adjustment;
8
 use App\Models\Accounting\Invoice;
8
 use App\Models\Accounting\Invoice;
9
+use App\Utilities\Currency\CurrencyAccessor;
9
 use App\Utilities\Currency\CurrencyConverter;
10
 use App\Utilities\Currency\CurrencyConverter;
11
+use App\Utilities\RateCalculator;
10
 
12
 
11
 class InvoiceTotalViewModel
13
 class InvoiceTotalViewModel
12
 {
14
 {
17
 
19
 
18
     public function buildViewData(): array
20
     public function buildViewData(): array
19
     {
21
     {
22
+        $currencyCode = $this->data['currency_code'] ?? CurrencyAccessor::getDefaultCurrency();
23
+        $defaultCurrencyCode = CurrencyAccessor::getDefaultCurrency();
24
+
20
         $lineItems = collect($this->data['lineItems'] ?? []);
25
         $lineItems = collect($this->data['lineItems'] ?? []);
21
 
26
 
22
-        $subtotal = $lineItems->sum(static function ($item) {
27
+        $subtotalInCents = $lineItems->sum(static function ($item) use ($currencyCode) {
23
             $quantity = max((float) ($item['quantity'] ?? 0), 0);
28
             $quantity = max((float) ($item['quantity'] ?? 0), 0);
24
             $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
29
             $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
25
 
30
 
26
-            return $quantity * $unitPrice;
31
+            $subtotal = $quantity * $unitPrice;
32
+
33
+            return CurrencyConverter::convertToCents($subtotal, $currencyCode);
27
         });
34
         });
28
 
35
 
29
-        $taxTotal = $lineItems->reduce(function ($carry, $item) {
36
+        $taxTotalInCents = $lineItems->reduce(function ($carry, $item) use ($currencyCode) {
30
             $quantity = max((float) ($item['quantity'] ?? 0), 0);
37
             $quantity = max((float) ($item['quantity'] ?? 0), 0);
31
             $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
38
             $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
32
             $salesTaxes = $item['salesTaxes'] ?? [];
39
             $salesTaxes = $item['salesTaxes'] ?? [];
33
             $lineTotal = $quantity * $unitPrice;
40
             $lineTotal = $quantity * $unitPrice;
34
 
41
 
35
-            $taxAmount = Adjustment::whereIn('id', $salesTaxes)
36
-                ->pluck('rate')
37
-                ->sum(fn ($rate) => $lineTotal * ($rate / 100));
42
+            $lineTotalInCents = CurrencyConverter::convertToCents($lineTotal, $currencyCode);
43
+
44
+            $taxAmountInCents = Adjustment::whereIn('id', $salesTaxes)
45
+                ->get()
46
+                ->sum(function (Adjustment $adjustment) use ($lineTotalInCents) {
47
+                    if ($adjustment->computation->isPercentage()) {
48
+                        return RateCalculator::calculatePercentage($lineTotalInCents, $adjustment->getRawOriginal('rate'));
49
+                    } else {
50
+                        return $adjustment->getRawOriginal('rate');
51
+                    }
52
+                });
38
 
53
 
39
-            return $carry + $taxAmount;
54
+            return $carry + $taxAmountInCents;
40
         }, 0);
55
         }, 0);
41
 
56
 
42
         // Calculate discount based on method
57
         // Calculate discount based on method
43
         $discountMethod = DocumentDiscountMethod::parse($this->data['discount_method']) ?? DocumentDiscountMethod::PerLineItem;
58
         $discountMethod = DocumentDiscountMethod::parse($this->data['discount_method']) ?? DocumentDiscountMethod::PerLineItem;
44
 
59
 
45
         if ($discountMethod->isPerLineItem()) {
60
         if ($discountMethod->isPerLineItem()) {
46
-            $discountTotal = $lineItems->reduce(function ($carry, $item) {
61
+            $discountTotalInCents = $lineItems->reduce(function ($carry, $item) use ($currencyCode) {
47
                 $quantity = max((float) ($item['quantity'] ?? 0), 0);
62
                 $quantity = max((float) ($item['quantity'] ?? 0), 0);
48
                 $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
63
                 $unitPrice = max((float) ($item['unit_price'] ?? 0), 0);
49
                 $salesDiscounts = $item['salesDiscounts'] ?? [];
64
                 $salesDiscounts = $item['salesDiscounts'] ?? [];
50
-                $lineTotal = $quantity * $unitPrice;
51
-
52
-                $discountAmount = Adjustment::whereIn('id', $salesDiscounts)
53
-                    ->pluck('rate')
54
-                    ->sum(fn ($rate) => $lineTotal * ($rate / 100));
55
-
56
-                return $carry + $discountAmount;
65
+                $lineTotalInCents = CurrencyConverter::convertToCents($quantity * $unitPrice, $currencyCode);
66
+
67
+                $discountAmountInCents = Adjustment::whereIn('id', $salesDiscounts)
68
+                    ->get()
69
+                    ->sum(function (Adjustment $adjustment) use ($lineTotalInCents) {
70
+                        if ($adjustment->computation->isPercentage()) {
71
+                            return RateCalculator::calculatePercentage($lineTotalInCents, $adjustment->getRawOriginal('rate'));
72
+                        } else {
73
+                            return $adjustment->getRawOriginal('rate');
74
+                        }
75
+                    });
76
+
77
+                return $carry + $discountAmountInCents;
57
             }, 0);
78
             }, 0);
58
         } else {
79
         } else {
59
             $discountComputation = AdjustmentComputation::parse($this->data['discount_computation']) ?? AdjustmentComputation::Percentage;
80
             $discountComputation = AdjustmentComputation::parse($this->data['discount_computation']) ?? AdjustmentComputation::Percentage;
60
-            $discountRate = (float) ($this->data['discount_rate'] ?? 0);
81
+            $discountRate = $this->data['discount_rate'] ?? '0';
61
 
82
 
62
             if ($discountComputation->isPercentage()) {
83
             if ($discountComputation->isPercentage()) {
63
-                $discountTotal = $subtotal * ($discountRate / 100);
84
+                $scaledDiscountRate = RateCalculator::parseLocalizedRate($discountRate);
85
+                $discountTotalInCents = RateCalculator::calculatePercentage($subtotalInCents, $scaledDiscountRate);
64
             } else {
86
             } else {
65
-                $discountTotal = $discountRate;
87
+                $discountTotalInCents = CurrencyConverter::convertToCents($discountRate, $currencyCode);
66
             }
88
             }
67
         }
89
         }
68
 
90
 
69
-        $grandTotal = $subtotal + ($taxTotal - $discountTotal);
91
+        $grandTotalInCents = $subtotalInCents + ($taxTotalInCents - $discountTotalInCents);
92
+
93
+        $conversionMessage = null;
94
+
95
+        if ($currencyCode !== $defaultCurrencyCode) {
96
+            $rate = currency($currencyCode)->getRate();
97
+
98
+            $convertedTotalInCents = CurrencyConverter::convertBalance($grandTotalInCents, $currencyCode, $defaultCurrencyCode);
99
+
100
+            $conversionMessage = sprintf(
101
+                'Currency conversion: %s (%s) at an exchange rate of %s',
102
+                CurrencyConverter::formatCentsToMoney($convertedTotalInCents, $defaultCurrencyCode),
103
+                $defaultCurrencyCode,
104
+                $rate
105
+            );
106
+        }
70
 
107
 
71
         return [
108
         return [
72
-            'subtotal' => CurrencyConverter::formatToMoney($subtotal),
73
-            'taxTotal' => CurrencyConverter::formatToMoney($taxTotal),
74
-            'discountTotal' => CurrencyConverter::formatToMoney($discountTotal),
75
-            'grandTotal' => CurrencyConverter::formatToMoney($grandTotal),
109
+            'subtotal' => CurrencyConverter::formatCentsToMoney($subtotalInCents, $currencyCode),
110
+            'taxTotal' => CurrencyConverter::formatCentsToMoney($taxTotalInCents, $currencyCode),
111
+            'discountTotal' => CurrencyConverter::formatCentsToMoney($discountTotalInCents, $currencyCode),
112
+            'grandTotal' => CurrencyConverter::formatCentsToMoney($grandTotalInCents, $currencyCode),
113
+            'conversionMessage' => $conversionMessage,
76
         ];
114
         ];
77
     }
115
     }
78
 }
116
 }

+ 1
- 1
config/money.php Visa fil

1101
             'symbol' => 'Fr.',
1101
             'symbol' => 'Fr.',
1102
             'symbol_first' => true,
1102
             'symbol_first' => true,
1103
             'decimal_mark' => '.',
1103
             'decimal_mark' => '.',
1104
-            'thousands_separator' => ',',
1104
+            'thousands_separator' => "'",
1105
         ],
1105
         ],
1106
 
1106
 
1107
         'MOP' => [
1107
         'MOP' => [

+ 7
- 7
resources/views/filament/company/resources/sales/invoices/components/invoice-view.blade.php Visa fil

113
                         @endif
113
                         @endif
114
                     </td>
114
                     </td>
115
                     <td class="text-center py-3">{{ $item->quantity }}</td>
115
                     <td class="text-center py-3">{{ $item->quantity }}</td>
116
-                    <td class="text-right py-3">{{ CurrencyConverter::formatToMoney($item->unit_price) }}</td>
117
-                    <td class="text-right pr-6 py-3">{{ CurrencyConverter::formatToMoney($item->subtotal) }}</td>
116
+                    <td class="text-right py-3">{{ CurrencyConverter::formatToMoney($item->unit_price, $invoice->currency_code) }}</td>
117
+                    <td class="text-right pr-6 py-3">{{ CurrencyConverter::formatToMoney($item->subtotal, $invoice->currency_code) }}</td>
118
                 </tr>
118
                 </tr>
119
             @endforeach
119
             @endforeach
120
             </tbody>
120
             </tbody>
122
             <tr>
122
             <tr>
123
                 <td class="pl-6 py-2" colspan="2"></td>
123
                 <td class="pl-6 py-2" colspan="2"></td>
124
                 <td class="text-right font-semibold py-2">Subtotal:</td>
124
                 <td class="text-right font-semibold py-2">Subtotal:</td>
125
-                <td class="text-right pr-6 py-2">{{ CurrencyConverter::formatToMoney($invoice->subtotal) }}</td>
125
+                <td class="text-right pr-6 py-2">{{ CurrencyConverter::formatToMoney($invoice->subtotal, $invoice->currency_code) }}</td>
126
             </tr>
126
             </tr>
127
             @if($invoice->discount_total)
127
             @if($invoice->discount_total)
128
                 <tr class="text-success-800 dark:text-success-600">
128
                 <tr class="text-success-800 dark:text-success-600">
129
                     <td class="pl-6 py-2" colspan="2"></td>
129
                     <td class="pl-6 py-2" colspan="2"></td>
130
                     <td class="text-right py-2">Discount:</td>
130
                     <td class="text-right py-2">Discount:</td>
131
                     <td class="text-right pr-6 py-2">
131
                     <td class="text-right pr-6 py-2">
132
-                        ({{ CurrencyConverter::formatToMoney($invoice->discount_total) }})
132
+                        ({{ CurrencyConverter::formatToMoney($invoice->discount_total, $invoice->currency_code) }})
133
                     </td>
133
                     </td>
134
                 </tr>
134
                 </tr>
135
             @endif
135
             @endif
137
                 <tr>
137
                 <tr>
138
                     <td class="pl-6 py-2" colspan="2"></td>
138
                     <td class="pl-6 py-2" colspan="2"></td>
139
                     <td class="text-right py-2">Tax:</td>
139
                     <td class="text-right py-2">Tax:</td>
140
-                    <td class="text-right pr-6 py-2">{{ CurrencyConverter::formatToMoney($invoice->tax_total) }}</td>
140
+                    <td class="text-right pr-6 py-2">{{ CurrencyConverter::formatToMoney($invoice->tax_total, $invoice->currency_code) }}</td>
141
                 </tr>
141
                 </tr>
142
             @endif
142
             @endif
143
             <tr>
143
             <tr>
144
                 <td class="pl-6 py-2" colspan="2"></td>
144
                 <td class="pl-6 py-2" colspan="2"></td>
145
                 <td class="text-right font-semibold border-t py-2">Total:</td>
145
                 <td class="text-right font-semibold border-t py-2">Total:</td>
146
-                <td class="text-right border-t pr-6 py-2">{{ CurrencyConverter::formatToMoney($invoice->total) }}</td>
146
+                <td class="text-right border-t pr-6 py-2">{{ CurrencyConverter::formatToMoney($invoice->total, $invoice->currency_code) }}</td>
147
             </tr>
147
             </tr>
148
             <tr>
148
             <tr>
149
                 <td class="pl-6 py-2" colspan="2"></td>
149
                 <td class="pl-6 py-2" colspan="2"></td>
150
                 <td class="text-right font-semibold border-t-4 border-double py-2">Amount Due
150
                 <td class="text-right font-semibold border-t-4 border-double py-2">Amount Due
151
                     ({{ $invoice->currency_code }}):
151
                     ({{ $invoice->currency_code }}):
152
                 </td>
152
                 </td>
153
-                <td class="text-right border-t-4 border-double pr-6 py-2">{{ CurrencyConverter::formatToMoney($invoice->amount_due) }}</td>
153
+                <td class="text-right border-t-4 border-double pr-6 py-2">{{ CurrencyConverter::formatToMoney($invoice->amount_due, $invoice->currency_code) }}</td>
154
             </tr>
154
             </tr>
155
             </tfoot>
155
             </tfoot>
156
         </table>
156
         </table>

+ 7
- 0
resources/views/filament/forms/components/invoice-totals.blade.php Visa fil

56
                 <td class="text-sm px-4 py-2 font-medium leading-6 text-gray-950 dark:text-white">Total:</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>
57
                 <td class="text-sm pl-4 py-2 leading-6">{{ $grandTotal }}</td>
58
             </tr>
58
             </tr>
59
+            @if($conversionMessage)
60
+                <tr>
61
+                    <td colspan="6" class="text-sm pl-4 py-2 leading-6 text-gray-600">
62
+                        {{ $conversionMessage }}
63
+                    </td>
64
+                </tr>
65
+            @endif
59
         </tbody>
66
         </tbody>
60
     </table>
67
     </table>
61
 </div>
68
 </div>

Laddar…
Avbryt
Spara