Andrew Wallo 8 个月前
父节点
当前提交
574d50b234

+ 24
- 39
app/Filament/Company/Clusters/Settings/Pages/CompanyProfile.php 查看文件

@@ -4,8 +4,7 @@ namespace App\Filament\Company\Clusters\Settings\Pages;
4 4
 
5 5
 use App\Enums\Setting\EntityType;
6 6
 use App\Filament\Company\Clusters\Settings;
7
-use App\Models\Locale\City;
8
-use App\Models\Locale\Country;
7
+use App\Filament\Forms\Components\CountrySelect;
9 8
 use App\Models\Locale\State;
10 9
 use App\Models\Setting\CompanyProfile as CompanyProfileModel;
11 10
 use App\Utilities\Localization\Timezone;
@@ -14,12 +13,12 @@ use Filament\Actions\ActionGroup;
14 13
 use Filament\Forms\Components\Component;
15 14
 use Filament\Forms\Components\FileUpload;
16 15
 use Filament\Forms\Components\Group;
16
+use Filament\Forms\Components\Hidden;
17 17
 use Filament\Forms\Components\Section;
18 18
 use Filament\Forms\Components\Select;
19 19
 use Filament\Forms\Components\TextInput;
20 20
 use Filament\Forms\Form;
21 21
 use Filament\Forms\Get;
22
-use Filament\Forms\Set;
23 22
 use Filament\Notifications\Notification;
24 23
 use Filament\Pages\Concerns\InteractsWithFormActions;
25 24
 use Filament\Pages\Page;
@@ -95,18 +94,7 @@ class CompanyProfile extends Page
95 94
             return;
96 95
         }
97 96
 
98
-        $countryChanged = $this->record->wasChanged('country');
99
-        $stateChanged = $this->record->wasChanged('state_id');
100
-
101 97
         $this->getSavedNotification()->send();
102
-
103
-        if ($countryChanged || $stateChanged) {
104
-            if ($countryChanged) {
105
-                $this->updateTimezone($this->record->country);
106
-            }
107
-
108
-            $this->getTimezoneChangeNotification()->send();
109
-        }
110 98
     }
111 99
 
112 100
     protected function updateTimezone(string $countryCode): void
@@ -198,39 +186,36 @@ class CompanyProfile extends Page
198 186
 
199 187
     protected function getLocationDetailsSection(): Component
200 188
     {
201
-        return Section::make('Location Details')
189
+        return Section::make('Address Information')
190
+            ->relationship('address')
202 191
             ->schema([
203
-                Select::make('country')
204
-                    ->searchable()
205
-                    ->localizeLabel()
206
-                    ->live()
207
-                    ->options(Country::getAvailableCountryOptions())
208
-                    ->afterStateUpdated(static function (Set $set) {
209
-                        $set('state_id', null);
210
-                        $set('city_id', null);
211
-                    })
192
+                Hidden::make('type')
193
+                    ->default('general'),
194
+                TextInput::make('address_line_1')
195
+                    ->label('Address Line 1')
196
+                    ->required()
197
+                    ->maxLength(255),
198
+                TextInput::make('address_line_2')
199
+                    ->label('Address Line 2')
200
+                    ->maxLength(255),
201
+                CountrySelect::make('country')
202
+                    ->clearStateField()
212 203
                     ->required(),
213 204
                 Select::make('state_id')
214 205
                     ->localizeLabel('State / Province')
215 206
                     ->searchable()
216
-                    ->live()
217 207
                     ->options(static fn (Get $get) => State::getStateOptions($get('country')))
218
-                    ->afterStateUpdated(static fn (Set $set) => $set('city_id', null))
219 208
                     ->nullable(),
220
-                TextInput::make('address')
221
-                    ->localizeLabel('Street Address')
222
-                    ->maxLength(255)
223
-                    ->nullable(),
224
-                Select::make('city_id')
209
+                TextInput::make('city')
225 210
                     ->localizeLabel('City / Town')
226
-                    ->searchable()
227
-                    ->options(static fn (Get $get) => City::getCityOptions($get('country'), $get('state_id')))
228
-                    ->nullable(),
229
-                TextInput::make('zip_code')
230
-                    ->localizeLabel('Zip / Postal Code')
231
-                    ->maxLength(20)
232
-                    ->nullable(),
233
-            ])->columns();
211
+                    ->required()
212
+                    ->maxLength(255),
213
+                TextInput::make('postal_code')
214
+                    ->label('Postal Code / Zip Code')
215
+                    ->required()
216
+                    ->maxLength(255),
217
+            ])
218
+            ->columns(2);
234 219
     }
235 220
 
236 221
     protected function getLegalAndComplianceSection(): Component

+ 1
- 1
app/Filament/Company/Clusters/Settings/Pages/Localization.php 查看文件

@@ -130,7 +130,7 @@ class Localization extends Page
130 130
                 Select::make('timezone')
131 131
                     ->softRequired()
132 132
                     ->localizeLabel()
133
-                    ->options(Timezone::getTimezoneOptions(CompanyProfileModel::first()->country))
133
+                    ->options(Timezone::getTimezoneOptions(CompanyProfileModel::first()->address->country))
134 134
                     ->searchable(),
135 135
             ])->columns();
136 136
     }

+ 9
- 1
app/Filament/Company/Pages/CreateCompany.php 查看文件

@@ -2,6 +2,7 @@
2 2
 
3 3
 namespace App\Filament\Company\Pages;
4 4
 
5
+use App\Enums\Common\AddressType;
5 6
 use App\Enums\Setting\EntityType;
6 7
 use App\Models\Company;
7 8
 use App\Models\Locale\Country;
@@ -66,6 +67,8 @@ class CreateCompany extends FilamentCreateCompany
66 67
                     ->live()
67 68
                     ->searchable()
68 69
                     ->options(Country::getAvailableCountryOptions())
70
+                    ->getSearchResultsUsing(fn (string $search): array => Country::getSearchResultsUsing($search))
71
+                    ->getOptionLabelUsing(fn ($value): ?string => Country::find($value)?->name . ' ' . Country::find($value)?->flag)
69 72
                     ->softRequired(),
70 73
                 Select::make('locale.language')
71 74
                     ->label('Language')
@@ -101,9 +104,14 @@ class CreateCompany extends FilamentCreateCompany
101 104
                 'personal_company' => $personalCompany,
102 105
             ]);
103 106
 
104
-            $company->profile()->create([
107
+            $profile = $company->profile()->create([
105 108
                 'email' => $data['profile']['email'],
106 109
                 'entity_type' => $data['profile']['entity_type'],
110
+            ]);
111
+
112
+            $profile->address()->create([
113
+                'company_id' => $company->id,
114
+                'type' => AddressType::General,
107 115
                 'country' => $data['profile']['country'],
108 116
             ]);
109 117
 

+ 12
- 9
app/Filament/Company/Resources/Purchases/VendorResource.php 查看文件

@@ -5,13 +5,16 @@ namespace App\Filament\Company\Resources\Purchases;
5 5
 use App\Enums\Common\ContractorType;
6 6
 use App\Enums\Common\VendorType;
7 7
 use App\Filament\Company\Resources\Purchases\VendorResource\Pages;
8
+use App\Filament\Forms\Components\CountrySelect;
8 9
 use App\Filament\Forms\Components\CreateCurrencySelect;
9 10
 use App\Filament\Forms\Components\CustomSection;
10 11
 use App\Filament\Forms\Components\PhoneBuilder;
11 12
 use App\Filament\Tables\Columns;
12 13
 use App\Models\Common\Vendor;
14
+use App\Models\Locale\State;
13 15
 use Filament\Forms;
14 16
 use Filament\Forms\Form;
17
+use Filament\Forms\Get;
15 18
 use Filament\Resources\Resource;
16 19
 use Filament\Tables;
17 20
 use Filament\Tables\Table;
@@ -149,22 +152,22 @@ class VendorResource extends Resource
149 152
                         Forms\Components\TextInput::make('address_line_2')
150 153
                             ->label('Address Line 2')
151 154
                             ->maxLength(255),
155
+                        CountrySelect::make('country')
156
+                            ->clearStateField()
157
+                            ->required(),
158
+                        Forms\Components\Select::make('state_id')
159
+                            ->localizeLabel('State / Province')
160
+                            ->searchable()
161
+                            ->options(static fn (Get $get) => State::getStateOptions($get('country')))
162
+                            ->nullable(),
152 163
                         Forms\Components\TextInput::make('city')
153
-                            ->label('City')
154
-                            ->required()
155
-                            ->maxLength(255),
156
-                        Forms\Components\TextInput::make('state')
157
-                            ->label('State')
164
+                            ->localizeLabel('City / Town')
158 165
                             ->required()
159 166
                             ->maxLength(255),
160 167
                         Forms\Components\TextInput::make('postal_code')
161 168
                             ->label('Postal Code / Zip Code')
162 169
                             ->required()
163 170
                             ->maxLength(255),
164
-                        Forms\Components\TextInput::make('country')
165
-                            ->label('Country')
166
-                            ->required()
167
-                            ->maxLength(255),
168 171
                     ])
169 172
                     ->columns(2),
170 173
             ]);

+ 5
- 17
app/Filament/Company/Resources/Sales/ClientResource.php 查看文件

@@ -3,13 +3,13 @@
3 3
 namespace App\Filament\Company\Resources\Sales;
4 4
 
5 5
 use App\Filament\Company\Resources\Sales\ClientResource\Pages;
6
+use App\Filament\Forms\Components\CountrySelect;
6 7
 use App\Filament\Forms\Components\CreateCurrencySelect;
7 8
 use App\Filament\Forms\Components\CustomSection;
8 9
 use App\Filament\Forms\Components\PhoneBuilder;
9 10
 use App\Filament\Tables\Columns;
10 11
 use App\Models\Common\Address;
11 12
 use App\Models\Common\Client;
12
-use App\Models\Locale\Country;
13 13
 use App\Models\Locale\State;
14 14
 use App\Utilities\Currency\CurrencyConverter;
15 15
 use Filament\Forms;
@@ -192,14 +192,8 @@ class ClientResource extends Resource
192 192
                                 Forms\Components\TextInput::make('address_line_2')
193 193
                                     ->label('Address Line 2')
194 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
-                                    })
195
+                                CountrySelect::make('country')
196
+                                    ->clearStateField()
203 197
                                     ->required(),
204 198
                                 Forms\Components\Select::make('state_id')
205 199
                                     ->localizeLabel('State / Province')
@@ -275,14 +269,8 @@ class ClientResource extends Resource
275 269
                                         Forms\Components\TextInput::make('address_line_2')
276 270
                                             ->label('Address Line 2')
277 271
                                             ->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
-                                            })
272
+                                        CountrySelect::make('country')
273
+                                            ->clearStateField()
286 274
                                             ->required(),
287 275
                                         Forms\Components\Select::make('state_id')
288 276
                                             ->localizeLabel('State / Province')

+ 49
- 0
app/Filament/Forms/Components/CountrySelect.php 查看文件

@@ -0,0 +1,49 @@
1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use App\Models\Locale\Country;
6
+use Filament\Forms\Components\Select;
7
+use Filament\Forms\Set;
8
+
9
+class CountrySelect extends Select
10
+{
11
+    protected ?string $stateFieldName = null;
12
+
13
+    protected function setUp(): void
14
+    {
15
+        parent::setUp();
16
+
17
+        $this
18
+            ->localizeLabel('Country')
19
+            ->searchable()
20
+            ->options($options = Country::getAvailableCountryOptions())
21
+            ->getSearchResultsUsing(static fn (string $search): array => Country::getSearchResultsUsing($search))
22
+            ->getOptionLabelUsing(static fn (string $value): ?string => $options[$value] ?? $value);
23
+
24
+        $this->afterStateUpdated(function (self $component, Set $set) {
25
+            if ($component->shouldClearStateField()) {
26
+                $set($component->getStateFieldName(), null);
27
+            }
28
+        });
29
+    }
30
+
31
+    public function clearStateField(string $fieldName = 'state_id'): static
32
+    {
33
+        $this->stateFieldName = $fieldName;
34
+
35
+        $this->live();
36
+
37
+        return $this;
38
+    }
39
+
40
+    public function getStateFieldName(): ?string
41
+    {
42
+        return $this->stateFieldName;
43
+    }
44
+
45
+    public function shouldClearStateField(): bool
46
+    {
47
+        return (bool) $this->stateFieldName;
48
+    }
49
+}

+ 0
- 45
app/Filament/Forms/Components/LabeledField.php 查看文件

@@ -1,45 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Forms\Components;
4
-
5
-use Closure;
6
-use Filament\Forms\Components\Component;
7
-use Illuminate\Contracts\Support\Htmlable;
8
-
9
-class LabeledField extends Component
10
-{
11
-    protected string $view = 'filament.forms.components.labeled-field';
12
-
13
-    protected string | Htmlable | Closure | null $prefixLabel = null;
14
-
15
-    protected string | Htmlable | Closure | null $suffixLabel = null;
16
-
17
-    public static function make(): static
18
-    {
19
-        return app(static::class);
20
-    }
21
-
22
-    public function prefix(string | Htmlable | Closure | null $label): static
23
-    {
24
-        $this->prefixLabel = $label;
25
-
26
-        return $this;
27
-    }
28
-
29
-    public function suffix(string | Htmlable | Closure | null $label): static
30
-    {
31
-        $this->suffixLabel = $label;
32
-
33
-        return $this;
34
-    }
35
-
36
-    public function getPrefixLabel(): string | Htmlable | null
37
-    {
38
-        return $this->evaluate($this->prefixLabel);
39
-    }
40
-
41
-    public function getSuffixLabel(): string | Htmlable | null
42
-    {
43
-        return $this->evaluate($this->suffixLabel);
44
-    }
45
-}

+ 0
- 165
app/Filament/Forms/Components/LineItemRepeater.php 查看文件

@@ -1,165 +0,0 @@
1
-<?php
2
-
3
-namespace App\Filament\Forms\Components;
4
-
5
-use Awcodes\TableRepeater\Components\TableRepeater;
6
-use Closure;
7
-use Filament\Forms\ComponentContainer;
8
-use Filament\Forms\Components\Component;
9
-
10
-class LineItemRepeater extends TableRepeater
11
-{
12
-    protected array | Closure $nestedSchema = [];
13
-
14
-    protected ?string $nestedColumn = null;
15
-
16
-    /**
17
-     * Set nested schema and optionally the column it belongs to.
18
-     *
19
-     * @param  array<Component> | Closure  $components
20
-     */
21
-    public function withNestedSchema(array | Closure $components, ?string $underColumn = null): static
22
-    {
23
-        $this->nestedSchema = $components;
24
-        $this->nestedColumn = $underColumn;
25
-
26
-        return $this;
27
-    }
28
-
29
-    /**
30
-     * Get the nested schema.
31
-     *
32
-     * @return array<Component>
33
-     */
34
-    public function getNestedSchema(): array
35
-    {
36
-        return $this->evaluate($this->nestedSchema);
37
-    }
38
-
39
-    /**
40
-     * Get the column under which the nested schema should be rendered.
41
-     */
42
-    public function getNestedColumn(): ?string
43
-    {
44
-        return $this->nestedColumn;
45
-    }
46
-
47
-    /**
48
-     * Determine if there is a nested schema defined.
49
-     */
50
-    public function hasNestedSchema(): bool
51
-    {
52
-        return ! empty($this->getNestedSchema());
53
-    }
54
-
55
-    public function getNestedSchemaMap(): array
56
-    {
57
-        return collect($this->getNestedSchema())
58
-            ->keyBy(fn ($component) => $component->getKey())
59
-            ->all();
60
-    }
61
-
62
-    /**
63
-     * Get all child components, including nested schema.
64
-     *
65
-     * @return array<Component>
66
-     */
67
-    public function getChildComponents(): array
68
-    {
69
-        $components = parent::getChildComponents();
70
-
71
-        if ($this->hasNestedSchema()) {
72
-            $components = array_merge($components, $this->getNestedSchema());
73
-        }
74
-
75
-        return $components;
76
-    }
77
-
78
-    public function getChildComponentContainers(bool $withHidden = false): array
79
-    {
80
-        if ((! $withHidden) && $this->isHidden()) {
81
-            return [];
82
-        }
83
-
84
-        $relationship = $this->getRelationship();
85
-
86
-        $records = $relationship ? $this->getCachedExistingRecords() : null;
87
-
88
-        $containers = [];
89
-
90
-        foreach ($this->getState() ?? [] as $itemKey => $itemData) {
91
-            $containers[$itemKey] = $this
92
-                ->getChildComponentContainer()
93
-                ->statePath($itemKey)
94
-                ->model($relationship ? $records[$itemKey] ?? $this->getRelatedModel() : null)
95
-                ->inlineLabel(false)
96
-                ->getClone();
97
-        }
98
-
99
-        return $containers;
100
-    }
101
-
102
-    public function getChildComponentContainersWithoutNestedSchema(bool $withHidden = false): array
103
-    {
104
-        if ((! $withHidden) && $this->isHidden()) {
105
-            return [];
106
-        }
107
-
108
-        $relationship = $this->getRelationship();
109
-        $records = $relationship ? $this->getCachedExistingRecords() : null;
110
-
111
-        $containers = [];
112
-
113
-        $childComponentsWithoutNestedSchema = $this->getChildComponentsWithoutNestedSchema();
114
-
115
-        foreach ($this->getState() ?? [] as $itemKey => $itemData) {
116
-            $containers[$itemKey] = ComponentContainer::make($this->getLivewire())
117
-                ->parentComponent($this)
118
-                ->statePath($itemKey)
119
-                ->model($relationship ? $records[$itemKey] ?? $this->getRelatedModel() : null)
120
-                ->components($childComponentsWithoutNestedSchema)
121
-                ->inlineLabel(false)
122
-                ->getClone();
123
-        }
124
-
125
-        return $containers;
126
-    }
127
-
128
-    public function getChildComponentContainer($key = null): ComponentContainer
129
-    {
130
-        if (filled($key) && array_key_exists($key, $containers = $this->getChildComponentContainers())) {
131
-            return $containers[$key];
132
-        }
133
-
134
-        return ComponentContainer::make($this->getLivewire())
135
-            ->parentComponent($this)
136
-            ->components($this->getChildComponents());
137
-    }
138
-
139
-    public function getChildComponentsWithoutNestedSchema(): array
140
-    {
141
-        // Fetch the nested schema components.
142
-        $nestedSchema = $this->getNestedSchema();
143
-
144
-        // Filter out the nested schema components.
145
-        return array_filter($this->getChildComponents(), function ($component) use ($nestedSchema) {
146
-            return ! in_array($component, $nestedSchema, true);
147
-        });
148
-    }
149
-
150
-    public function getNestedComponents(): array
151
-    {
152
-        // Fetch the nested schema components.
153
-        $nestedSchema = $this->getNestedSchema();
154
-
155
-        // Separate and return only the nested schema components.
156
-        return array_filter($this->getChildComponents(), function ($component) use ($nestedSchema) {
157
-            return in_array($component, $nestedSchema, true);
158
-        });
159
-    }
160
-
161
-    public function getView(): string
162
-    {
163
-        return 'filament.forms.components.line-item-repeater';
164
-    }
165
-}

+ 36
- 7
app/Models/Locale/Country.php 查看文件

@@ -2,7 +2,7 @@
2 2
 
3 3
 namespace App\Models\Locale;
4 4
 
5
-use App\Models\Setting\CompanyProfile;
5
+use App\Models\Common\Address;
6 6
 use Illuminate\Database\Eloquent\Casts\Attribute;
7 7
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
8 8
 use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -49,9 +49,9 @@ class Country extends Model
49 49
         return $this->belongsTo(Currency::class, 'currency_code', 'code');
50 50
     }
51 51
 
52
-    public function profiles(): HasMany
52
+    public function addresses(): HasMany
53 53
     {
54
-        return $this->hasMany(CompanyProfile::class, 'country', 'id');
54
+        return $this->hasMany(Address::class, 'country', 'id');
55 55
     }
56 56
 
57 57
     public function states(): HasMany
@@ -80,14 +80,43 @@ class Country extends Model
80 80
 
81 81
     public static function getAllCountryCodes(): Collection
82 82
     {
83
-        return self::all()->pluck('id');
83
+        return self::query()
84
+            ->select('id')
85
+            ->pluck('id');
84 86
     }
85 87
 
86 88
     public static function getAvailableCountryOptions(): array
87 89
     {
88
-        return self::all()->mapWithKeys(static function ($country): array {
89
-            return [$country->id => $country->name . ' ' . $country->flag];
90
-        })->toArray();
90
+        return self::query()
91
+            ->select(['id', 'name', 'flag'])
92
+            ->orderBy('name')
93
+            ->get()
94
+            ->mapWithKeys(static fn ($country) => [
95
+                $country->id => $country->name . ' ' . $country->flag,
96
+            ])
97
+            ->toArray();
98
+    }
99
+
100
+    public static function getSearchResultsUsing(string $search): array
101
+    {
102
+        return self::query()
103
+            ->select(['id', 'name', 'flag'])
104
+            ->whereLike('name', "%{$search}%")
105
+            ->orWhereLike('id', "%{$search}%")
106
+            ->orderByRaw('
107
+                CASE
108
+                    WHEN id = ? THEN 1
109
+                    WHEN id LIKE ? THEN 2
110
+                    WHEN name LIKE ? THEN 3
111
+                    ELSE 4
112
+                END
113
+            ', [$search, $search . '%', $search . '%'])
114
+            ->limit(50)
115
+            ->get()
116
+            ->mapWithKeys(static fn ($country) => [
117
+                $country->id => $country->name . ' ' . $country->flag,
118
+            ])
119
+            ->toArray();
91 120
     }
92 121
 
93 122
     public static function getLanguagesByCountryCode(?string $code = null): array

+ 4
- 26
app/Models/Setting/CompanyProfile.php 查看文件

@@ -5,15 +5,13 @@ namespace App\Models\Setting;
5 5
 use App\Concerns\Blamable;
6 6
 use App\Concerns\CompanyOwned;
7 7
 use App\Enums\Setting\EntityType;
8
-use App\Models\Locale\City;
9
-use App\Models\Locale\Country;
10
-use App\Models\Locale\State;
8
+use App\Models\Common\Address;
11 9
 use Database\Factories\Setting\CompanyProfileFactory;
12 10
 use Illuminate\Database\Eloquent\Casts\Attribute;
13 11
 use Illuminate\Database\Eloquent\Factories\Factory;
14 12
 use Illuminate\Database\Eloquent\Factories\HasFactory;
15 13
 use Illuminate\Database\Eloquent\Model;
16
-use Illuminate\Database\Eloquent\Relations\BelongsTo;
14
+use Illuminate\Database\Eloquent\Relations\MorphOne;
17 15
 use Illuminate\Support\Facades\Storage;
18 16
 
19 17
 class CompanyProfile extends Model
@@ -27,11 +25,6 @@ class CompanyProfile extends Model
27 25
     protected $fillable = [
28 26
         'company_id',
29 27
         'logo',
30
-        'address',
31
-        'city_id',
32
-        'zip_code',
33
-        'state_id',
34
-        'country',
35 28
         'phone_number',
36 29
         'email',
37 30
         'tax_id',
@@ -59,24 +52,9 @@ class CompanyProfile extends Model
59 52
         });
60 53
     }
61 54
 
62
-    public function country(): BelongsTo
55
+    public function address(): MorphOne
63 56
     {
64
-        return $this->belongsTo(Country::class, 'country', 'id');
65
-    }
66
-
67
-    public function city(): BelongsTo
68
-    {
69
-        return $this->belongsTo(City::class, 'city_id', 'id');
70
-    }
71
-
72
-    public function state(): BelongsTo
73
-    {
74
-        return $this->belongsTo(State::class, 'state_id', 'id');
75
-    }
76
-
77
-    public function getCountryName(): string
78
-    {
79
-        return Country::findByIsoCode2($this->country)?->name ?? '';
57
+        return $this->morphOne(Address::class, 'addressable');
80 58
     }
81 59
 
82 60
     protected static function newFactory(): Factory

+ 1
- 4
config/aws.php 查看文件

@@ -16,10 +16,7 @@ return [
16 16
     | http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/configuration.html
17 17
     |
18 18
     */
19
-    'credentials' => [
20
-        'key' => env('AWS_ACCESS_KEY_ID', ''),
21
-        'secret' => env('AWS_SECRET_ACCESS_KEY', ''),
22
-    ],
19
+    'credentials' => false,
23 20
     'region' => env('AWS_REGION', 'us-east-1'),
24 21
     'version' => 'latest',
25 22
     'ua_append' => [

+ 2
- 2
database/factories/CompanyFactory.php 查看文件

@@ -42,7 +42,7 @@ class CompanyFactory extends Factory
42 42
     public function withCompanyProfile(): self
43 43
     {
44 44
         return $this->afterCreating(function (Company $company) {
45
-            CompanyProfile::factory()->forCompany($company)->withCountry('US')->create();
45
+            CompanyProfile::factory()->forCompany($company)->withAddress()->create();
46 46
         });
47 47
     }
48 48
 
@@ -52,7 +52,7 @@ class CompanyFactory extends Factory
52 52
     public function withCompanyDefaults(): self
53 53
     {
54 54
         return $this->afterCreating(function (Company $company) {
55
-            $countryCode = $company->profile->country;
55
+            $countryCode = $company->profile->address->country;
56 56
             $companyDefaultService = app(CompanyDefaultService::class);
57 57
             $companyDefaultService->createCompanyDefaults($company, $company->owner, 'USD', $countryCode, 'en');
58 58
         });

+ 7
- 16
database/factories/Setting/CompanyProfileFactory.php 查看文件

@@ -4,6 +4,7 @@ namespace Database\Factories\Setting;
4 4
 
5 5
 use App\Enums\Setting\EntityType;
6 6
 use App\Faker\State;
7
+use App\Models\Common\Address;
7 8
 use App\Models\Company;
8 9
 use App\Models\Setting\CompanyProfile;
9 10
 use Illuminate\Database\Eloquent\Factories\Factory;
@@ -25,28 +26,13 @@ class CompanyProfileFactory extends Factory
25 26
      */
26 27
     public function definition(): array
27 28
     {
28
-        $countryCode = $this->faker->countryCode;
29
-
30 29
         return [
31
-            'address' => $this->faker->streetAddress,
32
-            'zip_code' => $this->faker->postcode,
33
-            'state_id' => $this->faker->state($countryCode),
34
-            'country' => $countryCode,
35
-            'phone_number' => $this->faker->phoneNumberForCountryCode($countryCode),
30
+            'phone_number' => $this->faker->phoneNumber,
36 31
             'email' => $this->faker->email,
37 32
             'entity_type' => $this->faker->randomElement(EntityType::class),
38 33
         ];
39 34
     }
40 35
 
41
-    public function withCountry(string $code): self
42
-    {
43
-        return $this->state([
44
-            'country' => $code,
45
-            'state_id' => $this->faker->state($code),
46
-            'phone_number' => $this->faker->phoneNumberForCountryCode($code),
47
-        ]);
48
-    }
49
-
50 36
     public function forCompany(Company $company): self
51 37
     {
52 38
         return $this->state([
@@ -55,4 +41,9 @@ class CompanyProfileFactory extends Factory
55 41
             'updated_by' => $company->owner->id,
56 42
         ]);
57 43
     }
44
+
45
+    public function withAddress(): self
46
+    {
47
+        return $this->has(Address::factory()->general());
48
+    }
58 49
 }

+ 4
- 4
database/migrations/2023_09_03_100000_create_accounting_tables.php 查看文件

@@ -98,10 +98,10 @@ return new class extends Migration
98 98
      */
99 99
     public function down(): void
100 100
     {
101
-        Schema::dropIfExists('institutions');
102
-        Schema::dropIfExists('account_subtypes');
103
-        Schema::dropIfExists('accounts');
104
-        Schema::dropIfExists('bank_accounts');
105 101
         Schema::dropIfExists('connected_bank_accounts');
102
+        Schema::dropIfExists('bank_accounts');
103
+        Schema::dropIfExists('accounts');
104
+        Schema::dropIfExists('account_subtypes');
105
+        Schema::dropIfExists('institutions');
106 106
     }
107 107
 };

+ 0
- 5
database/migrations/2023_09_14_034800_create_company_profiles_table.php 查看文件

@@ -15,11 +15,6 @@ return new class extends Migration
15 15
             $table->id();
16 16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17 17
             $table->string('logo')->nullable();
18
-            $table->string('address', 255)->nullable();
19
-            $table->unsignedMediumInteger('city_id')->nullable()->index();
20
-            $table->string('zip_code', 20)->nullable();
21
-            $table->unsignedSmallInteger('state_id')->nullable()->index();
22
-            $table->string('country')->nullable();
23 18
             $table->string('phone_number', 30)->nullable();
24 19
             $table->string('email', 255)->nullable();
25 20
             $table->string('tax_id', 50)->nullable();

+ 3
- 3
database/migrations/2024_11_19_225812_create_addresses_table.php 查看文件

@@ -19,12 +19,12 @@ return new class extends Migration
19 19
             $table->string('type'); // billing, shipping, etc.
20 20
             $table->string('recipient')->nullable();
21 21
             $table->string('phone')->nullable();
22
-            $table->string('address_line_1');
22
+            $table->string('address_line_1')->nullable();
23 23
             $table->string('address_line_2')->nullable();
24
-            $table->string('city');
24
+            $table->string('city')->nullable();
25 25
             $table->smallInteger('state_id')->nullable();
26 26
             $table->string('postal_code')->nullable();
27
-            $table->string('country')->nullable();
27
+            $table->string('country');
28 28
             $table->text('notes')->nullable();
29 29
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
30 30
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();

+ 39
- 1
resources/data/lang/es.json 查看文件

@@ -185,5 +185,43 @@
185 185
     "Currency List": "Lista de divisas",
186 186
     "Available": "Disponible",
187 187
     "Parent Department": "Departamento de padres",
188
-    "Live Rate": "Tarifa en vivo"
188
+    "Live Rate": "Tarifa en vivo",
189
+    "Category": "Categoría",
190
+    "Configuration": "Configuración",
191
+    "Dates": "Fechas",
192
+    "Adjustment Details": "Detalles del ajuste",
193
+    "Adjustments": "Ajustes",
194
+    "Sellable Configuration": "Configuración apta para venta",
195
+    "Purchasable Configuration": "Configuración que se puede comprar",
196
+    "Sale Information": "Información de venta",
197
+    "Purchase Information": "Información de compra",
198
+    "Billing": "Facturación",
199
+    "Shipping": "Envío",
200
+    "General Information": "Información general",
201
+    "Primary Contact": "Contacto principal",
202
+    "Billing Address": "Dirección de facturación",
203
+    "Shipping Address": "Dirección de envío",
204
+    "Invoice Header": "Encabezado de factura",
205
+    "Invoice Details": "Detalles de la factura",
206
+    "Secondary Contacts": "Contactos secundarios",
207
+    "Edit": "Editar",
208
+    "Bill Details": "Detalles de la factura",
209
+    "Frequency": "Frecuencia",
210
+    "Scheduling": "Programación",
211
+    "Notes": "Notas",
212
+    "Create": "Crear",
213
+    "Invoice Footer": "Pie de página de la factura",
214
+    "Time Zone": "Zona horaria",
215
+    "Footer": "Pie de página",
216
+    "Estimate Header": "Encabezado de estimación",
217
+    "Terms": "Términos",
218
+    "Estimate Footer": "Estimar pie de página",
219
+    "Scheduling Form": "Formulario de programación",
220
+    "Default :Type :Category": "Predeterminado :Type :Category",
221
+    "Approve": "Aprobar",
222
+    "Dates & Time": "Fechas y hora",
223
+    "Schedule Bounds": "Límites de programación",
224
+    "Ending Balance": "Saldo final",
225
+    "Address Information": "Información de dirección",
226
+    "Estimate Details": "Detalles de la estimación"
189 227
 }

+ 0
- 35
resources/views/filament/forms/components/labeled-field.blade.php 查看文件

@@ -1,35 +0,0 @@
1
-@php
2
-    $prefixLabel = $getPrefixLabel();
3
-    $suffixLabel = $getSuffixLabel();
4
-
5
-    $childComponentContainer = $getChildComponentContainer();
6
-    $childComponents = $childComponentContainer->getComponents();
7
-@endphp
8
-
9
-<div
10
-    {{
11
-        $attributes->class([
12
-            'flex items-center gap-x-4',
13
-        ])
14
-    }}
15
->
16
-    @if($prefixLabel)
17
-        <span class="whitespace-nowrap text-sm font-medium leading-6 text-gray-950 dark:text-white">{{ $prefixLabel }}</span>
18
-    @endif
19
-
20
-    @foreach($childComponents as $component)
21
-        @if(count($component->getChildComponents()) > 1)
22
-            <div>
23
-                {{ $component }}
24
-            </div>
25
-        @else
26
-            <div class="min-w-28 [&_.fi-fo-field-wrp]:m-0 [&_.grid]:!grid-cols-1 [&_.sm\:grid-cols-3]:!grid-cols-1 [&_.sm\:col-span-2]:!col-span-1">
27
-                {{ $component }}
28
-            </div>
29
-        @endif
30
-    @endforeach
31
-
32
-    @if($suffixLabel)
33
-        <span class="whitespace-nowrap text-sm font-medium leading-6 text-gray-950 dark:text-white">{{ $suffixLabel }}</span>
34
-    @endif
35
-</div>

+ 0
- 251
resources/views/filament/forms/components/line-item-repeater.blade.php 查看文件

@@ -1,251 +0,0 @@
1
-@php
2
-    use Filament\Forms\Components\Actions\Action;
3
-    use Filament\Support\Enums\Alignment;
4
-    use Filament\Support\Enums\MaxWidth;
5
-
6
-    $containers = $getChildComponentContainersWithoutNestedSchema();
7
-
8
-    $addAction = $getAction($getAddActionName());
9
-    $cloneAction = $getAction($getCloneActionName());
10
-    $deleteAction = $getAction($getDeleteActionName());
11
-    $moveDownAction = $getAction($getMoveDownActionName());
12
-    $moveUpAction = $getAction($getMoveUpActionName());
13
-    $reorderAction = $getAction($getReorderActionName());
14
-    $isReorderableWithButtons = $isReorderableWithButtons();
15
-    $extraItemActions = $getExtraItemActions();
16
-    $extraActions = $getExtraActions();
17
-    $visibleExtraItemActions = [];
18
-    $visibleExtraActions = [];
19
-
20
-    $headers = $getHeaders();
21
-    $renderHeader = $shouldRenderHeader();
22
-    $stackAt = $getStackAt();
23
-    $hasContainers = count($containers) > 0;
24
-    $emptyLabel = $getEmptyLabel();
25
-    $streamlined = $isStreamlined();
26
-
27
-    $statePath = $getStatePath();
28
-
29
-    foreach ($extraActions as $extraAction) {
30
-        $visibleExtraActions = array_filter(
31
-            $extraActions,
32
-            fn (Action $action): bool => $action->isVisible(),
33
-        );
34
-    }
35
-
36
-    foreach ($extraItemActions as $extraItemAction) {
37
-        $visibleExtraItemActions = array_filter(
38
-            $extraItemActions,
39
-            fn (Action $action): bool => $action->isVisible(),
40
-        );
41
-    }
42
-
43
-    $hasActions = $reorderAction->isVisible()
44
-        || $cloneAction->isVisible()
45
-        || $deleteAction->isVisible()
46
-        || $moveUpAction->isVisible()
47
-        || $moveDownAction->isVisible()
48
-        || filled($visibleExtraItemActions);
49
-
50
-    $hasNestedSchema = $hasNestedSchema();
51
-
52
-    $totalColumns = count($headers) + ($hasActions ? 1 : 0);
53
-    $nestedColspan = 4; // Nested schema spans the last 3 columns.
54
-    $emptyColspan = $totalColumns - $nestedColspan;
55
-@endphp
56
-
57
-<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
58
-    <div
59
-        x-data="{}"
60
-        {{ $attributes->merge($getExtraAttributes())->class([
61
-            'table-repeater-component space-y-6 relative',
62
-            'streamlined' => $streamlined,
63
-            match ($stackAt) {
64
-                'sm', MaxWidth::Small => 'break-point-sm',
65
-                'lg', MaxWidth::Large => 'break-point-lg',
66
-                'xl', MaxWidth::ExtraLarge => 'break-point-xl',
67
-                '2xl', MaxWidth::TwoExtraLarge => 'break-point-2xl',
68
-                default => 'break-point-md',
69
-            }
70
-        ]) }}
71
-    >
72
-        @if (count($containers) || $emptyLabel !== false)
73
-            <div class="table-repeater-container rounded-xl relative ring-1 ring-gray-950/5 dark:ring-white/20">
74
-                <table class="w-full">
75
-                    <thead @class([
76
-                        'table-repeater-header-hidden sr-only' => ! $renderHeader,
77
-                        'table-repeater-header rounded-t-xl overflow-hidden border-b border-gray-950/5 dark:border-white/20' => $renderHeader,
78
-                    ])>
79
-                    <tr class="text-xs md:divide-x md:divide-gray-950/5 dark:md:divide-white/20">
80
-                        @foreach ($headers as $key => $header)
81
-                            <th
82
-                                @class([
83
-                                    'table-repeater-header-column p-2 font-medium first:rounded-tl-xl last:rounded-tr-xl bg-gray-100 dark:text-gray-300 dark:bg-gray-900/60',
84
-                                    match($header->getAlignment()) {
85
-                                      'center', Alignment::Center => 'text-center',
86
-                                      'right', 'end', Alignment::Right, Alignment::End => 'text-end',
87
-                                      default => 'text-start'
88
-                                    }
89
-                                ])
90
-                                style="width: {{ $header->getWidth() }}"
91
-                            >
92
-                                {{ $header->getLabel() }}
93
-                                @if ($header->isRequired())
94
-                                    <span class="whitespace-nowrap">
95
-                                        <sup class="font-medium text-danger-700 dark:text-danger-400">*</sup>
96
-                                    </span>
97
-                                @endif
98
-                            </th>
99
-                        @endforeach
100
-                        @if ($hasActions && count($containers))
101
-                            <th class="table-repeater-header-column w-px last:rounded-tr-xl p-2 bg-gray-100 dark:bg-gray-900/60">
102
-                                <span class="sr-only">
103
-                                    {{ trans('table-repeater::components.repeater.row_actions.label') }}
104
-                                </span>
105
-                            </th>
106
-                        @endif
107
-                    </tr>
108
-                    </thead>
109
-                    <tbody
110
-                        x-sortable
111
-                        wire:end.stop="{{ 'mountFormComponentAction(\'' . $statePath . '\', \'reorder\', { items: $event.target.sortable.toArray() })' }}"
112
-                        class="table-repeater-rows-wrapper divide-y divide-gray-950/5 dark:divide-white/20"
113
-                    >
114
-                    @if (count($containers))
115
-                        @foreach ($containers as $uuid => $row)
116
-                            @php
117
-                                $visibleExtraItemActions = array_filter(
118
-                                    $extraItemActions,
119
-                                    fn (Action $action): bool => $action(['item' => $uuid])->isVisible(),
120
-                                );
121
-                            @endphp
122
-                            <tr
123
-                                wire:key="{{ $this->getId() }}.{{ $row->getStatePath() }}.{{ $field::class }}.item"
124
-                                x-sortable-item="{{ $uuid }}"
125
-                                class="table-repeater-row"
126
-                            >
127
-                                @php($counter = 0)
128
-                                @foreach($row->getComponents() as $cell)
129
-                                    @if($cell instanceof \Filament\Forms\Components\Hidden || $cell->isHidden())
130
-                                        {{ $cell }}
131
-                                    @else
132
-                                        <td
133
-                                            @class([
134
-                                                'table-repeater-column',
135
-                                                'p-2' => ! $streamlined,
136
-                                                'has-hidden-label' => $cell->isLabelHidden(),
137
-                                                match($headers[$counter++]->getAlignment()) {
138
-                                                  'center', Alignment::Center => 'text-center',
139
-                                                  'right', 'end', Alignment::Right, Alignment::End => 'text-end',
140
-                                                  default => 'text-start'
141
-                                                }
142
-                                            ])
143
-                                            style="width: {{ $cell->getMaxWidth() ?? 'auto' }}"
144
-                                        >
145
-                                            {{ $cell }}
146
-                                        </td>
147
-                                    @endif
148
-                                @endforeach
149
-
150
-                                @if ($hasActions)
151
-                                    <td class="table-repeater-column p-2 w-px">
152
-                                        <ul class="flex items-center table-repeater-row-actions gap-x-3 px-2">
153
-                                            @foreach ($visibleExtraItemActions as $extraItemAction)
154
-                                                <li>
155
-                                                    {{ $extraItemAction(['item' => $uuid]) }}
156
-                                                </li>
157
-                                            @endforeach
158
-
159
-                                            @if ($reorderAction->isVisible())
160
-                                                <li x-sortable-handle class="shrink-0">
161
-                                                    {{ $reorderAction }}
162
-                                                </li>
163
-                                            @endif
164
-
165
-                                            @if ($isReorderableWithButtons)
166
-                                                @if (! $loop->first)
167
-                                                    <li>
168
-                                                        {{ $moveUpAction(['item' => $uuid]) }}
169
-                                                    </li>
170
-                                                @endif
171
-
172
-                                                @if (! $loop->last)
173
-                                                    <li>
174
-                                                        {{ $moveDownAction(['item' => $uuid]) }}
175
-                                                    </li>
176
-                                                @endif
177
-                                            @endif
178
-
179
-                                            @if ($cloneAction->isVisible())
180
-                                                <li>
181
-                                                    {{ $cloneAction(['item' => $uuid]) }}
182
-                                                </li>
183
-                                            @endif
184
-
185
-                                            @if ($deleteAction->isVisible())
186
-                                                <li>
187
-                                                    {{ $deleteAction(['item' => $uuid]) }}
188
-                                                </li>
189
-                                            @endif
190
-                                        </ul>
191
-                                    </td>
192
-                                @endif
193
-                            </tr>
194
-                            @if ($hasNestedSchema)
195
-                                <tr class="table-repeater-nested-row">
196
-                                    {{-- Empty cells for the columns before the nested schema --}}
197
-                                    @if ($emptyColspan > 0)
198
-                                        <td colspan="{{ $emptyColspan }}"></td>
199
-                                    @endif
200
-
201
-                                    {{-- Nested schema spanning the last 3 columns --}}
202
-                                    <td colspan="{{ $nestedColspan }}" class="p-4 bg-gray-50 dark:bg-gray-900">
203
-                                        <div class="nested-schema-wrapper">
204
-                                            @foreach ($getNestedSchema() as $nestedComponent)
205
-                                                {{ $nestedComponent }}
206
-                                            @endforeach
207
-                                        </div>
208
-                                    </td>
209
-                                </tr>
210
-                            @endif
211
-                        @endforeach
212
-                    @else
213
-                        <tr class="table-repeater-row table-repeater-empty-row">
214
-                            <td colspan="{{ count($headers) + intval($hasActions) }}"
215
-                                class="table-repeater-column table-repeater-empty-column p-4 w-px text-center italic">
216
-                                {{ $emptyLabel ?: trans('table-repeater::components.repeater.empty.label') }}
217
-                            </td>
218
-                        </tr>
219
-                    @endif
220
-                    </tbody>
221
-                </table>
222
-            </div>
223
-        @endif
224
-
225
-        @if ($addAction->isVisible() || filled($visibleExtraActions))
226
-            <ul
227
-                @class([
228
-                    'relative flex gap-4',
229
-                    match ($getAddActionAlignment()) {
230
-                        Alignment::Start, Alignment::Left => 'justify-start',
231
-                        Alignment::End, Alignment::Right => 'justify-end',
232
-                        default =>  'justify-center',
233
-                    },
234
-                ])
235
-            >
236
-                @if ($addAction->isVisible())
237
-                    <li>
238
-                        {{ $addAction }}
239
-                    </li>
240
-                @endif
241
-                @if (filled($visibleExtraActions))
242
-                    @foreach ($visibleExtraActions as $extraAction)
243
-                        <li>
244
-                            {{ ($extraAction) }}
245
-                        </li>
246
-                    @endforeach
247
-                @endif
248
-            </ul>
249
-        @endif
250
-    </div>
251
-</x-dynamic-component>

正在加载...
取消
保存