Browse Source

wip Bills

3.x
Andrew Wallo 10 months ago
parent
commit
e4448788e4

+ 2
- 8
app/Filament/Company/Pages/Accounting/Transactions.php View File

@@ -12,7 +12,6 @@ use App\Filament\Forms\Components\DateRangeSelect;
12 12
 use App\Filament\Forms\Components\JournalEntryRepeater;
13 13
 use App\Filament\Tables\Actions\ReplicateBulkAction;
14 14
 use App\Models\Accounting\Account;
15
-use App\Models\Accounting\Invoice;
16 15
 use App\Models\Accounting\JournalEntry;
17 16
 use App\Models\Accounting\Transaction;
18 17
 use App\Models\Banking\BankAccount;
@@ -270,13 +269,8 @@ class Transactions extends Page implements HasTable
270 269
                     'journalEntries.account',
271 270
                 ])
272 271
                     ->where(function (Builder $query) {
273
-                        $query->whereDoesntHaveMorph(
274
-                            'transactionable',
275
-                            [Invoice::class],
276
-                            function (Builder $query) {
277
-                                $query->where('type', TransactionType::Journal);
278
-                            }
279
-                        );
272
+                        $query->whereNull('transactionable_id')
273
+                            ->orWhere('is_payment', true);
280 274
                     });
281 275
             })
282 276
             ->columns([

+ 34
- 1
app/Filament/Company/Pages/Reports/AccountTransactions.php View File

@@ -7,6 +7,8 @@ use App\DTO\ReportDTO;
7 7
 use App\Filament\Company\Pages\Accounting\Transactions;
8 8
 use App\Models\Accounting\Account;
9 9
 use App\Models\Accounting\JournalEntry;
10
+use App\Models\Common\Client;
11
+use App\Models\Common\Vendor;
10 12
 use App\Services\ExportService;
11 13
 use App\Services\ReportService;
12 14
 use App\Support\Column;
@@ -47,6 +49,10 @@ class AccountTransactions extends BaseReportPage
47 49
         if (empty($this->getFilterState('selectedAccount'))) {
48 50
             $this->setFilterState('selectedAccount', 'all');
49 51
         }
52
+
53
+        if (empty($this->getFilterState('selectedEntity'))) {
54
+            $this->setFilterState('selectedEntity', 'all');
55
+        }
50 56
     }
51 57
 
52 58
     /**
@@ -91,6 +97,10 @@ class AccountTransactions extends BaseReportPage
91 97
                 ])->extraFieldWrapperAttributes([
92 98
                     'class' => 'report-hidden-label',
93 99
                 ]),
100
+                Select::make('selectedEntity')
101
+                    ->label('Entity')
102
+                    ->options($this->getEntityOptions())
103
+                    ->searchable(),
94 104
                 Actions::make([
95 105
                     Actions\Action::make('applyFilters')
96 106
                         ->label('Update Report')
@@ -116,9 +126,32 @@ class AccountTransactions extends BaseReportPage
116 126
         return $allAccountsOption + $accounts;
117 127
     }
118 128
 
129
+    protected function getEntityOptions(): array
130
+    {
131
+        $clients = Client::query()
132
+            ->orderBy('name')
133
+            ->pluck('name', 'id')
134
+            ->toArray();
135
+
136
+        $vendors = Vendor::query()
137
+            ->orderBy('name')
138
+            ->pluck('name', 'id')
139
+            ->mapWithKeys(fn ($name, $id) => [-$id => $name])
140
+            ->toArray();
141
+
142
+        $allEntitiesOption = [
143
+            'All Entities' => ['all' => 'All Entities'],
144
+        ];
145
+
146
+        return $allEntitiesOption + [
147
+            'Clients' => $clients,
148
+            'Vendors' => $vendors,
149
+        ];
150
+    }
151
+
119 152
     protected function buildReport(array $columns): ReportDTO
120 153
     {
121
-        return $this->reportService->buildAccountTransactionsReport($this->getFormattedStartDate(), $this->getFormattedEndDate(), $columns, $this->getFilterState('selectedAccount'));
154
+        return $this->reportService->buildAccountTransactionsReport($this->getFormattedStartDate(), $this->getFormattedEndDate(), $columns, $this->getFilterState('selectedAccount'), $this->getFilterState('selectedEntity'));
122 155
     }
123 156
 
124 157
     protected function getTransformer(ReportDTO $reportDTO): ExportableReport

+ 4
- 0
app/Filament/Company/Resources/Purchases/BillResource.php View File

@@ -172,6 +172,10 @@ class BillResource extends Resource
172 172
                                     'discount_total' => $discountTotal,
173 173
                                     'total' => $grandTotal,
174 174
                                 ]);
175
+
176
+                                $bill->refresh();
177
+
178
+                                $bill->createInitialTransaction();
175 179
                             })
176 180
                             ->headers([
177 181
                                 Header::make('Items')->width('15%'),

+ 9
- 5
app/Models/Accounting/Bill.php View File

@@ -171,7 +171,7 @@ class Bill extends Model
171 171
     public function recordPayment(array $data): void
172 172
     {
173 173
         $transactionType = TransactionType::Withdrawal;
174
-        $transactionDescription = 'Payment for Bill #' . $this->bill_number;
174
+        $transactionDescription = "Bill #{$this->bill_number}: Payment to {$this->vendor->name}";
175 175
 
176 176
         // Create transaction
177 177
         $this->transactions()->create([
@@ -200,21 +200,25 @@ class Bill extends Model
200 200
             'description' => 'Bill Creation for Bill #' . $this->bill_number,
201 201
         ]);
202 202
 
203
+        $baseDescription = "{$this->vendor->name}: Bill #{$this->bill_number}";
204
+
203 205
         $transaction->journalEntries()->create([
204 206
             'company_id' => $this->company_id,
205 207
             'type' => JournalEntryType::Credit,
206 208
             'account_id' => Account::getAccountsPayableAccount()->id,
207 209
             'amount' => $this->total,
208
-            'description' => $transaction->description,
210
+            'description' => $baseDescription,
209 211
         ]);
210 212
 
211 213
         foreach ($this->lineItems as $lineItem) {
214
+            $lineItemDescription = "{$baseDescription} › {$lineItem->offering->name}";
215
+
212 216
             $transaction->journalEntries()->create([
213 217
                 'company_id' => $this->company_id,
214 218
                 'type' => JournalEntryType::Debit,
215 219
                 'account_id' => $lineItem->offering->expense_account_id,
216 220
                 'amount' => $lineItem->subtotal,
217
-                'description' => $transaction->description,
221
+                'description' => $lineItemDescription,
218 222
             ]);
219 223
 
220 224
             foreach ($lineItem->adjustments as $adjustment) {
@@ -224,7 +228,7 @@ class Bill extends Model
224 228
                         'type' => JournalEntryType::Debit,
225 229
                         'account_id' => $lineItem->offering->expense_account_id,
226 230
                         'amount' => $lineItem->calculateAdjustmentTotal($adjustment)->getAmount(),
227
-                        'description' => $transaction->description . " ($adjustment->name)",
231
+                        'description' => "{$lineItemDescription} ({$adjustment->name})",
228 232
                     ]);
229 233
                 } elseif ($adjustment->account_id) {
230 234
                     $transaction->journalEntries()->create([
@@ -232,7 +236,7 @@ class Bill extends Model
232 236
                         'type' => $adjustment->category->isDiscount() ? JournalEntryType::Credit : JournalEntryType::Debit,
233 237
                         'account_id' => $adjustment->account_id,
234 238
                         'amount' => $lineItem->calculateAdjustmentTotal($adjustment)->getAmount(),
235
-                        'description' => $transaction->description,
239
+                        'description' => $lineItemDescription,
236 240
                     ]);
237 241
                 }
238 242
             }

+ 22
- 2
app/Services/AccountService.php View File

@@ -4,6 +4,8 @@ namespace App\Services;
4 4
 
5 5
 use App\Enums\Accounting\AccountCategory;
6 6
 use App\Models\Accounting\Account;
7
+use App\Models\Accounting\Bill;
8
+use App\Models\Accounting\Invoice;
7 9
 use App\Models\Accounting\Transaction;
8 10
 use App\Repositories\Accounting\JournalEntryRepository;
9 11
 use App\Utilities\Currency\CurrencyAccessor;
@@ -111,21 +113,39 @@ class AccountService
111 113
         return array_filter($balances, static fn ($value) => $value !== null);
112 114
     }
113 115
 
114
-    public function getTransactionDetailsSubquery(string $startDate, string $endDate): Closure
116
+    public function getTransactionDetailsSubquery(string $startDate, string $endDate, ?string $entityId = null): Closure
115 117
     {
116
-        return static function ($query) use ($startDate, $endDate) {
118
+        return static function ($query) use ($startDate, $endDate, $entityId) {
117 119
             $query->select(
118 120
                 'journal_entries.id',
119 121
                 'journal_entries.account_id',
120 122
                 'journal_entries.transaction_id',
121 123
                 'journal_entries.type',
122 124
                 'journal_entries.amount',
125
+                'journal_entries.description',
123 126
                 DB::raw('journal_entries.amount * IF(journal_entries.type = "debit", 1, -1) AS signed_amount')
124 127
             )
125 128
                 ->whereBetween('transactions.posted_at', [$startDate, $endDate])
126 129
                 ->join('transactions', 'transactions.id', '=', 'journal_entries.transaction_id')
127 130
                 ->orderBy('transactions.posted_at')
128 131
                 ->with('transaction:id,type,description,posted_at');
132
+
133
+            if ($entityId) {
134
+                $entityId = (int) $entityId;
135
+                if ($entityId < 0) {
136
+                    $query->whereHas('transaction', function ($query) use ($entityId) {
137
+                        $query->whereHasMorph('transactionable', [Bill::class], function ($query) use ($entityId) {
138
+                            $query->where('vendor_id', abs($entityId));
139
+                        });
140
+                    });
141
+                } else {
142
+                    $query->whereHas('transaction', function ($query) use ($entityId) {
143
+                        $query->whereHasMorph('transactionable', [Invoice::class], function ($query) use ($entityId) {
144
+                            $query->where('client_id', $entityId);
145
+                        });
146
+                    });
147
+                }
148
+            }
129 149
         };
130 150
     }
131 151
 

+ 5
- 3
app/Services/ReportService.php View File

@@ -152,17 +152,19 @@ class ReportService
152 152
         return new Money($retainedEarnings, CurrencyAccessor::getDefaultCurrency());
153 153
     }
154 154
 
155
-    public function buildAccountTransactionsReport(string $startDate, string $endDate, ?array $columns = null, ?string $accountId = 'all'): ReportDTO
155
+    public function buildAccountTransactionsReport(string $startDate, string $endDate, ?array $columns = null, ?string $accountId = 'all', ?string $entityId = 'all'): ReportDTO
156 156
     {
157 157
         $columns ??= [];
158 158
         $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
159 159
 
160 160
         $accountIds = $accountId !== 'all' ? [$accountId] : [];
161 161
 
162
+        $entityId = $entityId !== 'all' ? $entityId : null;
163
+
162 164
         $query = $this->accountService->getAccountBalances($startDate, $endDate, $accountIds)
163 165
             ->orderByRaw('LENGTH(code), code');
164 166
 
165
-        $accounts = $query->with(['journalEntries' => $this->accountService->getTransactionDetailsSubquery($startDate, $endDate)])->get();
167
+        $accounts = $query->with(['journalEntries' => $this->accountService->getTransactionDetailsSubquery($startDate, $endDate, $entityId)])->get();
166 168
 
167 169
         $reportCategories = [];
168 170
 
@@ -197,7 +199,7 @@ class ReportService
197 199
                 $accountTransactions[] = new AccountTransactionDTO(
198 200
                     id: $transaction->id,
199 201
                     date: $transaction->posted_at->toDefaultDateFormat(),
200
-                    description: $transaction->description ?? 'Add a description',
202
+                    description: $journalEntry->description ?: $transaction->description ?? 'Add a description',
201 203
                     debit: $journalEntry->type->isDebit() ? $formattedAmount : '',
202 204
                     credit: $journalEntry->type->isCredit() ? $formattedAmount : '',
203 205
                     balance: money($currentBalance, $defaultCurrency)->format(),

+ 26
- 25
composer.lock View File

@@ -2778,16 +2778,16 @@
2778 2778
         },
2779 2779
         {
2780 2780
             "name": "jaocero/radio-deck",
2781
-            "version": "v1.2.8",
2781
+            "version": "v1.2.9",
2782 2782
             "source": {
2783 2783
                 "type": "git",
2784 2784
                 "url": "https://github.com/199ocero/radio-deck.git",
2785
-                "reference": "23815595cfdb441794964f0729e0ea57eec48f5e"
2785
+                "reference": "f99c40700d9ff1a8419cee44fcd4304908919f8e"
2786 2786
             },
2787 2787
             "dist": {
2788 2788
                 "type": "zip",
2789
-                "url": "https://api.github.com/repos/199ocero/radio-deck/zipball/23815595cfdb441794964f0729e0ea57eec48f5e",
2790
-                "reference": "23815595cfdb441794964f0729e0ea57eec48f5e",
2789
+                "url": "https://api.github.com/repos/199ocero/radio-deck/zipball/f99c40700d9ff1a8419cee44fcd4304908919f8e",
2790
+                "reference": "f99c40700d9ff1a8419cee44fcd4304908919f8e",
2791 2791
                 "shasum": ""
2792 2792
             },
2793 2793
             "require": {
@@ -2848,7 +2848,7 @@
2848 2848
                     "type": "github"
2849 2849
                 }
2850 2850
             ],
2851
-            "time": "2024-06-25T23:06:42+00:00"
2851
+            "time": "2024-12-07T13:41:25+00:00"
2852 2852
         },
2853 2853
         {
2854 2854
             "name": "kirschbaum-development/eloquent-power-joins",
@@ -3517,16 +3517,16 @@
3517 3517
         },
3518 3518
         {
3519 3519
             "name": "league/commonmark",
3520
-            "version": "2.5.3",
3520
+            "version": "2.6.0",
3521 3521
             "source": {
3522 3522
                 "type": "git",
3523 3523
                 "url": "https://github.com/thephpleague/commonmark.git",
3524
-                "reference": "b650144166dfa7703e62a22e493b853b58d874b0"
3524
+                "reference": "d150f911e0079e90ae3c106734c93137c184f932"
3525 3525
             },
3526 3526
             "dist": {
3527 3527
                 "type": "zip",
3528
-                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0",
3529
-                "reference": "b650144166dfa7703e62a22e493b853b58d874b0",
3528
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932",
3529
+                "reference": "d150f911e0079e90ae3c106734c93137c184f932",
3530 3530
                 "shasum": ""
3531 3531
             },
3532 3532
             "require": {
@@ -3551,8 +3551,9 @@
3551 3551
                 "phpstan/phpstan": "^1.8.2",
3552 3552
                 "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
3553 3553
                 "scrutinizer/ocular": "^1.8.1",
3554
-                "symfony/finder": "^5.3 | ^6.0 || ^7.0",
3555
-                "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0",
3554
+                "symfony/finder": "^5.3 | ^6.0 | ^7.0",
3555
+                "symfony/process": "^5.4 | ^6.0 | ^7.0",
3556
+                "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
3556 3557
                 "unleashedtech/php-coding-standard": "^3.1.1",
3557 3558
                 "vimeo/psalm": "^4.24.0 || ^5.0.0"
3558 3559
             },
@@ -3562,7 +3563,7 @@
3562 3563
             "type": "library",
3563 3564
             "extra": {
3564 3565
                 "branch-alias": {
3565
-                    "dev-main": "2.6-dev"
3566
+                    "dev-main": "2.7-dev"
3566 3567
                 }
3567 3568
             },
3568 3569
             "autoload": {
@@ -3619,7 +3620,7 @@
3619 3620
                     "type": "tidelift"
3620 3621
                 }
3621 3622
             ],
3622
-            "time": "2024-08-16T11:46:16+00:00"
3623
+            "time": "2024-12-07T15:34:16+00:00"
3623 3624
         },
3624 3625
         {
3625 3626
             "name": "league/config",
@@ -5920,16 +5921,16 @@
5920 5921
         },
5921 5922
         {
5922 5923
             "name": "psy/psysh",
5923
-            "version": "v0.12.5",
5924
+            "version": "v0.12.6",
5924 5925
             "source": {
5925 5926
                 "type": "git",
5926 5927
                 "url": "https://github.com/bobthecow/psysh.git",
5927
-                "reference": "36a03ff27986682c22985e56aabaf840dd173cb5"
5928
+                "reference": "3b5ea0efaa791cd1c65ecc493aec3e2aa55ff57c"
5928 5929
             },
5929 5930
             "dist": {
5930 5931
                 "type": "zip",
5931
-                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/36a03ff27986682c22985e56aabaf840dd173cb5",
5932
-                "reference": "36a03ff27986682c22985e56aabaf840dd173cb5",
5932
+                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/3b5ea0efaa791cd1c65ecc493aec3e2aa55ff57c",
5933
+                "reference": "3b5ea0efaa791cd1c65ecc493aec3e2aa55ff57c",
5933 5934
                 "shasum": ""
5934 5935
             },
5935 5936
             "require": {
@@ -5993,9 +5994,9 @@
5993 5994
             ],
5994 5995
             "support": {
5995 5996
                 "issues": "https://github.com/bobthecow/psysh/issues",
5996
-                "source": "https://github.com/bobthecow/psysh/tree/v0.12.5"
5997
+                "source": "https://github.com/bobthecow/psysh/tree/v0.12.6"
5997 5998
             },
5998
-            "time": "2024-11-29T06:14:30+00:00"
5999
+            "time": "2024-12-07T20:08:52+00:00"
5999 6000
         },
6000 6001
         {
6001 6002
             "name": "ralouphie/getallheaders",
@@ -10568,16 +10569,16 @@
10568 10569
         },
10569 10570
         {
10570 10571
             "name": "phpdocumentor/reflection-docblock",
10571
-            "version": "5.6.0",
10572
+            "version": "5.6.1",
10572 10573
             "source": {
10573 10574
                 "type": "git",
10574 10575
                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
10575
-                "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c"
10576
+                "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8"
10576 10577
             },
10577 10578
             "dist": {
10578 10579
                 "type": "zip",
10579
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/f3558a4c23426d12bffeaab463f8a8d8b681193c",
10580
-                "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c",
10580
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8",
10581
+                "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8",
10581 10582
                 "shasum": ""
10582 10583
             },
10583 10584
             "require": {
@@ -10626,9 +10627,9 @@
10626 10627
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
10627 10628
             "support": {
10628 10629
                 "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
10629
-                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.0"
10630
+                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1"
10630 10631
             },
10631
-            "time": "2024-11-12T11:25:25+00:00"
10632
+            "time": "2024-12-07T09:39:29+00:00"
10632 10633
         },
10633 10634
         {
10634 10635
             "name": "phpdocumentor/type-resolver",

+ 6
- 8
database/factories/Accounting/DocumentLineItemFactory.php View File

@@ -37,8 +37,7 @@ class DocumentLineItemFactory extends Factory
37 37
     public function forInvoice(): static
38 38
     {
39 39
         return $this->state(function (array $attributes) {
40
-            $offering = Offering::with(['salesTaxes', 'salesDiscounts'])
41
-                ->where('sellable', true)
40
+            $offering = Offering::where('sellable', true)
42 41
                 ->inRandomOrder()
43 42
                 ->first();
44 43
 
@@ -50,8 +49,8 @@ class DocumentLineItemFactory extends Factory
50 49
             $offering = $lineItem->offering;
51 50
 
52 51
             if ($offering) {
53
-                $lineItem->adjustments()->syncWithoutDetaching($offering->salesTaxes->pluck('id')->toArray());
54
-                $lineItem->adjustments()->syncWithoutDetaching($offering->salesDiscounts->pluck('id')->toArray());
52
+                $lineItem->salesTaxes()->syncWithoutDetaching($offering->salesTaxes->pluck('id')->toArray());
53
+                $lineItem->salesDiscounts()->syncWithoutDetaching($offering->salesDiscounts->pluck('id')->toArray());
55 54
             }
56 55
 
57 56
             $lineItem->refresh();
@@ -69,8 +68,7 @@ class DocumentLineItemFactory extends Factory
69 68
     public function forBill(): static
70 69
     {
71 70
         return $this->state(function (array $attributes) {
72
-            $offering = Offering::with(['purchaseTaxes', 'purchaseDiscounts'])
73
-                ->where('purchasable', true)
71
+            $offering = Offering::where('purchasable', true)
74 72
                 ->inRandomOrder()
75 73
                 ->first();
76 74
 
@@ -82,8 +80,8 @@ class DocumentLineItemFactory extends Factory
82 80
             $offering = $lineItem->offering;
83 81
 
84 82
             if ($offering) {
85
-                $lineItem->adjustments()->syncWithoutDetaching($offering->purchaseTaxes->pluck('id')->toArray());
86
-                $lineItem->adjustments()->syncWithoutDetaching($offering->purchaseDiscounts->pluck('id')->toArray());
83
+                $lineItem->purchaseTaxes()->syncWithoutDetaching($offering->purchaseTaxes->pluck('id')->toArray());
84
+                $lineItem->purchaseDiscounts()->syncWithoutDetaching($offering->purchaseDiscounts->pluck('id')->toArray());
87 85
             }
88 86
 
89 87
             $lineItem->refresh();

Loading…
Cancel
Save