Issues (73)

src/Traits/HasVariants.php (5 issues)

1
<?php
2
3
namespace Ronmrcdo\Inventory\Traits;
4
5
use Illuminate\Support\Facades\DB;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Eloquent\Relations\HasMany;
8
use Ronmrcdo\Inventory\Exceptions\InvalidVariantException;
9
use Ronmrcdo\Inventory\Exceptions\InvalidAttributeException;
10
use Illuminate\Database\Eloquent\ModelNotFoundException;
11
use Ronmrcdo\Inventory\Models\ProductVariant;
12
use Ronmrcdo\Inventory\Models\ProductSku;
13
14
trait HasVariants
15
{
16
	/**
17
	 * Add Variant to the product
18
	 * 
19
	 * @param array $variant
20
	 */
21
	public function addVariant($variant)
22
	{
23
		DB::beginTransaction();
24
25
		try {
26
			// if the give given variant array doesn't match the structure we want
27
			// it will automatically throw an InvalidVariant Exception
28
			// Verify if the given variant attributes already exist in the variants db
29
			if (in_array($this->sortAttributes($variant['variation']), $this->getVariants())) {
30
				throw new InvalidVariantException("Duplicate variation attributes!", 400);
31
			}
32
33
			// Create the sku first, so basically you can't add new attributes to the sku
34
			$sku = $this->skus()->create([
35
				'code' => $variant['sku'],
36
				'price' => $variant['price'],
37
				'cost' => $variant['cost']
38
			]);
39
40
			foreach ($variant['variation'] as $item) {
41
				$attribute = $this->attributes()->where('name', $item['option'])->firstOrFail();
0 ignored issues
show
It seems like attributes() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

41
				$attribute = $this->/** @scrutinizer ignore-call */ attributes()->where('name', $item['option'])->firstOrFail();
Loading history...
42
				$value = $attribute->values()->where('value', $item['value'])->firstOrFail();
43
	
44
				$this->variations()->create([
45
					'product_sku_id' => $sku->id,
46
					'product_attribute_id' => $attribute->id,
47
					'product_attribute_value_id' => $value->id
48
				]);
49
			}
50
			
51
			DB::commit();
52
		} catch (ModelNotFoundException $err) {
53
			DB::rollBack();
54
55
			throw new InvalidAttributeException($err->getMessage(), 404);
56
		
57
		} catch (\Throwable $err) {
58
			DB::rollBack();
59
60
			throw new InvalidVariantException($err->getMessage(), 400);
61
		}
62
63
		return $this;
64
	}
65
66
	/**
67
	 * Get the variations
68
	 * 
69
	 */
70
	public function getVariations()
71
	{
72
		return $this->skus;
73
	}
74
75
	/**
76
	 * Get existing variants of the product
77
	 * Note: There was a problem calling $this->variation relationship
78
	 * it doesn't update model about the relationship that's why it always
79
	 * return []
80
	 * 
81
	 * @return array
82
	 */
83
	protected function getVariants(): array
84
	{
85
		$variants = ProductVariant::where('product_id' , $this->id)->get();
86
87
		return $this->transformVariant($variants);
88
	}
89
90
	/**
91
	 * Sort the variant attributes by name. this is a helper function
92
	 * to assert if the variant attributes already exist.
93
	 * 
94
	 * @param array $variant
95
	 * @return array
96
	 */
97
	protected function sortAttributes($variant): array
98
	{
99
		return collect($variant)
100
			->sortBy('option')
101
			->map(function ($item) {
102
				return [
103
					'option' => strtolower($item['option']),
104
					'value' => strtolower($item['value'])
105
				];
106
			})
107
			->values()
108
			->toArray();
109
	}
110
111
	/**
112
	 * Transform the variant to match it to the input
113
	 * variant. To able to assert if the given new variant
114
	 * already exist with the current variations
115
	 * 
116
	 * @param \Ronmrcdo\Inventory\Models\ProductVariant Array<$variants>
0 ignored issues
show
Documentation Bug introduced by
The doc comment Array<$variants> at position 2 could not be parsed: Unknown type name '$variants' at position 2 in Array<$variants>.
Loading history...
117
	 * @return array
118
	 */
119
	protected function transformVariant($variants): array
120
	{
121
		return collect($variants)
122
				->map(function ($item) {
123
					return [
124
						'id' => $item->id,
125
						'sku' => $item->productSku->code,
126
						'attribute' => $item->attribute->name,
127
						'option' => $item->option->value
128
					];
129
				})
130
				->keyBy('id')
131
				->groupBy('sku')
132
				->map(function ($item) {
133
					return collect($item)
134
						->map(function ($var) {
135
							return [
136
								'option' => strtolower($var['attribute']),
137
								'value' => strtolower($var['option'])
138
							];
139
						})
140
						->sortBy('option')
141
						->values()
142
						->toArray();
143
				})
144
				->all();
145
	}
146
147
	/**
148
	 * Assert if the product has any sku given in the db
149
	 * 
150
	 * @return bool
151
	 */
152
	public function hasSku(): bool
153
	{
154
		return !! $this->skus()->count();
155
	}
156
157
	/**
158
	 * Static function that automatically query for the sku
159
	 * 
160
	 * @param string $sku
161
	 * @return \Ronmrcdo\Inventory\Models\Product
162
	 */
163
	public static function findBySku(string $sku)
164
	{
165
		return ProductSku::where('code', $sku)->firstOrFail();
0 ignored issues
show
Bug Best Practice introduced by
The expression return Ronmrcdo\Inventor...', $sku)->firstOrFail() returns the type Ronmrcdo\Inventory\Models\ProductSku which is incompatible with the documented return type Ronmrcdo\Inventory\Models\Product.
Loading history...
166
	}
167
168
	/**
169
	 * Scope for Find Product by sku
170
	 * 
171
	 * @param \Illuminate\Database\Eloquent\Builder  $query
172
	 * @param string $sku
173
	 * @return \Illuminate\Database\Eloquent\Builder
174
	 */
175
	public function scopeWhereSku(Builder $query, string $sku): Builder
176
	{
177
		return $query->whereHas('skus', function ($q) use ($sku) {
178
			$q->where('code', $sku);
179
		});
180
	}
181
182
	/**
183
	 * Create an sku for the product that has no
184
	 * possible variation
185
	 * 
186
	 * @param string $code
187
	 * @throw \Ronmrcdo\Inventory\Exceptions\InvalidVariantException
188
	 * @return void
189
	 */
190
	public function addSku(string $code, $price = 0.00, $cost = 0.00): void
191
	{
192
		if ($this->hasAttributes()) {
0 ignored issues
show
It seems like hasAttributes() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

192
		if ($this->/** @scrutinizer ignore-call */ hasAttributes()) {
Loading history...
193
			throw new InvalidVariantException("Cannot add single SKU due to there's a possible variation", 400);
194
		}
195
196
		$this->skus()->create([
197
			'code' => $code,
198
			'price' => $price,
199
			'cost' => $cost
200
		]);
201
	}
202
203
	/**
204
	 * Product sku relation
205
	 * 
206
	 * @return \Illuminate\Database\Eloquent\Relations\HasMany;
207
	 */
208
	public function skus(): HasMany
209
	{
210
		return $this->hasMany('Ronmrcdo\Inventory\Models\ProductSku');
0 ignored issues
show
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

210
		return $this->/** @scrutinizer ignore-call */ hasMany('Ronmrcdo\Inventory\Models\ProductSku');
Loading history...
211
	}
212
213
	/**
214
	 * Product Variations
215
	 * 
216
	 * @return \Illuminate\Database\Eloquent\Relations\HasMany;
217
	 */
218
	public function variations(): HasMany
219
	{
220
		return $this->hasMany('Ronmrcdo\Inventory\Models\ProductVariant');
221
	}
222
}