|
1 |
| -import type { ResourceId, Products, CheckoutBody } from '@cloudcommerce/types'; |
| 1 | +import type { Products, CheckoutBody } from '@cloudcommerce/types'; |
2 | 2 | import type { Items, Item } from '../../types';
|
3 | 3 | import api from '@cloudcommerce/api';
|
4 | 4 | import { logger } from '@cloudcommerce/firebase/lib/config';
|
5 | 5 |
|
6 | 6 | type BodyCheckItem = Products | Products & Exclude<Products['variations'], undefined>[number] | undefined;
|
7 | 7 |
|
8 |
| -const checkOnPromotion = (product: Products) => { |
| 8 | +const checkOnPromotion = (product: Products | Item) => { |
9 | 9 | if (typeof product !== 'object' || product === null) {
|
10 |
| - // prevent fatal error |
11 | 10 | logger.error(new Error('`product` must be an object'));
|
12 | 11 | return false;
|
13 | 12 | }
|
14 |
| - |
15 |
| - const promoDates = product.price_effective_date; |
| 13 | + const promoDates = (product as Products).price_effective_date; |
16 | 14 | if (promoDates) {
|
17 | 15 | const now = new Date();
|
18 | 16 | if (promoDates.start) {
|
19 |
| - // start date and time in ISO 8601 |
20 | 17 | if (new Date(promoDates.start) > now) {
|
21 | 18 | return false;
|
22 | 19 | }
|
23 | 20 | }
|
24 | 21 | if (promoDates.end) {
|
25 |
| - // promotion end date and time in ISO 8601 |
26 | 22 | if (new Date(promoDates.end) < now) {
|
27 | 23 | return false;
|
28 | 24 | }
|
29 | 25 | }
|
30 | 26 | }
|
31 |
| - // default to no promotion |
32 |
| - if (product.base_price && product.price) { |
33 |
| - return !!(product.base_price > product.price); |
34 |
| - } |
35 |
| - return false; |
| 27 | + return !!((product.base_price || 0) > (product.price || 0)); |
36 | 28 | };
|
37 | 29 |
|
38 |
| -const getPrice = (product: Products, item: Item) => { |
39 |
| - // promotional sale price |
| 30 | +const getPrice = (product: Products | Item) => { |
40 | 31 | if (checkOnPromotion(product)) {
|
41 | 32 | return product.price;
|
42 | 33 | }
|
43 | 34 | if (product) {
|
44 |
| - // Products do not have final_price properties |
45 |
| - // test final price for cart item object |
46 |
| - return (typeof item.final_price === 'number' |
47 |
| - ? item.final_price |
48 |
| - // use the maximum value between sale and base price |
49 |
| - : Math.max(product.base_price || 0, product.price || 0) |
50 |
| - ); |
| 35 | + if (typeof (product as Item).final_price === 'number') { |
| 36 | + return (product as Item).final_price; |
| 37 | + } |
| 38 | + return Math.max(product.base_price || 0, product.price || 0); |
51 | 39 | }
|
52 |
| - // default to zero |
53 | 40 | return 0;
|
54 | 41 | };
|
55 | 42 |
|
@@ -104,186 +91,154 @@ export default async (
|
104 | 91 | doFinally();
|
105 | 92 | };
|
106 | 93 |
|
107 |
| - const checkItem = (product:Products) => { |
108 |
| - if (!product.available) { |
109 |
| - removeItem(); |
110 |
| - } else { |
111 |
| - let body: BodyCheckItem; |
112 |
| - |
113 |
| - // check variation if any |
114 |
| - if (!item.variation_id) { |
115 |
| - body = product; |
116 |
| - } else { |
117 |
| - // find respective variation |
118 |
| - let variation:Exclude<Products['variations'], undefined>[number] | undefined; |
119 |
| - if (product.variations) { |
120 |
| - variation = product.variations.find( |
121 |
| - (variationFind) => variationFind._id === item.variation_id, |
122 |
| - ); |
123 |
| - } |
124 |
| - if (variation) { |
125 |
| - // merge product body with variation object |
126 |
| - body = Object.assign(product, variation); |
127 |
| - } |
| 94 | + let product: Products | undefined; |
| 95 | + try { |
| 96 | + // eslint-disable-next-line no-await-in-loop |
| 97 | + product = (await api.get(`products/${item.product_id}`, { |
| 98 | + headers: { 'x-primary-db': 'true' }, |
| 99 | + })).data; |
| 100 | + } catch (err) { |
| 101 | + logger.error(err); |
| 102 | + removeItem(); |
| 103 | + } |
| 104 | + if (!product?.available) { |
| 105 | + removeItem(); |
| 106 | + continue; |
| 107 | + } |
| 108 | + let body: BodyCheckItem; |
| 109 | + // check variation if any |
| 110 | + if (!item.variation_id) { |
| 111 | + body = product; |
| 112 | + } else { |
| 113 | + // find respective variation |
| 114 | + const variation = product.variations?.find((_variation) => { |
| 115 | + return _variation._id === item.variation_id; |
| 116 | + }); |
| 117 | + if (variation) { |
| 118 | + // merge product body with variation object |
| 119 | + body = Object.assign(product, variation); |
| 120 | + } |
| 121 | + } |
| 122 | + if (!body || (body.min_quantity && body.min_quantity > item.quantity)) { |
| 123 | + // cannot handle current item |
| 124 | + // invalid variation or quantity lower then minimum |
| 125 | + removeItem(); |
| 126 | + continue; |
| 127 | + } |
| 128 | + // check quantity |
| 129 | + if (body.quantity && body.quantity < item.quantity) { |
| 130 | + // reduce to max available quantity |
| 131 | + item.quantity = body.quantity; |
| 132 | + } |
| 133 | + // extend item properties with body |
| 134 | + [ |
| 135 | + 'sku', |
| 136 | + 'name', |
| 137 | + 'currency_id', |
| 138 | + 'currency_symbol', |
| 139 | + 'price', |
| 140 | + 'dimensions', |
| 141 | + 'weight', |
| 142 | + 'production_time', |
| 143 | + 'categories', |
| 144 | + 'brands', |
| 145 | + ].forEach((prop) => { |
| 146 | + if (body && body[prop] !== undefined) { |
| 147 | + item[prop] = body[prop]; |
| 148 | + } |
| 149 | + }); |
| 150 | + // price is required |
| 151 | + if (!item.price) { |
| 152 | + item.price = 0; |
| 153 | + } |
| 154 | + if (Array.isArray(item.flags)) { |
| 155 | + // prevent error with repeated flags |
| 156 | + const flags: string[] = []; |
| 157 | + item.flags.forEach((flag) => { |
| 158 | + if (!flags.includes(flag)) { |
| 159 | + flags.push(flag); |
128 | 160 | }
|
129 |
| - // logger.log(body._id) |
130 |
| - |
131 |
| - if (!body || (body.min_quantity && body.min_quantity > item.quantity)) { |
132 |
| - // cannot handle current item |
133 |
| - // invalid variation or quantity lower then minimum |
134 |
| - removeItem(); |
135 |
| - } else { |
136 |
| - // check quantity |
137 |
| - if (body.quantity && body.quantity < item.quantity) { |
138 |
| - // reduce to max available quantity |
139 |
| - item.quantity = body.quantity; |
140 |
| - } |
141 |
| - |
142 |
| - // extend item properties with body |
143 |
| - [ |
144 |
| - 'sku', |
145 |
| - 'name', |
146 |
| - 'currency_id', |
147 |
| - 'currency_symbol', |
148 |
| - 'price', |
149 |
| - 'dimensions', |
150 |
| - 'weight', |
151 |
| - 'production_time', |
152 |
| - 'categories', |
153 |
| - 'brands', |
154 |
| - ].forEach((prop) => { |
155 |
| - if (body && body[prop] !== undefined) { |
156 |
| - item[prop] = body[prop]; |
157 |
| - } |
158 |
| - }); |
159 |
| - // price is required |
160 |
| - if (!item.price) { |
161 |
| - item.price = 0; |
162 |
| - } |
| 161 | + }); |
| 162 | + item.flags = flags; |
| 163 | + } |
| 164 | + if (!item.kit_product) { |
| 165 | + item.final_price = getPrice(body); |
| 166 | + proceedItem(); |
| 167 | + continue; |
| 168 | + } |
163 | 169 |
|
164 |
| - if (Array.isArray(item.flags)) { |
165 |
| - // prevent error with repeated flags |
166 |
| - const flags: string[] = []; |
167 |
| - item.flags.forEach((flag) => { |
168 |
| - if (!flags.includes(flag)) { |
169 |
| - flags.push(flag); |
170 |
| - } |
171 |
| - }); |
172 |
| - item.flags = flags; |
173 |
| - } |
174 |
| - // |
175 |
| - item.final_price = getPrice(body, item); |
176 |
| - proceedItem(); |
| 170 | + const kitProductId = item.kit_product._id; |
| 171 | + let kitProduct: Products | undefined; |
| 172 | + try { |
| 173 | + // eslint-disable-next-line no-await-in-loop |
| 174 | + kitProduct = (await api.get(`products/${kitProductId}`, { |
| 175 | + headers: { 'x-primary-db': 'true' }, |
| 176 | + })).data; |
| 177 | + } catch (err) { |
| 178 | + logger.error(err); |
| 179 | + removeItem(); |
| 180 | + } |
| 181 | + if (kitProduct?.available && kitProduct.kit_composition) { |
| 182 | + // check kit composition and quantities |
| 183 | + let packQuantity = 0; |
| 184 | + let isFixedQuantity = true; |
| 185 | + let kitItem: Exclude<Products['kit_composition'], undefined>[number] | undefined; |
| 186 | + kitProduct.kit_composition.forEach((currentKitItem) => { |
| 187 | + if (currentKitItem.quantity) { |
| 188 | + packQuantity += currentKitItem.quantity; |
| 189 | + } else if (isFixedQuantity) { |
| 190 | + isFixedQuantity = false; |
| 191 | + } |
| 192 | + if (currentKitItem._id === item.product_id) { |
| 193 | + kitItem = currentKitItem; |
177 | 194 | }
|
| 195 | + }); |
| 196 | + if (!isFixedQuantity) { |
| 197 | + // use parent product min quantity |
| 198 | + packQuantity = kitProduct.min_quantity || 0; |
178 | 199 | }
|
179 |
| - }; |
180 |
| - |
181 |
| - const checkKitProduct = (kitProduct: Products, kitProductId: string) => { |
182 |
| - if (item.kit_product) { |
183 |
| - if (kitProduct.available && kitProduct.kit_composition) { |
184 |
| - // check kit composition and quantities |
185 |
| - let packQuantity = 0; |
186 |
| - let isFixedQuantity = true; |
187 |
| - let kitItem: Exclude<Products['kit_composition'], undefined>[number] | undefined; |
188 |
| - |
189 |
| - kitProduct.kit_composition.forEach((currentKitItem) => { |
190 |
| - if (currentKitItem.quantity) { |
191 |
| - packQuantity += currentKitItem.quantity; |
192 |
| - } else if (isFixedQuantity) { |
193 |
| - isFixedQuantity = false; |
194 |
| - } |
195 |
| - if (currentKitItem._id === item.product_id) { |
196 |
| - kitItem = currentKitItem; |
197 |
| - } |
198 |
| - }); |
199 |
| - if (!isFixedQuantity && kitProduct.min_quantity) { |
200 |
| - // use parent product min quantity |
201 |
| - packQuantity = kitProduct.min_quantity; |
| 200 | + if ( |
| 201 | + kitItem |
| 202 | + && (kitItem.quantity === undefined || item.quantity % kitItem.quantity === 0) |
| 203 | + ) { |
| 204 | + // valid kit item and quantity |
| 205 | + let kitTotalQuantity = 0; |
| 206 | + items.forEach((_item) => { |
| 207 | + if (_item.kit_product?._id === kitProduct._id) { |
| 208 | + kitTotalQuantity += _item.quantity; |
202 | 209 | }
|
203 |
| - |
204 |
| - if (kitItem && (kitItem.quantity === undefined |
205 |
| - || item.quantity % kitItem.quantity === 0)) { |
206 |
| - // valid kit item and quantity |
207 |
| - let kitTotalQuantity = 0; |
208 |
| - items.forEach((itemFind) => { |
209 |
| - if (itemFind.kit_product && itemFind.kit_product._id === kitProductId) { |
210 |
| - kitTotalQuantity += itemFind.quantity; |
211 |
| - } |
212 |
| - }); |
213 |
| - |
214 |
| - const minPacks = kitItem.quantity |
215 |
| - ? item.quantity / kitItem.quantity |
216 |
| - : 1; |
217 |
| - if (kitTotalQuantity && kitTotalQuantity % (minPacks * packQuantity) === 0) { |
218 |
| - // matched pack quantity |
219 |
| - item.kit_product.price = getPrice(kitProduct, item); |
220 |
| - item.kit_product.pack_quantity = packQuantity; |
221 |
| - /* TODO: slug is not a property on item |
222 |
| - if (kitProduct.slug) { |
223 |
| - item.slug = kitProduct.slug; |
224 |
| - } */ |
225 |
| - if (item.kit_product.price) { |
226 |
| - // set final price from kit |
227 |
| - item.final_price = item.kit_product.price / packQuantity; |
228 |
| - } |
229 |
| - proceedItem(); |
230 |
| - } |
| 210 | + }); |
| 211 | + const minPacks = kitItem.quantity |
| 212 | + ? item.quantity / kitItem.quantity |
| 213 | + : 1; |
| 214 | + if (kitTotalQuantity && kitTotalQuantity % (minPacks * packQuantity) === 0) { |
| 215 | + // matched pack quantity |
| 216 | + item.kit_product.price = getPrice(kitProduct); |
| 217 | + item.kit_product.pack_quantity = packQuantity; |
| 218 | + if (kitProduct.slug) { |
| 219 | + item.slug = kitProduct.slug; |
231 | 220 | }
|
232 |
| - } |
233 |
| - |
234 |
| - // remove items with invalid kit |
235 |
| - let index = 0; |
236 |
| - while (index < items.length) { |
237 |
| - const itemKit = items[index].kit_product; |
238 |
| - if (itemKit && itemKit._id === kitProductId) { |
239 |
| - items.splice(index, 1); |
240 |
| - } else { |
241 |
| - index += 1; |
| 221 | + if (item.kit_product.price) { |
| 222 | + // set final price from kit |
| 223 | + item.final_price = item.kit_product.price / packQuantity; |
242 | 224 | }
|
| 225 | + proceedItem(); |
| 226 | + continue; |
243 | 227 | }
|
244 |
| - doFinally(); |
245 |
| - } |
246 |
| - }; |
247 |
| - |
248 |
| - if (item.kit_product) { |
249 |
| - // GET public kit product object |
250 |
| - const kitProductId = item.kit_product._id; |
251 |
| - try { |
252 |
| - // eslint-disable-next-line no-await-in-loop |
253 |
| - const kitProduct = (await api.get(`products/${kitProductId}`, { |
254 |
| - isNoAuth: true, |
255 |
| - })).data; |
256 |
| - |
257 |
| - if (kitProduct) { |
258 |
| - checkKitProduct(kitProduct, kitProductId); |
259 |
| - } else { |
260 |
| - removeItem(); |
261 |
| - } |
262 |
| - } catch (err) { |
263 |
| - logger.error(err); |
264 |
| - // remove cart item |
265 |
| - removeItem(); |
266 | 228 | }
|
267 |
| - } else { |
268 |
| - // GET public product object |
269 |
| - try { |
270 |
| - // eslint-disable-next-line no-await-in-loop |
271 |
| - const product = (await api.get(`products/${item.product_id as ResourceId}`, { |
272 |
| - isNoAuth: true, |
273 |
| - })).data; |
274 |
| - |
275 |
| - if (product) { |
276 |
| - checkItem(product); |
277 |
| - } else { |
278 |
| - // remove cart item |
279 |
| - removeItem(); |
280 |
| - } |
281 |
| - } catch (err) { |
282 |
| - logger.error(err); |
283 |
| - // remove cart item |
284 |
| - removeItem(); |
| 229 | + } |
| 230 | + // remove all items with invalid kit |
| 231 | + let ii = 0; |
| 232 | + while (ii < items.length) { |
| 233 | + const itemKit = items[ii].kit_product; |
| 234 | + if (itemKit && itemKit._id === kitProductId) { |
| 235 | + items.splice(ii, 1); |
| 236 | + } else { |
| 237 | + ii += 1; |
285 | 238 | }
|
286 | 239 | }
|
| 240 | + doFinally(); |
287 | 241 | }
|
| 242 | + |
288 | 243 | return items;
|
289 | 244 | };
|
0 commit comments