Skip to content

Commit 9eba0fa

Browse files
Added subtitle to product forms + improved their layout and validation
1 parent 60fff5e commit 9eba0fa

20 files changed

Lines changed: 405 additions & 366 deletions

Changelog.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
## Unreleased
66
##### 2025-XX-YY
77

8-
- Added the new `priority` field to the product/master/variant screens
9-
- Added sorting by priority to the product list
8+
- Added the new `priority` and `subtitle` fields to the product/master/variant screens
9+
- Added sorting by priority to the product list
10+
- Added validation rule for every product/master/variant field in create/update request classes
11+
- Improved the product/master/variant forms by slight field reorganization and grouping
12+
- Changed the minimum product name length from 2 to 1 (eg. when a t-shirt is of size "S")
13+
- Changed the product/master/variant create/update actions to use `validated()` instead of `all()`
1014
- Fixed the `Attempt to read property "name" on null` error when displaying orders without shipping address
1115
- Fixed the error shown when trying to create a product with an empty `priority` field
1216
- Fixed the invisible validation error due to the collapsed block when the `priority` field is invalid on product forms

src/Http/Controllers/MasterProductController.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
namespace Vanilo\Admin\Http\Controllers;
1616

17+
use Illuminate\Support\Arr;
18+
use Illuminate\Support\Collection;
1719
use Illuminate\Support\Facades\DB;
1820
use Konekt\AppShell\Http\Controllers\BaseController;
1921
use Vanilo\Admin\Contracts\Requests\CreateMasterProduct;
@@ -61,7 +63,13 @@ public function store(CreateMasterProduct $request)
6163
try {
6264
DB::beginTransaction();
6365

64-
$product = MasterProductProxy::create($request->except('images'));
66+
$attributes = $request->validated();
67+
$attributes = match (true) {
68+
is_array($attributes) => Arr::except($attributes, ['images', 'channels']),
69+
$attributes instanceof Collection => $attributes->except(['images', 'channels']),
70+
default => $attributes,
71+
};
72+
$product = MasterProductProxy::create($attributes);
6573
if (Features::isMultiChannelEnabled()) {
6674
$product->assignChannels($request->channels());
6775
}
@@ -107,7 +115,7 @@ public function update(MasterProduct $product, UpdateMasterProduct $request)
107115
try {
108116
DB::transaction(function () use ($product, $request) {
109117
$wantsTaxCategoryUpdate = $product->tax_category_id !== $request->input('tax_category_id');
110-
$product->update($request->all());
118+
$product->update($request->validated());
111119

112120
if ($wantsTaxCategoryUpdate) {
113121
$product->variants()->update(['tax_category_id' => $request->input('tax_category_id')]);

src/Http/Controllers/MasterProductVariantController.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
namespace Vanilo\Admin\Http\Controllers;
1616

17+
use Illuminate\Support\Arr;
18+
use Illuminate\Support\Collection;
1719
use Konekt\AppShell\Http\Controllers\BaseController;
1820
use Vanilo\Admin\Contracts\Requests\CreateMasterProductVariant;
1921
use Vanilo\Admin\Contracts\Requests\UpdateMasterProductVariant;
@@ -40,14 +42,20 @@ public function create(MasterProduct $masterProduct)
4042
public function store(MasterProduct $masterProduct, CreateMasterProductVariant $request)
4143
{
4244
try {
45+
$attributes = $request->validated();
46+
$attributes = match (true) {
47+
is_array($attributes) => Arr::except($attributes, ['images']),
48+
$attributes instanceof Collection => $attributes->except(['images']),
49+
default => $attributes,
50+
};
4351
$variant = MasterProductVariantProxy::create(
4452
array_merge(
4553
[
4654
'master_product_id' => $masterProduct->id,
4755
'tax_category_id' => $masterProduct->tax_category_id,
4856
'priority' => 0,
4957
],
50-
$request->except('images')
58+
$attributes,
5159
)
5260
);
5361
$masterProduct->touch();
@@ -99,7 +107,7 @@ public function edit(MasterProduct $masterProduct, MasterProductVariant $masterP
99107
public function update(MasterProduct $masterProduct, MasterProductVariant $masterProductVariant, UpdateMasterProductVariant $request)
100108
{
101109
try {
102-
$masterProductVariant->update($request->all());
110+
$masterProductVariant->update($request->validated());
103111
$masterProduct->touch();
104112

105113
flash()->success(__(':name has been updated', ['name' => $masterProductVariant->name]));

src/Http/Controllers/ProductController.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
use Illuminate\Http\Request;
1818
use Illuminate\Pagination\LengthAwarePaginator;
19+
use Illuminate\Support\Arr;
20+
use Illuminate\Support\Collection;
1921
use Illuminate\Support\Facades\DB;
2022
use Illuminate\Support\LazyCollection;
2123
use Konekt\AppShell\Filters\Filters;
@@ -129,7 +131,13 @@ public function store(CreateProduct $request)
129131
try {
130132
DB::beginTransaction();
131133

132-
$product = ProductProxy::create($request->except(['images', 'channels']));
134+
$attributes = $request->validated();
135+
$attributes = match (true) {
136+
is_array($attributes) => Arr::except($attributes, ['images', 'channels']),
137+
$attributes instanceof Collection => $attributes->except(['images', 'channels']),
138+
default => $attributes,
139+
};
140+
$product = ProductProxy::create($attributes);
133141
if (Features::isMultiChannelEnabled()) {
134142
$product->assignChannels($request->channels());
135143
}
@@ -184,7 +192,7 @@ public function edit(Product $product)
184192
public function update(Product $product, UpdateProduct $request)
185193
{
186194
try {
187-
$product->update($request->all());
195+
$product->update($request->validated());
188196

189197
flash()->success(__(':name has been updated', ['name' => $product->name]));
190198
} catch (\Exception $e) {

src/Http/Requests/CreateMasterProduct.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,31 @@ class CreateMasterProduct extends FormRequest implements CreateMasterProductCont
2323
{
2424
use HasChannels;
2525

26-
public function rules()
26+
public function rules(): array
2727
{
2828
return [
29-
'name' => 'required|min:2|max:255',
30-
'state' => ['required', Rule::in(ProductStateProxy::values())],
31-
'tax_category_id' => 'sometimes|nullable|exists:tax_categories,id',
32-
'shipping_category_id' => 'sometimes|nullable|exists:shipping_categories,id',
29+
'name' => 'required|min:1|max:255',
30+
'state' => ['required', 'max:255', Rule::in(ProductStateProxy::values())],
31+
'tax_category_id' => 'sometimes|nullable|integer|exists:tax_categories,id',
32+
'shipping_category_id' => 'sometimes|nullable|integer|exists:shipping_categories,id',
3333
'price' => 'nullable|numeric',
3434
'original_price' => 'nullable|numeric',
3535
'description' => 'nullable|string',
3636
'priority' => 'sometimes|nullable|integer',
3737
'images' => 'nullable',
3838
'images.*' => 'image|mimes:jpg,jpeg,pjpg,png,gif,webp',
3939
'channels' => 'sometimes|array',
40+
'excerpt' => 'sometimes|nullable|string|max:16383',
41+
'ext_title' => 'sometimes|nullable|string|max:511',
42+
'subtitle' => 'sometimes|nullable|string|max:255',
43+
'slug' => 'sometimes|nullable|string|max:255',
44+
'meta_keywords' => 'sometimes|nullable|string|max:2047',
45+
'meta_description' => 'sometimes|nullable|string|max:4095',
46+
'weight' => 'sometimes|nullable|numeric',
47+
'height' => 'sometimes|nullable|numeric',
48+
'width' => 'sometimes|nullable|numeric',
49+
'length' => 'sometimes|nullable|numeric',
50+
'custom_attributes' => 'sometimes|nullable|array',
4051
];
4152
}
4253

src/Http/Requests/CreateMasterProductVariant.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,31 @@
2222

2323
class CreateMasterProductVariant extends FormRequest implements CreateMasterProductVariantContract
2424
{
25-
public function rules()
25+
public function rules(): array
2626
{
2727
return [
2828
'name' => 'required|min:1|max:255',
29-
'sku' => 'required|unique:products',
30-
'shipping_category_id' => 'sometimes|nullable|exists:shipping_categories,id',
29+
'sku' => 'required|max:255|unique:master_product_variants',
30+
'shipping_category_id' => 'sometimes|nullable|integer|exists:shipping_categories,id',
31+
'tax_category_id' => 'sometimes|nullable|integer|exists:tax_categories,id', // This field is not present on the form
3132
'price' => 'nullable|numeric',
3233
'original_price' => 'sometimes|nullable|numeric',
3334
'stock' => 'nullable|numeric',
3435
'priority' => 'sometimes|nullable|integer',
3536
'backorder' => 'nullable|numeric|min:0',
36-
'excerpt' => 'sometimes|nullable|max:8192',
37-
'description' => 'sometimes|nullable|max:32768',
37+
'excerpt' => 'sometimes|nullable|string|max:16383',
38+
'description' => 'sometimes|nullable|string',
3839
'images' => 'nullable',
3940
'images.*' => 'image|mimes:jpg,jpeg,pjpg,png,gif,webp',
4041
'state' => ['sometimes', 'nullable', Rule::in(ProductStateProxy::values())],
4142
'gtin' => ['bail', 'sometimes', 'nullable', new MustBeAValidGtin()],
43+
'subtitle' => 'sometimes|nullable|string|max:255',
44+
'slug' => 'sometimes|nullable|string|max:255',
45+
'weight' => 'sometimes|nullable|numeric',
46+
'height' => 'sometimes|nullable|numeric',
47+
'width' => 'sometimes|nullable|numeric',
48+
'length' => 'sometimes|nullable|numeric',
49+
'custom_attributes' => 'sometimes|nullable|array',
4250
];
4351
}
4452

src/Http/Requests/CreateProduct.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ class CreateProduct extends FormRequest implements CreateProductContract
2323
{
2424
use HasChannels;
2525

26-
public function rules()
26+
public function rules(): array
2727
{
2828
return [
29-
'name' => 'required|min:2|max:255',
30-
'sku' => 'required|unique:products',
31-
'state' => ['required', Rule::in(ProductStateProxy::values())],
32-
'tax_category_id' => 'sometimes|nullable|exists:tax_categories,id',
33-
'shipping_category_id' => 'sometimes|nullable|exists:shipping_categories,id',
29+
'name' => 'required|min:1|max:255',
30+
'sku' => 'required|max:255|unique:products',
31+
'state' => ['required', 'max:255', Rule::in(ProductStateProxy::values())],
32+
'tax_category_id' => 'sometimes|nullable|integer|exists:tax_categories,id',
33+
'shipping_category_id' => 'sometimes|nullable|integer|exists:shipping_categories,id',
3434
'price' => 'nullable|numeric',
3535
'original_price' => 'sometimes|nullable|numeric',
3636
'stock' => 'nullable|numeric',
@@ -40,6 +40,18 @@ public function rules()
4040
'images.*' => 'image|mimes:jpg,jpeg,pjpg,png,gif,webp',
4141
'channels' => 'sometimes|array',
4242
'gtin' => ['bail', 'sometimes', 'nullable', new MustBeAValidGtin()],
43+
'excerpt' => 'sometimes|nullable|string|max:16383',
44+
'meta_keywords' => 'sometimes|nullable|string|max:2047',
45+
'meta_description' => 'sometimes|nullable|string|max:4095',
46+
'ext_title' => 'sometimes|nullable|string|max:511',
47+
'subtitle' => 'sometimes|nullable|string|max:255',
48+
'slug' => 'sometimes|nullable|string|max:255',
49+
'description' => 'sometimes|nullable|string',
50+
'weight' => 'sometimes|nullable|numeric',
51+
'height' => 'sometimes|nullable|numeric',
52+
'width' => 'sometimes|nullable|numeric',
53+
'length' => 'sometimes|nullable|numeric',
54+
'custom_attributes' => 'sometimes|nullable|array',
4355
];
4456
}
4557

src/Http/Requests/UpdateMasterProduct.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,31 @@
2121

2222
class UpdateMasterProduct extends FormRequest implements UpdateMasterProductContract
2323
{
24-
public function rules()
24+
public function rules(): array
2525
{
2626
return [
27-
'name' => 'required|min:2|max:255',
28-
'state' => ['required', Rule::in(ProductStateProxy::values())],
29-
'tax_category_id' => 'sometimes|nullable|exists:tax_categories,id',
30-
'shipping_category_id' => 'sometimes|nullable|exists:shipping_categories,id',
27+
'name' => 'required|min:1|max:255',
28+
'state' => ['required', 'max:255', Rule::in(ProductStateProxy::values())],
29+
'tax_category_id' => 'sometimes|nullable|integer|exists:tax_categories,id',
30+
'shipping_category_id' => 'sometimes|nullable|integer|exists:shipping_categories,id',
3131
'price' => 'nullable|numeric',
3232
'original_price' => 'nullable|numeric',
3333
'description' => 'nullable|string',
3434
'priority' => 'sometimes|integer',
3535
'images' => 'nullable',
36-
'images.*' => 'image|mimes:jpg,jpeg,pjpg,png,gif,webp'
36+
'images.*' => 'image|mimes:jpg,jpeg,pjpg,png,gif,webp',
37+
'channels' => 'sometimes|array',
38+
'excerpt' => 'sometimes|nullable|string|max:16383',
39+
'ext_title' => 'sometimes|nullable|string|max:511',
40+
'subtitle' => 'sometimes|nullable|string|max:255',
41+
'slug' => 'sometimes|nullable|string|max:255',
42+
'meta_keywords' => 'sometimes|nullable|string|max:2047',
43+
'meta_description' => 'sometimes|nullable|string|max:4095',
44+
'weight' => 'sometimes|nullable|numeric',
45+
'height' => 'sometimes|nullable|numeric',
46+
'width' => 'sometimes|nullable|numeric',
47+
'length' => 'sometimes|nullable|numeric',
48+
'custom_attributes' => 'sometimes|nullable|array',
3749
];
3850
}
3951

src/Http/Requests/UpdateMasterProductVariant.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,35 @@
2222

2323
class UpdateMasterProductVariant extends FormRequest implements UpdateMasterProductVariantContract
2424
{
25-
public function rules()
25+
public function rules(): array
2626
{
2727
return [
2828
'name' => 'required|min:1|max:255',
29-
'sku' => 'required|unique:products',
29+
'sku' => [
30+
'required',
31+
'max:255',
32+
Rule::unique('master_product_variants')->ignore($this->route('masterProductVariant')->id),
33+
],
3034
'price' => 'nullable|numeric',
3135
'original_price' => 'sometimes|nullable|numeric',
32-
'shipping_category_id' => 'sometimes|nullable|exists:shipping_categories,id',
36+
'shipping_category_id' => 'sometimes|nullable|integer|exists:shipping_categories,id',
37+
'tax_category_id' => 'sometimes|nullable|integer|exists:tax_categories,id', // This field is not present on the form
3338
'stock' => 'nullable|numeric',
3439
'backorder' => 'nullable|numeric|min:0',
35-
'excerpt' => 'sometimes|nullable|max:8192',
36-
'description' => 'sometimes|nullable|max:32768',
40+
'excerpt' => 'sometimes|nullable|string|max:16383',
41+
'description' => 'sometimes|nullable|string',
3742
'priority' => 'sometimes|integer',
3843
'images' => 'nullable',
3944
'images.*' => 'image|mimes:jpg,jpeg,pjpg,png,gif,webp',
4045
'state' => ['sometimes', 'nullable', Rule::in(ProductStateProxy::values())],
4146
'gtin' => ['bail', 'sometimes', 'nullable', new MustBeAValidGtin()],
47+
'subtitle' => 'sometimes|nullable|string|max:255',
48+
'slug' => 'sometimes|nullable|string|max:255',
49+
'weight' => 'sometimes|nullable|numeric',
50+
'height' => 'sometimes|nullable|numeric',
51+
'width' => 'sometimes|nullable|numeric',
52+
'length' => 'sometimes|nullable|numeric',
53+
'custom_attributes' => 'sometimes|nullable|array',
4254
];
4355
}
4456

src/Http/Requests/UpdateProduct.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@
2121

2222
class UpdateProduct extends FormRequest implements UpdateProductContract
2323
{
24-
public function rules()
24+
public function rules(): array
2525
{
2626
return [
27-
'name' => 'required|min:2|max:255',
27+
'name' => 'required|min:1|max:255',
2828
'sku' => [
2929
'required',
30+
'max:255',
3031
Rule::unique('products')->ignore($this->route('product')->id),
31-
],
32-
'state' => ['required', Rule::in(ProductStateProxy::values())],
33-
'tax_category_id' => 'sometimes|nullable|exists:tax_categories,id',
34-
'shipping_category_id' => 'sometimes|nullable|exists:shipping_categories,id',
32+
],
33+
'state' => ['required', 'max:255', Rule::in(ProductStateProxy::values())],
34+
'tax_category_id' => 'sometimes|nullable|integer|exists:tax_categories,id',
35+
'shipping_category_id' => 'sometimes|nullable|integer|exists:shipping_categories,id',
3536
'price' => 'nullable|numeric',
3637
'original_price' => 'sometimes|nullable|numeric',
3738
'stock' => 'nullable|numeric',
@@ -40,6 +41,18 @@ public function rules()
4041
'images' => 'nullable',
4142
'images.*' => 'image|mimes:jpg,jpeg,pjpg,png,gif,webp',
4243
'gtin' => ['bail', 'sometimes', 'nullable', new MustBeAValidGtin()],
44+
'excerpt' => 'sometimes|nullable|string|max:16383',
45+
'meta_keywords' => 'sometimes|nullable|string|max:2047',
46+
'meta_description' => 'sometimes|nullable|string|max:4095',
47+
'ext_title' => 'sometimes|nullable|string|max:511',
48+
'subtitle' => 'sometimes|nullable|string|max:255',
49+
'slug' => 'sometimes|nullable|string|max:255',
50+
'description' => 'sometimes|nullable|string',
51+
'weight' => 'sometimes|nullable|numeric',
52+
'height' => 'sometimes|nullable|numeric',
53+
'width' => 'sometimes|nullable|numeric',
54+
'length' => 'sometimes|nullable|numeric',
55+
'custom_attributes' => 'sometimes|nullable|array',
4356
];
4457
}
4558

@@ -48,6 +61,12 @@ public function authorize()
4861
return true;
4962
}
5063

64+
public function attributesForCreate(): array
65+
{
66+
$except = ['id', 'deleted_at', 'updated_at', 'created_at', 'units_sold', 'last_sale_at'];
67+
return $this->validated(array_keys($this->rules()),['']);
68+
}
69+
5170
protected function prepareForValidation()
5271
{
5372
$this->merge([

0 commit comments

Comments
 (0)