Andrew Wallo 9 miesięcy temu
rodzic
commit
a0ecf7dd4f

+ 33
- 0
app/DTO/ClientDTO.php Wyświetl plik

@@ -0,0 +1,33 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+use App\Models\Common\Client;
6
+
7
+readonly class ClientDTO
8
+{
9
+    public function __construct(
10
+        public string $name,
11
+        public string $addressLine1,
12
+        public string $addressLine2,
13
+        public string $city,
14
+        public string $state,
15
+        public string $postalCode,
16
+        public string $country,
17
+    ) {}
18
+
19
+    public static function fromModel(Client $client): self
20
+    {
21
+        $address = $client->billingAddress ?? null;
22
+
23
+        return new self(
24
+            name: $client->name,
25
+            addressLine1: $address?->address_line_1 ?? '',
26
+            addressLine2: $address?->address_line_2 ?? '',
27
+            city: $address?->city ?? '',
28
+            state: $address?->state ?? '',
29
+            postalCode: $address?->postal_code ?? '',
30
+            country: $address?->country ?? '',
31
+        );
32
+    }
33
+}

+ 31
- 0
app/DTO/CompanyDTO.php Wyświetl plik

@@ -0,0 +1,31 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+use App\Models\Company;
6
+
7
+readonly class CompanyDTO
8
+{
9
+    public function __construct(
10
+        public string $name,
11
+        public string $address,
12
+        public string $city,
13
+        public string $state,
14
+        public string $zipCode,
15
+        public string $country,
16
+    ) {}
17
+
18
+    public static function fromModel(Company $company): self
19
+    {
20
+        $profile = $company->profile;
21
+
22
+        return new self(
23
+            name: $company->name,
24
+            address: $profile->address ?? '',
25
+            city: $profile->city?->name ?? '',
26
+            state: $profile->state?->name ?? '',
27
+            zipCode: $profile->zip_code ?? '',
28
+            country: $profile->state?->country->name ?? '',
29
+        );
30
+    }
31
+}

+ 71
- 0
app/DTO/DocumentDTO.php Wyświetl plik

@@ -0,0 +1,71 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+use App\Models\Accounting\Document;
6
+use App\Models\Setting\DocumentDefault;
7
+use App\Utilities\Currency\CurrencyAccessor;
8
+use App\Utilities\Currency\CurrencyConverter;
9
+
10
+readonly class DocumentDTO
11
+{
12
+    /**
13
+     * @param  LineItemDTO[]  $lineItems
14
+     */
15
+    public function __construct(
16
+        public ?string $header,
17
+        public ?string $footer,
18
+        public ?string $terms,
19
+        public ?string $logo,
20
+        public string $number,
21
+        public ?string $referenceNumber,
22
+        public string $date,
23
+        public string $dueDate,
24
+        public string $currencyCode,
25
+        public string $subtotal,
26
+        public string $discount,
27
+        public string $tax,
28
+        public string $total,
29
+        public string $amountDue,
30
+        public CompanyDTO $company,
31
+        public ClientDTO $client,
32
+        public iterable $lineItems,
33
+        public DocumentLabelDTO $label,
34
+        public string $accentColor = '#000000',
35
+        public bool $showLogo = true,
36
+    ) {}
37
+
38
+    public static function fromModel(Document $document): self
39
+    {
40
+        /** @var DocumentDefault $settings */
41
+        $settings = $document->company->defaultInvoice;
42
+
43
+        return new self(
44
+            header: $document->header,
45
+            footer: $document->footer,
46
+            terms: $document->terms,
47
+            logo: $document->logo,
48
+            number: $document->documentNumber(),
49
+            referenceNumber: $document->referenceNumber(),
50
+            date: $document->documentDate(),
51
+            dueDate: $document->dueDate(),
52
+            currencyCode: $document->currency_code ?? CurrencyAccessor::getDefaultCurrency(),
53
+            subtotal: self::formatToMoney($document->subtotal, $document->currency_code),
54
+            discount: self::formatToMoney($document->discount_total, $document->currency_code),
55
+            tax: self::formatToMoney($document->tax_total, $document->currency_code),
56
+            total: self::formatToMoney($document->total, $document->currency_code),
57
+            amountDue: self::formatToMoney($document->amountDue(), $document->currency_code),
58
+            company: CompanyDTO::fromModel($document->company),
59
+            client: ClientDTO::fromModel($document->client),
60
+            lineItems: $document->lineItems->map(fn ($item) => LineItemDTO::fromModel($item)),
61
+            label: $document->documentType()->getLabels(),
62
+            accentColor: $settings->accent_color ?? '#000000',
63
+            showLogo: $settings->show_logo ?? false,
64
+        );
65
+    }
66
+
67
+    private static function formatToMoney(float | string $value, ?string $currencyCode): string
68
+    {
69
+        return CurrencyConverter::formatToMoney($value, $currencyCode);
70
+    }
71
+}

+ 27
- 0
app/DTO/DocumentLabelDTO.php Wyświetl plik

@@ -0,0 +1,27 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+readonly class DocumentLabelDTO
6
+{
7
+    public function __construct(
8
+        public string $title,
9
+        public string $number,
10
+        public string $referenceNumber,
11
+        public string $date,
12
+        public string $dueDate,
13
+        public string $amountDue,
14
+    ) {}
15
+
16
+    public function toArray(): array
17
+    {
18
+        return [
19
+            'title' => $this->title,
20
+            'number' => $this->number,
21
+            'reference_number' => $this->referenceNumber,
22
+            'date' => $this->date,
23
+            'due_date' => $this->dueDate,
24
+            'amount_due' => $this->amountDue,
25
+        ];
26
+    }
27
+}

+ 33
- 0
app/DTO/LineItemDTO.php Wyświetl plik

@@ -0,0 +1,33 @@
1
+<?php
2
+
3
+namespace App\DTO;
4
+
5
+use App\Models\Accounting\DocumentLineItem;
6
+use App\Utilities\Currency\CurrencyConverter;
7
+
8
+readonly class LineItemDTO
9
+{
10
+    public function __construct(
11
+        public string $name,
12
+        public string $description,
13
+        public int $quantity,
14
+        public string $unitPrice,
15
+        public string $subtotal,
16
+    ) {}
17
+
18
+    public static function fromModel(DocumentLineItem $lineItem): self
19
+    {
20
+        return new self(
21
+            name: $lineItem->offering->name ?? '',
22
+            description: $lineItem->description ?? '',
23
+            quantity: $lineItem->quantity,
24
+            unitPrice: self::formatToMoney($lineItem->unit_price, $lineItem->documentable->currency_code),
25
+            subtotal: self::formatToMoney($lineItem->subtotal, $lineItem->documentable->currency_code),
26
+        );
27
+    }
28
+
29
+    private static function formatToMoney(float | string $value, ?string $currencyCode): string
30
+    {
31
+        return CurrencyConverter::formatToMoney($value, $currencyCode);
32
+    }
33
+}

+ 34
- 33
app/Enums/Accounting/DocumentType.php Wyświetl plik

@@ -2,6 +2,7 @@
2 2
 
3 3
 namespace App\Enums\Accounting;
4 4
 
5
+use App\DTO\DocumentLabelDTO;
5 6
 use Filament\Support\Contracts\HasIcon;
6 7
 use Filament\Support\Contracts\HasLabel;
7 8
 
@@ -47,41 +48,41 @@ enum DocumentType: string implements HasIcon, HasLabel
47 48
         };
48 49
     }
49 50
 
50
-    public function getLabels(): array
51
+    public function getLabels(): DocumentLabelDTO
51 52
     {
52 53
         return match ($this) {
53
-            self::Invoice => [
54
-                'title' => 'Invoice',
55
-                'number' => 'Invoice Number',
56
-                'reference_number' => 'P.O/S.O Number',
57
-                'date' => 'Invoice Date',
58
-                'due_date' => 'Payment Due',
59
-                'amount_due' => 'Amount Due',
60
-            ],
61
-            self::RecurringInvoice => [
62
-                'title' => 'Recurring Invoice',
63
-                'number' => 'Invoice Number',
64
-                'reference_number' => 'P.O/S.O Number',
65
-                'date' => 'Invoice Date',
66
-                'due_date' => 'Payment Due',
67
-                'amount_due' => 'Amount Due',
68
-            ],
69
-            self::Estimate => [
70
-                'title' => 'Estimate',
71
-                'number' => 'Estimate Number',
72
-                'reference_number' => 'Reference Number',
73
-                'date' => 'Estimate Date',
74
-                'due_date' => 'Expiration Date',
75
-                'amount_due' => 'Grand Total',
76
-            ],
77
-            self::Bill => [
78
-                'title' => 'Bill',
79
-                'number' => 'Bill Number',
80
-                'reference_number' => 'P.O/S.O Number',
81
-                'date' => 'Bill Date',
82
-                'due_date' => 'Payment Due',
83
-                'amount_due' => 'Amount Due',
84
-            ],
54
+            self::Invoice => new DocumentLabelDTO(
55
+                title: 'Invoice',
56
+                number: 'Invoice Number',
57
+                referenceNumber: 'P.O/S.O Number',
58
+                date: 'Invoice Date',
59
+                dueDate: 'Payment Due',
60
+                amountDue: 'Amount Due',
61
+            ),
62
+            self::RecurringInvoice => new DocumentLabelDTO(
63
+                title: 'Recurring Invoice',
64
+                number: 'Invoice Number',
65
+                referenceNumber: 'P.O/S.O Number',
66
+                date: 'Invoice Date',
67
+                dueDate: 'Payment Due',
68
+                amountDue: 'Amount Due',
69
+            ),
70
+            self::Estimate => new DocumentLabelDTO(
71
+                title: 'Estimate',
72
+                number: 'Estimate Number',
73
+                referenceNumber: 'Reference Number',
74
+                date: 'Estimate Date',
75
+                dueDate: 'Expiration Date',
76
+                amountDue: 'Grand Total',
77
+            ),
78
+            self::Bill => new DocumentLabelDTO(
79
+                title: 'Bill',
80
+                number: 'Bill Number',
81
+                referenceNumber: 'P.O/S.O Number',
82
+                date: 'Bill Date',
83
+                dueDate: 'Payment Due',
84
+                amountDue: 'Amount Due',
85
+            ),
85 86
         };
86 87
     }
87 88
 }

+ 0
- 9
app/Filament/Company/Resources/Sales/RecurringInvoiceResource/Pages/ViewRecurringInvoice.php Wyświetl plik

@@ -2,7 +2,6 @@
2 2
 
3 3
 namespace App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
4 4
 
5
-use App\Enums\Accounting\DayOfMonth;
6 5
 use App\Enums\Accounting\DocumentType;
7 6
 use App\Filament\Company\Resources\Sales\ClientResource;
8 7
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource;
@@ -25,14 +24,6 @@ class ViewRecurringInvoice extends ViewRecord
25 24
 {
26 25
     protected static string $resource = RecurringInvoiceResource::class;
27 26
 
28
-    protected function mutateFormDataBeforeFill(array $data): array
29
-    {
30
-        $data['day_of_month'] ??= DayOfMonth::First;
31
-        $data['start_date'] ??= now()->addMonth()->startOfMonth();
32
-
33
-        return $data;
34
-    }
35
-
36 27
     public function getMaxContentWidth(): MaxWidth | string | null
37 28
     {
38 29
         return MaxWidth::SixExtraLarge;

+ 32
- 23
app/Models/Accounting/Bill.php Wyświetl plik

@@ -5,11 +5,10 @@ namespace App\Models\Accounting;
5 5
 use App\Casts\MoneyCast;
6 6
 use App\Casts\RateCast;
7 7
 use App\Collections\Accounting\DocumentCollection;
8
-use App\Concerns\Blamable;
9
-use App\Concerns\CompanyOwned;
10 8
 use App\Enums\Accounting\AdjustmentComputation;
11 9
 use App\Enums\Accounting\BillStatus;
12 10
 use App\Enums\Accounting\DocumentDiscountMethod;
11
+use App\Enums\Accounting\DocumentType;
13 12
 use App\Enums\Accounting\JournalEntryType;
14 13
 use App\Enums\Accounting\TransactionType;
15 14
 use App\Filament\Company\Resources\Purchases\BillResource;
@@ -25,7 +24,6 @@ use Illuminate\Database\Eloquent\Attributes\CollectedBy;
25 24
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
26 25
 use Illuminate\Database\Eloquent\Builder;
27 26
 use Illuminate\Database\Eloquent\Casts\Attribute;
28
-use Illuminate\Database\Eloquent\Factories\HasFactory;
29 27
 use Illuminate\Database\Eloquent\Model;
30 28
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
31 29
 use Illuminate\Database\Eloquent\Relations\MorphMany;
@@ -34,12 +32,8 @@ use Illuminate\Support\Carbon;
34 32
 
35 33
 #[CollectedBy(DocumentCollection::class)]
36 34
 #[ObservedBy(BillObserver::class)]
37
-class Bill extends Model
35
+class Bill extends Document
38 36
 {
39
-    use Blamable;
40
-    use CompanyOwned;
41
-    use HasFactory;
42
-
43 37
     protected $table = 'bills';
44 38
 
45 39
     protected $fillable = [
@@ -81,21 +75,11 @@ class Bill extends Model
81 75
         'amount_due' => MoneyCast::class,
82 76
     ];
83 77
 
84
-    public function currency(): BelongsTo
85
-    {
86
-        return $this->belongsTo(Currency::class, 'currency_code', 'code');
87
-    }
88
-
89 78
     public function vendor(): BelongsTo
90 79
     {
91 80
         return $this->belongsTo(Vendor::class);
92 81
     }
93 82
 
94
-    public function lineItems(): MorphMany
95
-    {
96
-        return $this->morphMany(DocumentLineItem::class, 'documentable');
97
-    }
98
-
99 83
     public function transactions(): MorphMany
100 84
     {
101 85
         return $this->morphMany(Transaction::class, 'transactionable');
@@ -122,6 +106,36 @@ class Bill extends Model
122 106
             ->where('type', TransactionType::Journal);
123 107
     }
124 108
 
109
+    public function documentType(): DocumentType
110
+    {
111
+        return DocumentType::Bill;
112
+    }
113
+
114
+    public function documentNumber(): ?string
115
+    {
116
+        return $this->bill_number;
117
+    }
118
+
119
+    public function documentDate(): ?string
120
+    {
121
+        return $this->date?->toDefaultDateFormat();
122
+    }
123
+
124
+    public function dueDate(): ?string
125
+    {
126
+        return $this->due_date?->toDefaultDateFormat();
127
+    }
128
+
129
+    public function referenceNumber(): ?string
130
+    {
131
+        return $this->order_number;
132
+    }
133
+
134
+    public function amountDue(): ?string
135
+    {
136
+        return $this->amount_due;
137
+    }
138
+
125 139
     protected function isCurrentlyOverdue(): Attribute
126 140
     {
127 141
         return Attribute::get(function () {
@@ -152,11 +166,6 @@ class Bill extends Model
152 166
         ]) && $this->currency_code === CurrencyAccessor::getDefaultCurrency();
153 167
     }
154 168
 
155
-    public function hasLineItems(): bool
156
-    {
157
-        return $this->lineItems()->exists();
158
-    }
159
-
160 169
     public function hasPayments(): bool
161 170
     {
162 171
         return $this->payments->isNotEmpty();

+ 46
- 0
app/Models/Accounting/Document.php Wyświetl plik

@@ -0,0 +1,46 @@
1
+<?php
2
+
3
+namespace App\Models\Accounting;
4
+
5
+use App\Concerns\Blamable;
6
+use App\Concerns\CompanyOwned;
7
+use App\Enums\Accounting\DocumentType;
8
+use App\Models\Setting\Currency;
9
+use Illuminate\Database\Eloquent\Factories\HasFactory;
10
+use Illuminate\Database\Eloquent\Model;
11
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
12
+use Illuminate\Database\Eloquent\Relations\MorphMany;
13
+
14
+abstract class Document extends Model
15
+{
16
+    use Blamable;
17
+    use CompanyOwned;
18
+    use HasFactory;
19
+
20
+    public function currency(): BelongsTo
21
+    {
22
+        return $this->belongsTo(Currency::class, 'currency_code', 'code');
23
+    }
24
+
25
+    public function lineItems(): MorphMany
26
+    {
27
+        return $this->morphMany(DocumentLineItem::class, 'documentable');
28
+    }
29
+
30
+    public function hasLineItems(): bool
31
+    {
32
+        return $this->lineItems()->exists();
33
+    }
34
+
35
+    abstract public function documentType(): DocumentType;
36
+
37
+    abstract public function documentNumber(): ?string;
38
+
39
+    abstract public function documentDate(): ?string;
40
+
41
+    abstract public function dueDate(): ?string;
42
+
43
+    abstract public function referenceNumber(): ?string;
44
+
45
+    abstract public function amountDue(): ?string;
46
+}

+ 28
- 21
app/Models/Accounting/Estimate.php Wyświetl plik

@@ -5,16 +5,14 @@ namespace App\Models\Accounting;
5 5
 use App\Casts\MoneyCast;
6 6
 use App\Casts\RateCast;
7 7
 use App\Collections\Accounting\DocumentCollection;
8
-use App\Concerns\Blamable;
9
-use App\Concerns\CompanyOwned;
10 8
 use App\Enums\Accounting\AdjustmentComputation;
11 9
 use App\Enums\Accounting\DocumentDiscountMethod;
10
+use App\Enums\Accounting\DocumentType;
12 11
 use App\Enums\Accounting\EstimateStatus;
13 12
 use App\Enums\Accounting\InvoiceStatus;
14 13
 use App\Filament\Company\Resources\Sales\EstimateResource;
15 14
 use App\Filament\Company\Resources\Sales\InvoiceResource;
16 15
 use App\Models\Common\Client;
17
-use App\Models\Setting\Currency;
18 16
 use App\Observers\EstimateObserver;
19 17
 use Filament\Actions\Action;
20 18
 use Filament\Actions\MountableAction;
@@ -23,21 +21,15 @@ use Illuminate\Database\Eloquent\Attributes\CollectedBy;
23 21
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
24 22
 use Illuminate\Database\Eloquent\Builder;
25 23
 use Illuminate\Database\Eloquent\Casts\Attribute;
26
-use Illuminate\Database\Eloquent\Factories\HasFactory;
27 24
 use Illuminate\Database\Eloquent\Model;
28 25
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
29 26
 use Illuminate\Database\Eloquent\Relations\HasOne;
30
-use Illuminate\Database\Eloquent\Relations\MorphMany;
31 27
 use Illuminate\Support\Carbon;
32 28
 
33 29
 #[CollectedBy(DocumentCollection::class)]
34 30
 #[ObservedBy(EstimateObserver::class)]
35
-class Estimate extends Model
31
+class Estimate extends Document
36 32
 {
37
-    use Blamable;
38
-    use CompanyOwned;
39
-    use HasFactory;
40
-
41 33
     protected $fillable = [
42 34
         'company_id',
43 35
         'client_id',
@@ -92,19 +84,39 @@ class Estimate extends Model
92 84
         return $this->belongsTo(Client::class);
93 85
     }
94 86
 
95
-    public function currency(): BelongsTo
87
+    public function invoice(): HasOne
96 88
     {
97
-        return $this->belongsTo(Currency::class, 'currency_code', 'code');
89
+        return $this->hasOne(Invoice::class);
98 90
     }
99 91
 
100
-    public function invoice(): HasOne
92
+    public function documentType(): DocumentType
101 93
     {
102
-        return $this->hasOne(Invoice::class);
94
+        return DocumentType::Estimate;
95
+    }
96
+
97
+    public function documentNumber(): ?string
98
+    {
99
+        return $this->estimate_number;
100
+    }
101
+
102
+    public function documentDate(): ?string
103
+    {
104
+        return $this->date?->toDateString();
105
+    }
106
+
107
+    public function dueDate(): ?string
108
+    {
109
+        return $this->expiration_date?->toDateString();
110
+    }
111
+
112
+    public function referenceNumber(): ?string
113
+    {
114
+        return $this->reference_number;
103 115
     }
104 116
 
105
-    public function lineItems(): MorphMany
117
+    public function amountDue(): ?string
106 118
     {
107
-        return $this->morphMany(DocumentLineItem::class, 'documentable');
119
+        return $this->total;
108 120
     }
109 121
 
110 122
     protected function isCurrentlyExpired(): Attribute
@@ -190,11 +202,6 @@ class Estimate extends Model
190 202
             && ! $this->wasConverted();
191 203
     }
192 204
 
193
-    public function hasLineItems(): bool
194
-    {
195
-        return $this->lineItems()->exists();
196
-    }
197
-
198 205
     public function scopeActive(Builder $query): Builder
199 206
     {
200 207
         return $query->whereIn('status', [

+ 32
- 24
app/Models/Accounting/Invoice.php Wyświetl plik

@@ -5,10 +5,9 @@ namespace App\Models\Accounting;
5 5
 use App\Casts\MoneyCast;
6 6
 use App\Casts\RateCast;
7 7
 use App\Collections\Accounting\DocumentCollection;
8
-use App\Concerns\Blamable;
9
-use App\Concerns\CompanyOwned;
10 8
 use App\Enums\Accounting\AdjustmentComputation;
11 9
 use App\Enums\Accounting\DocumentDiscountMethod;
10
+use App\Enums\Accounting\DocumentType;
12 11
 use App\Enums\Accounting\InvoiceStatus;
13 12
 use App\Enums\Accounting\JournalEntryType;
14 13
 use App\Enums\Accounting\TransactionType;
@@ -16,7 +15,6 @@ use App\Filament\Company\Resources\Sales\InvoiceResource;
16 15
 use App\Models\Banking\BankAccount;
17 16
 use App\Models\Common\Client;
18 17
 use App\Models\Company;
19
-use App\Models\Setting\Currency;
20 18
 use App\Observers\InvoiceObserver;
21 19
 use App\Utilities\Currency\CurrencyAccessor;
22 20
 use App\Utilities\Currency\CurrencyConverter;
@@ -27,7 +25,6 @@ use Illuminate\Database\Eloquent\Attributes\CollectedBy;
27 25
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
28 26
 use Illuminate\Database\Eloquent\Builder;
29 27
 use Illuminate\Database\Eloquent\Casts\Attribute;
30
-use Illuminate\Database\Eloquent\Factories\HasFactory;
31 28
 use Illuminate\Database\Eloquent\Model;
32 29
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
33 30
 use Illuminate\Database\Eloquent\Relations\MorphMany;
@@ -36,12 +33,8 @@ use Illuminate\Support\Carbon;
36 33
 
37 34
 #[CollectedBy(DocumentCollection::class)]
38 35
 #[ObservedBy(InvoiceObserver::class)]
39
-class Invoice extends Model
36
+class Invoice extends Document
40 37
 {
41
-    use Blamable;
42
-    use CompanyOwned;
43
-    use HasFactory;
44
-
45 38
     protected $table = 'invoices';
46 39
 
47 40
     protected $fillable = [
@@ -100,11 +93,6 @@ class Invoice extends Model
100 93
         return $this->belongsTo(Client::class);
101 94
     }
102 95
 
103
-    public function currency(): BelongsTo
104
-    {
105
-        return $this->belongsTo(Currency::class, 'currency_code', 'code');
106
-    }
107
-
108 96
     public function estimate(): BelongsTo
109 97
     {
110 98
         return $this->belongsTo(Estimate::class);
@@ -115,11 +103,6 @@ class Invoice extends Model
115 103
         return $this->belongsTo(RecurringInvoice::class);
116 104
     }
117 105
 
118
-    public function lineItems(): MorphMany
119
-    {
120
-        return $this->morphMany(DocumentLineItem::class, 'documentable');
121
-    }
122
-
123 106
     public function transactions(): MorphMany
124 107
     {
125 108
         return $this->morphMany(Transaction::class, 'transactionable');
@@ -146,6 +129,36 @@ class Invoice extends Model
146 129
             ->where('type', TransactionType::Journal);
147 130
     }
148 131
 
132
+    public function documentType(): DocumentType
133
+    {
134
+        return DocumentType::Invoice;
135
+    }
136
+
137
+    public function documentNumber(): ?string
138
+    {
139
+        return $this->invoice_number;
140
+    }
141
+
142
+    public function documentDate(): ?string
143
+    {
144
+        return $this->date?->toDefaultDateFormat();
145
+    }
146
+
147
+    public function dueDate(): ?string
148
+    {
149
+        return $this->due_date?->toDefaultDateFormat();
150
+    }
151
+
152
+    public function referenceNumber(): ?string
153
+    {
154
+        return $this->order_number;
155
+    }
156
+
157
+    public function amountDue(): ?string
158
+    {
159
+        return $this->amount_due;
160
+    }
161
+
149 162
     public function scopeUnpaid(Builder $query): Builder
150 163
     {
151 164
         return $query->whereNotIn('status', [
@@ -222,11 +235,6 @@ class Invoice extends Model
222 235
         return ! $this->hasBeenSent();
223 236
     }
224 237
 
225
-    public function hasLineItems(): bool
226
-    {
227
-        return $this->lineItems()->exists();
228
-    }
229
-
230 238
     public function hasPayments(): bool
231 239
     {
232 240
         return $this->payments()->exists();

+ 35
- 37
app/Models/Accounting/RecurringInvoice.php Wyświetl plik

@@ -5,12 +5,11 @@ namespace App\Models\Accounting;
5 5
 use App\Casts\MoneyCast;
6 6
 use App\Casts\RateCast;
7 7
 use App\Collections\Accounting\DocumentCollection;
8
-use App\Concerns\Blamable;
9
-use App\Concerns\CompanyOwned;
10 8
 use App\Enums\Accounting\AdjustmentComputation;
11 9
 use App\Enums\Accounting\DayOfMonth;
12 10
 use App\Enums\Accounting\DayOfWeek;
13 11
 use App\Enums\Accounting\DocumentDiscountMethod;
12
+use App\Enums\Accounting\DocumentType;
14 13
 use App\Enums\Accounting\EndType;
15 14
 use App\Enums\Accounting\Frequency;
16 15
 use App\Enums\Accounting\IntervalType;
@@ -20,32 +19,23 @@ use App\Enums\Setting\PaymentTerms;
20 19
 use App\Filament\Forms\Components\CustomSection;
21 20
 use App\Models\Common\Client;
22 21
 use App\Models\Setting\CompanyProfile;
23
-use App\Models\Setting\Currency;
24 22
 use App\Observers\RecurringInvoiceObserver;
25 23
 use App\Utilities\Localization\Timezone;
26 24
 use Filament\Actions\Action;
27 25
 use Filament\Actions\MountableAction;
28 26
 use Filament\Forms;
29 27
 use Filament\Forms\Form;
30
-use Filament\Support\Enums\MaxWidth;
31 28
 use Guava\FilamentClusters\Forms\Cluster;
32 29
 use Illuminate\Database\Eloquent\Attributes\CollectedBy;
33 30
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
34
-use Illuminate\Database\Eloquent\Factories\HasFactory;
35
-use Illuminate\Database\Eloquent\Model;
36 31
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
37 32
 use Illuminate\Database\Eloquent\Relations\HasMany;
38
-use Illuminate\Database\Eloquent\Relations\MorphMany;
39 33
 use Illuminate\Support\Carbon;
40 34
 
41 35
 #[CollectedBy(DocumentCollection::class)]
42 36
 #[ObservedBy(RecurringInvoiceObserver::class)]
43
-class RecurringInvoice extends Model
37
+class RecurringInvoice extends Document
44 38
 {
45
-    use Blamable;
46
-    use CompanyOwned;
47
-    use HasFactory;
48
-
49 39
     protected $table = 'recurring_invoices';
50 40
 
51 41
     protected $fillable = [
@@ -120,19 +110,39 @@ class RecurringInvoice extends Model
120 110
         return $this->belongsTo(Client::class);
121 111
     }
122 112
 
123
-    public function currency(): BelongsTo
113
+    public function invoices(): HasMany
124 114
     {
125
-        return $this->belongsTo(Currency::class, 'currency_code', 'code');
115
+        return $this->hasMany(Invoice::class, 'recurring_invoice_id');
126 116
     }
127 117
 
128
-    public function invoices(): HasMany
118
+    public function documentType(): DocumentType
129 119
     {
130
-        return $this->hasMany(Invoice::class, 'recurring_invoice_id');
120
+        return DocumentType::RecurringInvoice;
121
+    }
122
+
123
+    public function documentNumber(): ?string
124
+    {
125
+        return 'Auto-generated';
126
+    }
127
+
128
+    public function documentDate(): ?string
129
+    {
130
+        return $this->calculateNextDate()?->toDefaultDateFormat() ?? 'Auto-generated';
131
+    }
132
+
133
+    public function dueDate(): ?string
134
+    {
135
+        return $this->calculateNextDueDate()?->toDefaultDateFormat() ?? 'Auto-generated';
136
+    }
137
+
138
+    public function referenceNumber(): ?string
139
+    {
140
+        return $this->order_number;
131 141
     }
132 142
 
133
-    public function lineItems(): MorphMany
143
+    public function amountDue(): ?string
134 144
     {
135
-        return $this->morphMany(DocumentLineItem::class, 'documentable');
145
+        return $this->total;
136 146
     }
137 147
 
138 148
     public function isDraft(): bool
@@ -170,11 +180,6 @@ class RecurringInvoice extends Model
170 180
         return $this->isActive() && ! $this->wasEnded();
171 181
     }
172 182
 
173
-    public function hasLineItems(): bool
174
-    {
175
-        return $this->lineItems()->exists();
176
-    }
177
-
178 183
     public function hasSchedule(): bool
179 184
     {
180 185
         return $this->start_date !== null;
@@ -345,7 +350,6 @@ class RecurringInvoice extends Model
345 350
             ->label(fn (self $record) => $record->hasSchedule() ? 'Update Schedule' : 'Set Schedule')
346 351
             ->icon('heroicon-o-calendar-date-range')
347 352
             ->slideOver()
348
-            ->modalWidth(MaxWidth::FiveExtraLarge)
349 353
             ->successNotificationTitle('Schedule Updated')
350 354
             ->mountUsing(function (self $record, Form $form) {
351 355
                 $data = $record->attributesToArray();
@@ -412,12 +416,10 @@ class RecurringInvoice extends Model
412 416
                         // Custom frequency fields in a nested grid
413 417
                         Cluster::make([
414 418
                             Forms\Components\TextInput::make('interval_value')
415
-                                ->label('every')
416 419
                                 ->softRequired()
417 420
                                 ->numeric()
418 421
                                 ->default(1),
419 422
                             Forms\Components\Select::make('interval_type')
420
-                                ->label('Interval Type')
421 423
                                 ->options(IntervalType::class)
422 424
                                 ->softRequired()
423 425
                                 ->default(IntervalType::Month)
@@ -449,7 +451,7 @@ class RecurringInvoice extends Model
449 451
                                 }),
450 452
                         ])
451 453
                             ->live()
452
-                            ->label('Interval')
454
+                            ->label('Every')
453 455
                             ->required()
454 456
                             ->markAsRequired(false)
455 457
                             ->visible(fn (Forms\Get $get) => Frequency::parse($get('frequency'))?->isCustom()),
@@ -485,11 +487,11 @@ class RecurringInvoice extends Model
485 487
                             ),
486 488
                     ])->columns(2),
487 489
 
488
-                CustomSection::make('Dates')
490
+                CustomSection::make('Dates & Time')
489 491
                     ->contained(false)
490 492
                     ->schema([
491 493
                         Forms\Components\DatePicker::make('start_date')
492
-                            ->label('Create First Invoice')
494
+                            ->label('First Invoice Date')
493 495
                             ->softRequired(),
494 496
 
495 497
                         Forms\Components\Group::make(function (Forms\Get $get) {
@@ -524,6 +526,7 @@ class RecurringInvoice extends Model
524 526
                             if ($endType?->isAfter()) {
525 527
                                 $components[] = Forms\Components\TextInput::make('max_occurrences')
526 528
                                     ->numeric()
529
+                                    ->suffix('invoices')
527 530
                                     ->live();
528 531
                             }
529 532
 
@@ -534,23 +537,18 @@ class RecurringInvoice extends Model
534 537
 
535 538
                             return [
536 539
                                 Cluster::make($components)
537
-                                    ->label('Ends')
540
+                                    ->label('Schedule Ends')
538 541
                                     ->required()
539 542
                                     ->markAsRequired(false),
540 543
                             ];
541 544
                         }),
542
-                    ])
543
-                    ->columns(2),
544 545
 
545
-                CustomSection::make('Time Zone')
546
-                    ->contained(false)
547
-                    ->columns(2)
548
-                    ->schema([
549 546
                         Forms\Components\Select::make('timezone')
550 547
                             ->options(Timezone::getTimezoneOptions(CompanyProfile::first()->country))
551 548
                             ->searchable()
552 549
                             ->softRequired(),
553
-                    ]),
550
+                    ])
551
+                    ->columns(2),
554 552
             ])
555 553
             ->action(function (self $record, array $data, MountableAction $action) {
556 554
                 $record->update($data);

+ 0
- 125
app/View/Models/DocumentPreviewViewModel.php Wyświetl plik

@@ -1,125 +0,0 @@
1
-<?php
2
-
3
-namespace App\View\Models;
4
-
5
-use App\Enums\Accounting\DocumentType;
6
-use App\Models\Accounting\DocumentLineItem;
7
-use App\Models\Common\Client;
8
-use App\Models\Company;
9
-use App\Models\Setting\DocumentDefault;
10
-use App\Utilities\Currency\CurrencyAccessor;
11
-use App\Utilities\Currency\CurrencyConverter;
12
-use Illuminate\Database\Eloquent\Model;
13
-
14
-class DocumentPreviewViewModel
15
-{
16
-    public function __construct(
17
-        public Model $document,
18
-        public DocumentType $documentType = DocumentType::Invoice,
19
-    ) {}
20
-
21
-    public function buildViewData(): array
22
-    {
23
-        return [
24
-            'company' => $this->getCompanyDetails(),
25
-            'client' => $this->getClientDetails(),
26
-            'metadata' => $this->getDocumentMetadata(),
27
-            'lineItems' => $this->getLineItems(),
28
-            'totals' => $this->getTotals(),
29
-            'header' => $this->document->header,
30
-            'footer' => $this->document->footer,
31
-            'terms' => $this->document->terms,
32
-            'logo' => $this->document->logo,
33
-            'style' => $this->getStyle(),
34
-            'labels' => $this->documentType->getLabels(),
35
-        ];
36
-    }
37
-
38
-    private function getCompanyDetails(): array
39
-    {
40
-        /** @var Company $company */
41
-        $company = $this->document->company;
42
-        $profile = $company->profile;
43
-
44
-        return [
45
-            'name' => $company->name,
46
-            'address' => $profile->address ?? '',
47
-            'city' => $profile->city?->name ?? '',
48
-            'state' => $profile->state?->name ?? '',
49
-            'zip_code' => $profile->zip_code ?? '',
50
-            'country' => $profile->state?->country->name ?? '',
51
-        ];
52
-    }
53
-
54
-    private function getClientDetails(): array
55
-    {
56
-        /** @var Client $client */
57
-        $client = $this->document->client;
58
-        $address = $client->billingAddress ?? null;
59
-
60
-        return [
61
-            'name' => $client->name,
62
-            'address_line_1' => $address->address_line_1 ?? '',
63
-            'address_line_2' => $address->address_line_2 ?? '',
64
-            'city' => $address->city ?? '',
65
-            'state' => $address->state ?? '',
66
-            'postal_code' => $address->postal_code ?? '',
67
-            'country' => $address->country ?? '',
68
-        ];
69
-    }
70
-
71
-    private function getDocumentMetadata(): array
72
-    {
73
-        $number = match ($this->documentType) {
74
-            DocumentType::Invoice => $this->document->invoice_number,
75
-            DocumentType::RecurringInvoice => 'Auto-generated',
76
-            DocumentType::Bill => $this->document->bill_number,
77
-            DocumentType::Estimate => $this->document->estimate_number,
78
-        };
79
-
80
-        return [
81
-            'number' => $number,
82
-            'reference_number' => $this->document->order_number ?? $this->document->reference_number,
83
-            'date' => $this->document->date?->toDefaultDateFormat() ?? $this->document->calculateNextDate()?->toDefaultDateFormat(),
84
-            'due_date' => $this->document->due_date?->toDefaultDateFormat() ?? $this->document->expiration_date?->toDefaultDateFormat() ?? $this->document->calculateNextDueDate()?->toDefaultDateFormat(),
85
-            'currency_code' => $this->document->currency_code ?? CurrencyAccessor::getDefaultCurrency(),
86
-        ];
87
-    }
88
-
89
-    private function getLineItems(): array
90
-    {
91
-        $currencyCode = $this->document->currency_code ?? CurrencyAccessor::getDefaultCurrency();
92
-
93
-        return $this->document->lineItems->map(fn (DocumentLineItem $item) => [
94
-            'name' => $item->offering->name ?? '',
95
-            'description' => $item->description ?? '',
96
-            'quantity' => $item->quantity,
97
-            'unit_price' => CurrencyConverter::formatToMoney($item->unit_price, $currencyCode),
98
-            'subtotal' => CurrencyConverter::formatToMoney($item->subtotal, $currencyCode),
99
-        ])->toArray();
100
-    }
101
-
102
-    private function getTotals(): array
103
-    {
104
-        $currencyCode = $this->document->currency_code ?? CurrencyAccessor::getDefaultCurrency();
105
-
106
-        return [
107
-            'subtotal' => CurrencyConverter::formatToMoney($this->document->subtotal, $currencyCode),
108
-            'discount' => CurrencyConverter::formatToMoney($this->document->discount_total, $currencyCode),
109
-            'tax' => CurrencyConverter::formatToMoney($this->document->tax_total, $currencyCode),
110
-            'total' => CurrencyConverter::formatToMoney($this->document->total, $currencyCode),
111
-            'amount_due' => $this->document->amount_due ? CurrencyConverter::formatToMoney($this->document->amount_due, $currencyCode) : CurrencyConverter::formatToMoney($this->document->total, $currencyCode),
112
-        ];
113
-    }
114
-
115
-    private function getStyle(): array
116
-    {
117
-        /** @var DocumentDefault $settings */
118
-        $settings = $this->document->company->defaultInvoice;
119
-
120
-        return [
121
-            'accent_color' => $settings->accent_color ?? '#000000',
122
-            'show_logo' => $settings->show_logo ?? false,
123
-        ];
124
-    }
125
-}

+ 1
- 0
composer.json Wyświetl plik

@@ -25,6 +25,7 @@
25 25
         "laravel/framework": "^11.0",
26 26
         "laravel/sanctum": "^4.0",
27 27
         "laravel/tinker": "^2.9",
28
+        "spatie/laravel-view-models": "^1.6",
28 29
         "squirephp/model": "^3.4",
29 30
         "squirephp/repository": "^3.4",
30 31
         "symfony/intl": "^6.3"

+ 72
- 3
composer.lock Wyświetl plik

@@ -4,7 +4,7 @@
4 4
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 5
         "This file is @generated automatically"
6 6
     ],
7
-    "content-hash": "095bb4040f9910ddd128bd53c0670a55",
7
+    "content-hash": "decc627f2a7bd0c6546114ebb1f500f9",
8 8
     "packages": [
9 9
         {
10 10
             "name": "akaunting/laravel-money",
@@ -6551,6 +6551,75 @@
6551 6551
             ],
6552 6552
             "time": "2024-12-30T13:13:39+00:00"
6553 6553
         },
6554
+        {
6555
+            "name": "spatie/laravel-view-models",
6556
+            "version": "1.6.0",
6557
+            "source": {
6558
+                "type": "git",
6559
+                "url": "https://github.com/spatie/laravel-view-models.git",
6560
+                "reference": "c8c74e26e2cc78d04e581867ce74c8b772279015"
6561
+            },
6562
+            "dist": {
6563
+                "type": "zip",
6564
+                "url": "https://api.github.com/repos/spatie/laravel-view-models/zipball/c8c74e26e2cc78d04e581867ce74c8b772279015",
6565
+                "reference": "c8c74e26e2cc78d04e581867ce74c8b772279015",
6566
+                "shasum": ""
6567
+            },
6568
+            "require": {
6569
+                "illuminate/support": "^8.0|^9.0|^10.0|^11.0",
6570
+                "php": "^7.3|^8.0"
6571
+            },
6572
+            "require-dev": {
6573
+                "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0",
6574
+                "pestphp/pest": "^1.22|^2.34"
6575
+            },
6576
+            "type": "library",
6577
+            "extra": {
6578
+                "laravel": {
6579
+                    "providers": [
6580
+                        "Spatie\\ViewModels\\Providers\\ViewModelsServiceProvider"
6581
+                    ]
6582
+                }
6583
+            },
6584
+            "autoload": {
6585
+                "psr-4": {
6586
+                    "Spatie\\ViewModels\\": "src"
6587
+                }
6588
+            },
6589
+            "notification-url": "https://packagist.org/downloads/",
6590
+            "license": [
6591
+                "MIT"
6592
+            ],
6593
+            "authors": [
6594
+                {
6595
+                    "name": "Brent Roose",
6596
+                    "email": "brent@spatie.be",
6597
+                    "homepage": "https://spatie.be",
6598
+                    "role": "Developer"
6599
+                }
6600
+            ],
6601
+            "description": "View models in Laravel",
6602
+            "homepage": "https://github.com/spatie/laravel-view-models",
6603
+            "keywords": [
6604
+                "laravel-view-models",
6605
+                "spatie"
6606
+            ],
6607
+            "support": {
6608
+                "issues": "https://github.com/spatie/laravel-view-models/issues",
6609
+                "source": "https://github.com/spatie/laravel-view-models/tree/1.6.0"
6610
+            },
6611
+            "funding": [
6612
+                {
6613
+                    "url": "https://spatie.be/open-source/support-us",
6614
+                    "type": "custom"
6615
+                },
6616
+                {
6617
+                    "url": "https://github.com/spatie",
6618
+                    "type": "github"
6619
+                }
6620
+            ],
6621
+            "time": "2024-03-13T17:58:20+00:00"
6622
+        },
6554 6623
         {
6555 6624
             "name": "squirephp/model",
6556 6625
             "version": "v3.7.0",
@@ -13398,7 +13467,7 @@
13398 13467
     ],
13399 13468
     "aliases": [],
13400 13469
     "minimum-stability": "stable",
13401
-    "stability-flags": [],
13470
+    "stability-flags": {},
13402 13471
     "prefer-stable": true,
13403 13472
     "prefer-lowest": false,
13404 13473
     "platform": {
@@ -13406,6 +13475,6 @@
13406 13475
         "ext-bcmath": "*",
13407 13476
         "ext-intl": "*"
13408 13477
     },
13409
-    "platform-dev": [],
13478
+    "platform-dev": {},
13410 13479
     "plugin-api-version": "2.6.0"
13411 13480
 }

+ 27
- 0
resources/css/filament/company/custom-data-table.css Wyświetl plik

@@ -0,0 +1,27 @@
1
+.es-table__header-ctn, .es-table__footer-ctn {
2
+    @apply divide-y divide-gray-200 dark:divide-white/10 min-h-12;
3
+}
4
+
5
+.es-table .es-table__rowgroup td:first-child {
6
+    padding-left: 3rem;
7
+}
8
+
9
+.es-table .es-table__rowgroup .es-table__row > td:nth-child(2) {
10
+    word-wrap: break-word;
11
+    word-break: break-word;
12
+    white-space: normal;
13
+}
14
+
15
+.es-table .es-table__rowgroup .es-table__row > td:nth-child(3) {
16
+    word-wrap: break-word;
17
+    word-break: break-word;
18
+    white-space: normal;
19
+}
20
+
21
+.es-table .es-table__rowgroup .es-table__row > td:nth-child(4) {
22
+    white-space: nowrap;
23
+}
24
+
25
+.es-table .es-table__rowgroup .es-table__row > td:last-child {
26
+    padding-right: 3rem;
27
+}

+ 125
- 0
resources/css/filament/company/custom-section.css Wyświetl plik

@@ -0,0 +1,125 @@
1
+.fi-custom-section {
2
+    &:not(.fi-section-not-contained) {
3
+        & .fi-section-content {
4
+            @apply p-6;
5
+        }
6
+
7
+        & .fi-section-footer {
8
+            @apply border-t border-gray-200 px-6 py-4 dark:border-white/10;
9
+        }
10
+
11
+        &:not(.fi-aside) {
12
+            @apply rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10;
13
+
14
+            & .fi-section-header {
15
+                @apply px-6 py-4;
16
+            }
17
+
18
+            &.fi-section-has-header {
19
+                & .fi-section-content-ctn {
20
+                    @apply border-t border-gray-200 dark:border-white/10;
21
+                }
22
+            }
23
+        }
24
+
25
+        &.fi-aside {
26
+            & .fi-section-content-ctn {
27
+                @apply rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10 md:col-span-2;
28
+            }
29
+        }
30
+
31
+        &.fi-compact {
32
+            &:not(.fi-aside) {
33
+                & .fi-section-header {
34
+                    @apply px-4 py-2.5;
35
+                }
36
+            }
37
+
38
+            & .fi-section-content {
39
+                @apply p-4;
40
+            }
41
+
42
+            & .fi-section-footer {
43
+                @apply px-4 py-2.5;
44
+            }
45
+        }
46
+    }
47
+
48
+    &.fi-section-not-contained:not(.fi-aside) {
49
+        @apply grid gap-y-4;
50
+
51
+        & .fi-section-header {
52
+            @apply py-2;
53
+        }
54
+
55
+        & .fi-section-content-ctn {
56
+            @apply grid gap-y-4;
57
+        }
58
+
59
+        &.fi-compact {
60
+            @apply gap-y-2.5;
61
+
62
+            & .fi-section-content-ctn {
63
+                @apply gap-y-2.5;
64
+            }
65
+        }
66
+    }
67
+
68
+    &.fi-aside {
69
+        @apply grid grid-cols-1 items-start gap-x-6 gap-y-4 md:grid-cols-3;
70
+    }
71
+
72
+    &.fi-collapsible {
73
+        & .fi-section-header {
74
+            @apply cursor-pointer;
75
+        }
76
+    }
77
+
78
+    &.fi-collapsed {
79
+        & .fi-section-collapse-btn {
80
+            @apply rotate-180;
81
+        }
82
+
83
+        & .fi-section-content-ctn {
84
+            @apply invisible absolute h-0 overflow-hidden border-none;
85
+        }
86
+    }
87
+
88
+    &.fi-section-has-content-before {
89
+        & .fi-section-content-ctn {
90
+            @apply md:order-first;
91
+        }
92
+    }
93
+
94
+    & .fi-section-header {
95
+        @apply flex items-center gap-3;
96
+    }
97
+
98
+    & .fi-section-header-icon {
99
+        @apply size-6 self-start text-gray-400 dark:text-gray-500;
100
+
101
+        &.fi-color-custom {
102
+            @apply text-custom-500 dark:text-custom-400;
103
+        }
104
+
105
+        &.fi-size-sm {
106
+            @apply mt-1 size-4;
107
+        }
108
+
109
+        &.fi-size-md {
110
+            @apply mt-0.5 size-5;
111
+        }
112
+    }
113
+
114
+    & .fi-section-header-text-ctn {
115
+        @apply grid flex-1 gap-y-1;
116
+    }
117
+
118
+    & .fi-section-header-heading {
119
+        @apply text-base font-semibold leading-6 text-gray-950 dark:text-white;
120
+    }
121
+
122
+    & .fi-section-header-description {
123
+        @apply overflow-hidden break-words text-sm text-gray-500 dark:text-gray-400;
124
+    }
125
+}

+ 92
- 0
resources/css/filament/company/form-fields.css Wyświetl plik

@@ -0,0 +1,92 @@
1
+/* Filament Repeater Styles */
2
+.fi-fo-repeater.uncontained .fi-fo-repeater-item {
3
+    @apply divide-y-0 rounded-none bg-none shadow-none ring-0 ring-gray-950/5 dark:divide-white/10 dark:bg-white/5 dark:ring-white/10;
4
+
5
+    .fi-fo-repeater-item-header {
6
+        @apply px-0;
7
+
8
+        > h4 {
9
+            @apply text-base font-semibold leading-6 text-gray-950 dark:text-white;
10
+        }
11
+    }
12
+
13
+    .fi-fo-repeater-item-content {
14
+        @apply py-4 px-0;
15
+    }
16
+}
17
+
18
+.fi-fo-repeater-item {
19
+    @apply divide-y divide-gray-200 rounded-xl bg-white dark:bg-gray-900;
20
+}
21
+
22
+/* Report Field Styles */
23
+.fi-fo-field-wrp.report-hidden-label > div.grid.gap-y-2 > div.flex.items-center {
24
+    @apply hidden;
25
+}
26
+
27
+.fi-fo-field-wrp.report-hidden-label {
28
+    @apply lg:mt-8;
29
+}
30
+
31
+/* Choices.js select field overrides */
32
+.choices__list.choices__list--single {
33
+    @apply w-full;
34
+}
35
+
36
+.choices:focus-visible {
37
+    outline: none;
38
+}
39
+
40
+.choices__group {
41
+    @apply text-gray-900 dark:text-white font-semibold;
42
+}
43
+
44
+.choices[data-type="select-one"] .choices__inner {
45
+    line-height: 1.5;
46
+    display: flex;
47
+    align-items: center;
48
+    min-height: 2.25rem;
49
+    box-sizing: border-box;
50
+}
51
+
52
+.choices:not(.is-disabled) .choices__item {
53
+    cursor: pointer;
54
+}
55
+
56
+/* Table Repeater Styles */
57
+.table-repeater-container {
58
+    @apply rounded-none ring-0;
59
+}
60
+
61
+.table-repeater-component {
62
+    @apply space-y-10;
63
+}
64
+
65
+.table-repeater-component ul {
66
+    @apply justify-start;
67
+}
68
+
69
+.table-repeater-row {
70
+    @apply divide-x-0 !important;
71
+}
72
+
73
+.table-repeater-column {
74
+    @apply py-2 !important;
75
+}
76
+
77
+.table-repeater-header {
78
+    @apply rounded-t-none !important;
79
+}
80
+
81
+.table-repeater-rows-wrapper {
82
+    @apply divide-gray-300 last:border-b last:border-gray-300 dark:divide-white/20 dark:last:border-white/20;
83
+}
84
+
85
+.table-repeater-header tr {
86
+    @apply divide-x-0 text-base sm:text-sm sm:leading-6 !important;
87
+}
88
+
89
+.table-repeater-header-column {
90
+    @apply ps-3 pe-3 font-semibold bg-gray-200 dark:bg-gray-800 rounded-none !important;
91
+}
92
+

+ 18
- 0
resources/css/filament/company/modal.css Wyświetl plik

@@ -0,0 +1,18 @@
1
+/* Journal Entry Modal Styles */
2
+.fi-modal.fi-width-screen {
3
+    .fi-modal-header {
4
+        @apply xl:px-80;
5
+
6
+        .absolute.end-4.top-4 {
7
+            @apply xl:end-80;
8
+        }
9
+    }
10
+
11
+    .fi-modal-content {
12
+        @apply xl:px-80;
13
+    }
14
+
15
+    .fi-modal-footer {
16
+        @apply xl:px-80;
17
+    }
18
+}

+ 15
- 0
resources/css/filament/company/report-card.css Wyświetl plik

@@ -0,0 +1,15 @@
1
+.es-report-card {
2
+    @apply md:!grid-cols-2;
3
+
4
+    .fi-fo-component-ctn {
5
+        @apply divide-y divide-gray-200 dark:divide-white/10 !gap-0;
6
+    }
7
+
8
+    .fi-section-content-ctn {
9
+        @apply md:!col-span-1;
10
+    }
11
+
12
+    .fi-section-content {
13
+        @apply !p-0;
14
+    }
15
+}

+ 11
- 176
resources/css/filament/company/theme.css Wyświetl plik

@@ -1,163 +1,23 @@
1 1
 @import '/vendor/filament/filament/resources/css/theme.css';
2
-@import 'tooltip.css';
3 2
 @import '/vendor/awcodes/filament-table-repeater/resources/css/plugin.css';
3
+@import 'custom-data-table.css';
4
+@import 'custom-section.css';
5
+@import 'form-fields.css';
6
+@import 'modal.css';
7
+@import 'report-card.css';
8
+@import 'tooltip.css';
9
+@import 'top-navigation.css';
4 10
 
5 11
 @config 'tailwind.config.js';
6 12
 
7
-.fi-fo-repeater.uncontained .fi-fo-repeater-item {
8
-    @apply divide-y-0 rounded-none bg-none shadow-none ring-0 ring-gray-950/5 dark:divide-white/10 dark:bg-white/5 dark:ring-white/10;
9
-
10
-    .fi-fo-repeater-item-header {
11
-        @apply px-0;
12
-
13
-        > h4 {
14
-            @apply text-base font-semibold leading-6 text-gray-950 dark:text-white;
15
-        }
16
-    }
17
-
18
-    .fi-fo-repeater-item-content {
19
-        @apply py-4 px-0;
20
-    }
21
-}
22
-
23
-.fi-fo-field-wrp.report-hidden-label > div.grid.gap-y-2 > div.flex.items-center {
24
-    @apply hidden;
25
-}
26
-
27
-.fi-fo-repeater-item {
28
-    @apply divide-y divide-gray-200 rounded-xl bg-white dark:bg-gray-900;
29
-}
30
-
31
-.fi-fo-field-wrp.report-hidden-label {
32
-    @apply lg:mt-8;
33
-}
34
-
35
-.choices__list.choices__list--single {
36
-    @apply w-full;
37
-}
38
-
39
-.choices:focus-visible {
40
-    outline: none;
41
-}
42
-
43
-.choices__group {
44
-    @apply text-gray-900 dark:text-white font-semibold;
45
-}
46
-
47
-.choices[data-type="select-one"] .choices__inner {
48
-    line-height: 1.5;
49
-    display: flex;
50
-    align-items: center;
51
-    min-height: 2.25rem;
52
-    box-sizing: border-box;
53
-}
54
-
55
-.choices:not(.is-disabled) .choices__item {
56
-    cursor: pointer;
57
-}
58
-
59
-.table-repeater-container {
60
-    @apply rounded-none ring-0;
61
-}
62
-
63
-.table-repeater-component {
64
-    @apply space-y-10;
65
-}
66
-
67
-.table-repeater-component ul {
68
-    @apply justify-start;
69
-}
70
-
71
-.table-repeater-row {
72
-    @apply divide-x-0 !important;
73
-}
74
-
75
-.table-repeater-column {
76
-    @apply py-2 !important;
77
-}
78
-
79
-.table-repeater-header {
80
-    @apply rounded-t-none !important;
81
-}
82
-
83
-.table-repeater-rows-wrapper {
84
-    @apply divide-gray-300 last:border-b last:border-gray-300 dark:divide-white/20 dark:last:border-white/20;
85
-}
86
-
87
-.table-repeater-header tr {
88
-    @apply divide-x-0 text-base sm:text-sm sm:leading-6 !important;
89
-}
90
-
91
-.table-repeater-header-column {
92
-    @apply ps-3 pe-3 font-semibold bg-gray-200 dark:bg-gray-800 rounded-none !important;
93
-}
94
-
95
-.es-report-card {
96
-    @apply md:!grid-cols-2;
97
-
98
-    .fi-fo-component-ctn {
99
-        @apply divide-y divide-gray-200 dark:divide-white/10 !gap-0;
100
-    }
101
-
102
-    .fi-section-content-ctn {
103
-        @apply md:!col-span-1;
104
-    }
105
-
106
-    .fi-section-content {
107
-        @apply !p-0;
108
-    }
109
-}
110
-
111
-.fi-modal.fi-width-screen {
112
-    .fi-modal-header {
113
-        @apply xl:px-80;
114
-
115
-        .absolute.end-4.top-4 {
116
-            @apply xl:end-80;
117
-        }
118
-    }
119
-
120
-    .fi-modal-content {
121
-        @apply xl:px-80;
122
-    }
123
-
124
-    .fi-modal-footer {
125
-        @apply xl:px-80;
126
-    }
127
-}
128
-
129
-.es-table__header-ctn, .es-table__footer-ctn {
130
-    @apply divide-y divide-gray-200 dark:divide-white/10 min-h-12;
131
-}
132
-
133
-.es-table .es-table__rowgroup td:first-child {
134
-    padding-left: 3rem;
135
-}
136
-
137
-.es-table .es-table__rowgroup .es-table__row > td:nth-child(2) {
138
-    word-wrap: break-word;
139
-    word-break: break-word;
140
-    white-space: normal;
141
-}
142
-
143
-.es-table .es-table__rowgroup .es-table__row > td:nth-child(3) {
144
-    word-wrap: break-word;
145
-    word-break: break-word;
146
-    white-space: normal;
147
-}
148
-
149
-.es-table .es-table__rowgroup .es-table__row > td:nth-child(4) {
150
-    white-space: nowrap;
151
-}
152
-
153
-.es-table .es-table__rowgroup .es-table__row > td:last-child {
154
-    padding-right: 3rem;
155
-}
156
-
157 13
 .fi-ta-empty-state-icon-ctn {
158 14
     @apply bg-platinum;
159 15
 }
160 16
 
17
+.fi-badge {
18
+    display: inline-flex;
19
+}
20
+
161 21
 :not(.dark) .fi-body {
162 22
     position: relative;
163 23
     background-color: #E8E9EB;
@@ -180,28 +40,3 @@
180 40
     pointer-events: none;
181 41
     z-index: -1;
182 42
 }
183
-
184
-.fi-topbar > nav, .fi-sidebar-header {
185
-    @apply bg-transparent ring-0 shadow-none !important;
186
-    transition: background-color 0.3s, top 0.3s;
187
-}
188
-
189
-.fi-topbar > nav.topbar-hovered, .fi-sidebar-header.topbar-hovered {
190
-    background-color: rgba(255, 255, 255, 0.75) !important;
191
-}
192
-
193
-:is(.dark .fi-topbar > nav.topbar-hovered, .dark .fi-sidebar-header.topbar-hovered) {
194
-    @apply bg-gray-900/75 !important;
195
-}
196
-
197
-.fi-topbar > nav.topbar-scrolled, .fi-sidebar-header.topbar-scrolled {
198
-    background-color: rgba(255, 255, 255, 0.5) !important;
199
-}
200
-
201
-:is(.dark .fi-topbar > nav.topbar-scrolled, .dark .fi-sidebar-header.topbar-scrolled) {
202
-    @apply bg-gray-900/50 !important;
203
-}
204
-
205
-.fi-badge {
206
-    display: inline-flex;
207
-}

+ 20
- 0
resources/css/filament/company/top-navigation.css Wyświetl plik

@@ -0,0 +1,20 @@
1
+.fi-topbar > nav, .fi-sidebar-header {
2
+    @apply bg-transparent ring-0 shadow-none !important;
3
+    transition: background-color 0.3s, top 0.3s;
4
+}
5
+
6
+.fi-topbar > nav.topbar-hovered, .fi-sidebar-header.topbar-hovered {
7
+    background-color: rgba(255, 255, 255, 0.75) !important;
8
+}
9
+
10
+:is(.dark .fi-topbar > nav.topbar-hovered, .dark .fi-sidebar-header.topbar-hovered) {
11
+    @apply bg-gray-900/75 !important;
12
+}
13
+
14
+.fi-topbar > nav.topbar-scrolled, .fi-sidebar-header.topbar-scrolled {
15
+    background-color: rgba(255, 255, 255, 0.5) !important;
16
+}
17
+
18
+:is(.dark .fi-topbar > nav.topbar-scrolled, .dark .fi-sidebar-header.topbar-scrolled) {
19
+    @apply bg-gray-900/50 !important;
20
+}

+ 2
- 1
resources/data/lang/en.json Wyświetl plik

@@ -222,5 +222,6 @@
222 222
     "Approve": "Approve",
223 223
     "Frequency": "Frequency",
224 224
     "Schedule Bounds": "Schedule Bounds",
225
-    "Time Zone": "Time Zone"
225
+    "Time Zone": "Time Zone",
226
+    "Dates & Time": "Dates & Time"
226 227
 }

+ 16
- 46
resources/views/components/custom-section.blade.php Wyświetl plik

@@ -66,10 +66,13 @@
66 66
     @endif
67 67
     {{
68 68
         $attributes->class([
69
-            'fi-section',
70
-            'fi-aside grid grid-cols-1 items-start gap-x-6 gap-y-4 md:grid-cols-3' => $aside && $contained,
71
-            'fi-aside grid grid-cols-1 items-start gap-x-6 gap-y-4 md:grid-cols-3 pt-4' => $aside && ! $contained,
72
-            'rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10' => $contained && ! $aside,
69
+            'fi-custom-section',
70
+            'fi-section-not-contained' => ! $contained,
71
+            'fi-section-has-content-before' => $contentBefore,
72
+            'fi-section-has-header' => $hasHeader,
73
+            'fi-aside' => $aside,
74
+            'fi-compact' => $compact,
75
+            'fi-collapsible' => $collapsible,
73 76
         ])
74 77
     }}
75 78
 >
@@ -78,31 +81,20 @@
78 81
             @if ($collapsible)
79 82
                 x-on:click="isCollapsed = ! isCollapsed"
80 83
             @endif
81
-            @class([
82
-                'fi-section-header flex flex-col gap-3',
83
-                'cursor-pointer' => $collapsible,
84
-                'px-6 py-4' => $contained && ! $aside,
85
-                'px-4 py-2.5' => $compact && ! $aside,
86
-                'py-4' => ! $compact && ! $aside,
87
-            ])
84
+            class="fi-section-header"
88 85
         >
89 86
             <div class="flex items-center gap-3">
90 87
                 @if ($hasIcon)
91 88
                     <x-filament::icon
92 89
                         :icon="$icon"
93 90
                         @class([
94
-                            'fi-section-header-icon self-start',
91
+                            'fi-section-header-icon',
95 92
                             match ($iconColor) {
96
-                                'gray' => 'text-gray-400 dark:text-gray-500',
97
-                                default => 'fi-color-custom text-custom-500 dark:text-custom-400',
93
+                                'gray' => null,
94
+                                default => 'fi-color-custom',
98 95
                             },
99 96
                             is_string($iconColor) ? "fi-color-{$iconColor}" : null,
100
-                            match ($iconSize) {
101
-                                IconSize::Small, 'sm' => 'h-4 w-4 mt-1',
102
-                                IconSize::Medium, 'md' => 'h-5 w-5 mt-0.5',
103
-                                IconSize::Large, 'lg' => 'h-6 w-6',
104
-                                default => $iconSize,
105
-                            },
97
+                            ($iconSize instanceof IconSize) ? "fi-size-{$iconSize->value}" : (is_string($iconSize) ? $iconSize : null),
106 98
                         ])
107 99
                         @style([
108 100
                             \Filament\Support\get_color_css_variables(
@@ -115,7 +107,7 @@
115 107
                 @endif
116 108
 
117 109
                 @if ($hasHeading || $hasDescription)
118
-                    <div class="grid flex-1 gap-y-1">
110
+                    <div class="fi-section-header-text-ctn">
119 111
                         @if ($hasHeading)
120 112
                             <x-filament::section.heading>
121 113
                                 {{ $heading }}
@@ -171,37 +163,15 @@
171 163
             @if ($collapsed || $persistCollapsed)
172 164
                 x-cloak
173 165
             @endif
174
-            x-bind:class="{ 'invisible h-0 overflow-y-hidden border-none': isCollapsed }"
175 166
         @endif
176
-        @class([
177
-            'fi-section-content-ctn',
178
-            'border-t border-gray-200 dark:border-white/10' => $hasHeader && ! $aside && $contained,
179
-            'rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10 md:col-span-2' => $aside && $contained,
180
-            'md:col-span-2' => $aside && ! $contained,
181
-            'md:order-first' => $contentBefore,
182
-        ])
167
+        class="fi-section-content-ctn"
183 168
     >
184
-        <div
185
-            @class([
186
-                'fi-section-content',
187
-                'pt-4' => ! $contained && ! $aside,
188
-                'p-4' => $compact && $contained,
189
-                'p-6' => ! $compact && $contained,
190
-            ])
191
-        >
169
+        <div class="fi-section-content">
192 170
             {{ $slot }}
193 171
         </div>
194 172
 
195 173
         @if ($hasFooterActions)
196
-            <footer
197
-                @class([
198
-                    'fi-section-footer',
199
-                    'border-t border-gray-200 dark:border-white/10' => $contained,
200
-                    'mt-6' => ! $contained,
201
-                    'px-6 py-4' => ! $compact && $contained,
202
-                    'px-4 py-2.5' => $compact && $contained,
203
-                ])
204
-            >
174
+            <footer class="fi-section-footer">
205 175
                 <x-filament::actions
206 176
                     :actions="$footerActions"
207 177
                     :alignment="$footerActionsAlignment"

+ 49
- 55
resources/views/filament/infolists/components/document-preview.blade.php Wyświetl plik

@@ -1,10 +1,5 @@
1 1
 @php
2
-    use App\View\Models\DocumentPreviewViewModel;
3
-    use App\Enums\Accounting\DocumentType;
4
-
5
-    $type = $getType();
6
-    $viewModel = new DocumentPreviewViewModel($getRecord(), $type);
7
-    extract($viewModel->buildViewData(), EXTR_SKIP);
2
+    $document = \App\DTO\DocumentDTO::fromModel($getRecord());
8 3
 @endphp
9 4
 
10 5
 <div {{ $attributes }}>
@@ -13,16 +8,16 @@
13 8
         <x-company.invoice.header class="bg-gray-800 h-24">
14 9
             <!-- Logo -->
15 10
             <div class="w-2/3">
16
-                @if($logo && $style['show_logo'])
17
-                    <x-company.invoice.logo class="ml-8" :src="$logo"/>
11
+                @if($document->logo && $document->showLogo)
12
+                    <x-company.invoice.logo class="ml-8" :src="$document->logo"/>
18 13
                 @endif
19 14
             </div>
20 15
 
21 16
             <!-- Ribbon Container -->
22 17
             <div class="w-1/3 absolute right-0 top-0 p-3 h-32 flex flex-col justify-end rounded-bl-sm"
23
-                 style="background: {{ $style['accent_color'] }};">
24
-                @if($header)
25
-                    <h1 class="text-4xl font-bold text-white text-center uppercase">{{ $header }}</h1>
18
+                 style="background: {{ $document->accentColor }};">
19
+                @if($document->header)
20
+                    <h1 class="text-4xl font-bold text-white text-center uppercase">{{ $document->header }}</h1>
26 21
                 @endif
27 22
             </div>
28 23
         </x-company.invoice.header>
@@ -30,12 +25,11 @@
30 25
         <!-- Company Details -->
31 26
         <x-company.invoice.metadata class="modern-template-metadata space-y-8">
32 27
             <div class="text-sm">
33
-                <h2 class="text-lg font-semibold">{{ $company['name'] }}</h2>
34
-                @if($company['address'] && $company['city'] && $company['state'] && $company['zip_code'])
35
-                    <p>{{ $company['address'] }}</p>
36
-                    <p>{{ $company['city'] }}
37
-                        , {{ $company['state'] }} {{ $company['zip_code'] }}</p>
38
-                    <p>{{ $company['country'] }}</p>
28
+                <h2 class="text-lg font-semibold">{{ $document->company->name }}</h2>
29
+                @if($document->company->address && $document->company->city && $document->company->state && $document->company->zipCode)
30
+                    <p>{{ $document->company->address }}</p>
31
+                    <p>{{ $document->company->city }}, {{ $document->company->state }} {{ $document->company->zipCode }}</p>
32
+                    <p>{{ $document->company->country }}</p>
39 33
                 @endif
40 34
             </div>
41 35
 
@@ -44,20 +38,20 @@
44 38
                 <div class="text-sm tracking-tight">
45 39
                     <h3 class="text-gray-600 dark:text-gray-400 font-medium tracking-tight mb-1">BILL TO</h3>
46 40
                     <p class="text-base font-bold"
47
-                       style="color: {{ $style['accent_color'] }}">{{ $client['name'] }}</p>
41
+                       style="color: {{ $document->accentColor }}">{{ $document->client->name }}</p>
48 42
 
49
-                    @if($client['address_line_1'])
50
-                        <p>{{ $client['address_line_1'] }}</p>
43
+                    @if($document->client->addressLine1)
44
+                        <p>{{ $document->client->addressLine1 }}</p>
51 45
 
52
-                        @if($client['address_line_2'])
53
-                            <p>{{ $client['address_line_2'] }}</p>
46
+                        @if($document->client->addressLine2)
47
+                            <p>{{ $document->client->addressLine2 }}</p>
54 48
                         @endif
55 49
                         <p>
56
-                            {{ $client['city'] }}{{ $client['state'] ? ', ' . $client['state'] : '' }}
57
-                            {{ $client['postal_code'] }}
50
+                            {{ $document->client->city }}{{ $document->client->state ? ', ' . $document->client->state: '' }}
51
+                            {{ $document->client->postalCode }}
58 52
                         </p>
59
-                        @if($client['country'])
60
-                            <p>{{ $client['country'] }}</p>
53
+                        @if($document->client->country)
54
+                            <p>{{ $document->client->country }}</p>
61 55
                         @endif
62 56
                     @endif
63 57
                 </div>
@@ -66,22 +60,22 @@
66 60
                     <table class="min-w-full">
67 61
                         <tbody>
68 62
                         <tr>
69
-                            <td class="font-semibold text-right pr-2">{{ $labels['number'] }}:</td>
70
-                            <td class="text-left pl-2">{{ $metadata['number'] }}</td>
63
+                            <td class="font-semibold text-right pr-2">{{ $document->label->number }}:</td>
64
+                            <td class="text-left pl-2">{{ $document->number }}</td>
71 65
                         </tr>
72
-                        @if($metadata['reference_number'])
66
+                        @if($document->referenceNumber)
73 67
                             <tr>
74
-                                <td class="font-semibold text-right pr-2">{{ $labels['reference_number'] }}:</td>
75
-                                <td class="text-left pl-2">{{ $metadata['reference_number'] }}</td>
68
+                                <td class="font-semibold text-right pr-2">{{ $document->label->referenceNumber }}:</td>
69
+                                <td class="text-left pl-2">{{ $document->referenceNumber }}</td>
76 70
                             </tr>
77 71
                         @endif
78 72
                         <tr>
79
-                            <td class="font-semibold text-right pr-2">{{ $labels['date'] }}:</td>
80
-                            <td class="text-left pl-2">{{ $metadata['date'] }}</td>
73
+                            <td class="font-semibold text-right pr-2">{{ $document->label->date }}:</td>
74
+                            <td class="text-left pl-2">{{ $document->date }}</td>
81 75
                         </tr>
82 76
                         <tr>
83
-                            <td class="font-semibold text-right pr-2">{{ $labels['due_date'] }}:</td>
84
-                            <td class="text-left pl-2">{{ $metadata['due_date'] }}</td>
77
+                            <td class="font-semibold text-right pr-2">{{ $document->label->dueDate }}:</td>
78
+                            <td class="text-left pl-2">{{ $document->dueDate }}</td>
85 79
                         </tr>
86 80
                         </tbody>
87 81
                     </table>
@@ -101,17 +95,17 @@
101 95
                 </tr>
102 96
                 </thead>
103 97
                 <tbody class="text-sm tracking-tight border-y-2">
104
-                @foreach($lineItems as $index => $item)
98
+                @foreach($document->lineItems as $index => $item)
105 99
                     <tr @class(['bg-gray-100 dark:bg-gray-800' => $index % 2 === 0])>
106 100
                         <td class="text-left pl-6 font-semibold py-3">
107
-                            {{ $item['name'] }}
108
-                            @if($item['description'])
109
-                                <div class="text-gray-600 font-normal line-clamp-2 mt-1">{{ $item['description'] }}</div>
101
+                            {{ $item->name }}
102
+                            @if($item->description)
103
+                                <div class="text-gray-600 font-normal line-clamp-2 mt-1">{{ $item->description }}</div>
110 104
                             @endif
111 105
                         </td>
112
-                        <td class="text-center py-3">{{ $item['quantity'] }}</td>
113
-                        <td class="text-right py-3">{{ $item['unit_price'] }}</td>
114
-                        <td class="text-right pr-6 py-3">{{ $item['subtotal'] }}</td>
106
+                        <td class="text-center py-3">{{ $item->quantity }}</td>
107
+                        <td class="text-right py-3">{{ $item->unitPrice }}</td>
108
+                        <td class="text-right pr-6 py-3">{{ $item->subtotal }}</td>
115 109
                     </tr>
116 110
                 @endforeach
117 111
                 </tbody>
@@ -119,36 +113,36 @@
119 113
                 <tr>
120 114
                     <td class="pl-6 py-2" colspan="2"></td>
121 115
                     <td class="text-right font-semibold py-2">Subtotal:</td>
122
-                    <td class="text-right pr-6 py-2">{{ $totals['subtotal'] }}</td>
116
+                    <td class="text-right pr-6 py-2">{{ $document->subtotal }}</td>
123 117
                 </tr>
124
-                @if($totals['discount'])
118
+                @if($document->discount)
125 119
                     <tr class="text-success-800 dark:text-success-600">
126 120
                         <td class="pl-6 py-2" colspan="2"></td>
127 121
                         <td class="text-right py-2">Discount:</td>
128 122
                         <td class="text-right pr-6 py-2">
129
-                            ({{ $totals['discount'] }})
123
+                            ({{ $document->discount }})
130 124
                         </td>
131 125
                     </tr>
132 126
                 @endif
133
-                @if($totals['tax'])
127
+                @if($document->tax)
134 128
                     <tr>
135 129
                         <td class="pl-6 py-2" colspan="2"></td>
136 130
                         <td class="text-right py-2">Tax:</td>
137
-                        <td class="text-right pr-6 py-2">{{ $totals['tax'] }}</td>
131
+                        <td class="text-right pr-6 py-2">{{ $document->tax }}</td>
138 132
                     </tr>
139 133
                 @endif
140 134
                 <tr>
141 135
                     <td class="pl-6 py-2" colspan="2"></td>
142 136
                     <td class="text-right font-semibold border-t py-2">Total:</td>
143
-                    <td class="text-right border-t pr-6 py-2">{{ $totals['total'] }}</td>
137
+                    <td class="text-right border-t pr-6 py-2">{{ $document->total }}</td>
144 138
                 </tr>
145
-                @if($totals['amount_due'])
139
+                @if($document->amountDue)
146 140
                     <tr>
147 141
                         <td class="pl-6 py-2" colspan="2"></td>
148
-                        <td class="text-right font-semibold border-t-4 border-double py-2">{{ $labels['amount_due'] }}
149
-                            ({{ $metadata['currency_code'] }}):
142
+                        <td class="text-right font-semibold border-t-4 border-double py-2">{{ $document->label->amountDue }}
143
+                            ({{ $document->currencyCode }}):
150 144
                         </td>
151
-                        <td class="text-right border-t-4 border-double pr-6 py-2">{{ $totals['amount_due'] }}</td>
145
+                        <td class="text-right border-t-4 border-double pr-6 py-2">{{ $document->amountDue }}</td>
152 146
                     </tr>
153 147
                 @endif
154 148
                 </tfoot>
@@ -157,13 +151,13 @@
157 151
 
158 152
         <!-- Footer Notes -->
159 153
         <x-company.invoice.footer class="modern-template-footer tracking-tight">
160
-            <h4 class="font-semibold px-6 text-sm" style="color: {{ $style['accent_color'] }}">
154
+            <h4 class="font-semibold px-6 text-sm" style="color: {{ $document->accentColor }}">
161 155
                 Terms & Conditions
162 156
             </h4>
163 157
             <span class="border-t-2 my-2 border-gray-300 block w-full"></span>
164 158
             <div class="flex justify-between space-x-4 px-6 text-sm">
165
-                <p class="w-1/2 break-words line-clamp-4">{{ $terms }}</p>
166
-                <p class="w-1/2 break-words line-clamp-4">{{ $footer }}</p>
159
+                <p class="w-1/2 break-words line-clamp-4">{{ $document->terms }}</p>
160
+                <p class="w-1/2 break-words line-clamp-4">{{ $document->footer }}</p>
167 161
             </div>
168 162
         </x-company.invoice.footer>
169 163
     </x-company.invoice.container>

Ładowanie…
Anuluj
Zapisz