Andrew Wallo 9 months ago
parent
commit
d6994fae65

+ 2
- 5
app/Filament/Company/Resources/Purchases/BillResource.php View File

10
 use App\Filament\Forms\Components\CreateCurrencySelect;
10
 use App\Filament\Forms\Components\CreateCurrencySelect;
11
 use App\Filament\Forms\Components\DocumentTotals;
11
 use App\Filament\Forms\Components\DocumentTotals;
12
 use App\Filament\Tables\Actions\ReplicateBulkAction;
12
 use App\Filament\Tables\Actions\ReplicateBulkAction;
13
+use App\Filament\Tables\Columns;
13
 use App\Filament\Tables\Filters\DateRangeFilter;
14
 use App\Filament\Tables\Filters\DateRangeFilter;
14
 use App\Models\Accounting\Adjustment;
15
 use App\Models\Accounting\Adjustment;
15
 use App\Models\Accounting\Bill;
16
 use App\Models\Accounting\Bill;
234
         return $table
235
         return $table
235
             ->defaultSort('due_date')
236
             ->defaultSort('due_date')
236
             ->columns([
237
             ->columns([
237
-                Tables\Columns\TextColumn::make('id')
238
-                    ->label('ID')
239
-                    ->sortable()
240
-                    ->toggleable(isToggledHiddenByDefault: true)
241
-                    ->searchable(),
238
+                Columns::id(),
242
                 Tables\Columns\TextColumn::make('status')
239
                 Tables\Columns\TextColumn::make('status')
243
                     ->badge()
240
                     ->badge()
244
                     ->searchable(),
241
                     ->searchable(),

+ 2
- 0
app/Filament/Company/Resources/Purchases/VendorResource.php View File

8
 use App\Filament\Forms\Components\CreateCurrencySelect;
8
 use App\Filament\Forms\Components\CreateCurrencySelect;
9
 use App\Filament\Forms\Components\CustomSection;
9
 use App\Filament\Forms\Components\CustomSection;
10
 use App\Filament\Forms\Components\PhoneBuilder;
10
 use App\Filament\Forms\Components\PhoneBuilder;
11
+use App\Filament\Tables\Columns;
11
 use App\Models\Common\Vendor;
12
 use App\Models\Common\Vendor;
12
 use Filament\Forms;
13
 use Filament\Forms;
13
 use Filament\Forms\Form;
14
 use Filament\Forms\Form;
173
     {
174
     {
174
         return $table
175
         return $table
175
             ->columns([
176
             ->columns([
177
+                Columns::id(),
176
                 Tables\Columns\TextColumn::make('type')
178
                 Tables\Columns\TextColumn::make('type')
177
                     ->badge()
179
                     ->badge()
178
                     ->searchable(),
180
                     ->searchable(),

+ 136
- 34
app/Filament/Company/Resources/Sales/ClientResource.php View File

6
 use App\Filament\Forms\Components\CreateCurrencySelect;
6
 use App\Filament\Forms\Components\CreateCurrencySelect;
7
 use App\Filament\Forms\Components\CustomSection;
7
 use App\Filament\Forms\Components\CustomSection;
8
 use App\Filament\Forms\Components\PhoneBuilder;
8
 use App\Filament\Forms\Components\PhoneBuilder;
9
+use App\Filament\Tables\Columns;
10
+use App\Models\Common\Address;
9
 use App\Models\Common\Client;
11
 use App\Models\Common\Client;
12
+use App\Models\Locale\Country;
13
+use App\Models\Locale\State;
14
+use App\Utilities\Currency\CurrencyConverter;
10
 use Filament\Forms;
15
 use Filament\Forms;
11
 use Filament\Forms\Form;
16
 use Filament\Forms\Form;
17
+use Filament\Forms\Get;
18
+use Filament\Forms\Set;
12
 use Filament\Resources\Resource;
19
 use Filament\Resources\Resource;
13
 use Filament\Tables;
20
 use Filament\Tables;
14
 use Filament\Tables\Table;
21
 use Filament\Tables\Table;
22
+use Illuminate\Database\Eloquent\Builder;
23
+use Illuminate\Support\HtmlString;
15
 
24
 
16
 class ClientResource extends Resource
25
 class ClientResource extends Resource
17
 {
26
 {
170
                         CreateCurrencySelect::make('currency_code'),
179
                         CreateCurrencySelect::make('currency_code'),
171
                         CustomSection::make('Billing Address')
180
                         CustomSection::make('Billing Address')
172
                             ->relationship('billingAddress')
181
                             ->relationship('billingAddress')
182
+                            ->saveRelationshipsUsing(null)
183
+                            ->dehydrated(true)
173
                             ->contained(false)
184
                             ->contained(false)
174
                             ->schema([
185
                             ->schema([
175
                                 Forms\Components\Hidden::make('type')
186
                                 Forms\Components\Hidden::make('type')
181
                                 Forms\Components\TextInput::make('address_line_2')
192
                                 Forms\Components\TextInput::make('address_line_2')
182
                                     ->label('Address Line 2')
193
                                     ->label('Address Line 2')
183
                                     ->maxLength(255),
194
                                     ->maxLength(255),
195
+                                Forms\Components\Select::make('country')
196
+                                    ->searchable()
197
+                                    ->localizeLabel()
198
+                                    ->live()
199
+                                    ->options(Country::getAvailableCountryOptions())
200
+                                    ->afterStateUpdated(static function (Set $set) {
201
+                                        $set('state_id', null);
202
+                                    })
203
+                                    ->required(),
204
+                                Forms\Components\Select::make('state_id')
205
+                                    ->localizeLabel('State / Province')
206
+                                    ->searchable()
207
+                                    ->options(static fn (Get $get) => State::getStateOptions($get('country')))
208
+                                    ->nullable(),
184
                                 Forms\Components\TextInput::make('city')
209
                                 Forms\Components\TextInput::make('city')
185
                                     ->label('City')
210
                                     ->label('City')
186
                                     ->required()
211
                                     ->required()
187
                                     ->maxLength(255),
212
                                     ->maxLength(255),
188
-                                Forms\Components\TextInput::make('state')
189
-                                    ->label('State')
190
-                                    ->required()
191
-                                    ->maxLength(255),
192
                                 Forms\Components\TextInput::make('postal_code')
213
                                 Forms\Components\TextInput::make('postal_code')
193
                                     ->label('Postal Code / Zip Code')
214
                                     ->label('Postal Code / Zip Code')
194
                                     ->required()
215
                                     ->required()
195
                                     ->maxLength(255),
216
                                     ->maxLength(255),
196
-                                Forms\Components\TextInput::make('country')
197
-                                    ->label('Country')
198
-                                    ->required()
199
-                                    ->maxLength(255),
200
                             ])->columns(),
217
                             ])->columns(),
201
                     ])
218
                     ])
202
                     ->columns(1),
219
                     ->columns(1),
203
                 Forms\Components\Section::make('Shipping')
220
                 Forms\Components\Section::make('Shipping')
204
                     ->relationship('shippingAddress')
221
                     ->relationship('shippingAddress')
222
+                    ->saveRelationshipsUsing(null)
223
+                    ->dehydrated(true)
205
                     ->schema([
224
                     ->schema([
225
+                        Forms\Components\Hidden::make('type')
226
+                            ->default('shipping'),
206
                         Forms\Components\TextInput::make('recipient')
227
                         Forms\Components\TextInput::make('recipient')
207
                             ->label('Recipient')
228
                             ->label('Recipient')
208
                             ->required()
229
                             ->required()
209
                             ->maxLength(255),
230
                             ->maxLength(255),
210
-                        Forms\Components\Hidden::make('type')
211
-                            ->default('shipping'),
212
                         Forms\Components\TextInput::make('phone')
231
                         Forms\Components\TextInput::make('phone')
213
                             ->label('Phone')
232
                             ->label('Phone')
214
                             ->required()
233
                             ->required()
216
                         CustomSection::make('Shipping Address')
235
                         CustomSection::make('Shipping Address')
217
                             ->contained(false)
236
                             ->contained(false)
218
                             ->schema([
237
                             ->schema([
219
-                                Forms\Components\TextInput::make('address_line_1')
220
-                                    ->label('Address Line 1')
221
-                                    ->required()
222
-                                    ->maxLength(255),
223
-                                Forms\Components\TextInput::make('address_line_2')
224
-                                    ->label('Address Line 2')
225
-                                    ->maxLength(255),
226
-                                Forms\Components\TextInput::make('city')
227
-                                    ->label('City')
228
-                                    ->required()
229
-                                    ->maxLength(255),
230
-                                Forms\Components\TextInput::make('state')
231
-                                    ->label('State')
232
-                                    ->required()
233
-                                    ->maxLength(255),
234
-                                Forms\Components\TextInput::make('postal_code')
235
-                                    ->label('Postal Code / Zip Code')
236
-                                    ->required()
237
-                                    ->maxLength(255),
238
-                                Forms\Components\TextInput::make('country')
239
-                                    ->label('Country')
240
-                                    ->required()
241
-                                    ->maxLength(255),
238
+                                Forms\Components\Checkbox::make('same_as_billing')
239
+                                    ->label('Same as Billing Address')
240
+                                    ->live()
241
+                                    ->afterStateHydrated(function (?Address $record, Forms\Components\Checkbox $component) {
242
+                                        if (! $record || $record->parent_address_id) {
243
+                                            return $component->state(true);
244
+                                        }
245
+
246
+                                        return $component->state(false);
247
+                                    })
248
+                                    ->afterStateUpdated(static function (Get $get, Set $set, $state) {
249
+                                        if ($state) {
250
+                                            return;
251
+                                        }
252
+
253
+                                        $billingAddress = $get('../billingAddress');
254
+
255
+                                        $fieldsToSync = [
256
+                                            'address_line_1',
257
+                                            'address_line_2',
258
+                                            'country',
259
+                                            'state_id',
260
+                                            'city',
261
+                                            'postal_code',
262
+                                        ];
263
+
264
+                                        foreach ($fieldsToSync as $field) {
265
+                                            $set($field, $billingAddress[$field]);
266
+                                        }
267
+                                    })
268
+                                    ->columnSpanFull(),
269
+                                Forms\Components\Grid::make()
270
+                                    ->schema([
271
+                                        Forms\Components\TextInput::make('address_line_1')
272
+                                            ->label('Address Line 1')
273
+                                            ->required()
274
+                                            ->maxLength(255),
275
+                                        Forms\Components\TextInput::make('address_line_2')
276
+                                            ->label('Address Line 2')
277
+                                            ->maxLength(255),
278
+                                        Forms\Components\Select::make('country')
279
+                                            ->searchable()
280
+                                            ->localizeLabel()
281
+                                            ->live()
282
+                                            ->options(Country::getAvailableCountryOptions())
283
+                                            ->afterStateUpdated(static function (Set $set) {
284
+                                                $set('state_id', null);
285
+                                            })
286
+                                            ->required(),
287
+                                        Forms\Components\Select::make('state_id')
288
+                                            ->localizeLabel('State / Province')
289
+                                            ->searchable()
290
+                                            ->options(static fn (Get $get) => State::getStateOptions($get('country')))
291
+                                            ->nullable(),
292
+                                        Forms\Components\TextInput::make('city')
293
+                                            ->label('City')
294
+                                            ->required()
295
+                                            ->maxLength(255),
296
+                                        Forms\Components\TextInput::make('postal_code')
297
+                                            ->label('Postal Code / Zip Code')
298
+                                            ->required()
299
+                                            ->maxLength(255),
300
+                                    ])
301
+                                    ->visible(fn (Get $get) => ! $get('same_as_billing')),
242
                                 Forms\Components\Textarea::make('notes')
302
                                 Forms\Components\Textarea::make('notes')
243
                                     ->label('Delivery Instructions')
303
                                     ->label('Delivery Instructions')
244
                                     ->maxLength(255)
304
                                     ->maxLength(255)
252
     {
312
     {
253
         return $table
313
         return $table
254
             ->columns([
314
             ->columns([
315
+                Columns::id(),
255
                 Tables\Columns\TextColumn::make('name')
316
                 Tables\Columns\TextColumn::make('name')
256
                     ->searchable()
317
                     ->searchable()
318
+                    ->sortable()
257
                     ->description(fn (Client $client) => $client->primaryContact->full_name),
319
                     ->description(fn (Client $client) => $client->primaryContact->full_name),
320
+                Tables\Columns\TextColumn::make('account_number')
321
+                    ->label('Account Number')
322
+                    ->searchable()
323
+                    ->sortable(),
258
                 Tables\Columns\TextColumn::make('primaryContact.email')
324
                 Tables\Columns\TextColumn::make('primaryContact.email')
259
                     ->label('Email')
325
                     ->label('Email')
260
-                    ->searchable(),
326
+                    ->searchable()
327
+                    ->toggleable(),
261
                 Tables\Columns\TextColumn::make('primaryContact.phones')
328
                 Tables\Columns\TextColumn::make('primaryContact.phones')
262
                     ->label('Phone')
329
                     ->label('Phone')
330
+                    ->toggleable()
263
                     ->state(fn (Client $client) => $client->primaryContact->first_available_phone),
331
                     ->state(fn (Client $client) => $client->primaryContact->first_available_phone),
332
+                Tables\Columns\TextColumn::make('billingAddress.address_string')
333
+                    ->label('Billing Address')
334
+                    ->searchable()
335
+                    ->toggleable()
336
+                    ->listWithLineBreaks(),
337
+                Tables\Columns\TextColumn::make('balance')
338
+                    ->label('Balance')
339
+                    ->getStateUsing(function (Client $client) {
340
+                        return $client->invoices()
341
+                            ->unpaid()
342
+                            ->get()
343
+                            ->sumMoneyInDefaultCurrency('amount_due');
344
+                    })
345
+                    ->description(function (Client $client) {
346
+                        $overdue = $client->invoices()
347
+                            ->overdue()
348
+                            ->get()
349
+                            ->sumMoneyInDefaultCurrency('amount_due');
350
+
351
+                        if ($overdue <= 0) {
352
+                            return null;
353
+                        }
354
+
355
+                        $formattedOverdue = CurrencyConverter::formatCentsToMoney($overdue);
356
+
357
+                        return new HtmlString("<span class='text-danger-700 dark:text-danger-400'>Overdue: {$formattedOverdue}</span>");
358
+                    })
359
+                    ->sortable(query: function (Builder $query, string $direction) {
360
+                        return $query
361
+                            ->withSum(['invoices' => fn (Builder $query) => $query->unpaid()], 'amount_due')
362
+                            ->orderBy('invoices_sum_amount_due', $direction);
363
+                    })
364
+                    ->currency(fn (Client $client) => $client->currency_code, false)
365
+                    ->alignEnd(),
264
             ])
366
             ])
265
             ->filters([
367
             ->filters([
266
                 //
368
                 //

+ 52
- 0
app/Filament/Company/Resources/Sales/ClientResource/Pages/CreateClient.php View File

3
 namespace App\Filament\Company\Resources\Sales\ClientResource\Pages;
3
 namespace App\Filament\Company\Resources\Sales\ClientResource\Pages;
4
 
4
 
5
 use App\Concerns\RedirectToListPage;
5
 use App\Concerns\RedirectToListPage;
6
+use App\Enums\Common\AddressType;
6
 use App\Filament\Company\Resources\Sales\ClientResource;
7
 use App\Filament\Company\Resources\Sales\ClientResource;
8
+use App\Models\Common\Client;
7
 use Filament\Resources\Pages\CreateRecord;
9
 use Filament\Resources\Pages\CreateRecord;
8
 use Filament\Support\Enums\MaxWidth;
10
 use Filament\Support\Enums\MaxWidth;
11
+use Illuminate\Database\Eloquent\Model;
9
 
12
 
10
 class CreateClient extends CreateRecord
13
 class CreateClient extends CreateRecord
11
 {
14
 {
17
     {
20
     {
18
         return MaxWidth::FiveExtraLarge;
21
         return MaxWidth::FiveExtraLarge;
19
     }
22
     }
23
+
24
+    protected function handleRecordCreation(array $data): Model
25
+    {
26
+        /** @var Client $record */
27
+        $record = parent::handleRecordCreation($data);
28
+
29
+        // Create billing address first
30
+        $billingAddress = $record->addresses()->create([
31
+            ...$data['billingAddress'],
32
+            'type' => AddressType::Billing,
33
+        ]);
34
+
35
+        // Create shipping address with reference to billing if needed
36
+        $shippingData = $data['shippingAddress'];
37
+
38
+        $shippingAddress = [
39
+            'type' => AddressType::Shipping,
40
+            'recipient' => $shippingData['recipient'],
41
+            'phone' => $shippingData['phone'],
42
+            'notes' => $shippingData['notes'],
43
+        ];
44
+
45
+        if ($shippingData['same_as_billing']) {
46
+            $shippingAddress = [
47
+                ...$shippingAddress,
48
+                'parent_address_id' => $billingAddress->id,
49
+                'address_line_1' => $billingAddress->address_line_1,
50
+                'address_line_2' => $billingAddress->address_line_2,
51
+                'country' => $billingAddress->country,
52
+                'state_id' => $billingAddress->state_id,
53
+                'city' => $billingAddress->city,
54
+                'postal_code' => $billingAddress->postal_code,
55
+            ];
56
+        } else {
57
+            $shippingAddress = [
58
+                ...$shippingAddress,
59
+                'address_line_1' => $shippingData['address_line_1'],
60
+                'address_line_2' => $shippingData['address_line_2'],
61
+                'country' => $shippingData['country'],
62
+                'state_id' => $shippingData['state_id'],
63
+                'city' => $shippingData['city'],
64
+                'postal_code' => $shippingData['postal_code'],
65
+            ];
66
+        }
67
+
68
+        $record->addresses()->create($shippingAddress);
69
+
70
+        return $record;
71
+    }
20
 }
72
 }

+ 2
- 5
app/Filament/Company/Resources/Sales/EstimateResource.php View File

10
 use App\Filament\Forms\Components\CreateCurrencySelect;
10
 use App\Filament\Forms\Components\CreateCurrencySelect;
11
 use App\Filament\Forms\Components\DocumentTotals;
11
 use App\Filament\Forms\Components\DocumentTotals;
12
 use App\Filament\Tables\Actions\ReplicateBulkAction;
12
 use App\Filament\Tables\Actions\ReplicateBulkAction;
13
+use App\Filament\Tables\Columns;
13
 use App\Filament\Tables\Filters\DateRangeFilter;
14
 use App\Filament\Tables\Filters\DateRangeFilter;
14
 use App\Models\Accounting\Adjustment;
15
 use App\Models\Accounting\Adjustment;
15
 use App\Models\Accounting\Estimate;
16
 use App\Models\Accounting\Estimate;
286
         return $table
287
         return $table
287
             ->defaultSort('expiration_date')
288
             ->defaultSort('expiration_date')
288
             ->columns([
289
             ->columns([
289
-                Tables\Columns\TextColumn::make('id')
290
-                    ->label('ID')
291
-                    ->sortable()
292
-                    ->toggleable(isToggledHiddenByDefault: true)
293
-                    ->searchable(),
290
+                Columns::id(),
294
                 Tables\Columns\TextColumn::make('status')
291
                 Tables\Columns\TextColumn::make('status')
295
                     ->badge()
292
                     ->badge()
296
                     ->searchable(),
293
                     ->searchable(),

+ 2
- 5
app/Filament/Company/Resources/Sales/InvoiceResource.php View File

13
 use App\Filament\Forms\Components\CreateCurrencySelect;
13
 use App\Filament\Forms\Components\CreateCurrencySelect;
14
 use App\Filament\Forms\Components\DocumentTotals;
14
 use App\Filament\Forms\Components\DocumentTotals;
15
 use App\Filament\Tables\Actions\ReplicateBulkAction;
15
 use App\Filament\Tables\Actions\ReplicateBulkAction;
16
+use App\Filament\Tables\Columns;
16
 use App\Filament\Tables\Filters\DateRangeFilter;
17
 use App\Filament\Tables\Filters\DateRangeFilter;
17
 use App\Models\Accounting\Adjustment;
18
 use App\Models\Accounting\Adjustment;
18
 use App\Models\Accounting\Invoice;
19
 use App\Models\Accounting\Invoice;
306
                 return $query;
307
                 return $query;
307
             })
308
             })
308
             ->columns([
309
             ->columns([
309
-                Tables\Columns\TextColumn::make('id')
310
-                    ->label('ID')
311
-                    ->sortable()
312
-                    ->toggleable(isToggledHiddenByDefault: true)
313
-                    ->searchable(),
310
+                Columns::id(),
314
                 Tables\Columns\TextColumn::make('status')
311
                 Tables\Columns\TextColumn::make('status')
315
                     ->badge()
312
                     ->badge()
316
                     ->searchable(),
313
                     ->searchable(),

+ 2
- 5
app/Filament/Company/Resources/Sales/RecurringInvoiceResource.php View File

9
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
9
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
10
 use App\Filament\Forms\Components\CreateCurrencySelect;
10
 use App\Filament\Forms\Components\CreateCurrencySelect;
11
 use App\Filament\Forms\Components\DocumentTotals;
11
 use App\Filament\Forms\Components\DocumentTotals;
12
+use App\Filament\Tables\Columns;
12
 use App\Models\Accounting\Adjustment;
13
 use App\Models\Accounting\Adjustment;
13
 use App\Models\Accounting\RecurringInvoice;
14
 use App\Models\Accounting\RecurringInvoice;
14
 use App\Models\Common\Client;
15
 use App\Models\Common\Client;
272
         return $table
273
         return $table
273
             ->defaultSort('next_date')
274
             ->defaultSort('next_date')
274
             ->columns([
275
             ->columns([
275
-                Tables\Columns\TextColumn::make('id')
276
-                    ->label('ID')
277
-                    ->sortable()
278
-                    ->toggleable(isToggledHiddenByDefault: true)
279
-                    ->searchable(),
276
+                Columns::id(),
280
                 Tables\Columns\TextColumn::make('status')
277
                 Tables\Columns\TextColumn::make('status')
281
                     ->badge()
278
                     ->badge()
282
                     ->searchable(),
279
                     ->searchable(),

+ 17
- 0
app/Filament/Tables/Columns.php View File

1
+<?php
2
+
3
+namespace App\Filament\Tables;
4
+
5
+use Filament\Tables\Columns\TextColumn;
6
+
7
+class Columns
8
+{
9
+    public static function id(): TextColumn
10
+    {
11
+        return TextColumn::make('id')
12
+            ->label('ID')
13
+            ->sortable()
14
+            ->toggleable(isToggledHiddenByDefault: true)
15
+            ->searchable();
16
+    }
17
+}

+ 7
- 0
app/Models/Accounting/Invoice.php View File

180
         ]);
180
         ]);
181
     }
181
     }
182
 
182
 
183
+    public function scopeOverdue(Builder $query): Builder
184
+    {
185
+        return $query
186
+            ->unpaid()
187
+            ->where('status', InvoiceStatus::Overdue);
188
+    }
189
+
183
     protected function isCurrentlyOverdue(): Attribute
190
     protected function isCurrentlyOverdue(): Attribute
184
     {
191
     {
185
         return Attribute::get(function () {
192
         return Attribute::get(function () {

+ 46
- 1
app/Models/Common/Address.php View File

5
 use App\Concerns\Blamable;
5
 use App\Concerns\Blamable;
6
 use App\Concerns\CompanyOwned;
6
 use App\Concerns\CompanyOwned;
7
 use App\Enums\Common\AddressType;
7
 use App\Enums\Common\AddressType;
8
+use App\Models\Locale\Country;
9
+use App\Models\Locale\State;
10
+use Illuminate\Database\Eloquent\Casts\Attribute;
8
 use Illuminate\Database\Eloquent\Factories\HasFactory;
11
 use Illuminate\Database\Eloquent\Factories\HasFactory;
9
 use Illuminate\Database\Eloquent\Model;
12
 use Illuminate\Database\Eloquent\Model;
13
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
14
+use Illuminate\Database\Eloquent\Relations\HasMany;
10
 use Illuminate\Database\Eloquent\Relations\MorphTo;
15
 use Illuminate\Database\Eloquent\Relations\MorphTo;
11
 
16
 
12
 class Address extends Model
17
 class Address extends Model
19
 
24
 
20
     protected $fillable = [
25
     protected $fillable = [
21
         'company_id',
26
         'company_id',
27
+        'parent_address_id',
22
         'type',
28
         'type',
23
         'recipient',
29
         'recipient',
24
         'phone',
30
         'phone',
25
         'address_line_1',
31
         'address_line_1',
26
         'address_line_2',
32
         'address_line_2',
27
         'city',
33
         'city',
28
-        'state',
34
+        'state_id',
29
         'postal_code',
35
         'postal_code',
30
         'country',
36
         'country',
31
         'notes',
37
         'notes',
41
     {
47
     {
42
         return $this->morphTo();
48
         return $this->morphTo();
43
     }
49
     }
50
+
51
+    public function parentAddress(): BelongsTo
52
+    {
53
+        return $this->belongsTo(self::class, 'parent_address_id', 'id');
54
+    }
55
+
56
+    public function childAddresses(): HasMany
57
+    {
58
+        return $this->hasMany(self::class, 'parent_address_id', 'id');
59
+    }
60
+
61
+    public function country(): BelongsTo
62
+    {
63
+        return $this->belongsTo(Country::class, 'country', 'id');
64
+    }
65
+
66
+    public function state(): BelongsTo
67
+    {
68
+        return $this->belongsTo(State::class, 'state_id', 'id');
69
+    }
70
+
71
+    protected function addressString(): Attribute
72
+    {
73
+        return Attribute::get(function () {
74
+            $street = array_filter([
75
+                $this->address_line_1,
76
+                $this->address_line_2,
77
+            ]);
78
+
79
+            return array_filter([
80
+                implode(', ', $street), // Street 1 & 2 on same line if both exist
81
+                implode(', ', array_filter([
82
+                    $this->city,
83
+                    $this->state->state_code,
84
+                    $this->postal_code,
85
+                ])),
86
+            ]);
87
+        });
88
+    }
44
 }
89
 }

+ 13
- 4
app/Providers/MacroServiceProvider.php View File

189
             return $this;
189
             return $this;
190
         });
190
         });
191
 
191
 
192
-        TextColumn::macro('currencyWithConversion', function (string | Closure | null $currency = null): static {
192
+        TextColumn::macro('currencyWithConversion', function (string | Closure | null $currency = null, ?bool $convertFromCents = null): static {
193
             $currency ??= CurrencyAccessor::getDefaultCurrency();
193
             $currency ??= CurrencyAccessor::getDefaultCurrency();
194
+            $convertFromCents ??= false;
194
 
195
 
195
-            $this->formatStateUsing(static function (TextColumn $column, $state) use ($currency): ?string {
196
+            $this->formatStateUsing(static function (TextColumn $column, $state) use ($currency, $convertFromCents): ?string {
196
                 if (blank($state)) {
197
                 if (blank($state)) {
197
                     return null;
198
                     return null;
198
                 }
199
                 }
199
 
200
 
200
                 $currency = $column->evaluate($currency);
201
                 $currency = $column->evaluate($currency);
201
 
202
 
203
+                if ($convertFromCents) {
204
+                    return CurrencyConverter::formatCentsToMoney($state, $currency);
205
+                }
206
+
202
                 return CurrencyConverter::formatToMoney($state, $currency);
207
                 return CurrencyConverter::formatToMoney($state, $currency);
203
             });
208
             });
204
 
209
 
205
-            $this->description(static function (TextColumn $column, $state) use ($currency): ?string {
210
+            $this->description(static function (TextColumn $column, $state) use ($currency, $convertFromCents): ?string {
206
                 if (blank($state)) {
211
                 if (blank($state)) {
207
                     return null;
212
                     return null;
208
                 }
213
                 }
214
                     return null;
219
                     return null;
215
                 }
220
                 }
216
 
221
 
217
-                $balanceInCents = CurrencyConverter::convertToCents($state, $oldCurrency);
222
+                if ($convertFromCents) {
223
+                    $balanceInCents = $state;
224
+                } else {
225
+                    $balanceInCents = CurrencyConverter::convertToCents($state, $oldCurrency);
226
+                }
218
 
227
 
219
                 $convertedBalanceInCents = CurrencyConverter::convertBalance($balanceInCents, $oldCurrency, $newCurrency);
228
                 $convertedBalanceInCents = CurrencyConverter::convertBalance($balanceInCents, $oldCurrency, $newCurrency);
220
 
229
 

+ 6
- 6
composer.lock View File

497
         },
497
         },
498
         {
498
         {
499
             "name": "aws/aws-sdk-php",
499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.336.15",
500
+            "version": "3.337.0",
501
             "source": {
501
             "source": {
502
                 "type": "git",
502
                 "type": "git",
503
                 "url": "https://github.com/aws/aws-sdk-php.git",
503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "f8028ef4b8dcb0acfe86c33e207fd3cb0b9cbf3b"
504
+                "reference": "7e40cecb3ce66749bbd5eaa9f370de48c16acd6c"
505
             },
505
             },
506
             "dist": {
506
             "dist": {
507
                 "type": "zip",
507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f8028ef4b8dcb0acfe86c33e207fd3cb0b9cbf3b",
509
-                "reference": "f8028ef4b8dcb0acfe86c33e207fd3cb0b9cbf3b",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7e40cecb3ce66749bbd5eaa9f370de48c16acd6c",
509
+                "reference": "7e40cecb3ce66749bbd5eaa9f370de48c16acd6c",
510
                 "shasum": ""
510
                 "shasum": ""
511
             },
511
             },
512
             "require": {
512
             "require": {
589
             "support": {
589
             "support": {
590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
592
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.336.15"
592
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.337.0"
593
             },
593
             },
594
-            "time": "2025-01-14T19:03:58+00:00"
594
+            "time": "2025-01-15T19:58:52+00:00"
595
         },
595
         },
596
         {
596
         {
597
             "name": "aws/aws-sdk-php-laravel",
597
             "name": "aws/aws-sdk-php-laravel",

+ 1
- 1
database/factories/Common/AddressFactory.php View File

31
             'address_line_1' => $this->faker->streetAddress,
31
             'address_line_1' => $this->faker->streetAddress,
32
             'address_line_2' => $this->faker->streetAddress,
32
             'address_line_2' => $this->faker->streetAddress,
33
             'city' => $this->faker->city,
33
             'city' => $this->faker->city,
34
-            'state' => $this->faker->state('US'),
34
+            'state_id' => $this->faker->state('US'),
35
             'postal_code' => $this->faker->postcode,
35
             'postal_code' => $this->faker->postcode,
36
             'country' => 'US',
36
             'country' => 'US',
37
             'notes' => $this->faker->sentence,
37
             'notes' => $this->faker->sentence,

+ 2
- 1
database/migrations/2024_11_19_225812_create_addresses_table.php View File

14
         Schema::create('addresses', function (Blueprint $table) {
14
         Schema::create('addresses', function (Blueprint $table) {
15
             $table->id();
15
             $table->id();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
+            $table->foreignId('parent_address_id')->nullable()->constrained('addresses')->nullOnDelete();
17
             $table->morphs('addressable');
18
             $table->morphs('addressable');
18
             $table->string('type'); // billing, shipping, etc.
19
             $table->string('type'); // billing, shipping, etc.
19
             $table->string('recipient')->nullable();
20
             $table->string('recipient')->nullable();
21
             $table->string('address_line_1');
22
             $table->string('address_line_1');
22
             $table->string('address_line_2')->nullable();
23
             $table->string('address_line_2')->nullable();
23
             $table->string('city');
24
             $table->string('city');
24
-            $table->string('state')->nullable();
25
+            $table->smallInteger('state_id')->nullable();
25
             $table->string('postal_code')->nullable();
26
             $table->string('postal_code')->nullable();
26
             $table->string('country')->nullable();
27
             $table->string('country')->nullable();
27
             $table->text('notes')->nullable();
28
             $table->text('notes')->nullable();

+ 3
- 3
package-lock.json View File

1235
             "license": "MIT"
1235
             "license": "MIT"
1236
         },
1236
         },
1237
         "node_modules/electron-to-chromium": {
1237
         "node_modules/electron-to-chromium": {
1238
-            "version": "1.5.82",
1239
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.82.tgz",
1240
-            "integrity": "sha512-Zq16uk1hfQhyGx5GpwPAYDwddJuSGhtRhgOA2mCxANYaDT79nAeGnaXogMGng4KqLaJUVnOnuL0+TDop9nLOiA==",
1238
+            "version": "1.5.83",
1239
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.83.tgz",
1240
+            "integrity": "sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==",
1241
             "dev": true,
1241
             "dev": true,
1242
             "license": "ISC"
1242
             "license": "ISC"
1243
         },
1243
         },

+ 2
- 2
resources/views/components/report-summary-section.blade.php View File

20
                         <strong
20
                         <strong
21
                             @class([
21
                             @class([
22
                                 'text-lg',
22
                                 'text-lg',
23
-                                'text-green-700 dark:text-green-500' => $isTargetLabel && $isPositive,
24
-                                'text-danger-700 dark:text-danger-500' => $isTargetLabel && ! $isPositive,
23
+                                'text-success-700 dark:text-success-400' => $isTargetLabel && $isPositive,
24
+                                'text-danger-700 dark:text-danger-400' => $isTargetLabel && ! $isPositive,
25
                             ])
25
                             ])
26
                         >
26
                         >
27
                             {{ $summary['value'] }}
27
                             {{ $summary['value'] }}

Loading…
Cancel
Save