Skip to content

Commit

Permalink
Merge pull request #131 from vtex-apps/fix/QUICKORDER-33
Browse files Browse the repository at this point in the history
[QUICKORDER-33] Add partial availability check
  • Loading branch information
AnnaChiu95 authored Oct 12, 2022
2 parents 158efc3 + 5510d47 commit 7456148
Show file tree
Hide file tree
Showing 17 changed files with 128 additions and 30 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Partial availability error message in `ReviewBlock` when full item quantity entered is not available in selected seller

### Changed

- Run checkout simulation with item quantity input in `TextArea` and `Upload` blocks
- Check for SKU match in full `items` list from `productSuggestions` query results

## [3.11.0] - 2022-10-06

### Added
Expand Down
3 changes: 3 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ type Query {
refids: [String]
orderFormId: String
refIdSellerMap: RefIdSellerMap
refIdQuantityMap: RefIdQuantityMap
): Refids @cacheControl(scope: SEGMENT, maxAge: MEDIUM) @withSegment
sellers: SellersType
@cacheControl(scope: SEGMENT, maxAge: MEDIUM)
@withSegment
}

scalar RefIdSellerMap

scalar RefIdQuantityMap
1 change: 1 addition & 0 deletions graphql/types/Refids.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ type ItemsSeller {
name: String
availability: String
unitMultiplier: Float
availableQuantity: Int
}
1 change: 1 addition & 0 deletions messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"store/quickorder.withoutPriceFulfillment": "المنتج بدون سعر",
"store/quickorder.withoutStock": "بدون مخزون",
"store/quickorder.limited": "Restricted Item",
"store/quickorder.partiallyAvailable": "الكمية القصوى هي {quantity}. فقط {totalQuantity} وحدات إجمالية متاحة.",
"store/toaster.cart.duplicated": "عنصر مكرر",
"store/toaster.cart.error": "خطأ في إضافة المنتجات إلى عربة التسوق",
"store/toaster.cart.seeCart": "مشاهدة العربة",
Expand Down
1 change: 1 addition & 0 deletions messages/context.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"store/quickorder.withoutPriceFulfillment": "store/quickorder.withoutPriceFulfillment",
"store/quickorder.withoutStock": "store/quickorder.withoutStock",
"store/quickorder.limited": "store/quickorder.limited",
"store/quickorder.partiallyAvailable": "store/quickorder.partiallyAvailable",
"store/toaster.cart.duplicated": "store/toaster.cart.duplicated",
"store/toaster.cart.error": "store/toaster.cart.error",
"store/toaster.cart.seeCart": "store/toaster.cart.seeCart",
Expand Down
1 change: 1 addition & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"store/quickorder.withoutPriceFulfillment": "Product without price",
"store/quickorder.withoutStock": "Without stock",
"store/quickorder.limited": "Restricted Item",
"store/quickorder.partiallyAvailable": "Max quantity is {quantity}. Only {totalQuantity} total units available.",
"store/toaster.cart.duplicated": "Item duplicated",
"store/toaster.cart.error": "Error adding products to the cart",
"store/toaster.cart.seeCart": "See cart",
Expand Down
1 change: 1 addition & 0 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"store/quickorder.withoutPriceFulfillment": "Producto sin precio",
"store/quickorder.withoutStock": "Sin stock",
"store/quickorder.limited": "Restricted Item",
"store/quickorder.partiallyAvailable": "La cantidad máxima es {quantity}. Solo hay {totalQuantity} unidades disponibles.",
"store/toaster.cart.duplicated": "Elemento duplicado",
"store/toaster.cart.error": "Error al agregar productos al carrito",
"store/toaster.cart.seeCart": "Ver el carrito",
Expand Down
1 change: 1 addition & 0 deletions messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"store/quickorder.withoutPriceFulfillment": "가격정보가 없습니다",
"store/quickorder.withoutStock": "재고 부족",
"store/quickorder.limited": "Restricted Item",
"store/quickorder.partiallyAvailable": "최대 수량은 {quantity}입니다. {totalQuantity} 총 단위만 사용할 수 있습니다.",
"store/toaster.cart.duplicated": "중복된 항목이 있습니다",
"store/toaster.cart.error": "장바구니에 담기가 실패하였습니다",
"store/toaster.cart.seeCart": "장바구니",
Expand Down
1 change: 1 addition & 0 deletions messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"store/quickorder.withoutPriceFulfillment": "Produto sem preço",
"store/quickorder.withoutStock": "Sem estoque",
"store/quickorder.limited": "Restricted Item",
"store/quickorder.partiallyAvailable": "A quantidade máxima é {quantity}. Existem apenas {totalQuantity} unidades disponíveis.",
"store/toaster.cart.duplicated": "Item duplicado",
"store/toaster.cart.error": "Erro adicionando produtos ao carrinho",
"store/toaster.cart.seeCart": "Ver o carrinho",
Expand Down
1 change: 1 addition & 0 deletions messages/ro.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"store/quickorder.withoutPriceFulfillment": "Produs fără preț",
"store/quickorder.withoutStock": "Fără stoc",
"store/quickorder.limited": "Restricted Item",
"store/quickorder.partiallyAvailable": "Cantitatea maximă este {quantity}. Sunt disponibile doar {totalQuantity} unități în total.",
"store/toaster.cart.duplicated": "Articol duplicat",
"store/toaster.cart.error": "Eroare la adaugarea produselor in cos",
"store/toaster.cart.seeCart": "Vezi cos",
Expand Down
2 changes: 1 addition & 1 deletion node/clients/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class Search extends JanusClient {
refIdSellerMap[item.refid].forEach(sellerId => {
simulateItems.push({
id: item.sku,
quantity: 1,
quantity: item.quantity,
seller: sellerId,
})
})
Expand Down
29 changes: 24 additions & 5 deletions node/resolvers/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const checkoutSimulation = async (
seller: item.seller,
availability: item.availability ?? '',
unitMultiplier: item.unitMultiplier ?? 1,
quantity: item.quantity,
}

return {
Expand Down Expand Up @@ -119,13 +120,14 @@ const getSkuSellers = async (

result = await Promise.all(
result.map(async (item: any) => {
const { sku, refid } = item
const { sku, refid, quantity } = item

if (sku === null) {
return {
sku,
refid,
sellers: null,
quantity,
}
}

Expand All @@ -150,6 +152,7 @@ const getSkuSellers = async (
sku,
refid,
sellers: validSellers,
quantity,
}
})
.catch((error: any) => {
Expand All @@ -164,6 +167,7 @@ const getSkuSellers = async (
sku,
refid,
sellers: null,
quantity,
}
})
})
Expand All @@ -186,12 +190,20 @@ const getSkuSellerInfo = (simulationResults: any, result: any) => {
(s: any) => s.seller === seller.id
)

const { availability = '', unitMultiplier = 1 } = currSeller ?? {}
const {
availability = '',
unitMultiplier = 1,
quantity: availableQuantity = undefined,
} = currSeller ?? {}
const isPartiallyAvailable = availableQuantity < item.quantity

return {
...seller,
availability,
availability: isPartiallyAvailable
? 'partiallyAvailable'
: availability,
unitMultiplier,
availableQuantity,
}
})

Expand All @@ -208,15 +220,20 @@ const getSkuSellerInfo = (simulationResults: any, result: any) => {
export const queries = {
skuFromRefIds: async (
_: any,
args: { refids: string; orderFormId: string; refIdSellerMap: any },
args: {
refids: string
orderFormId: string
refIdSellerMap: any
refIdQuantityMap: any
},
ctx: Context
): Promise<any> => {
const {
clients: { search },
vtex: { segment, logger },
} = ctx

const { refids, orderFormId, refIdSellerMap } = args
const { refids, orderFormId, refIdSellerMap, refIdQuantityMap } = args

if (!refids) {
throw new UserInputError('No refids provided')
Expand Down Expand Up @@ -252,7 +269,9 @@ export const queries = {
sku: skuIds[id],
refid: id,
sellers: sellersList,
quantity: refIdQuantityMap?.[id] ?? 1,
}

result.push(resultStr[id])
})

Expand Down
1 change: 1 addition & 0 deletions node/tests/resolvers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('Graphql resolvers', () => {
refids: true,
orderFormId: '',
refIdSellerMap: {},
refIdQuantityMap: {},
},
{
clients,
Expand Down
2 changes: 2 additions & 0 deletions node/tests/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ jest.mock('@vtex/api', () => {
{
sku: 'SKU-MOCKED-1',
id: '100',
quantity: 12,
seller: '1',
},
],
})
Expand Down
96 changes: 73 additions & 23 deletions react/components/ReviewBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
return Promise.all(
data.map(async (item: any) => {
const res: any = await checkRestriction(item.refid)
if (
res?.data?.productSuggestions?.products[0]?.items[0]?.itemId ===
item.sku
) {
return item
}

const foundSku = res?.data?.productSuggestions?.products[0]?.items.find(
(suggestedItem) => suggestedItem.itemId === item.sku
)

return foundSku ? item : null
})
)
}
Expand All @@ -87,6 +87,7 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
'store/quickorder.available': messages.available,
'store/quickorder.invalidPattern': messages.invalidPattern,
'store/quickorder.skuNotFound': messages.skuNotFound,
'store/quickorder.partiallyAvailable': messages.partiallyAvailable,
'store/quickorder.withoutStock': messages.withoutStock,
'store/quickorder.withoutPriceFulfillment':
messages.withoutPriceFulfillment,
Expand Down Expand Up @@ -129,7 +130,9 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
refidData?.skuFromRefIds.items.forEach((item: any) => {
if (!item.sellers) return
item.sellers = item.sellers.filter(
(seller: any) => seller.availability === 'available'
(seller: any) =>
seller.availability === 'available' ||
seller.availability === 'partiallyAvailable'
)
})

Expand Down Expand Up @@ -165,13 +168,20 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
return item != null
})
)

restrictedData.forEach((item: any) => {
mappedRefId[item.refid] = item
})
}

const errorMsg = (item: any) => {
const errorMsg = (item: any, sellerWithStock: string) => {
let ret: any = null

/* order of precedence for errors
* 1) Item not found
* 2) Item availability
* 3) Item restriction
*/
const notfound = refIdNotFound.find((curr: any) => {
return curr.refid === item.sku && curr.sku === null
})
Expand All @@ -180,19 +190,38 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
return curr.refid === item.sku && curr.sku !== null
})

let selectedSellerHasPartialStock
const foundHasStock =
found?.sellers?.length &&
found.sellers.filter(
(seller: any) =>
seller.availability && seller.availability === 'available'
).length
found.sellers.filter((seller: any) => {
if (seller.id === sellerWithStock) {
selectedSellerHasPartialStock =
seller.availability === 'partiallyAvailable'
}

ret = notfound
? 'store/quickorder.skuNotFound'
: foundHasStock
return (
seller.availability &&
(seller.availability === 'available' ||
seller.availability === 'partiallyAvailable')
)
}).length

const itemRestricted = sellerWithStock
? null
: `store/quickorder.limited`

const partialStockError = selectedSellerHasPartialStock
? 'store/quickorder.partiallyAvailable'
: null

const availabilityError = foundHasStock
? partialStockError
: `store/quickorder.withoutStock`

ret = notfound
? 'store/quickorder.skuNotFound'
: availabilityError ?? itemRestricted

return ret
}

Expand All @@ -205,7 +234,9 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
? item.seller
: item.sku && mappedRefId[item.sku]?.sellers?.length
? mappedRefId[item.sku]?.sellers.find(
(seller: any) => seller.availability === 'available'
(seller: any) =>
seller.availability === 'available' ||
seller.availability === 'partiallyAvailable'
)?.id ?? ''
: ''

Expand All @@ -216,6 +247,13 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
)?.unitMultiplier ?? '1'
: '1'

const sellerAvailableQuantity =
item.sku && mappedRefId[item.sku]?.sellers?.length
? mappedRefId[item.sku]?.sellers.find(
(seller: any) => seller.id === sellerWithStock
)?.availableQuantity
: null

return {
...item,
sellers: item.sku ? mappedRefId[item.sku]?.sellers : [],
Expand All @@ -225,9 +263,8 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
totalQuantity: sellerUnitMultiplier
? sellerUnitMultiplier * item.quantity
: '',
error:
errorMsg(item) ??
(sellerWithStock ? null : `store/quickorder.limited`),
availableQuantity: sellerAvailableQuantity ?? item.quantity,
error: errorMsg(item, sellerWithStock),
}
})

Expand Down Expand Up @@ -262,9 +299,16 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
onRefidLoading(true)
const refids = _refids.length ? Array.from(new Set(_refids)) : []

const refIdQuantityMap = reviewed.reduce((prev, item) => {
return {
...prev,
[item.sku]: item.quantity,
}
}, {})

const query = {
query: getRefIdTranslation,
variables: { refids, orderFormId, refIdSellerMap },
variables: { refids, orderFormId, refIdSellerMap, refIdQuantityMap },
}

try {
Expand Down Expand Up @@ -506,9 +550,15 @@ const ReviewBlock: FunctionComponent<WrappedComponentProps & any> = ({
width: 75,
cellRenderer: ({ cellData, rowData }: any) => {
if (rowData.error) {
const text = intl.formatMessage(
errorMessage[cellData || 'store/quickorder.valid']
)
const errMsg = errorMessage[cellData || 'store/quickorder.valid']
const text =
errMsg === messages.partiallyAvailable
? intl.formatMessage(errMsg, {
quantity: rowData.availableQuantity,
totalQuantity:
rowData.availableQuantity * rowData.unitMultiplier,
})
: intl.formatMessage(errMsg)

return (
<span className="pa3 br2 dib mr5 mv0">
Expand Down
Loading

0 comments on commit 7456148

Please sign in to comment.