Browse Source

Merge pull request #158 from andrewdwallo/development-3.x

Development 3.x
3.x
Andrew Wallo 5 months ago
parent
commit
332c3e32f8
No account linked to committer's email address

+ 1
- 0
app/Filament/Company/Clusters/Settings/Pages/CompanyProfile.php View File

201
                 Hidden::make('type')
201
                 Hidden::make('type')
202
                     ->default('general'),
202
                     ->default('general'),
203
                 AddressFields::make()
203
                 AddressFields::make()
204
+                    ->required()
204
                     ->softRequired()
205
                     ->softRequired()
205
                     ->disabledCountry(is_demo_environment()),
206
                     ->disabledCountry(is_demo_environment()),
206
             ])
207
             ])

+ 95
- 80
app/Filament/Company/Resources/Common/OfferingResource.php View File

56
 
56
 
57
                         return new HtmlString($output);
57
                         return new HtmlString($output);
58
                     }),
58
                     }),
59
-                Forms\Components\Section::make('General')
60
-                    ->schema([
61
-                        RadioDeck::make('type')
62
-                            ->options(OfferingType::class)
63
-                            ->default(OfferingType::Product)
64
-                            ->icons(OfferingType::class)
65
-                            ->color('primary')
66
-                            ->columns()
67
-                            ->required(),
68
-                        Forms\Components\TextInput::make('name')
69
-                            ->autofocus()
70
-                            ->required()
71
-                            ->columnStart(1)
72
-                            ->maxLength(255),
73
-                        Forms\Components\TextInput::make('price')
74
-                            ->required()
75
-                            ->money(),
76
-                        Forms\Components\Textarea::make('description')
77
-                            ->label('Description')
78
-                            ->columnSpan(2)
79
-                            ->rows(3),
80
-                        Forms\Components\CheckboxList::make('attributes')
81
-                            ->options([
82
-                                'Sellable' => 'Sellable',
83
-                                'Purchasable' => 'Purchasable',
84
-                            ])
85
-                            ->hiddenLabel()
86
-                            ->required()
87
-                            ->live()
88
-                            ->bulkToggleable()
89
-                            ->validationMessages([
90
-                                'required' => 'The offering must be either sellable or purchasable.',
91
-                            ]),
92
-                    ])->columns(),
59
+                static::getGeneralSection(),
93
                 // Sellable Section
60
                 // Sellable Section
94
-                Forms\Components\Section::make('Sale Information')
95
-                    ->schema([
96
-                        CreateAccountSelect::make('income_account_id')
97
-                            ->label('Income account')
98
-                            ->category(AccountCategory::Revenue)
99
-                            ->type(AccountType::OperatingRevenue)
100
-                            ->required()
101
-                            ->validationMessages([
102
-                                'required' => 'The income account is required for sellable offerings.',
103
-                            ]),
104
-                        CreateAdjustmentSelect::make('salesTaxes')
105
-                            ->label('Sales tax')
106
-                            ->category(AdjustmentCategory::Tax)
107
-                            ->type(AdjustmentType::Sales)
108
-                            ->multiple(),
109
-                        CreateAdjustmentSelect::make('salesDiscounts')
110
-                            ->label('Sales discount')
111
-                            ->category(AdjustmentCategory::Discount)
112
-                            ->type(AdjustmentType::Sales)
113
-                            ->multiple(),
114
-                    ])
115
-                    ->columns()
116
-                    ->visible(static fn (Forms\Get $get) => in_array('Sellable', $get('attributes') ?? [])),
117
-
61
+                static::getSellableSection(),
118
                 // Purchasable Section
62
                 // Purchasable Section
119
-                Forms\Components\Section::make('Purchase Information')
120
-                    ->schema([
121
-                        CreateAccountSelect::make('expense_account_id')
122
-                            ->label('Expense account')
123
-                            ->category(AccountCategory::Expense)
124
-                            ->type(AccountType::OperatingExpense)
125
-                            ->required()
126
-                            ->validationMessages([
127
-                                'required' => 'The expense account is required for purchasable offerings.',
128
-                            ]),
129
-                        CreateAdjustmentSelect::make('purchaseTaxes')
130
-                            ->label('Purchase tax')
131
-                            ->category(AdjustmentCategory::Tax)
132
-                            ->type(AdjustmentType::Purchase)
133
-                            ->multiple(),
134
-                        CreateAdjustmentSelect::make('purchaseDiscounts')
135
-                            ->label('Purchase discount')
136
-                            ->category(AdjustmentCategory::Discount)
137
-                            ->type(AdjustmentType::Purchase)
138
-                            ->multiple(),
139
-                    ])
63
+                static::getPurchasableSection(),
64
+            ])->columns();
65
+    }
66
+
67
+    public static function getGeneralSection(bool $hasAttributeChoices = true): Forms\Components\Section
68
+    {
69
+        return Forms\Components\Section::make('General')
70
+            ->schema([
71
+                RadioDeck::make('type')
72
+                    ->options(OfferingType::class)
73
+                    ->default(OfferingType::Product)
74
+                    ->icons(OfferingType::class)
75
+                    ->color('primary')
140
                     ->columns()
76
                     ->columns()
141
-                    ->visible(static fn (Forms\Get $get) => in_array('Purchasable', $get('attributes') ?? [])),
77
+                    ->required(),
78
+                Forms\Components\TextInput::make('name')
79
+                    ->autofocus()
80
+                    ->required()
81
+                    ->columnStart(1)
82
+                    ->maxLength(255),
83
+                Forms\Components\TextInput::make('price')
84
+                    ->required()
85
+                    ->money(),
86
+                Forms\Components\Textarea::make('description')
87
+                    ->label('Description')
88
+                    ->columnSpan(2)
89
+                    ->rows(3),
90
+                Forms\Components\CheckboxList::make('attributes')
91
+                    ->options([
92
+                        'Sellable' => 'Sellable',
93
+                        'Purchasable' => 'Purchasable',
94
+                    ])
95
+                    ->visible($hasAttributeChoices)
96
+                    ->hiddenLabel()
97
+                    ->required()
98
+                    ->live()
99
+                    ->bulkToggleable()
100
+                    ->validationMessages([
101
+                        'required' => 'The offering must be either sellable or purchasable.',
102
+                    ]),
142
             ])->columns();
103
             ])->columns();
143
     }
104
     }
144
 
105
 
106
+    public static function getSellableSection(bool $showByDefault = false): Forms\Components\Section
107
+    {
108
+        return Forms\Components\Section::make('Sale Information')
109
+            ->schema([
110
+                CreateAccountSelect::make('income_account_id')
111
+                    ->label('Income account')
112
+                    ->category(AccountCategory::Revenue)
113
+                    ->type(AccountType::OperatingRevenue)
114
+                    ->required()
115
+                    ->validationMessages([
116
+                        'required' => 'The income account is required for sellable offerings.',
117
+                    ]),
118
+                CreateAdjustmentSelect::make('salesTaxes')
119
+                    ->label('Sales tax')
120
+                    ->category(AdjustmentCategory::Tax)
121
+                    ->type(AdjustmentType::Sales)
122
+                    ->multiple(),
123
+                CreateAdjustmentSelect::make('salesDiscounts')
124
+                    ->label('Sales discount')
125
+                    ->category(AdjustmentCategory::Discount)
126
+                    ->type(AdjustmentType::Sales)
127
+                    ->multiple(),
128
+            ])
129
+            ->columns()
130
+            ->visible(static fn (Forms\Get $get) => in_array('Sellable', $get('attributes') ?? []));
131
+    }
132
+
133
+    public static function getPurchasableSection(): Forms\Components\Section
134
+    {
135
+        return Forms\Components\Section::make('Purchase Information')
136
+            ->schema([
137
+                CreateAccountSelect::make('expense_account_id')
138
+                    ->label('Expense account')
139
+                    ->category(AccountCategory::Expense)
140
+                    ->type(AccountType::OperatingExpense)
141
+                    ->required()
142
+                    ->validationMessages([
143
+                        'required' => 'The expense account is required for purchasable offerings.',
144
+                    ]),
145
+                CreateAdjustmentSelect::make('purchaseTaxes')
146
+                    ->label('Purchase tax')
147
+                    ->category(AdjustmentCategory::Tax)
148
+                    ->type(AdjustmentType::Purchase)
149
+                    ->multiple(),
150
+                CreateAdjustmentSelect::make('purchaseDiscounts')
151
+                    ->label('Purchase discount')
152
+                    ->category(AdjustmentCategory::Discount)
153
+                    ->type(AdjustmentType::Purchase)
154
+                    ->multiple(),
155
+            ])
156
+            ->columns()
157
+            ->visible(static fn (Forms\Get $get) => in_array('Purchasable', $get('attributes') ?? []));
158
+    }
159
+
145
     public static function table(Table $table): Table
160
     public static function table(Table $table): Table
146
     {
161
     {
147
         return $table
162
         return $table

+ 6
- 8
app/Filament/Company/Resources/Purchases/BillResource.php View File

14
 use App\Filament\Company\Resources\Purchases\VendorResource\RelationManagers\BillsRelationManager;
14
 use App\Filament\Company\Resources\Purchases\VendorResource\RelationManagers\BillsRelationManager;
15
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
15
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
16
 use App\Filament\Forms\Components\CreateCurrencySelect;
16
 use App\Filament\Forms\Components\CreateCurrencySelect;
17
+use App\Filament\Forms\Components\CreateOfferingSelect;
18
+use App\Filament\Forms\Components\CreateVendorSelect;
17
 use App\Filament\Forms\Components\DocumentTotals;
19
 use App\Filament\Forms\Components\DocumentTotals;
18
 use App\Filament\Tables\Actions\ReplicateBulkAction;
20
 use App\Filament\Tables\Actions\ReplicateBulkAction;
19
 use App\Filament\Tables\Columns;
21
 use App\Filament\Tables\Columns;
60
                     ->schema([
62
                     ->schema([
61
                         Forms\Components\Split::make([
63
                         Forms\Components\Split::make([
62
                             Forms\Components\Group::make([
64
                             Forms\Components\Group::make([
63
-                                Forms\Components\Select::make('vendor_id')
64
-                                    ->relationship('vendor', 'name')
65
-                                    ->preload()
66
-                                    ->searchable()
65
+                                CreateVendorSelect::make('vendor_id')
66
+                                    ->label('Vendor')
67
                                     ->required()
67
                                     ->required()
68
                                     ->live()
68
                                     ->live()
69
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
69
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
209
                                 return $headers;
209
                                 return $headers;
210
                             })
210
                             })
211
                             ->schema([
211
                             ->schema([
212
-                                Forms\Components\Select::make('offering_id')
212
+                                CreateOfferingSelect::make('offering_id')
213
                                     ->label('Item')
213
                                     ->label('Item')
214
-                                    ->relationship('purchasableOffering', 'name')
215
-                                    ->preload()
216
-                                    ->searchable()
217
                                     ->required()
214
                                     ->required()
218
                                     ->live()
215
                                     ->live()
216
+                                    ->purchasable()
219
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state, ?DocumentLineItem $record) {
217
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state, ?DocumentLineItem $record) {
220
                                         $offeringId = $state;
218
                                         $offeringId = $state;
221
                                         $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));
219
                                         $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));

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

45
                                     ->default(VendorType::Regular)
45
                                     ->default(VendorType::Regular)
46
                                     ->columnSpanFull(),
46
                                     ->columnSpanFull(),
47
                                 CreateCurrencySelect::make('currency_code')
47
                                 CreateCurrencySelect::make('currency_code')
48
-                                    ->nullable()
48
+                                    ->softRequired()
49
                                     ->visible(static fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Regular),
49
                                     ->visible(static fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Regular),
50
                                 Forms\Components\Select::make('contractor_type')
50
                                 Forms\Components\Select::make('contractor_type')
51
                                     ->label('Contractor type')
51
                                     ->label('Contractor type')
80
                             ]),
80
                             ]),
81
                         CustomSection::make('Primary Contact')
81
                         CustomSection::make('Primary Contact')
82
                             ->relationship('contact')
82
                             ->relationship('contact')
83
+                            ->saveRelationshipsUsing(null)
84
+                            ->saveRelationshipsBeforeChildrenUsing(null)
85
+                            ->dehydrated(true)
83
                             ->contained(false)
86
                             ->contained(false)
84
                             ->schema([
87
                             ->schema([
85
                                 Forms\Components\Hidden::make('is_primary')
88
                                 Forms\Components\Hidden::make('is_primary')
86
                                     ->default(true),
89
                                     ->default(true),
87
                                 Forms\Components\TextInput::make('first_name')
90
                                 Forms\Components\TextInput::make('first_name')
88
                                     ->label('First name')
91
                                     ->label('First name')
89
-                                    ->required()
90
                                     ->maxLength(255),
92
                                     ->maxLength(255),
91
                                 Forms\Components\TextInput::make('last_name')
93
                                 Forms\Components\TextInput::make('last_name')
92
                                     ->label('Last name')
94
                                     ->label('Last name')
93
-                                    ->required()
94
                                     ->maxLength(255),
95
                                     ->maxLength(255),
95
                                 Forms\Components\TextInput::make('email')
96
                                 Forms\Components\TextInput::make('email')
96
                                     ->label('Email')
97
                                     ->label('Email')
97
-                                    ->required()
98
                                     ->email()
98
                                     ->email()
99
                                     ->columnSpanFull()
99
                                     ->columnSpanFull()
100
                                     ->maxLength(255),
100
                                     ->maxLength(255),
110
                                             ->schema([
110
                                             ->schema([
111
                                                 Forms\Components\TextInput::make('number')
111
                                                 Forms\Components\TextInput::make('number')
112
                                                     ->label('Phone')
112
                                                     ->label('Phone')
113
-                                                    ->required()
114
                                                     ->maxLength(15),
113
                                                     ->maxLength(15),
115
                                             ])->maxItems(1),
114
                                             ])->maxItems(1),
116
                                         Forms\Components\Builder\Block::make('mobile')
115
                                         Forms\Components\Builder\Block::make('mobile')
117
                                             ->schema([
116
                                             ->schema([
118
                                                 Forms\Components\TextInput::make('number')
117
                                                 Forms\Components\TextInput::make('number')
119
                                                     ->label('Mobile')
118
                                                     ->label('Mobile')
120
-                                                    ->required()
121
                                                     ->maxLength(15),
119
                                                     ->maxLength(15),
122
                                             ])->maxItems(1),
120
                                             ])->maxItems(1),
123
                                         Forms\Components\Builder\Block::make('toll_free')
121
                                         Forms\Components\Builder\Block::make('toll_free')
124
                                             ->schema([
122
                                             ->schema([
125
                                                 Forms\Components\TextInput::make('number')
123
                                                 Forms\Components\TextInput::make('number')
126
                                                     ->label('Toll free')
124
                                                     ->label('Toll free')
127
-                                                    ->required()
128
                                                     ->maxLength(15),
125
                                                     ->maxLength(15),
129
                                             ])->maxItems(1),
126
                                             ])->maxItems(1),
130
                                         Forms\Components\Builder\Block::make('fax')
127
                                         Forms\Components\Builder\Block::make('fax')
143
                     ])->columns(1),
140
                     ])->columns(1),
144
                 Forms\Components\Section::make('Address Information')
141
                 Forms\Components\Section::make('Address Information')
145
                     ->relationship('address')
142
                     ->relationship('address')
143
+                    ->saveRelationshipsUsing(null)
144
+                    ->saveRelationshipsBeforeChildrenUsing(null)
145
+                    ->dehydrated(true)
146
                     ->schema([
146
                     ->schema([
147
                         Forms\Components\Hidden::make('type')
147
                         Forms\Components\Hidden::make('type')
148
                             ->default('general'),
148
                             ->default('general'),

+ 7
- 0
app/Filament/Company/Resources/Purchases/VendorResource/Pages/CreateVendor.php View File

4
 
4
 
5
 use App\Concerns\HandlePageRedirect;
5
 use App\Concerns\HandlePageRedirect;
6
 use App\Filament\Company\Resources\Purchases\VendorResource;
6
 use App\Filament\Company\Resources\Purchases\VendorResource;
7
+use App\Models\Common\Vendor;
7
 use Filament\Resources\Pages\CreateRecord;
8
 use Filament\Resources\Pages\CreateRecord;
8
 use Filament\Support\Enums\MaxWidth;
9
 use Filament\Support\Enums\MaxWidth;
10
+use Illuminate\Database\Eloquent\Model;
9
 
11
 
10
 class CreateVendor extends CreateRecord
12
 class CreateVendor extends CreateRecord
11
 {
13
 {
13
 
15
 
14
     protected static string $resource = VendorResource::class;
16
     protected static string $resource = VendorResource::class;
15
 
17
 
18
+    protected function handleRecordCreation(array $data): Model
19
+    {
20
+        return Vendor::createWithRelations($data);
21
+    }
22
+
16
     public function getMaxContentWidth(): MaxWidth | string | null
23
     public function getMaxContentWidth(): MaxWidth | string | null
17
     {
24
     {
18
         return MaxWidth::FiveExtraLarge;
25
         return MaxWidth::FiveExtraLarge;

+ 10
- 0
app/Filament/Company/Resources/Purchases/VendorResource/Pages/EditVendor.php View File

4
 
4
 
5
 use App\Concerns\HandlePageRedirect;
5
 use App\Concerns\HandlePageRedirect;
6
 use App\Filament\Company\Resources\Purchases\VendorResource;
6
 use App\Filament\Company\Resources\Purchases\VendorResource;
7
+use App\Models\Common\Vendor;
7
 use Filament\Actions;
8
 use Filament\Actions;
8
 use Filament\Resources\Pages\EditRecord;
9
 use Filament\Resources\Pages\EditRecord;
9
 use Filament\Support\Enums\MaxWidth;
10
 use Filament\Support\Enums\MaxWidth;
11
+use Illuminate\Database\Eloquent\Model;
10
 
12
 
11
 class EditVendor extends EditRecord
13
 class EditVendor extends EditRecord
12
 {
14
 {
14
 
16
 
15
     protected static string $resource = VendorResource::class;
17
     protected static string $resource = VendorResource::class;
16
 
18
 
19
+    protected function handleRecordUpdate(Model $record, array $data): Model
20
+    {
21
+        /** @var Vendor $record */
22
+        $record->updateWithRelations($data);
23
+
24
+        return $record;
25
+    }
26
+
17
     protected function getHeaderActions(): array
27
     protected function getHeaderActions(): array
18
     {
28
     {
19
         return [
29
         return [

+ 10
- 13
app/Filament/Company/Resources/Sales/ClientResource.php View File

47
                             ]),
47
                             ]),
48
                         CustomSection::make('Primary Contact')
48
                         CustomSection::make('Primary Contact')
49
                             ->relationship('primaryContact')
49
                             ->relationship('primaryContact')
50
+                            ->saveRelationshipsUsing(null)
51
+                            ->saveRelationshipsBeforeChildrenUsing(null)
52
+                            ->dehydrated(true)
50
                             ->contained(false)
53
                             ->contained(false)
51
                             ->schema([
54
                             ->schema([
52
                                 Forms\Components\Hidden::make('is_primary')
55
                                 Forms\Components\Hidden::make('is_primary')
53
                                     ->default(true),
56
                                     ->default(true),
54
                                 Forms\Components\TextInput::make('first_name')
57
                                 Forms\Components\TextInput::make('first_name')
55
                                     ->label('First name')
58
                                     ->label('First name')
56
-                                    ->required()
57
                                     ->maxLength(255),
59
                                     ->maxLength(255),
58
                                 Forms\Components\TextInput::make('last_name')
60
                                 Forms\Components\TextInput::make('last_name')
59
                                     ->label('Last name')
61
                                     ->label('Last name')
60
-                                    ->required()
61
                                     ->maxLength(255),
62
                                     ->maxLength(255),
62
                                 Forms\Components\TextInput::make('email')
63
                                 Forms\Components\TextInput::make('email')
63
                                     ->label('Email')
64
                                     ->label('Email')
64
-                                    ->required()
65
                                     ->email()
65
                                     ->email()
66
                                     ->columnSpanFull()
66
                                     ->columnSpanFull()
67
                                     ->maxLength(255),
67
                                     ->maxLength(255),
77
                                             ->schema([
77
                                             ->schema([
78
                                                 Forms\Components\TextInput::make('number')
78
                                                 Forms\Components\TextInput::make('number')
79
                                                     ->label('Phone')
79
                                                     ->label('Phone')
80
-                                                    ->required()
81
                                                     ->maxLength(15),
80
                                                     ->maxLength(15),
82
                                             ])->maxItems(1),
81
                                             ])->maxItems(1),
83
                                         Forms\Components\Builder\Block::make('mobile')
82
                                         Forms\Components\Builder\Block::make('mobile')
84
                                             ->schema([
83
                                             ->schema([
85
                                                 Forms\Components\TextInput::make('number')
84
                                                 Forms\Components\TextInput::make('number')
86
                                                     ->label('Mobile')
85
                                                     ->label('Mobile')
87
-                                                    ->required()
88
                                                     ->maxLength(15),
86
                                                     ->maxLength(15),
89
                                             ])->maxItems(1),
87
                                             ])->maxItems(1),
90
                                         Forms\Components\Builder\Block::make('toll_free')
88
                                         Forms\Components\Builder\Block::make('toll_free')
91
                                             ->schema([
89
                                             ->schema([
92
                                                 Forms\Components\TextInput::make('number')
90
                                                 Forms\Components\TextInput::make('number')
93
                                                     ->label('Toll free')
91
                                                     ->label('Toll free')
94
-                                                    ->required()
95
                                                     ->maxLength(15),
92
                                                     ->maxLength(15),
96
                                             ])->maxItems(1),
93
                                             ])->maxItems(1),
97
                                         Forms\Components\Builder\Block::make('fax')
94
                                         Forms\Components\Builder\Block::make('fax')
109
                             ])->columns(),
106
                             ])->columns(),
110
                         Forms\Components\Repeater::make('secondaryContacts')
107
                         Forms\Components\Repeater::make('secondaryContacts')
111
                             ->relationship()
108
                             ->relationship()
109
+                            ->saveRelationshipsUsing(null)
110
+                            ->saveRelationshipsBeforeChildrenUsing(null)
111
+                            ->dehydrated(true)
112
                             ->hiddenLabel()
112
                             ->hiddenLabel()
113
                             ->extraAttributes([
113
                             ->extraAttributes([
114
                                 'class' => 'uncontained',
114
                                 'class' => 'uncontained',
138
                             ->schema([
138
                             ->schema([
139
                                 Forms\Components\TextInput::make('first_name')
139
                                 Forms\Components\TextInput::make('first_name')
140
                                     ->label('First name')
140
                                     ->label('First name')
141
-                                    ->required()
142
                                     ->live(onBlur: true)
141
                                     ->live(onBlur: true)
143
                                     ->maxLength(255),
142
                                     ->maxLength(255),
144
                                 Forms\Components\TextInput::make('last_name')
143
                                 Forms\Components\TextInput::make('last_name')
145
                                     ->label('Last name')
144
                                     ->label('Last name')
146
-                                    ->required()
147
                                     ->live(onBlur: true)
145
                                     ->live(onBlur: true)
148
                                     ->maxLength(255),
146
                                     ->maxLength(255),
149
                                 Forms\Components\TextInput::make('email')
147
                                 Forms\Components\TextInput::make('email')
150
                                     ->label('Email')
148
                                     ->label('Email')
151
-                                    ->required()
152
                                     ->email()
149
                                     ->email()
153
                                     ->maxLength(255),
150
                                     ->maxLength(255),
154
                                 PhoneBuilder::make('phones')
151
                                 PhoneBuilder::make('phones')
162
                                             ->schema([
159
                                             ->schema([
163
                                                 Forms\Components\TextInput::make('number')
160
                                                 Forms\Components\TextInput::make('number')
164
                                                     ->label('Phone')
161
                                                     ->label('Phone')
165
-                                                    ->required()
166
                                                     ->maxLength(255),
162
                                                     ->maxLength(255),
167
                                             ])->maxItems(1),
163
                                             ])->maxItems(1),
168
                                     ])
164
                                     ])
174
                     ])->columns(1),
170
                     ])->columns(1),
175
                 Forms\Components\Section::make('Billing')
171
                 Forms\Components\Section::make('Billing')
176
                     ->schema([
172
                     ->schema([
177
-                        CreateCurrencySelect::make('currency_code'),
173
+                        CreateCurrencySelect::make('currency_code')
174
+                            ->softRequired(),
178
                         CustomSection::make('Billing Address')
175
                         CustomSection::make('Billing Address')
179
                             ->relationship('billingAddress')
176
                             ->relationship('billingAddress')
180
                             ->saveRelationshipsUsing(null)
177
                             ->saveRelationshipsUsing(null)
178
+                            ->saveRelationshipsBeforeChildrenUsing(null)
181
                             ->dehydrated(true)
179
                             ->dehydrated(true)
182
                             ->contained(false)
180
                             ->contained(false)
183
                             ->schema([
181
                             ->schema([
190
                 Forms\Components\Section::make('Shipping')
188
                 Forms\Components\Section::make('Shipping')
191
                     ->relationship('shippingAddress')
189
                     ->relationship('shippingAddress')
192
                     ->saveRelationshipsUsing(null)
190
                     ->saveRelationshipsUsing(null)
191
+                    ->saveRelationshipsBeforeChildrenUsing(null)
193
                     ->dehydrated(true)
192
                     ->dehydrated(true)
194
                     ->schema([
193
                     ->schema([
195
                         Forms\Components\Hidden::make('type')
194
                         Forms\Components\Hidden::make('type')
196
                             ->default('shipping'),
195
                             ->default('shipping'),
197
                         Forms\Components\TextInput::make('recipient')
196
                         Forms\Components\TextInput::make('recipient')
198
                             ->label('Recipient')
197
                             ->label('Recipient')
199
-                            ->required()
200
                             ->maxLength(255),
198
                             ->maxLength(255),
201
                         Forms\Components\TextInput::make('phone')
199
                         Forms\Components\TextInput::make('phone')
202
                             ->label('Phone')
200
                             ->label('Phone')
203
-                            ->required()
204
                             ->maxLength(255),
201
                             ->maxLength(255),
205
                         CustomSection::make('Shipping Address')
202
                         CustomSection::make('Shipping Address')
206
                             ->contained(false)
203
                             ->contained(false)

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

+ 1
- 41
app/Filament/Company/Resources/Sales/ClientResource/Pages/EditClient.php View File

31
     protected function handleRecordUpdate(Model $record, array $data): Model
31
     protected function handleRecordUpdate(Model $record, array $data): Model
32
     {
32
     {
33
         /** @var Client $record */
33
         /** @var Client $record */
34
-        $record = parent::handleRecordUpdate($record, $data);
35
-
36
-        // Update billing address
37
-        $billingAddress = $record->billingAddress;
38
-        $billingAddress->update($data['billingAddress']);
39
-
40
-        // Update shipping address
41
-        $shippingAddress = $record->shippingAddress;
42
-        $shippingData = $data['shippingAddress'];
43
-
44
-        $shippingUpdateData = [
45
-            'recipient' => $shippingData['recipient'],
46
-            'phone' => $shippingData['phone'],
47
-            'notes' => $shippingData['notes'],
48
-        ];
49
-
50
-        if ($shippingData['same_as_billing']) {
51
-            $shippingUpdateData = [
52
-                ...$shippingUpdateData,
53
-                'parent_address_id' => $billingAddress->id,
54
-                'address_line_1' => $billingAddress->address_line_1,
55
-                'address_line_2' => $billingAddress->address_line_2,
56
-                'country_code' => $billingAddress->country_code,
57
-                'state_id' => $billingAddress->state_id,
58
-                'city' => $billingAddress->city,
59
-                'postal_code' => $billingAddress->postal_code,
60
-            ];
61
-        } else {
62
-            $shippingUpdateData = [
63
-                ...$shippingUpdateData,
64
-                'parent_address_id' => null,
65
-                'address_line_1' => $shippingData['address_line_1'],
66
-                'address_line_2' => $shippingData['address_line_2'],
67
-                'country_code' => $shippingData['country_code'],
68
-                'state_id' => $shippingData['state_id'],
69
-                'city' => $shippingData['city'],
70
-                'postal_code' => $shippingData['postal_code'],
71
-            ];
72
-        }
73
-
74
-        $shippingAddress->update($shippingUpdateData);
34
+        $record->updateWithRelations($data);
75
 
35
 
76
         return $record;
36
         return $record;
77
     }
37
     }

+ 7
- 8
app/Filament/Company/Resources/Sales/EstimateResource.php View File

13
 use App\Filament\Company\Resources\Sales\EstimateResource\Pages;
13
 use App\Filament\Company\Resources\Sales\EstimateResource\Pages;
14
 use App\Filament\Company\Resources\Sales\EstimateResource\Widgets;
14
 use App\Filament\Company\Resources\Sales\EstimateResource\Widgets;
15
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
15
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
16
+use App\Filament\Forms\Components\CreateClientSelect;
16
 use App\Filament\Forms\Components\CreateCurrencySelect;
17
 use App\Filament\Forms\Components\CreateCurrencySelect;
18
+use App\Filament\Forms\Components\CreateOfferingSelect;
17
 use App\Filament\Forms\Components\DocumentFooterSection;
19
 use App\Filament\Forms\Components\DocumentFooterSection;
18
 use App\Filament\Forms\Components\DocumentHeaderSection;
20
 use App\Filament\Forms\Components\DocumentHeaderSection;
19
 use App\Filament\Forms\Components\DocumentTotals;
21
 use App\Filament\Forms\Components\DocumentTotals;
61
                     ->schema([
63
                     ->schema([
62
                         Forms\Components\Split::make([
64
                         Forms\Components\Split::make([
63
                             Forms\Components\Group::make([
65
                             Forms\Components\Group::make([
64
-                                Forms\Components\Select::make('client_id')
65
-                                    ->relationship('client', 'name')
66
-                                    ->preload()
67
-                                    ->searchable()
66
+                                CreateClientSelect::make('client_id')
67
+                                    ->label('Client')
68
                                     ->required()
68
                                     ->required()
69
                                     ->live()
69
                                     ->live()
70
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
70
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
207
                                 return $headers;
207
                                 return $headers;
208
                             })
208
                             })
209
                             ->schema([
209
                             ->schema([
210
-                                Forms\Components\Select::make('offering_id')
211
-                                    ->relationship('sellableOffering', 'name')
212
-                                    ->preload()
213
-                                    ->searchable()
210
+                                CreateOfferingSelect::make('offering_id')
211
+                                    ->label('Item')
214
                                     ->required()
212
                                     ->required()
215
                                     ->live()
213
                                     ->live()
214
+                                    ->sellable()
216
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state, ?DocumentLineItem $record) {
215
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state, ?DocumentLineItem $record) {
217
                                         $offeringId = $state;
216
                                         $offeringId = $state;
218
                                         $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));
217
                                         $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));

+ 7
- 8
app/Filament/Company/Resources/Sales/InvoiceResource.php View File

15
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
15
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
16
 use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
16
 use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
17
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
17
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
18
+use App\Filament\Forms\Components\CreateClientSelect;
18
 use App\Filament\Forms\Components\CreateCurrencySelect;
19
 use App\Filament\Forms\Components\CreateCurrencySelect;
20
+use App\Filament\Forms\Components\CreateOfferingSelect;
19
 use App\Filament\Forms\Components\DocumentFooterSection;
21
 use App\Filament\Forms\Components\DocumentFooterSection;
20
 use App\Filament\Forms\Components\DocumentHeaderSection;
22
 use App\Filament\Forms\Components\DocumentHeaderSection;
21
 use App\Filament\Forms\Components\DocumentTotals;
23
 use App\Filament\Forms\Components\DocumentTotals;
67
                     ->schema([
69
                     ->schema([
68
                         Forms\Components\Split::make([
70
                         Forms\Components\Split::make([
69
                             Forms\Components\Group::make([
71
                             Forms\Components\Group::make([
70
-                                Forms\Components\Select::make('client_id')
71
-                                    ->relationship('client', 'name')
72
-                                    ->preload()
73
-                                    ->searchable()
72
+                                CreateClientSelect::make('client_id')
73
+                                    ->label('Client')
74
                                     ->required()
74
                                     ->required()
75
                                     ->live()
75
                                     ->live()
76
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
76
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
220
                                 return $headers;
220
                                 return $headers;
221
                             })
221
                             })
222
                             ->schema([
222
                             ->schema([
223
-                                Forms\Components\Select::make('offering_id')
224
-                                    ->relationship('sellableOffering', 'name')
225
-                                    ->preload()
226
-                                    ->searchable()
223
+                                CreateOfferingSelect::make('offering_id')
224
+                                    ->label('Item')
227
                                     ->required()
225
                                     ->required()
228
                                     ->live()
226
                                     ->live()
227
+                                    ->sellable()
229
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state, ?DocumentLineItem $record) {
228
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state, ?DocumentLineItem $record) {
230
                                         $offeringId = $state;
229
                                         $offeringId = $state;
231
                                         $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));
230
                                         $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));

+ 7
- 8
app/Filament/Company/Resources/Sales/RecurringInvoiceResource.php View File

12
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\RecurringInvoicesRelationManager;
12
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\RecurringInvoicesRelationManager;
13
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
13
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
14
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
14
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
15
+use App\Filament\Forms\Components\CreateClientSelect;
15
 use App\Filament\Forms\Components\CreateCurrencySelect;
16
 use App\Filament\Forms\Components\CreateCurrencySelect;
17
+use App\Filament\Forms\Components\CreateOfferingSelect;
16
 use App\Filament\Forms\Components\DocumentFooterSection;
18
 use App\Filament\Forms\Components\DocumentFooterSection;
17
 use App\Filament\Forms\Components\DocumentHeaderSection;
19
 use App\Filament\Forms\Components\DocumentHeaderSection;
18
 use App\Filament\Forms\Components\DocumentTotals;
20
 use App\Filament\Forms\Components\DocumentTotals;
53
                     ->schema([
55
                     ->schema([
54
                         Forms\Components\Split::make([
56
                         Forms\Components\Split::make([
55
                             Forms\Components\Group::make([
57
                             Forms\Components\Group::make([
56
-                                Forms\Components\Select::make('client_id')
57
-                                    ->relationship('client', 'name')
58
-                                    ->preload()
59
-                                    ->searchable()
58
+                                CreateClientSelect::make('client_id')
59
+                                    ->label('Client')
60
                                     ->required()
60
                                     ->required()
61
                                     ->live()
61
                                     ->live()
62
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
62
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) {
133
                                 return $headers;
133
                                 return $headers;
134
                             })
134
                             })
135
                             ->schema([
135
                             ->schema([
136
-                                Forms\Components\Select::make('offering_id')
137
-                                    ->relationship('sellableOffering', 'name')
138
-                                    ->preload()
139
-                                    ->searchable()
136
+                                CreateOfferingSelect::make('offering_id')
137
+                                    ->label('Item')
140
                                     ->required()
138
                                     ->required()
141
                                     ->live()
139
                                     ->live()
140
+                                    ->sellable()
142
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state, ?DocumentLineItem $record) {
141
                                     ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state, ?DocumentLineItem $record) {
143
                                         $offeringId = $state;
142
                                         $offeringId = $state;
144
                                         $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));
143
                                         $discountMethod = DocumentDiscountMethod::parse($get('../../discount_method'));

+ 17
- 3
app/Filament/Forms/Components/AddressFields.php View File

13
 
13
 
14
     protected bool | Closure $isCountryDisabled = false;
14
     protected bool | Closure $isCountryDisabled = false;
15
 
15
 
16
+    protected bool | Closure $isRequired = false;
17
+
16
     protected function setUp(): void
18
     protected function setUp(): void
17
     {
19
     {
18
         parent::setUp();
20
         parent::setUp();
20
         $this->schema([
22
         $this->schema([
21
             TextInput::make('address_line_1')
23
             TextInput::make('address_line_1')
22
                 ->label('Address line 1')
24
                 ->label('Address line 1')
23
-                ->required()
25
+                ->required(fn () => $this->isRequired())
24
                 ->maxLength(255),
26
                 ->maxLength(255),
25
             TextInput::make('address_line_2')
27
             TextInput::make('address_line_2')
26
                 ->label('Address line 2')
28
                 ->label('Address line 2')
28
             CountrySelect::make('country_code')
30
             CountrySelect::make('country_code')
29
                 ->disabled(fn () => $this->isCountryDisabled())
31
                 ->disabled(fn () => $this->isCountryDisabled())
30
                 ->clearStateField()
32
                 ->clearStateField()
31
-                ->required(),
33
+                ->required(fn () => $this->isRequired()),
32
             StateSelect::make('state_id'),
34
             StateSelect::make('state_id'),
33
             TextInput::make('city')
35
             TextInput::make('city')
34
                 ->label('City')
36
                 ->label('City')
35
-                ->required()
37
+                ->required(fn () => $this->isRequired())
36
                 ->maxLength(255),
38
                 ->maxLength(255),
37
             TextInput::make('postal_code')
39
             TextInput::make('postal_code')
38
                 ->label('Postal code')
40
                 ->label('Postal code')
60
         }
62
         }
61
     }
63
     }
62
 
64
 
65
+    public function required(bool | Closure $condition = true): static
66
+    {
67
+        $this->isRequired = $condition;
68
+
69
+        return $this;
70
+    }
71
+
72
+    public function isRequired(): bool
73
+    {
74
+        return (bool) $this->evaluate($this->isRequired);
75
+    }
76
+
63
     public function disabledCountry(bool | Closure $condition = true): static
77
     public function disabledCountry(bool | Closure $condition = true): static
64
     {
78
     {
65
         $this->isCountryDisabled = $condition;
79
         $this->isCountryDisabled = $condition;

+ 2
- 2
app/Filament/Forms/Components/CreateAdjustmentSelect.php View File

211
     {
211
     {
212
         $categoryLabel = $this->getCategory()?->getLabel() ?? 'Adjustment';
212
         $categoryLabel = $this->getCategory()?->getLabel() ?? 'Adjustment';
213
         $typeLabel = $this->getType()?->getLabel() ?? '';
213
         $typeLabel = $this->getType()?->getLabel() ?? '';
214
-        $label = trim($typeLabel . ' ' . $categoryLabel);
214
+        $label = strtolower(trim($typeLabel . ' ' . $categoryLabel));
215
 
215
 
216
         return $action
216
         return $action
217
             ->label('Create ' . $label)
217
             ->label('Create ' . $label)
218
             ->slideOver()
218
             ->slideOver()
219
             ->modalWidth(MaxWidth::ExtraLarge)
219
             ->modalWidth(MaxWidth::ExtraLarge)
220
-            ->modalHeading('Create a new ' . strtolower($label));
220
+            ->modalHeading('Create a new ' . $label);
221
     }
221
     }
222
 }
222
 }

+ 49
- 0
app/Filament/Forms/Components/CreateClientSelect.php View File

1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use App\Filament\Company\Resources\Sales\ClientResource;
6
+use App\Models\Common\Client;
7
+use Filament\Forms\Components\Actions\Action;
8
+use Filament\Forms\Components\Select;
9
+use Filament\Forms\Form;
10
+use Filament\Support\Enums\MaxWidth;
11
+use Illuminate\Support\Facades\DB;
12
+
13
+class CreateClientSelect extends Select
14
+{
15
+    protected function setUp(): void
16
+    {
17
+        parent::setUp();
18
+
19
+        $this
20
+            ->searchable()
21
+            ->preload()
22
+            ->createOptionForm(fn (Form $form) => $this->createClientForm($form))
23
+            ->createOptionAction(fn (Action $action) => $this->createClientAction($action));
24
+
25
+        $this->relationship('client', 'name');
26
+
27
+        $this->createOptionUsing(static function (array $data) {
28
+            return DB::transaction(static function () use ($data) {
29
+                $client = Client::createWithRelations($data);
30
+
31
+                return $client->getKey();
32
+            });
33
+        });
34
+    }
35
+
36
+    protected function createClientForm(Form $form): Form
37
+    {
38
+        return ClientResource::form($form);
39
+    }
40
+
41
+    protected function createClientAction(Action $action): Action
42
+    {
43
+        return $action
44
+            ->label('Create client')
45
+            ->slideOver()
46
+            ->modalWidth(MaxWidth::ThreeExtraLarge)
47
+            ->modalHeading('Create a new client');
48
+    }
49
+}

+ 3
- 2
app/Filament/Forms/Components/CreateCurrencySelect.php View File

63
     protected function createCurrencyAction(Action $action): Action
63
     protected function createCurrencyAction(Action $action): Action
64
     {
64
     {
65
         return $action
65
         return $action
66
-            ->label('Add currency')
66
+            ->label('Create currency')
67
             ->slideOver()
67
             ->slideOver()
68
-            ->modalWidth(MaxWidth::Medium);
68
+            ->modalWidth(MaxWidth::Medium)
69
+            ->modalHeading('Create a new currency');
69
     }
70
     }
70
 }
71
 }

+ 111
- 0
app/Filament/Forms/Components/CreateOfferingSelect.php View File

1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use App\Filament\Company\Resources\Common\OfferingResource;
6
+use App\Models\Common\Offering;
7
+use Filament\Forms\Components\Actions\Action;
8
+use Filament\Forms\Components\Select;
9
+use Filament\Forms\Form;
10
+use Filament\Forms\Get;
11
+use Filament\Support\Enums\MaxWidth;
12
+
13
+class CreateOfferingSelect extends Select
14
+{
15
+    protected bool $isPurchasable = true;
16
+
17
+    protected bool $isSellable = true;
18
+
19
+    public function purchasable(bool $condition = true): static
20
+    {
21
+        $this->isPurchasable = $condition;
22
+        $this->isSellable = false;
23
+
24
+        return $this;
25
+    }
26
+
27
+    public function sellable(bool $condition = true): static
28
+    {
29
+        $this->isSellable = $condition;
30
+        $this->isPurchasable = false;
31
+
32
+        return $this;
33
+    }
34
+
35
+    protected function setUp(): void
36
+    {
37
+        parent::setUp();
38
+
39
+        $this
40
+            ->searchable()
41
+            ->preload()
42
+            ->createOptionForm(fn (Form $form) => $this->createOfferingForm($form))
43
+            ->createOptionAction(fn (Action $action) => $this->createOfferingAction($action));
44
+
45
+        $this->relationship(
46
+            name: fn () => $this->isPurchasable() && ! $this->isSellable() ? 'purchasableOffering' : ($this->isSellable() && ! $this->isPurchasable() ? 'sellableOffering' : 'offering'),
47
+            titleAttribute: 'name'
48
+        );
49
+
50
+        $this->createOptionUsing(function (array $data, Form $form) {
51
+            if ($this->isSellableAndPurchasable()) {
52
+                $attributes = array_flip($data['attributes'] ?? []);
53
+
54
+                $data['sellable'] = isset($attributes['Sellable']);
55
+                $data['purchasable'] = isset($attributes['Purchasable']);
56
+            } else {
57
+                $data['sellable'] = $this->isSellable;
58
+                $data['purchasable'] = $this->isPurchasable;
59
+            }
60
+
61
+            unset($data['attributes']);
62
+
63
+            $offering = Offering::create($data);
64
+
65
+            $form->model($offering)->saveRelationships();
66
+
67
+            return $offering->getKey();
68
+        });
69
+    }
70
+
71
+    protected function createOfferingForm(Form $form): Form
72
+    {
73
+        return $form->schema([
74
+            OfferingResource::getGeneralSection($this->isSellableAndPurchasable()),
75
+            OfferingResource::getSellableSection()->visible(
76
+                fn (Get $get) => $this->isSellableAndPurchasable()
77
+                    ? in_array('Sellable', $get('attributes') ?? [])
78
+                    : $this->isSellable()
79
+            ),
80
+            OfferingResource::getPurchasableSection()->visible(
81
+                fn (Get $get) => $this->isSellableAndPurchasable()
82
+                    ? in_array('Purchasable', $get('attributes') ?? [])
83
+                    : $this->isPurchasable()
84
+            ),
85
+        ]);
86
+    }
87
+
88
+    protected function createOfferingAction(Action $action): Action
89
+    {
90
+        return $action
91
+            ->label('Create offering')
92
+            ->slideOver()
93
+            ->modalWidth(MaxWidth::ThreeExtraLarge)
94
+            ->modalHeading('Create a new offering');
95
+    }
96
+
97
+    public function isSellable(): bool
98
+    {
99
+        return $this->isSellable;
100
+    }
101
+
102
+    public function isPurchasable(): bool
103
+    {
104
+        return $this->isPurchasable;
105
+    }
106
+
107
+    public function isSellableAndPurchasable(): bool
108
+    {
109
+        return $this->isSellable && $this->isPurchasable;
110
+    }
111
+}

+ 49
- 0
app/Filament/Forms/Components/CreateVendorSelect.php View File

1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use App\Filament\Company\Resources\Purchases\VendorResource;
6
+use App\Models\Common\Vendor;
7
+use Filament\Forms\Components\Actions\Action;
8
+use Filament\Forms\Components\Select;
9
+use Filament\Forms\Form;
10
+use Filament\Support\Enums\MaxWidth;
11
+use Illuminate\Support\Facades\DB;
12
+
13
+class CreateVendorSelect extends Select
14
+{
15
+    protected function setUp(): void
16
+    {
17
+        parent::setUp();
18
+
19
+        $this
20
+            ->searchable()
21
+            ->preload()
22
+            ->createOptionForm(fn (Form $form) => $this->createVendorForm($form))
23
+            ->createOptionAction(fn (Action $action) => $this->createVendorAction($action));
24
+
25
+        $this->relationship('vendor', 'name');
26
+
27
+        $this->createOptionUsing(static function (array $data) {
28
+            return DB::transaction(static function () use ($data) {
29
+                $vendor = Vendor::createWithRelations($data);
30
+
31
+                return $vendor->getKey();
32
+            });
33
+        });
34
+    }
35
+
36
+    protected function createVendorForm(Form $form): Form
37
+    {
38
+        return VendorResource::form($form);
39
+    }
40
+
41
+    protected function createVendorAction(Action $action): Action
42
+    {
43
+        return $action
44
+            ->label('Create vendor')
45
+            ->slideOver()
46
+            ->modalWidth(MaxWidth::ThreeExtraLarge)
47
+            ->modalHeading('Create a new vendor');
48
+    }
49
+}

+ 180
- 0
app/Models/Common/Client.php View File

35
         'updated_by',
35
         'updated_by',
36
     ];
36
     ];
37
 
37
 
38
+    public static function createWithRelations(array $data): self
39
+    {
40
+        /** @var Client $client */
41
+        $client = self::create($data);
42
+
43
+        if (isset($data['primaryContact'], $data['primaryContact']['first_name'])) {
44
+            $client->primaryContact()->create([
45
+                'is_primary' => true,
46
+                'first_name' => $data['primaryContact']['first_name'],
47
+                'last_name' => $data['primaryContact']['last_name'],
48
+                'email' => $data['primaryContact']['email'],
49
+                'phones' => $data['primaryContact']['phones'] ?? [],
50
+            ]);
51
+        }
52
+
53
+        if (isset($data['secondaryContacts'])) {
54
+            foreach ($data['secondaryContacts'] as $contactData) {
55
+                if (isset($contactData['first_name'])) {
56
+                    $client->secondaryContacts()->create([
57
+                        'is_primary' => false,
58
+                        'first_name' => $contactData['first_name'],
59
+                        'last_name' => $contactData['last_name'],
60
+                        'email' => $contactData['email'],
61
+                        'phones' => $contactData['phones'] ?? [],
62
+                    ]);
63
+                }
64
+            }
65
+        }
66
+
67
+        if (isset($data['billingAddress'], $data['billingAddress']['address_line_1'])) {
68
+            $client->billingAddress()->create([
69
+                'type' => AddressType::Billing,
70
+                'address_line_1' => $data['billingAddress']['address_line_1'],
71
+                'address_line_2' => $data['billingAddress']['address_line_2'] ?? null,
72
+                'country_code' => $data['billingAddress']['country_code'] ?? null,
73
+                'state_id' => $data['billingAddress']['state_id'] ?? null,
74
+                'city' => $data['billingAddress']['city'] ?? null,
75
+                'postal_code' => $data['billingAddress']['postal_code'] ?? null,
76
+            ]);
77
+        }
78
+
79
+        if (isset($data['shippingAddress'])) {
80
+            $shippingData = $data['shippingAddress'];
81
+            $shippingAddress = [
82
+                'type' => AddressType::Shipping,
83
+                'recipient' => $shippingData['recipient'] ?? null,
84
+                'phone' => $shippingData['phone'] ?? null,
85
+                'notes' => $shippingData['notes'] ?? null,
86
+            ];
87
+
88
+            if ($shippingData['same_as_billing'] ?? false) {
89
+                $billingAddress = $client->billingAddress;
90
+                if ($billingAddress) {
91
+                    $shippingAddress = [
92
+                        ...$shippingAddress,
93
+                        'parent_address_id' => $billingAddress->id,
94
+                        'address_line_1' => $billingAddress->address_line_1,
95
+                        'address_line_2' => $billingAddress->address_line_2,
96
+                        'country_code' => $billingAddress->country_code,
97
+                        'state_id' => $billingAddress->state_id,
98
+                        'city' => $billingAddress->city,
99
+                        'postal_code' => $billingAddress->postal_code,
100
+                    ];
101
+                    $client->shippingAddress()->create($shippingAddress);
102
+                }
103
+            } elseif (isset($shippingData['address_line_1'])) {
104
+                $shippingAddress = [
105
+                    ...$shippingAddress,
106
+                    'address_line_1' => $shippingData['address_line_1'],
107
+                    'address_line_2' => $shippingData['address_line_2'] ?? null,
108
+                    'country_code' => $shippingData['country_code'] ?? null,
109
+                    'state_id' => $shippingData['state_id'] ?? null,
110
+                    'city' => $shippingData['city'] ?? null,
111
+                    'postal_code' => $shippingData['postal_code'] ?? null,
112
+                ];
113
+
114
+                $client->shippingAddress()->create($shippingAddress);
115
+            }
116
+        }
117
+
118
+        return $client;
119
+    }
120
+
121
+    public function updateWithRelations(array $data): self
122
+    {
123
+        $this->update($data);
124
+
125
+        if (isset($data['primaryContact'], $data['primaryContact']['first_name'])) {
126
+            $this->primaryContact()->updateOrCreate(
127
+                ['is_primary' => true],
128
+                [
129
+                    'first_name' => $data['primaryContact']['first_name'],
130
+                    'last_name' => $data['primaryContact']['last_name'],
131
+                    'email' => $data['primaryContact']['email'],
132
+                    'phones' => $data['primaryContact']['phones'] ?? [],
133
+                ]
134
+            );
135
+        }
136
+
137
+        if (isset($data['secondaryContacts'])) {
138
+            // Delete removed contacts
139
+            $existingIds = collect($data['secondaryContacts'])->pluck('id')->filter()->all();
140
+            $this->secondaryContacts()->whereNotIn('id', $existingIds)->delete();
141
+
142
+            // Update or create contacts
143
+            foreach ($data['secondaryContacts'] as $contactData) {
144
+                if (isset($contactData['first_name'])) {
145
+                    $this->secondaryContacts()->updateOrCreate(
146
+                        ['id' => $contactData['id'] ?? null],
147
+                        [
148
+                            'is_primary' => false,
149
+                            'first_name' => $contactData['first_name'],
150
+                            'last_name' => $contactData['last_name'],
151
+                            'email' => $contactData['email'],
152
+                            'phones' => $contactData['phones'] ?? [],
153
+                        ]
154
+                    );
155
+                }
156
+            }
157
+        }
158
+
159
+        if (isset($data['billingAddress'], $data['billingAddress']['address_line_1'])) {
160
+            $this->billingAddress()->updateOrCreate(
161
+                ['type' => AddressType::Billing],
162
+                [
163
+                    'address_line_1' => $data['billingAddress']['address_line_1'],
164
+                    'address_line_2' => $data['billingAddress']['address_line_2'] ?? null,
165
+                    'country_code' => $data['billingAddress']['country_code'] ?? null,
166
+                    'state_id' => $data['billingAddress']['state_id'] ?? null,
167
+                    'city' => $data['billingAddress']['city'] ?? null,
168
+                    'postal_code' => $data['billingAddress']['postal_code'] ?? null,
169
+                ]
170
+            );
171
+        }
172
+
173
+        if (isset($data['shippingAddress'])) {
174
+            $shippingData = $data['shippingAddress'];
175
+            $shippingAddress = [
176
+                'type' => AddressType::Shipping,
177
+                'recipient' => $shippingData['recipient'] ?? null,
178
+                'phone' => $shippingData['phone'] ?? null,
179
+                'notes' => $shippingData['notes'] ?? null,
180
+            ];
181
+
182
+            if ($shippingData['same_as_billing'] ?? false) {
183
+                $billingAddress = $this->billingAddress;
184
+                if ($billingAddress) {
185
+                    $shippingAddress = [
186
+                        ...$shippingAddress,
187
+                        'parent_address_id' => $billingAddress->id,
188
+                        'address_line_1' => $billingAddress->address_line_1,
189
+                        'address_line_2' => $billingAddress->address_line_2,
190
+                        'country_code' => $billingAddress->country_code,
191
+                        'state_id' => $billingAddress->state_id,
192
+                        'city' => $billingAddress->city,
193
+                        'postal_code' => $billingAddress->postal_code,
194
+                    ];
195
+                }
196
+            } elseif (isset($shippingData['address_line_1'])) {
197
+                $shippingAddress = [
198
+                    ...$shippingAddress,
199
+                    'parent_address_id' => null,
200
+                    'address_line_1' => $shippingData['address_line_1'],
201
+                    'address_line_2' => $shippingData['address_line_2'] ?? null,
202
+                    'country_code' => $shippingData['country_code'] ?? null,
203
+                    'state_id' => $shippingData['state_id'] ?? null,
204
+                    'city' => $shippingData['city'] ?? null,
205
+                    'postal_code' => $shippingData['postal_code'] ?? null,
206
+                ];
207
+            }
208
+
209
+            $this->shippingAddress()->updateOrCreate(
210
+                ['type' => AddressType::Shipping],
211
+                $shippingAddress
212
+            );
213
+        }
214
+
215
+        return $this;
216
+    }
217
+
38
     public function contacts(): MorphMany
218
     public function contacts(): MorphMany
39
     {
219
     {
40
         return $this->morphMany(Contact::class, 'contactable');
220
         return $this->morphMany(Contact::class, 'contactable');

+ 63
- 0
app/Models/Common/Vendor.php View File

44
         'ein' => 'encrypted',
44
         'ein' => 'encrypted',
45
     ];
45
     ];
46
 
46
 
47
+    public static function createWithRelations(array $data): self
48
+    {
49
+        /** @var Vendor $vendor */
50
+        $vendor = self::create($data);
51
+
52
+        if (isset($data['contact'], $data['contact']['first_name'])) {
53
+            $vendor->contact()->create([
54
+                'is_primary' => true,
55
+                'first_name' => $data['contact']['first_name'],
56
+                'last_name' => $data['contact']['last_name'],
57
+                'email' => $data['contact']['email'],
58
+                'phones' => $data['contact']['phones'] ?? [],
59
+            ]);
60
+        }
61
+
62
+        if (isset($data['address'], $data['address']['type'], $data['address']['address_line_1'])) {
63
+            $vendor->address()->create([
64
+                'type' => $data['address']['type'],
65
+                'address_line_1' => $data['address']['address_line_1'],
66
+                'address_line_2' => $data['address']['address_line_2'] ?? null,
67
+                'country_code' => $data['address']['country_code'] ?? null,
68
+                'state_id' => $data['address']['state_id'] ?? null,
69
+                'city' => $data['address']['city'] ?? null,
70
+                'postal_code' => $data['address']['postal_code'] ?? null,
71
+            ]);
72
+        }
73
+
74
+        return $vendor;
75
+    }
76
+
77
+    public function updateWithRelations(array $data): self
78
+    {
79
+        $this->update($data);
80
+
81
+        if (isset($data['contact'], $data['contact']['first_name'])) {
82
+            $this->contact()->updateOrCreate(
83
+                ['is_primary' => true],
84
+                [
85
+                    'first_name' => $data['contact']['first_name'],
86
+                    'last_name' => $data['contact']['last_name'],
87
+                    'email' => $data['contact']['email'],
88
+                    'phones' => $data['contact']['phones'] ?? [],
89
+                ]
90
+            );
91
+        }
92
+
93
+        if (isset($data['address'], $data['address']['type'], $data['address']['address_line_1'])) {
94
+            $this->address()->updateOrCreate(
95
+                ['type' => $data['address']['type']],
96
+                [
97
+                    'address_line_1' => $data['address']['address_line_1'],
98
+                    'address_line_2' => $data['address']['address_line_2'] ?? null,
99
+                    'country_code' => $data['address']['country_code'] ?? null,
100
+                    'state_id' => $data['address']['state_id'] ?? null,
101
+                    'city' => $data['address']['city'] ?? null,
102
+                    'postal_code' => $data['address']['postal_code'] ?? null,
103
+                ]
104
+            );
105
+        }
106
+
107
+        return $this;
108
+    }
109
+
47
     public function bills(): HasMany
110
     public function bills(): HasMany
48
     {
111
     {
49
         return $this->hasMany(Bill::class);
112
         return $this->hasMany(Bill::class);

+ 172
- 172
composer.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save