Andrew Wallo 10 miesięcy temu
rodzic
commit
dd1b5ccc59

+ 2
- 7
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/ListInvoices.php Wyświetl plik

@@ -44,13 +44,6 @@ class ListInvoices extends ListRecords
44 44
             'all' => Tab::make()
45 45
                 ->label('All'),
46 46
 
47
-            'overdue' => Tab::make()
48
-                ->label('Overdue')
49
-                ->modifyQueryUsing(function (Builder $query) {
50
-                    $query->where('status', InvoiceStatus::Overdue);
51
-                })
52
-                ->badge(Invoice::where('status', InvoiceStatus::Overdue)->count()),
53
-
54 47
             'unpaid' => Tab::make()
55 48
                 ->label('Unpaid')
56 49
                 ->modifyQueryUsing(function (Builder $query) {
@@ -58,12 +51,14 @@ class ListInvoices extends ListRecords
58 51
                         InvoiceStatus::Unsent,
59 52
                         InvoiceStatus::Sent,
60 53
                         InvoiceStatus::Partial,
54
+                        InvoiceStatus::Overdue,
61 55
                     ]);
62 56
                 })
63 57
                 ->badge(Invoice::whereIn('status', [
64 58
                     InvoiceStatus::Unsent,
65 59
                     InvoiceStatus::Sent,
66 60
                     InvoiceStatus::Partial,
61
+                    InvoiceStatus::Overdue,
67 62
                 ])->count()),
68 63
 
69 64
             'draft' => Tab::make()

+ 2
- 10
app/Filament/Company/Resources/Sales/InvoiceResource/Widgets/InvoiceOverview.php Wyświetl plik

@@ -59,16 +59,8 @@ class InvoiceOverview extends BaseWidget
59 59
         $averageInvoiceTotal = $totalValidInvoiceCount > 0 ? $totalValidInvoiceAmount / $totalValidInvoiceCount : 0;
60 60
 
61 61
         $averagePaymentTime = $this->getPageTableQuery()
62
-            ->withWhereHas('statusHistories', function ($query) {
63
-                $query->where('new_status', InvoiceStatus::Paid);
64
-            })
65
-            ->selectRaw('AVG(TIMESTAMPDIFF(DAY, date, (
66
-                SELECT changed_at
67
-                FROM invoice_status_histories
68
-                WHERE invoice_status_histories.invoice_id = invoices.id
69
-                AND status = ?
70
-                LIMIT 1
71
-            ))) as avg_days', [InvoiceStatus::Paid])
62
+            ->whereNotNull('paid_at')
63
+            ->selectRaw('AVG(TIMESTAMPDIFF(DAY, date, paid_at)) as avg_days')
72 64
             ->value('avg_days');
73 65
 
74 66
         return [

+ 11
- 37
app/Models/Accounting/Invoice.php Wyświetl plik

@@ -17,7 +17,6 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
17 17
 use Illuminate\Database\Eloquent\Factories\HasFactory;
18 18
 use Illuminate\Database\Eloquent\Model;
19 19
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
20
-use Illuminate\Database\Eloquent\Relations\HasMany;
21 20
 use Illuminate\Database\Eloquent\Relations\MorphMany;
22 21
 use Illuminate\Database\Eloquent\Relations\MorphOne;
23 22
 use Illuminate\Support\Carbon;
@@ -42,6 +41,9 @@ class Invoice extends Model
42 41
         'order_number',
43 42
         'date',
44 43
         'due_date',
44
+        'approved_at',
45
+        'paid_at',
46
+        'last_sent',
45 47
         'status',
46 48
         'currency_code',
47 49
         'subtotal',
@@ -58,6 +60,9 @@ class Invoice extends Model
58 60
     protected $casts = [
59 61
         'date' => 'date',
60 62
         'due_date' => 'date',
63
+        'approved_at' => 'datetime',
64
+        'paid_at' => 'datetime',
65
+        'last_sent' => 'datetime',
61 66
         'status' => InvoiceStatus::class,
62 67
         'subtotal' => MoneyCast::class,
63 68
         'tax_total' => MoneyCast::class,
@@ -97,46 +102,12 @@ class Invoice extends Model
97 102
         return $this->transactions()->where('type', TransactionType::Withdrawal)->where('is_payment', true);
98 103
     }
99 104
 
100
-    protected function lastSent(): Attribute
101
-    {
102
-        return Attribute::get(function () {
103
-            return $this->getStatusChangedAt(InvoiceStatus::Sent);
104
-        });
105
-    }
106
-
107
-    protected function approvedAt(): Attribute
108
-    {
109
-        return Attribute::get(function () {
110
-            return $this->getStatusChangedAt(InvoiceStatus::Unsent);
111
-        });
112
-    }
113
-
114
-    protected function paidAt(): Attribute
115
-    {
116
-        return Attribute::get(function () {
117
-            return $this->getStatusChangedAt(InvoiceStatus::Paid);
118
-        });
119
-    }
120
-
121
-    protected function getStatusChangedAt(InvoiceStatus $status): ?Carbon
122
-    {
123
-        return $this->statusHistories
124
-            ->where('new_status', $status)
125
-            ->sortByDesc('changed_at')
126
-            ->first()?->changed_at;
127
-    }
128
-
129 105
     public function approvalTransaction(): MorphOne
130 106
     {
131 107
         return $this->morphOne(Transaction::class, 'transactionable')
132 108
             ->where('type', TransactionType::Journal);
133 109
     }
134 110
 
135
-    public function statusHistories(): HasMany
136
-    {
137
-        return $this->hasMany(InvoiceStatusHistory::class);
138
-    }
139
-
140 111
     protected function isCurrentlyOverdue(): Attribute
141 112
     {
142 113
         return Attribute::get(function () {
@@ -233,16 +204,18 @@ class Invoice extends Model
233 204
         ]);
234 205
     }
235 206
 
236
-    public function approveDraft(): void
207
+    public function approveDraft(?Carbon $approvedAt = null): void
237 208
     {
238 209
         if (! $this->isDraft()) {
239 210
             throw new \RuntimeException('Invoice is not in draft status.');
240 211
         }
241 212
 
213
+        $approvedAt ??= now();
214
+
242 215
         $transaction = $this->transactions()->create([
243 216
             'company_id' => $this->company_id,
244 217
             'type' => TransactionType::Journal,
245
-            'posted_at' => now(),
218
+            'posted_at' => $approvedAt,
246 219
             'amount' => $this->total,
247 220
             'description' => 'Invoice Approval for Invoice #' . $this->invoice_number,
248 221
         ]);
@@ -276,6 +249,7 @@ class Invoice extends Model
276 249
         }
277 250
 
278 251
         $this->update([
252
+            'approved_at' => $approvedAt,
279 253
             'status' => InvoiceStatus::Unsent,
280 254
         ]);
281 255
     }

+ 0
- 41
app/Models/Accounting/InvoiceStatusHistory.php Wyświetl plik

@@ -1,41 +0,0 @@
1
-<?php
2
-
3
-namespace App\Models\Accounting;
4
-
5
-use App\Concerns\CompanyOwned;
6
-use App\Enums\Accounting\InvoiceStatus;
7
-use App\Models\User;
8
-use Illuminate\Database\Eloquent\Factories\HasFactory;
9
-use Illuminate\Database\Eloquent\Model;
10
-use Illuminate\Database\Eloquent\Relations\BelongsTo;
11
-
12
-class InvoiceStatusHistory extends Model
13
-{
14
-    use CompanyOwned;
15
-    use HasFactory;
16
-
17
-    protected $fillable = [
18
-        'company_id',
19
-        'invoice_id',
20
-        'old_status',
21
-        'new_status',
22
-        'changed_by',
23
-        'changed_at',
24
-    ];
25
-
26
-    protected $casts = [
27
-        'changed_at' => 'datetime',
28
-        'old_status' => InvoiceStatus::class,
29
-        'new_status' => InvoiceStatus::class,
30
-    ];
31
-
32
-    public function invoice(): BelongsTo
33
-    {
34
-        return $this->belongsTo(Invoice::class);
35
-    }
36
-
37
-    public function changedBy(): BelongsTo
38
-    {
39
-        return $this->belongsTo(User::class, 'changed_by');
40
-    }
41
-}

+ 0
- 5
app/Models/Company.php Wyświetl plik

@@ -171,9 +171,4 @@ class Company extends FilamentCompaniesCompany implements HasAvatar
171 171
     {
172 172
         return $this->hasMany(Common\Vendor::class, 'company_id');
173 173
     }
174
-
175
-    public function invoiceStatusHistories(): HasMany
176
-    {
177
-        return $this->hasMany(Accounting\InvoiceStatusHistory::class, 'company_id');
178
-    }
179 174
 }

+ 5
- 7
app/Observers/InvoiceObserver.php Wyświetl plik

@@ -24,12 +24,10 @@ class InvoiceObserver
24 24
     public function updated(Invoice $invoice): void
25 25
     {
26 26
         if ($invoice->wasChanged('status')) {
27
-            $invoice->statusHistories()->create([
28
-                'company_id' => $invoice->company_id,
29
-                'old_status' => $invoice->getOriginal('status'),
30
-                'new_status' => $invoice->status,
31
-                'changed_by' => $invoice->updated_by,
32
-            ]);
27
+            match ($invoice->status) {
28
+                InvoiceStatus::Sent => $invoice->updateQuietly(['last_sent' => now()]),
29
+                default => null,
30
+            };
33 31
         }
34 32
     }
35 33
 
@@ -40,7 +38,7 @@ class InvoiceObserver
40 38
 
41 39
     public function saving(Invoice $invoice): void
42 40
     {
43
-        if ($invoice->is_currently_overdue) {
41
+        if ($invoice->approved_at && $invoice->is_currently_overdue) {
44 42
             $invoice->status = InvoiceStatus::Overdue;
45 43
         }
46 44
     }

+ 16
- 6
app/Observers/TransactionObserver.php Wyświetl plik

@@ -107,14 +107,24 @@ class TransactionObserver
107 107
 
108 108
         $invoiceTotal = (int) $invoice->getRawOriginal('total');
109 109
 
110
+        $newStatus = match (true) {
111
+            $totalPaid > $invoiceTotal => InvoiceStatus::Overpaid,
112
+            $totalPaid === $invoiceTotal => InvoiceStatus::Paid,
113
+            default => InvoiceStatus::Partial,
114
+        };
115
+
116
+        $paidAt = $invoice->paid_at;
117
+
118
+        if (in_array($newStatus, [InvoiceStatus::Paid, InvoiceStatus::Overpaid]) && ! $paidAt) {
119
+            $paidAt = $invoice->deposits()
120
+                ->latest('posted_at')
121
+                ->value('posted_at');
122
+        }
123
+
110 124
         $invoice->update([
111 125
             'amount_paid' => CurrencyConverter::convertCentsToFloat($totalPaid),
112
-            'status' => match (true) {
113
-                $totalPaid > $invoiceTotal => InvoiceStatus::Overpaid,
114
-                $totalPaid === $invoiceTotal => InvoiceStatus::Paid,
115
-                $totalPaid === 0 => InvoiceStatus::Sent,
116
-                default => InvoiceStatus::Partial,
117
-            },
126
+            'status' => $newStatus,
127
+            'paid_at' => $paidAt,
118 128
         ]);
119 129
     }
120 130
 }

+ 69
- 69
composer.lock Wyświetl plik

@@ -497,16 +497,16 @@
497 497
         },
498 498
         {
499 499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.334.0",
500
+            "version": "3.334.1",
501 501
             "source": {
502 502
                 "type": "git",
503 503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "8afe50cb2a93051dafef21eb616e297a449764aa"
504
+                "reference": "3938b3467f64a30fed7ee1762a6785f808a5ae4d"
505 505
             },
506 506
             "dist": {
507 507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8afe50cb2a93051dafef21eb616e297a449764aa",
509
-                "reference": "8afe50cb2a93051dafef21eb616e297a449764aa",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3938b3467f64a30fed7ee1762a6785f808a5ae4d",
509
+                "reference": "3938b3467f64a30fed7ee1762a6785f808a5ae4d",
510 510
                 "shasum": ""
511 511
             },
512 512
             "require": {
@@ -589,9 +589,9 @@
589 589
             "support": {
590 590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
591 591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
592
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.334.0"
592
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.334.1"
593 593
             },
594
-            "time": "2024-12-04T19:09:04+00:00"
594
+            "time": "2024-12-05T01:17:41+00:00"
595 595
         },
596 596
         {
597 597
             "name": "aws/aws-sdk-php-laravel",
@@ -1664,16 +1664,16 @@
1664 1664
         },
1665 1665
         {
1666 1666
             "name": "filament/actions",
1667
-            "version": "v3.2.127",
1667
+            "version": "v3.2.128",
1668 1668
             "source": {
1669 1669
                 "type": "git",
1670 1670
                 "url": "https://github.com/filamentphp/actions.git",
1671
-                "reference": "f325e315c365cfcea5c9da96662ddea37e3663fc"
1671
+                "reference": "1ee8b0a890b53e8b0b341134d3ba9bdaeee294d3"
1672 1672
             },
1673 1673
             "dist": {
1674 1674
                 "type": "zip",
1675
-                "url": "https://api.github.com/repos/filamentphp/actions/zipball/f325e315c365cfcea5c9da96662ddea37e3663fc",
1676
-                "reference": "f325e315c365cfcea5c9da96662ddea37e3663fc",
1675
+                "url": "https://api.github.com/repos/filamentphp/actions/zipball/1ee8b0a890b53e8b0b341134d3ba9bdaeee294d3",
1676
+                "reference": "1ee8b0a890b53e8b0b341134d3ba9bdaeee294d3",
1677 1677
                 "shasum": ""
1678 1678
             },
1679 1679
             "require": {
@@ -1713,20 +1713,20 @@
1713 1713
                 "issues": "https://github.com/filamentphp/filament/issues",
1714 1714
                 "source": "https://github.com/filamentphp/filament"
1715 1715
             },
1716
-            "time": "2024-11-29T09:30:57+00:00"
1716
+            "time": "2024-12-05T08:56:37+00:00"
1717 1717
         },
1718 1718
         {
1719 1719
             "name": "filament/filament",
1720
-            "version": "v3.2.127",
1720
+            "version": "v3.2.128",
1721 1721
             "source": {
1722 1722
                 "type": "git",
1723 1723
                 "url": "https://github.com/filamentphp/panels.git",
1724
-                "reference": "4aea767e8c872842b624fe47affe078433111259"
1724
+                "reference": "27b834f6f1213c547580443e28e5028dfe125bdd"
1725 1725
             },
1726 1726
             "dist": {
1727 1727
                 "type": "zip",
1728
-                "url": "https://api.github.com/repos/filamentphp/panels/zipball/4aea767e8c872842b624fe47affe078433111259",
1729
-                "reference": "4aea767e8c872842b624fe47affe078433111259",
1728
+                "url": "https://api.github.com/repos/filamentphp/panels/zipball/27b834f6f1213c547580443e28e5028dfe125bdd",
1729
+                "reference": "27b834f6f1213c547580443e28e5028dfe125bdd",
1730 1730
                 "shasum": ""
1731 1731
             },
1732 1732
             "require": {
@@ -1778,20 +1778,20 @@
1778 1778
                 "issues": "https://github.com/filamentphp/filament/issues",
1779 1779
                 "source": "https://github.com/filamentphp/filament"
1780 1780
             },
1781
-            "time": "2024-11-29T09:30:58+00:00"
1781
+            "time": "2024-12-05T08:56:42+00:00"
1782 1782
         },
1783 1783
         {
1784 1784
             "name": "filament/forms",
1785
-            "version": "v3.2.127",
1785
+            "version": "v3.2.128",
1786 1786
             "source": {
1787 1787
                 "type": "git",
1788 1788
                 "url": "https://github.com/filamentphp/forms.git",
1789
-                "reference": "c78071f1aabb63a0d9bf74268005d3294b61dc2a"
1789
+                "reference": "c86af3606b8fd3f908b29a03e3056628e4cea57e"
1790 1790
             },
1791 1791
             "dist": {
1792 1792
                 "type": "zip",
1793
-                "url": "https://api.github.com/repos/filamentphp/forms/zipball/c78071f1aabb63a0d9bf74268005d3294b61dc2a",
1794
-                "reference": "c78071f1aabb63a0d9bf74268005d3294b61dc2a",
1793
+                "url": "https://api.github.com/repos/filamentphp/forms/zipball/c86af3606b8fd3f908b29a03e3056628e4cea57e",
1794
+                "reference": "c86af3606b8fd3f908b29a03e3056628e4cea57e",
1795 1795
                 "shasum": ""
1796 1796
             },
1797 1797
             "require": {
@@ -1834,11 +1834,11 @@
1834 1834
                 "issues": "https://github.com/filamentphp/filament/issues",
1835 1835
                 "source": "https://github.com/filamentphp/filament"
1836 1836
             },
1837
-            "time": "2024-11-29T09:30:53+00:00"
1837
+            "time": "2024-12-05T08:56:35+00:00"
1838 1838
         },
1839 1839
         {
1840 1840
             "name": "filament/infolists",
1841
-            "version": "v3.2.127",
1841
+            "version": "v3.2.128",
1842 1842
             "source": {
1843 1843
                 "type": "git",
1844 1844
                 "url": "https://github.com/filamentphp/infolists.git",
@@ -1889,7 +1889,7 @@
1889 1889
         },
1890 1890
         {
1891 1891
             "name": "filament/notifications",
1892
-            "version": "v3.2.127",
1892
+            "version": "v3.2.128",
1893 1893
             "source": {
1894 1894
                 "type": "git",
1895 1895
                 "url": "https://github.com/filamentphp/notifications.git",
@@ -1941,16 +1941,16 @@
1941 1941
         },
1942 1942
         {
1943 1943
             "name": "filament/support",
1944
-            "version": "v3.2.127",
1944
+            "version": "v3.2.128",
1945 1945
             "source": {
1946 1946
                 "type": "git",
1947 1947
                 "url": "https://github.com/filamentphp/support.git",
1948
-                "reference": "a720fb2508a1d84a9b35aedc9991d4b53d18fea6"
1948
+                "reference": "437d4f3305458f29c32ef4de5ef1d9dbdc74c3fe"
1949 1949
             },
1950 1950
             "dist": {
1951 1951
                 "type": "zip",
1952
-                "url": "https://api.github.com/repos/filamentphp/support/zipball/a720fb2508a1d84a9b35aedc9991d4b53d18fea6",
1953
-                "reference": "a720fb2508a1d84a9b35aedc9991d4b53d18fea6",
1952
+                "url": "https://api.github.com/repos/filamentphp/support/zipball/437d4f3305458f29c32ef4de5ef1d9dbdc74c3fe",
1953
+                "reference": "437d4f3305458f29c32ef4de5ef1d9dbdc74c3fe",
1954 1954
                 "shasum": ""
1955 1955
             },
1956 1956
             "require": {
@@ -1996,20 +1996,20 @@
1996 1996
                 "issues": "https://github.com/filamentphp/filament/issues",
1997 1997
                 "source": "https://github.com/filamentphp/filament"
1998 1998
             },
1999
-            "time": "2024-11-29T09:31:13+00:00"
1999
+            "time": "2024-12-05T08:56:49+00:00"
2000 2000
         },
2001 2001
         {
2002 2002
             "name": "filament/tables",
2003
-            "version": "v3.2.127",
2003
+            "version": "v3.2.128",
2004 2004
             "source": {
2005 2005
                 "type": "git",
2006 2006
                 "url": "https://github.com/filamentphp/tables.git",
2007
-                "reference": "c287a68e084c96c3f2991eaddf1d6b5159af5147"
2007
+                "reference": "4a60fda65574f248e082f109345216a38567093a"
2008 2008
             },
2009 2009
             "dist": {
2010 2010
                 "type": "zip",
2011
-                "url": "https://api.github.com/repos/filamentphp/tables/zipball/c287a68e084c96c3f2991eaddf1d6b5159af5147",
2012
-                "reference": "c287a68e084c96c3f2991eaddf1d6b5159af5147",
2011
+                "url": "https://api.github.com/repos/filamentphp/tables/zipball/4a60fda65574f248e082f109345216a38567093a",
2012
+                "reference": "4a60fda65574f248e082f109345216a38567093a",
2013 2013
                 "shasum": ""
2014 2014
             },
2015 2015
             "require": {
@@ -2048,11 +2048,11 @@
2048 2048
                 "issues": "https://github.com/filamentphp/filament/issues",
2049 2049
                 "source": "https://github.com/filamentphp/filament"
2050 2050
             },
2051
-            "time": "2024-11-30T09:21:26+00:00"
2051
+            "time": "2024-12-05T08:56:53+00:00"
2052 2052
         },
2053 2053
         {
2054 2054
             "name": "filament/widgets",
2055
-            "version": "v3.2.127",
2055
+            "version": "v3.2.128",
2056 2056
             "source": {
2057 2057
                 "type": "git",
2058 2058
                 "url": "https://github.com/filamentphp/widgets.git",
@@ -4442,16 +4442,16 @@
4442 4442
         },
4443 4443
         {
4444 4444
             "name": "monolog/monolog",
4445
-            "version": "3.8.0",
4445
+            "version": "3.8.1",
4446 4446
             "source": {
4447 4447
                 "type": "git",
4448 4448
                 "url": "https://github.com/Seldaek/monolog.git",
4449
-                "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67"
4449
+                "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4"
4450 4450
             },
4451 4451
             "dist": {
4452 4452
                 "type": "zip",
4453
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/32e515fdc02cdafbe4593e30a9350d486b125b67",
4454
-                "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67",
4453
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
4454
+                "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
4455 4455
                 "shasum": ""
4456 4456
             },
4457 4457
             "require": {
@@ -4529,7 +4529,7 @@
4529 4529
             ],
4530 4530
             "support": {
4531 4531
                 "issues": "https://github.com/Seldaek/monolog/issues",
4532
-                "source": "https://github.com/Seldaek/monolog/tree/3.8.0"
4532
+                "source": "https://github.com/Seldaek/monolog/tree/3.8.1"
4533 4533
             },
4534 4534
             "funding": [
4535 4535
                 {
@@ -4541,7 +4541,7 @@
4541 4541
                     "type": "tidelift"
4542 4542
                 }
4543 4543
             ],
4544
-            "time": "2024-11-12T13:57:08+00:00"
4544
+            "time": "2024-12-05T17:15:07+00:00"
4545 4545
         },
4546 4546
         {
4547 4547
             "name": "mtdowling/jmespath.php",
@@ -5064,16 +5064,16 @@
5064 5064
         },
5065 5065
         {
5066 5066
             "name": "openspout/openspout",
5067
-            "version": "v4.28.0",
5067
+            "version": "v4.28.1",
5068 5068
             "source": {
5069 5069
                 "type": "git",
5070 5070
                 "url": "https://github.com/openspout/openspout.git",
5071
-                "reference": "3e9ef74f13ba5e887e4afc7a4e0110e63559e902"
5071
+                "reference": "229a9c837bd768e8767660671f9fdf429f343a74"
5072 5072
             },
5073 5073
             "dist": {
5074 5074
                 "type": "zip",
5075
-                "url": "https://api.github.com/repos/openspout/openspout/zipball/3e9ef74f13ba5e887e4afc7a4e0110e63559e902",
5076
-                "reference": "3e9ef74f13ba5e887e4afc7a4e0110e63559e902",
5075
+                "url": "https://api.github.com/repos/openspout/openspout/zipball/229a9c837bd768e8767660671f9fdf429f343a74",
5076
+                "reference": "229a9c837bd768e8767660671f9fdf429f343a74",
5077 5077
                 "shasum": ""
5078 5078
             },
5079 5079
             "require": {
@@ -5141,7 +5141,7 @@
5141 5141
             ],
5142 5142
             "support": {
5143 5143
                 "issues": "https://github.com/openspout/openspout/issues",
5144
-                "source": "https://github.com/openspout/openspout/tree/v4.28.0"
5144
+                "source": "https://github.com/openspout/openspout/tree/v4.28.1"
5145 5145
             },
5146 5146
             "funding": [
5147 5147
                 {
@@ -5153,7 +5153,7 @@
5153 5153
                     "type": "github"
5154 5154
                 }
5155 5155
             ],
5156
-            "time": "2024-11-29T09:45:53+00:00"
5156
+            "time": "2024-12-05T13:34:00+00:00"
5157 5157
         },
5158 5158
         {
5159 5159
             "name": "paragonie/constant_time_encoding",
@@ -9240,16 +9240,16 @@
9240 9240
     "packages-dev": [
9241 9241
         {
9242 9242
             "name": "brianium/paratest",
9243
-            "version": "v7.6.0",
9243
+            "version": "v7.6.1",
9244 9244
             "source": {
9245 9245
                 "type": "git",
9246 9246
                 "url": "https://github.com/paratestphp/paratest.git",
9247
-                "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254"
9247
+                "reference": "9ac8eda68f17acda4dad4aa02ecdcc327d7e6675"
9248 9248
             },
9249 9249
             "dist": {
9250 9250
                 "type": "zip",
9251
-                "url": "https://api.github.com/repos/paratestphp/paratest/zipball/68ff89a8de47d086588e391a516d2a5b5fde6254",
9252
-                "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254",
9251
+                "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9ac8eda68f17acda4dad4aa02ecdcc327d7e6675",
9252
+                "reference": "9ac8eda68f17acda4dad4aa02ecdcc327d7e6675",
9253 9253
                 "shasum": ""
9254 9254
             },
9255 9255
             "require": {
@@ -9258,26 +9258,26 @@
9258 9258
                 "ext-reflection": "*",
9259 9259
                 "ext-simplexml": "*",
9260 9260
                 "fidry/cpu-core-counter": "^1.2.0",
9261
-                "jean85/pretty-package-versions": "^2.0.6",
9261
+                "jean85/pretty-package-versions": "^2.1.0",
9262 9262
                 "php": "~8.2.0 || ~8.3.0 || ~8.4.0",
9263 9263
                 "phpunit/php-code-coverage": "^11.0.7",
9264 9264
                 "phpunit/php-file-iterator": "^5.1.0",
9265 9265
                 "phpunit/php-timer": "^7.0.1",
9266
-                "phpunit/phpunit": "^11.4.1",
9266
+                "phpunit/phpunit": "^11.4.4",
9267 9267
                 "sebastian/environment": "^7.2.0",
9268
-                "symfony/console": "^6.4.11 || ^7.1.5",
9269
-                "symfony/process": "^6.4.8 || ^7.1.5"
9268
+                "symfony/console": "^6.4.14 || ^7.1.7",
9269
+                "symfony/process": "^6.4.14 || ^7.1.7"
9270 9270
             },
9271 9271
             "require-dev": {
9272 9272
                 "doctrine/coding-standard": "^12.0.0",
9273 9273
                 "ext-pcov": "*",
9274 9274
                 "ext-posix": "*",
9275
-                "phpstan/phpstan": "^1.12.6",
9276
-                "phpstan/phpstan-deprecation-rules": "^1.2.1",
9277
-                "phpstan/phpstan-phpunit": "^1.4.0",
9278
-                "phpstan/phpstan-strict-rules": "^1.6.1",
9279
-                "squizlabs/php_codesniffer": "^3.10.3",
9280
-                "symfony/filesystem": "^6.4.9 || ^7.1.5"
9275
+                "phpstan/phpstan": "^2",
9276
+                "phpstan/phpstan-deprecation-rules": "^2",
9277
+                "phpstan/phpstan-phpunit": "^2",
9278
+                "phpstan/phpstan-strict-rules": "^2",
9279
+                "squizlabs/php_codesniffer": "^3.11.1",
9280
+                "symfony/filesystem": "^6.4.13 || ^7.1.6"
9281 9281
             },
9282 9282
             "bin": [
9283 9283
                 "bin/paratest",
@@ -9317,7 +9317,7 @@
9317 9317
             ],
9318 9318
             "support": {
9319 9319
                 "issues": "https://github.com/paratestphp/paratest/issues",
9320
-                "source": "https://github.com/paratestphp/paratest/tree/v7.6.0"
9320
+                "source": "https://github.com/paratestphp/paratest/tree/v7.6.1"
9321 9321
             },
9322 9322
             "funding": [
9323 9323
                 {
@@ -9329,7 +9329,7 @@
9329 9329
                     "type": "paypal"
9330 9330
                 }
9331 9331
             ],
9332
-            "time": "2024-10-15T12:38:31+00:00"
9332
+            "time": "2024-12-05T10:55:39+00:00"
9333 9333
         },
9334 9334
         {
9335 9335
             "name": "fakerphp/faker",
@@ -11766,16 +11766,16 @@
11766 11766
         },
11767 11767
         {
11768 11768
             "name": "sebastian/exporter",
11769
-            "version": "6.1.3",
11769
+            "version": "6.3.0",
11770 11770
             "source": {
11771 11771
                 "type": "git",
11772 11772
                 "url": "https://github.com/sebastianbergmann/exporter.git",
11773
-                "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e"
11773
+                "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3"
11774 11774
             },
11775 11775
             "dist": {
11776 11776
                 "type": "zip",
11777
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e",
11778
-                "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e",
11777
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3",
11778
+                "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3",
11779 11779
                 "shasum": ""
11780 11780
             },
11781 11781
             "require": {
@@ -11784,7 +11784,7 @@
11784 11784
                 "sebastian/recursion-context": "^6.0"
11785 11785
             },
11786 11786
             "require-dev": {
11787
-                "phpunit/phpunit": "^11.2"
11787
+                "phpunit/phpunit": "^11.3"
11788 11788
             },
11789 11789
             "type": "library",
11790 11790
             "extra": {
@@ -11832,7 +11832,7 @@
11832 11832
             "support": {
11833 11833
                 "issues": "https://github.com/sebastianbergmann/exporter/issues",
11834 11834
                 "security": "https://github.com/sebastianbergmann/exporter/security/policy",
11835
-                "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3"
11835
+                "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0"
11836 11836
             },
11837 11837
             "funding": [
11838 11838
                 {
@@ -11840,7 +11840,7 @@
11840 11840
                     "type": "github"
11841 11841
                 }
11842 11842
             ],
11843
-            "time": "2024-07-03T04:56:19+00:00"
11843
+            "time": "2024-12-05T09:17:50+00:00"
11844 11844
         },
11845 11845
         {
11846 11846
             "name": "sebastian/global-state",

+ 27
- 9
database/factories/Accounting/InvoiceFactory.php Wyświetl plik

@@ -10,6 +10,7 @@ use App\Models\Banking\BankAccount;
10 10
 use App\Models\Common\Client;
11 11
 use App\Utilities\Currency\CurrencyConverter;
12 12
 use Illuminate\Database\Eloquent\Factories\Factory;
13
+use Illuminate\Support\Carbon;
13 14
 
14 15
 /**
15 16
  * @extends Factory<Invoice>
@@ -28,6 +29,8 @@ class InvoiceFactory extends Factory
28 29
      */
29 30
     public function definition(): array
30 31
     {
32
+        $invoiceDate = $this->faker->dateTimeBetween('-1 year');
33
+
31 34
         return [
32 35
             'company_id' => 1,
33 36
             'client_id' => Client::inRandomOrder()->value('id'),
@@ -35,8 +38,8 @@ class InvoiceFactory extends Factory
35 38
             'subheader' => 'Invoice',
36 39
             'invoice_number' => $this->faker->unique()->numerify('INV-#####'),
37 40
             'order_number' => $this->faker->unique()->numerify('ORD-#####'),
38
-            'date' => $this->faker->dateTimeBetween('-1 year'),
39
-            'due_date' => $this->faker->dateTimeBetween('-2 months', '+2 months'),
41
+            'date' => $invoiceDate,
42
+            'due_date' => Carbon::parse($invoiceDate)->addDays($this->faker->numberBetween(14, 60)),
40 43
             'status' => InvoiceStatus::Draft,
41 44
             'currency_code' => 'USD',
42 45
             'terms' => $this->faker->sentence,
@@ -60,7 +63,9 @@ class InvoiceFactory extends Factory
60 63
 
61 64
             $this->recalculateTotals($invoice);
62 65
 
63
-            $invoice->approveDraft();
66
+            $approvedAt = Carbon::parse($invoice->date)->addHours($this->faker->numberBetween(1, 24));
67
+
68
+            $invoice->approveDraft($approvedAt);
64 69
         });
65 70
     }
66 71
 
@@ -68,10 +73,10 @@ class InvoiceFactory extends Factory
68 73
     {
69 74
         return $this->afterCreating(function (Invoice $invoice) use ($invoiceStatus, $max, $min) {
70 75
             if ($invoice->isDraft()) {
71
-
72 76
                 $this->recalculateTotals($invoice);
73 77
 
74
-                $invoice->approveDraft();
78
+                $approvedAt = Carbon::parse($invoice->date)->addHours($this->faker->numberBetween(1, 24));
79
+                $invoice->approveDraft($approvedAt);
75 80
             }
76 81
 
77 82
             $invoice->refresh();
@@ -89,10 +94,12 @@ class InvoiceFactory extends Factory
89 94
             }
90 95
 
91 96
             $paymentCount = $max && $min ? $this->faker->numberBetween($min, $max) : $min;
92
-
93 97
             $paymentAmount = (int) floor($totalAmountDue / $paymentCount);
94 98
             $remainingAmount = $totalAmountDue;
95 99
 
100
+            $paymentDate = Carbon::parse($invoice->approved_at);
101
+            $paymentDates = [];
102
+
96 103
             for ($i = 0; $i < $paymentCount; $i++) {
97 104
                 $amount = $i === $paymentCount - 1 ? $remainingAmount : $paymentAmount;
98 105
 
@@ -100,8 +107,11 @@ class InvoiceFactory extends Factory
100 107
                     break;
101 108
                 }
102 109
 
110
+                $postedAt = $paymentDate->copy()->addDays($this->faker->numberBetween(1, 30));
111
+                $paymentDates[] = $postedAt;
112
+
103 113
                 $data = [
104
-                    'posted_at' => $invoice->date->addDay(),
114
+                    'posted_at' => $postedAt,
105 115
                     'amount' => CurrencyConverter::convertCentsToFormatSimple($amount, $invoice->currency_code),
106 116
                     'payment_method' => $this->faker->randomElement(PaymentMethod::class),
107 117
                     'bank_account_id' => BankAccount::inRandomOrder()->value('id'),
@@ -109,9 +119,17 @@ class InvoiceFactory extends Factory
109 119
                 ];
110 120
 
111 121
                 $invoice->recordPayment($data);
112
-
113 122
                 $remainingAmount -= $amount;
114 123
             }
124
+
125
+            // If it's a paid invoice, use the latest payment date as paid_at
126
+            if ($invoiceStatus === InvoiceStatus::Paid) {
127
+                $latestPaymentDate = max($paymentDates);
128
+                $invoice->updateQuietly([
129
+                    'status' => InvoiceStatus::Paid,
130
+                    'paid_at' => $latestPaymentDate,
131
+                ]);
132
+            }
115 133
         });
116 134
     }
117 135
 
@@ -128,7 +146,7 @@ class InvoiceFactory extends Factory
128 146
 
129 147
             $this->recalculateTotals($invoice);
130 148
 
131
-            if ($invoice->is_currently_overdue) {
149
+            if ($invoice->approved_at && $invoice->is_currently_overdue) {
132 150
                 $invoice->updateQuietly([
133 151
                     'status' => InvoiceStatus::Overdue,
134 152
                 ]);

+ 0
- 23
database/factories/Accounting/InvoiceStatusHistoryFactory.php Wyświetl plik

@@ -1,23 +0,0 @@
1
-<?php
2
-
3
-namespace Database\Factories\Accounting;
4
-
5
-use Illuminate\Database\Eloquent\Factories\Factory;
6
-
7
-/**
8
- * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Accounting\InvoiceStatusHistory>
9
- */
10
-class InvoiceStatusHistoryFactory extends Factory
11
-{
12
-    /**
13
-     * Define the model's default state.
14
-     *
15
-     * @return array<string, mixed>
16
-     */
17
-    public function definition(): array
18
-    {
19
-        return [
20
-            //
21
-        ];
22
-    }
23
-}

+ 3
- 0
database/migrations/2024_11_27_223015_create_invoices_table.php Wyświetl plik

@@ -22,6 +22,9 @@ return new class extends Migration
22 22
             $table->string('order_number')->nullable(); // PO, SO, etc.
23 23
             $table->date('date')->nullable();
24 24
             $table->date('due_date')->nullable();
25
+            $table->timestamp('approved_at')->nullable();
26
+            $table->timestamp('paid_at')->nullable();
27
+            $table->timestamp('last_sent')->nullable();
25 28
             $table->string('status')->default('draft');
26 29
             $table->string('currency_code')->nullable();
27 30
             $table->integer('subtotal')->default(0);

+ 0
- 33
database/migrations/2024_12_05_003805_create_invoice_status_histories_table.php Wyświetl plik

@@ -1,33 +0,0 @@
1
-<?php
2
-
3
-use Illuminate\Database\Migrations\Migration;
4
-use Illuminate\Database\Schema\Blueprint;
5
-use Illuminate\Support\Facades\Schema;
6
-
7
-return new class extends Migration
8
-{
9
-    /**
10
-     * Run the migrations.
11
-     */
12
-    public function up(): void
13
-    {
14
-        Schema::create('invoice_status_histories', function (Blueprint $table) {
15
-            $table->id();
16
-            $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
-            $table->foreignId('invoice_id')->constrained()->cascadeOnDelete();
18
-            $table->string('old_status');
19
-            $table->string('new_status');
20
-            $table->foreignId('changed_by')->nullable()->constrained('users')->nullOnDelete();
21
-            $table->timestamp('changed_at')->useCurrent();
22
-            $table->timestamps();
23
-        });
24
-    }
25
-
26
-    /**
27
-     * Reverse the migrations.
28
-     */
29
-    public function down(): void
30
-    {
31
-        Schema::dropIfExists('invoice_status_histories');
32
-    }
33
-};

+ 6
- 6
package-lock.json Wyświetl plik

@@ -1204,9 +1204,9 @@
1204 1204
             "license": "MIT"
1205 1205
         },
1206 1206
         "node_modules/electron-to-chromium": {
1207
-            "version": "1.5.68",
1208
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.68.tgz",
1209
-            "integrity": "sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==",
1207
+            "version": "1.5.71",
1208
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz",
1209
+            "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==",
1210 1210
             "dev": true,
1211 1211
             "license": "ISC"
1212 1212
         },
@@ -2588,9 +2588,9 @@
2588 2588
             "license": "MIT"
2589 2589
         },
2590 2590
         "node_modules/vite": {
2591
-            "version": "6.0.2",
2592
-            "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.2.tgz",
2593
-            "integrity": "sha512-XdQ+VsY2tJpBsKGs0wf3U/+azx8BBpYRHFAyKm5VeEZNOJZRB63q7Sc8Iup3k0TrN3KO6QgyzFf+opSbfY1y0g==",
2591
+            "version": "6.0.3",
2592
+            "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz",
2593
+            "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==",
2594 2594
             "dev": true,
2595 2595
             "license": "MIT",
2596 2596
             "dependencies": {

Ładowanie…
Anuluj
Zapisz