Skip to content

Commit 16f2639

Browse files
committed
fix(modules): Fix handling kit products on checkout
Refactoring fix items script with general fixes
1 parent e932e01 commit 16f2639

File tree

1 file changed

+146
-191
lines changed
  • packages/modules/src/firebase/functions-checkout

1 file changed

+146
-191
lines changed
Lines changed: 146 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,42 @@
1-
import type { ResourceId, Products, CheckoutBody } from '@cloudcommerce/types';
1+
import type { Products, CheckoutBody } from '@cloudcommerce/types';
22
import type { Items, Item } from '../../types';
33
import api from '@cloudcommerce/api';
44
import { logger } from '@cloudcommerce/firebase/lib/config';
55

66
type BodyCheckItem = Products | Products & Exclude<Products['variations'], undefined>[number] | undefined;
77

8-
const checkOnPromotion = (product: Products) => {
8+
const checkOnPromotion = (product: Products | Item) => {
99
if (typeof product !== 'object' || product === null) {
10-
// prevent fatal error
1110
logger.error(new Error('`product` must be an object'));
1211
return false;
1312
}
14-
15-
const promoDates = product.price_effective_date;
13+
const promoDates = (product as Products).price_effective_date;
1614
if (promoDates) {
1715
const now = new Date();
1816
if (promoDates.start) {
19-
// start date and time in ISO 8601
2017
if (new Date(promoDates.start) > now) {
2118
return false;
2219
}
2320
}
2421
if (promoDates.end) {
25-
// promotion end date and time in ISO 8601
2622
if (new Date(promoDates.end) < now) {
2723
return false;
2824
}
2925
}
3026
}
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));
3628
};
3729

38-
const getPrice = (product: Products, item: Item) => {
39-
// promotional sale price
30+
const getPrice = (product: Products | Item) => {
4031
if (checkOnPromotion(product)) {
4132
return product.price;
4233
}
4334
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);
5139
}
52-
// default to zero
5340
return 0;
5441
};
5542

@@ -104,186 +91,154 @@ export default async (
10491
doFinally();
10592
};
10693

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);
128160
}
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+
}
163169

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;
177194
}
195+
});
196+
if (!isFixedQuantity) {
197+
// use parent product min quantity
198+
packQuantity = kitProduct.min_quantity || 0;
178199
}
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;
202209
}
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;
231220
}
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;
242224
}
225+
proceedItem();
226+
continue;
243227
}
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();
266228
}
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;
285238
}
286239
}
240+
doFinally();
287241
}
242+
288243
return items;
289244
};

0 commit comments

Comments
 (0)