Fulfillment creation refactor (#1896)
* Create change warehouse dialog (#1850) * Add allocations & variant stocks to order details query * Add asc sorting to warehouse search query * Add OrderChangeWarehouseDialog component * Add key to warehouse list in dialog * Update snapshots * Remove debug directive * Remove IDs from messages * Fix typo in method name & extract messages * Add quantity to allocations in order details query * Add types to functions * Move functions to local utils file * Add utility type WithOptional * Fix warehouse types * Change multiple items unavailable message name * Fix fetching onScroll * Fix types in utils * Add backdrop click support * Add id to stocks and allocations * Change unavailableLines from .map to .filter Co-authored-by: Wojciech Mista <wojciech.mista@hotmail.com> * Fix linter issue Co-authored-by: Wojciech Mista <wojciech.mista@hotmail.com> * Refactor order cards headers (#1875) * Add keys to TableLines * Bump macaw * Move & rename CardTitle used in Cards with order lines * Improve OrderCardTitle typography * Replace StatusLabels with CircleIndicators * Fix card title divs placement * Add warehouse selection button to OrderUnfulfilledCard * Fix fulfill button placement * Update snapshots * Make warehouse in order details view optional so that it works with uncofirmed orders * Fix undefined lines in warehouse dialog * Fix spacing in warehouse change button * Fix macaw dependency * Bump macaw-ui * Extract messages * Implement default warehouse selection logic * Move CircleIndicator render condition to wrapper * Fix failing reduce on orders with no lines * Improve warehousesAvailable early return * Drop counter in favor of filter().length * Fix tests post-rebase * Refactor fulfillment details page (#1915) * Add shipment information card * Refactor multiple warehouse fulfilling to one warehouse * Fix fulfill button navigation * Remove redundant code from OrderFulfill view * Fix OrderFulfill story * Move styling to seperate file & remove unused classes * Replace colQuantityTotal class with colStock * Add warehouse label under page header * Fix preorder cases * Change default values to maximum * Simplify logic * Add badge for preorder & deleted variant cases * Remove unused data * Add yellow outline for exceeding stock * Fix failing tests * Extract messages * Fix tests post-rebase * Add support for tracking number * Block fulfilling no items * Fix deleted variant order details bug * Fix preorder & deleted variant cases * Update snapshots * Remove redundant import * Fix linter issue * Extract fulfillment lines as separate component * Fix types * Export styles & messages to seperate files * Simplify formset changes * Fix warning input styling * Fix shouldEnableSave for overfulfillment cases * Simplify preorder rendering * Move empty line rendering * Change Warehouse prop to just id of it * Add endAdornment for deleted variant cases * Update snapshots * Fix linter issue * Extract messages * Fix incorrect operator precedence resulting in NaN values * Extract fulfillment lines to fragment * Replace nested types with fragment type * Match fragment names * Update snapshots * Create exceeding stock confirmation dialog (#1970) * Cherry-pick OrderFulfillStockExceededDialog * Fix types to graphql-codegen * Unify names in OrderFulfillStockExceededDialogLines * Fix types in OrderFulfillStockExceededDialogLines * Fix types in util * Change utils for usage with single warehouse context * Refactor OrderFulfillStockExceededDialogLine for usage with single warehouse context * Fix deleted variant cases in OrderFulfillStockExceededDialogLine * Include only exceeded lines * Display stock exceeded dialog on error * Add confirm button state * Change fixed height in OrderFulfillStockExceededDialog to responsive * Extract messages * Move initial form data after interfaces * Change nested type to fragment * Reuse logic * Remove unused import * Remove redundant isStockError * Remove unused imports * Fix minor bugs in fulfillment creation refactor (#1972) * Fix unfulfilled card header quantity calculation * Fix formset default value for deleted variants * Update snapshots * Fix default warehouse selection in order draft (#1971) * Fix default warehouse selection * Replace Skeleton with circular progress * Reuse logic * Reuse logic * Apply CR fixes * Remove unused imports * Fix canceled order header status * Update snapshots * Revert CircularProgress & change to Skeleton * Change complex types to fragments * Extract default warehouse logic to hook * Fix linter issue * Remove type assertion from OrderFulfillPage story * Handle exceeding stock fulfillment approvals (#1988) * wip Add OrderFulfillStockExceeded modal for fulfillment approvals * wip Fix types & imports * wip Fix union typing in stock exceeded dialog for both views * Add allowStockToBeExceeded flag on submit * Fix lines keys in FulfilledCard * Remove redundant import * Extract attributes caption function * Use getById util * Fix deleted warehouse cases in approvals * Fix typo * Fix styling for long warehouse names (#2019) * Fix styling in change warehouse dialog * Fix styling in warehouse selection button * Add extra margin in button * Update snapshots Co-authored-by: Wojciech Mista <wojciech.mista@hotmail.com>
This commit is contained in:
parent
e0cc89478f
commit
6cf148c4c3
45 changed files with 5536 additions and 2501 deletions
|
@ -4313,6 +4313,70 @@
|
|||
"src_dot_orders_dot_components_dot_OrderCannotCancelOrderDialog_dot_775268031": {
|
||||
"string": "There are still fulfillments created for this order. Cancel the fulfillments first before you cancel the order."
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_canceled": {
|
||||
"context": "canceled fulfillment, section header",
|
||||
"string": "Canceled ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_fulfilled": {
|
||||
"context": "section header",
|
||||
"string": "Fulfilled ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_fulfilledFrom": {
|
||||
"context": "fulfilled fulfillment, section header",
|
||||
"string": "Fulfilled from {warehouseName}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_refunded": {
|
||||
"context": "refunded fulfillment, section header",
|
||||
"string": "Refunded ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_refundedAndReturned": {
|
||||
"context": "cancelled fulfillment, section header",
|
||||
"string": "Refunded and Returned ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_replaced": {
|
||||
"context": "refunded fulfillment, section header",
|
||||
"string": "Replaced ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_returned": {
|
||||
"context": "refunded fulfillment, section header",
|
||||
"string": "Returned ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_unfulfilled": {
|
||||
"context": "section header",
|
||||
"string": "Unfulfilled ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCardTitle_dot_waitingForApproval": {
|
||||
"context": "unapproved fulfillment, section header",
|
||||
"string": "Waiting for approval ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderChangeWarehouseDialog_dot_currentSelection": {
|
||||
"context": "label for currently selected warehouse",
|
||||
"string": "currently selected"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderChangeWarehouseDialog_dot_dialogDescription": {
|
||||
"context": "change warehouse dialog description",
|
||||
"string": "Choose warehouse you want to fulfill this order from"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderChangeWarehouseDialog_dot_dialogTitle": {
|
||||
"context": "change warehouse dialog title",
|
||||
"string": "Change warehouse"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderChangeWarehouseDialog_dot_multipleProductsUnavailable": {
|
||||
"context": "warehouse label when multiple products are unavailable",
|
||||
"string": "{productCount} products are unavailable at this location"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderChangeWarehouseDialog_dot_productUnavailable": {
|
||||
"context": "warehouse label when one product is unavailable",
|
||||
"string": "{productName} is unavailable at this location"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderChangeWarehouseDialog_dot_searchFieldPlaceholder": {
|
||||
"context": "change warehouse dialog search placeholder",
|
||||
"string": "Search warehouses"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderChangeWarehouseDialog_dot_warehouseListLabel": {
|
||||
"context": "change warehouse dialog warehouse list label",
|
||||
"string": "Warehouses A to Z"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderChannelSectionCard_dot_1243773440": {
|
||||
"context": "section header",
|
||||
"string": "Sales channel"
|
||||
|
@ -4600,6 +4664,18 @@
|
|||
"context": "button",
|
||||
"string": "Finalize"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillLine_dot_deletedVariantWarning": {
|
||||
"context": "tooltip content when line's variant has been deleted",
|
||||
"string": "This variant no longer exists. You can still fulfill it."
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillLine_dot_preorderWarning": {
|
||||
"context": "tooltip content when product is in preorder",
|
||||
"string": "This product is still in preorder. You will be able to fulfill it after it reaches it’s release date"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_fulfillingFrom": {
|
||||
"context": "Support text under page header",
|
||||
"string": "Fulfilling from {warehouseName}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_headerOrder": {
|
||||
"context": "page header",
|
||||
"string": "Order"
|
||||
|
@ -4624,6 +4700,10 @@
|
|||
"context": "name",
|
||||
"string": "Product name"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_quantity": {
|
||||
"context": "Header row quantity label",
|
||||
"string": "Quantity"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_quantityToFulfill": {
|
||||
"context": "quantity of fulfilled products",
|
||||
"string": "Quantity to fulfill"
|
||||
|
@ -4632,10 +4712,18 @@
|
|||
"context": "checkbox label",
|
||||
"string": "Send shipment details to customer"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_shipmentInformation": {
|
||||
"context": "Shipment information card header",
|
||||
"string": "Shipment information"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_sku": {
|
||||
"context": "product's sku",
|
||||
"string": "SKU"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_stock": {
|
||||
"context": "Header row stock label",
|
||||
"string": "Stock"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_submitFulfillment": {
|
||||
"context": "fulfill order, button",
|
||||
"string": "Fulfill"
|
||||
|
@ -4644,6 +4732,46 @@
|
|||
"context": "prepare order fulfillment, button",
|
||||
"string": "Prepare fulfillment"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillPage_dot_trackingNumber": {
|
||||
"context": "Tracking number input label",
|
||||
"string": "Tracking number"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_availableStockLabel": {
|
||||
"context": "table header available stock label",
|
||||
"string": "Available"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_cancelButton": {
|
||||
"context": "cancel button label",
|
||||
"string": "Cancel"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_fulfillButton": {
|
||||
"context": "fulfill button label",
|
||||
"string": "Fulfill anyway"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_infoLabel": {
|
||||
"context": "stock exceeded dialog description",
|
||||
"string": "Stock for items shown below are not enough to prepare fulfillment:"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_productLabel": {
|
||||
"context": "table header product label",
|
||||
"string": "Product"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_questionLabel": {
|
||||
"context": "stock exceeded action question label",
|
||||
"string": "Are you sure you want to fulfill those products anyway?"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_requiredStockLabel": {
|
||||
"context": "table header required stock label",
|
||||
"string": "Required"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_title": {
|
||||
"context": "stock exceeded dialog title",
|
||||
"string": "Not enough stock"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillStockExceededDialog_dot_warehouseStockLabel": {
|
||||
"context": "table header warehouse stock label",
|
||||
"string": "Warehouse stock"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_addTracking": {
|
||||
"context": "add tracking button",
|
||||
"string": "Add tracking"
|
||||
|
@ -5345,42 +5473,14 @@
|
|||
"context": "button",
|
||||
"string": "Set maximal quantities"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_cancelled": {
|
||||
"context": "cancelled fulfillment, section header",
|
||||
"string": "Cancelled ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_description": {
|
||||
"context": "product no longer exists error description",
|
||||
"string": "This product is no longer in database so it can’t be replaced, nor returned"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_fulfilled": {
|
||||
"context": "section header",
|
||||
"string": "Fulfilled ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_fulfilledFrom": {
|
||||
"context": "fulfilled fulfillment, section header",
|
||||
"string": "Fulfilled from {warehouseName}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_improperValue": {
|
||||
"context": "error message",
|
||||
"string": "Improper value"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_refunded": {
|
||||
"context": "refunded fulfillment, section header",
|
||||
"string": "Refunded ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_refundedAndReturned": {
|
||||
"context": "cancelled fulfillment, section header",
|
||||
"string": "Refunded and Returned ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_replaced": {
|
||||
"context": "refunded fulfillment, section header",
|
||||
"string": "Replaced ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_returned": {
|
||||
"context": "refunded fulfillment, section header",
|
||||
"string": "Returned ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_title": {
|
||||
"context": "product no longer exists error title",
|
||||
"string": "Product no longer exists"
|
||||
|
@ -5393,14 +5493,6 @@
|
|||
"context": "section header",
|
||||
"string": "Unfulfilled Items"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_unfulfilled": {
|
||||
"context": "section header",
|
||||
"string": "Unfulfilled"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_waitingForApproval": {
|
||||
"context": "unapproved fulfillment, section header",
|
||||
"string": "Waiting for approval ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_appTitle": {
|
||||
"context": "page header with order number",
|
||||
"string": "Order #{orderNumber}"
|
||||
|
|
|
@ -74,12 +74,22 @@ export const fragmentOrderLine = gql`
|
|||
fragment OrderLine on OrderLine {
|
||||
id
|
||||
isShippingRequired
|
||||
allocations {
|
||||
id
|
||||
quantity
|
||||
warehouse {
|
||||
id
|
||||
}
|
||||
}
|
||||
variant {
|
||||
id
|
||||
quantityAvailable
|
||||
preorder {
|
||||
endDate
|
||||
}
|
||||
stocks {
|
||||
...Stock
|
||||
}
|
||||
}
|
||||
productName
|
||||
productSku
|
||||
|
@ -324,3 +334,60 @@ export const fragmentShopOrderSettings = gql`
|
|||
fulfillmentAllowUnpaid
|
||||
}
|
||||
`;
|
||||
|
||||
export const fragmentOrderFulfillLine = gql`
|
||||
fragment OrderFulfillLine on OrderLine {
|
||||
id
|
||||
isShippingRequired
|
||||
productName
|
||||
quantity
|
||||
allocations {
|
||||
quantity
|
||||
warehouse {
|
||||
id
|
||||
}
|
||||
}
|
||||
quantityFulfilled
|
||||
quantityToFulfill
|
||||
variant {
|
||||
id
|
||||
name
|
||||
sku
|
||||
preorder {
|
||||
endDate
|
||||
}
|
||||
attributes {
|
||||
values {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
stocks {
|
||||
...Stock
|
||||
}
|
||||
trackInventory
|
||||
}
|
||||
thumbnail(size: 64) {
|
||||
url
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const fragmentOrderLineStockData = gql`
|
||||
fragment OrderLineStockData on OrderLine {
|
||||
id
|
||||
allocations {
|
||||
quantity
|
||||
warehouse {
|
||||
id
|
||||
}
|
||||
}
|
||||
quantity
|
||||
quantityToFulfill
|
||||
variant {
|
||||
stocks {
|
||||
...Stock
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -6,8 +6,7 @@ export const stockFragment = gql`
|
|||
quantity
|
||||
quantityAllocated
|
||||
warehouse {
|
||||
id
|
||||
name
|
||||
...Warehouse
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1182,16 +1182,42 @@ export const OrderEventFragmentDoc = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const WarehouseFragmentDoc = gql`
|
||||
fragment Warehouse on Warehouse {
|
||||
id
|
||||
name
|
||||
}
|
||||
`;
|
||||
export const StockFragmentDoc = gql`
|
||||
fragment Stock on Stock {
|
||||
id
|
||||
quantity
|
||||
quantityAllocated
|
||||
warehouse {
|
||||
...Warehouse
|
||||
}
|
||||
}
|
||||
${WarehouseFragmentDoc}`;
|
||||
export const OrderLineFragmentDoc = gql`
|
||||
fragment OrderLine on OrderLine {
|
||||
id
|
||||
isShippingRequired
|
||||
allocations {
|
||||
id
|
||||
quantity
|
||||
warehouse {
|
||||
id
|
||||
}
|
||||
}
|
||||
variant {
|
||||
id
|
||||
quantityAvailable
|
||||
preorder {
|
||||
endDate
|
||||
}
|
||||
stocks {
|
||||
...Stock
|
||||
}
|
||||
}
|
||||
productName
|
||||
productSku
|
||||
|
@ -1230,7 +1256,7 @@ export const OrderLineFragmentDoc = gql`
|
|||
url
|
||||
}
|
||||
}
|
||||
`;
|
||||
${StockFragmentDoc}`;
|
||||
export const FulfillmentFragmentDoc = gql`
|
||||
fragment Fulfillment on Fulfillment {
|
||||
id
|
||||
|
@ -1421,6 +1447,61 @@ export const ShopOrderSettingsFragmentDoc = gql`
|
|||
fulfillmentAllowUnpaid
|
||||
}
|
||||
`;
|
||||
export const OrderFulfillLineFragmentDoc = gql`
|
||||
fragment OrderFulfillLine on OrderLine {
|
||||
id
|
||||
isShippingRequired
|
||||
productName
|
||||
quantity
|
||||
allocations {
|
||||
quantity
|
||||
warehouse {
|
||||
id
|
||||
}
|
||||
}
|
||||
quantityFulfilled
|
||||
quantityToFulfill
|
||||
variant {
|
||||
id
|
||||
name
|
||||
sku
|
||||
preorder {
|
||||
endDate
|
||||
}
|
||||
attributes {
|
||||
values {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
stocks {
|
||||
...Stock
|
||||
}
|
||||
trackInventory
|
||||
}
|
||||
thumbnail(size: 64) {
|
||||
url
|
||||
}
|
||||
}
|
||||
${StockFragmentDoc}`;
|
||||
export const OrderLineStockDataFragmentDoc = gql`
|
||||
fragment OrderLineStockData on OrderLine {
|
||||
id
|
||||
allocations {
|
||||
quantity
|
||||
warehouse {
|
||||
id
|
||||
}
|
||||
}
|
||||
quantity
|
||||
quantityToFulfill
|
||||
variant {
|
||||
stocks {
|
||||
...Stock
|
||||
}
|
||||
}
|
||||
}
|
||||
${StockFragmentDoc}`;
|
||||
export const PageTypeFragmentDoc = gql`
|
||||
fragment PageType on PageType {
|
||||
id
|
||||
|
@ -1803,17 +1884,6 @@ export const ProductMediaFragmentDoc = gql`
|
|||
oembedData
|
||||
}
|
||||
`;
|
||||
export const StockFragmentDoc = gql`
|
||||
fragment Stock on Stock {
|
||||
id
|
||||
quantity
|
||||
quantityAllocated
|
||||
warehouse {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const PreorderFragmentDoc = gql`
|
||||
fragment Preorder on PreorderData {
|
||||
globalThreshold
|
||||
|
@ -2566,12 +2636,6 @@ export const AttributeValueTranslatableContentFragmentDoc = gql`
|
|||
}
|
||||
}
|
||||
${AttributeChoicesTranslationFragmentDoc}`;
|
||||
export const WarehouseFragmentDoc = gql`
|
||||
fragment Warehouse on Warehouse {
|
||||
id
|
||||
name
|
||||
}
|
||||
`;
|
||||
export const WarehouseWithShippingFragmentDoc = gql`
|
||||
fragment WarehouseWithShipping on Warehouse {
|
||||
...Warehouse
|
||||
|
@ -8334,8 +8398,12 @@ export type OrderFulfillmentUpdateTrackingMutationHookResult = ReturnType<typeof
|
|||
export type OrderFulfillmentUpdateTrackingMutationResult = Apollo.MutationResult<Types.OrderFulfillmentUpdateTrackingMutation>;
|
||||
export type OrderFulfillmentUpdateTrackingMutationOptions = Apollo.BaseMutationOptions<Types.OrderFulfillmentUpdateTrackingMutation, Types.OrderFulfillmentUpdateTrackingMutationVariables>;
|
||||
export const OrderFulfillmentApproveDocument = gql`
|
||||
mutation OrderFulfillmentApprove($id: ID!, $notifyCustomer: Boolean!) {
|
||||
orderFulfillmentApprove(id: $id, notifyCustomer: $notifyCustomer) {
|
||||
mutation OrderFulfillmentApprove($id: ID!, $notifyCustomer: Boolean!, $allowStockToBeExceeded: Boolean) {
|
||||
orderFulfillmentApprove(
|
||||
id: $id
|
||||
notifyCustomer: $notifyCustomer
|
||||
allowStockToBeExceeded: $allowStockToBeExceeded
|
||||
) {
|
||||
errors {
|
||||
...OrderError
|
||||
}
|
||||
|
@ -8363,6 +8431,7 @@ export type OrderFulfillmentApproveMutationFn = Apollo.MutationFunction<Types.Or
|
|||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* notifyCustomer: // value for 'notifyCustomer'
|
||||
* allowStockToBeExceeded: // value for 'allowStockToBeExceeded'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
@ -9147,49 +9216,12 @@ export const OrderFulfillDataDocument = gql`
|
|||
}
|
||||
}
|
||||
lines {
|
||||
id
|
||||
isShippingRequired
|
||||
productName
|
||||
quantity
|
||||
allocations {
|
||||
quantity
|
||||
warehouse {
|
||||
id
|
||||
}
|
||||
}
|
||||
quantityFulfilled
|
||||
quantityToFulfill
|
||||
variant {
|
||||
id
|
||||
name
|
||||
sku
|
||||
preorder {
|
||||
endDate
|
||||
}
|
||||
attributes {
|
||||
values {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
stocks {
|
||||
id
|
||||
warehouse {
|
||||
...Warehouse
|
||||
}
|
||||
quantity
|
||||
quantityAllocated
|
||||
}
|
||||
trackInventory
|
||||
}
|
||||
thumbnail(size: 64) {
|
||||
url
|
||||
}
|
||||
...OrderFulfillLine
|
||||
}
|
||||
number
|
||||
}
|
||||
}
|
||||
${WarehouseFragmentDoc}`;
|
||||
${OrderFulfillLineFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useOrderFulfillDataQuery__
|
||||
|
@ -13441,7 +13473,12 @@ export type SearchStaffMembersLazyQueryHookResult = ReturnType<typeof useSearchS
|
|||
export type SearchStaffMembersQueryResult = Apollo.QueryResult<Types.SearchStaffMembersQuery, Types.SearchStaffMembersQueryVariables>;
|
||||
export const SearchWarehousesDocument = gql`
|
||||
query SearchWarehouses($after: String, $first: Int!, $query: String!) {
|
||||
search: warehouses(after: $after, first: $first, filter: {search: $query}) {
|
||||
search: warehouses(
|
||||
after: $after
|
||||
first: $first
|
||||
sortBy: {direction: ASC, field: NAME}
|
||||
filter: {search: $query}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -23,7 +23,7 @@ export { useLazyQuery, LazyQueryHookOptions } from "@apollo/client";
|
|||
const getPermissionKey = (permission: string) =>
|
||||
`PERMISSION_${permission}` as PrefixedPermissions;
|
||||
|
||||
const allPermissions: Record<PrefixedPermissions, boolean> = Object.keys(
|
||||
export const allPermissions: Record<PrefixedPermissions, boolean> = Object.keys(
|
||||
PermissionEnum
|
||||
).reduce(
|
||||
(prev, code) => ({
|
||||
|
|
|
@ -514,3 +514,6 @@ export const combinedMultiAutocompleteChoices = (
|
|||
|
||||
export const isInDevelopment =
|
||||
!process.env.NODE_ENV || process.env.NODE_ENV === "development";
|
||||
|
||||
export type WithOptional<T, K extends keyof T> = Omit<T, K> &
|
||||
Partial<Pick<T, K>>;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Typography } from "@material-ui/core";
|
||||
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
|
||||
import DefaultCardTitle from "@saleor/components/CardTitle";
|
||||
import { FulfillmentStatus } from "@saleor/graphql";
|
||||
import { makeStyles, Pill } from "@saleor/macaw-ui";
|
||||
import { CircleIndicator, makeStyles } from "@saleor/macaw-ui";
|
||||
import { StatusType } from "@saleor/types";
|
||||
import camelCase from "lodash/camelCase";
|
||||
import React from "react";
|
||||
|
@ -12,7 +13,7 @@ const useStyles = makeStyles(
|
|||
title: {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "space-between"
|
||||
justifyContent: "flex-start"
|
||||
},
|
||||
orderNumber: {
|
||||
display: "inline",
|
||||
|
@ -23,15 +24,26 @@ const useStyles = makeStyles(
|
|||
alignSelf: "center",
|
||||
color: theme.palette.text.secondary,
|
||||
margin: `auto ${theme.spacing(1)} auto auto`
|
||||
},
|
||||
cardHeader: {
|
||||
fontSize: "24px",
|
||||
fontWeight: 500,
|
||||
lineHeight: "29px",
|
||||
letterSpacing: "0.02em",
|
||||
textAlign: "left"
|
||||
},
|
||||
indicator: {
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}
|
||||
}),
|
||||
{ name: "CardTitle" }
|
||||
{ name: "OrderCardTitle" }
|
||||
);
|
||||
|
||||
const messages = defineMessages({
|
||||
cancelled: {
|
||||
defaultMessage: "Cancelled ({quantity})",
|
||||
description: "cancelled fulfillment, section header"
|
||||
canceled: {
|
||||
defaultMessage: "Canceled ({quantity})",
|
||||
description: "canceled fulfillment, section header"
|
||||
},
|
||||
fulfilled: {
|
||||
defaultMessage: "Fulfilled ({quantity})",
|
||||
|
@ -58,7 +70,7 @@ const messages = defineMessages({
|
|||
description: "unapproved fulfillment, section header"
|
||||
},
|
||||
unfulfilled: {
|
||||
defaultMessage: "Unfulfilled",
|
||||
defaultMessage: "Unfulfilled ({quantity})",
|
||||
description: "section header"
|
||||
},
|
||||
fulfilledFrom: {
|
||||
|
@ -71,9 +83,10 @@ type CardTitleStatus = FulfillmentStatus | "unfulfilled";
|
|||
|
||||
type CardTitleLines = Array<{
|
||||
quantity: number;
|
||||
quantityToFulfill?: number;
|
||||
}>;
|
||||
|
||||
interface CardTitleProps {
|
||||
interface OrderCardTitleProps {
|
||||
lines?: CardTitleLines;
|
||||
fulfillmentOrder?: number;
|
||||
status: CardTitleStatus;
|
||||
|
@ -81,6 +94,7 @@ interface CardTitleProps {
|
|||
orderNumber?: string;
|
||||
warehouseName?: string;
|
||||
withStatus?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const selectStatus = (status: CardTitleStatus) => {
|
||||
|
@ -100,18 +114,19 @@ const selectStatus = (status: CardTitleStatus) => {
|
|||
case FulfillmentStatus.CANCELED:
|
||||
return StatusType.ERROR;
|
||||
default:
|
||||
return StatusType.WARNING;
|
||||
return StatusType.ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
const CardTitle: React.FC<CardTitleProps> = ({
|
||||
const OrderCardTitle: React.FC<OrderCardTitleProps> = ({
|
||||
lines = [],
|
||||
fulfillmentOrder,
|
||||
status,
|
||||
orderNumber = "",
|
||||
warehouseName,
|
||||
withStatus = false,
|
||||
toolbar
|
||||
toolbar,
|
||||
className
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
@ -123,35 +138,36 @@ const CardTitle: React.FC<CardTitleProps> = ({
|
|||
|
||||
const messageForStatus = messages[camelCase(status)] || messages.unfulfilled;
|
||||
|
||||
const totalQuantity = lines.reduce(
|
||||
const totalQuantity =
|
||||
status === "unfulfilled"
|
||||
? lines.reduce(
|
||||
(resultQuantity, line) =>
|
||||
resultQuantity + (line.quantityToFulfill ?? line.quantity),
|
||||
0
|
||||
)
|
||||
: lines.reduce(
|
||||
(resultQuantity, { quantity }) => resultQuantity + quantity,
|
||||
0
|
||||
);
|
||||
|
||||
const title = (
|
||||
<>
|
||||
{intl.formatMessage(messageForStatus, {
|
||||
fulfillmentName,
|
||||
quantity: totalQuantity
|
||||
})}
|
||||
{fulfillmentName && (
|
||||
<Typography className={classes.orderNumber} variant="body1">
|
||||
{fulfillmentName}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DefaultCardTitle
|
||||
toolbar={toolbar}
|
||||
className={className}
|
||||
title={
|
||||
<div className={classes.title}>
|
||||
{withStatus ? (
|
||||
<Pill label={title} color={selectStatus(status)} />
|
||||
) : (
|
||||
title
|
||||
{withStatus && (
|
||||
<div className={classes.indicator}>
|
||||
<CircleIndicator color={selectStatus(status)} />
|
||||
</div>
|
||||
)}
|
||||
<HorizontalSpacer spacing={2} />
|
||||
<Typography className={classes.cardHeader}>
|
||||
{intl.formatMessage(messageForStatus, {
|
||||
fulfillmentName,
|
||||
quantity: totalQuantity
|
||||
})}
|
||||
</Typography>
|
||||
{!!warehouseName && (
|
||||
<Typography className={classes.warehouseName} variant="caption">
|
||||
<FormattedMessage
|
||||
|
@ -168,4 +184,5 @@ const CardTitle: React.FC<CardTitleProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default CardTitle;
|
||||
OrderCardTitle.displayName = "OrderCardTitle";
|
||||
export default OrderCardTitle;
|
2
src/orders/components/OrderCardTitle/index.ts
Normal file
2
src/orders/components/OrderCardTitle/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderCardTitle";
|
||||
export * from "./OrderCardTitle";
|
|
@ -0,0 +1,45 @@
|
|||
import { MockedProvider, MockedResponse } from "@apollo/client/testing";
|
||||
import { allPermissions } from "@saleor/hooks/makeQuery";
|
||||
import { order, warehouseSearch } from "@saleor/orders/fixtures";
|
||||
import { searchWarehouses } from "@saleor/searches/useWarehouseSearch";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import OrderChangeWarehouseDialog, { OrderChangeWarehouseDialogProps } from ".";
|
||||
|
||||
const props: OrderChangeWarehouseDialogProps = {
|
||||
open: true,
|
||||
lines: order("abc").lines,
|
||||
currentWarehouse: null,
|
||||
onConfirm: () => null,
|
||||
onClose: () => null
|
||||
};
|
||||
|
||||
const mocks: MockedResponse[] = [
|
||||
{
|
||||
request: {
|
||||
query: searchWarehouses,
|
||||
variables: {
|
||||
first: 20,
|
||||
after: null,
|
||||
query: "",
|
||||
...allPermissions
|
||||
}
|
||||
},
|
||||
result: {
|
||||
data: { search: warehouseSearch }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
storiesOf(
|
||||
"Orders / Order details fulfillment warehouse selection modal",
|
||||
module
|
||||
)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => (
|
||||
<MockedProvider mocks={mocks}>
|
||||
<OrderChangeWarehouseDialog {...props} />
|
||||
</MockedProvider>
|
||||
));
|
|
@ -0,0 +1,213 @@
|
|||
import {
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
FormControlLabel,
|
||||
InputAdornment,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
TableCell,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import Debounce from "@saleor/components/Debounce";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import { OrderLineFragment, WarehouseFragment } from "@saleor/graphql";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import {
|
||||
Button,
|
||||
DialogHeader,
|
||||
DialogTable,
|
||||
isScrolledToBottom,
|
||||
isScrolledToTop,
|
||||
ScrollShadow,
|
||||
SearchIcon,
|
||||
useElementScroll
|
||||
} from "@saleor/macaw-ui";
|
||||
import { isLineAvailableInWarehouse } from "@saleor/orders/utils/data";
|
||||
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
|
||||
import { mapEdgesToItems } from "@saleor/utils/maps";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { getById } from "../OrderReturnPage/utils";
|
||||
import { changeWarehouseDialogMessages as messages } from "./messages";
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
export interface OrderChangeWarehouseDialogProps {
|
||||
open: boolean;
|
||||
lines: OrderLineFragment[];
|
||||
currentWarehouse: WarehouseFragment;
|
||||
onConfirm: (warehouse: WarehouseFragment) => void;
|
||||
onClose();
|
||||
}
|
||||
|
||||
export const OrderChangeWarehouseDialog: React.FC<OrderChangeWarehouseDialogProps> = ({
|
||||
open,
|
||||
lines,
|
||||
currentWarehouse,
|
||||
onConfirm,
|
||||
onClose
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const intl = useIntl();
|
||||
|
||||
const { anchor, position, setAnchor } = useElementScroll();
|
||||
const topShadow = isScrolledToTop(anchor, position, 20) === false;
|
||||
const bottomShadow = isScrolledToBottom(anchor, position, 20) === false;
|
||||
|
||||
const [query, setQuery] = React.useState<string>("");
|
||||
const [selectedWarehouseId, setSelectedWarehouseId] = React.useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (currentWarehouse?.id) {
|
||||
setSelectedWarehouseId(currentWarehouse.id);
|
||||
}
|
||||
}, [currentWarehouse]);
|
||||
|
||||
const { result: warehousesOpts, loadMore, search } = useWarehouseSearch({
|
||||
variables: {
|
||||
after: null,
|
||||
first: 20,
|
||||
query: ""
|
||||
}
|
||||
});
|
||||
const filteredWarehouses = mapEdgesToItems(warehousesOpts?.data?.search);
|
||||
|
||||
const selectedWarehouse = filteredWarehouses?.find(
|
||||
getById(selectedWarehouseId ?? "")
|
||||
);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSelectedWarehouseId(e.target.value);
|
||||
};
|
||||
const handleSubmit = () => {
|
||||
onConfirm(selectedWarehouse);
|
||||
onClose();
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!bottomShadow) {
|
||||
loadMore();
|
||||
}
|
||||
}, [bottomShadow]);
|
||||
|
||||
return (
|
||||
<Dialog fullWidth open={open} onClose={onClose}>
|
||||
<ScrollShadow variant="top" show={topShadow}>
|
||||
<DialogHeader onClose={onClose}>
|
||||
<FormattedMessage {...messages.dialogTitle} />
|
||||
</DialogHeader>
|
||||
|
||||
<DialogContent className={classes.container}>
|
||||
<FormattedMessage {...messages.dialogDescription} />
|
||||
<Debounce debounceFn={search}>
|
||||
{debounceSearchChange => {
|
||||
const handleSearchChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const value = event.target.value;
|
||||
setQuery(value);
|
||||
debounceSearchChange(value);
|
||||
};
|
||||
return (
|
||||
<TextField
|
||||
className={classes.searchBox}
|
||||
value={query}
|
||||
variant="outlined"
|
||||
onChange={handleSearchChange}
|
||||
placeholder={intl.formatMessage(
|
||||
messages.searchFieldPlaceholder
|
||||
)}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
inputProps={{ className: classes.searchInput }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Debounce>
|
||||
|
||||
<Typography className={classes.supportHeader}>
|
||||
<FormattedMessage {...messages.warehouseListLabel} />
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
</ScrollShadow>
|
||||
|
||||
<DialogTable ref={setAnchor}>
|
||||
{filteredWarehouses ? (
|
||||
<RadioGroup value={selectedWarehouseId} onChange={handleChange}>
|
||||
{filteredWarehouses.map(warehouse => {
|
||||
const unavailableLines = lines?.filter(
|
||||
line => !isLineAvailableInWarehouse(line, warehouse)
|
||||
);
|
||||
const someLinesUnavailable = unavailableLines?.length > 0;
|
||||
return (
|
||||
<TableRow key={warehouse.id}>
|
||||
<TableCell>
|
||||
<FormControlLabel
|
||||
value={warehouse.id}
|
||||
control={<Radio color="primary" />}
|
||||
label={
|
||||
<div className={classes.radioLabelContainer}>
|
||||
<span className={classes.warehouseName}>
|
||||
{warehouse.name}
|
||||
</span>
|
||||
{someLinesUnavailable && (
|
||||
<Typography className={classes.supportText}>
|
||||
{unavailableLines.length === 1
|
||||
? intl.formatMessage(
|
||||
messages.productUnavailable,
|
||||
{
|
||||
productName:
|
||||
unavailableLines[0].productName
|
||||
}
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.multipleProductsUnavailable,
|
||||
{ productCount: unavailableLines.length }
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{currentWarehouse?.id === warehouse?.id && (
|
||||
<Typography className={classes.helpText}>
|
||||
{intl.formatMessage(messages.currentSelection)}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</DialogTable>
|
||||
<ScrollShadow variant="bottom" show={bottomShadow}>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
color="primary"
|
||||
variant="primary"
|
||||
disabled={!selectedWarehouse}
|
||||
>
|
||||
{intl.formatMessage(buttonMessages.select)}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</ScrollShadow>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
OrderChangeWarehouseDialog.displayName = "OrderChangeWarehouseDialog";
|
||||
export default OrderChangeWarehouseDialog;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderChangeWarehouseDialog";
|
||||
export * from "./OrderChangeWarehouseDialog";
|
32
src/orders/components/OrderChangeWarehouseDialog/messages.ts
Normal file
32
src/orders/components/OrderChangeWarehouseDialog/messages.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const changeWarehouseDialogMessages = defineMessages({
|
||||
dialogTitle: {
|
||||
defaultMessage: "Change warehouse",
|
||||
description: "change warehouse dialog title"
|
||||
},
|
||||
dialogDescription: {
|
||||
defaultMessage: "Choose warehouse you want to fulfill this order from",
|
||||
description: "change warehouse dialog description"
|
||||
},
|
||||
searchFieldPlaceholder: {
|
||||
defaultMessage: "Search warehouses",
|
||||
description: "change warehouse dialog search placeholder"
|
||||
},
|
||||
warehouseListLabel: {
|
||||
defaultMessage: "Warehouses A to Z",
|
||||
description: "change warehouse dialog warehouse list label"
|
||||
},
|
||||
productUnavailable: {
|
||||
defaultMessage: "{productName} is unavailable at this location",
|
||||
description: "warehouse label when one product is unavailable"
|
||||
},
|
||||
multipleProductsUnavailable: {
|
||||
defaultMessage: "{productCount} products are unavailable at this location",
|
||||
description: "warehouse label when multiple products are unavailable"
|
||||
},
|
||||
currentSelection: {
|
||||
defaultMessage: "currently selected",
|
||||
description: "label for currently selected warehouse"
|
||||
}
|
||||
});
|
46
src/orders/components/OrderChangeWarehouseDialog/styles.ts
Normal file
46
src/orders/components/OrderChangeWarehouseDialog/styles.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
container: {
|
||||
paddingTop: 0
|
||||
},
|
||||
searchBox: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2)
|
||||
},
|
||||
searchInput: {
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2)
|
||||
},
|
||||
supportHeader: {
|
||||
textTransform: "uppercase",
|
||||
color: theme.palette.saleor.main[3],
|
||||
fontWeight: 500,
|
||||
letterSpacing: "0.1em",
|
||||
fontSize: "12px",
|
||||
lineHeight: "160%"
|
||||
},
|
||||
helpText: {
|
||||
display: "inline",
|
||||
fontSize: "12px",
|
||||
lineHeight: "160%",
|
||||
color: theme.palette.saleor.main[3]
|
||||
},
|
||||
supportText: {
|
||||
fontSize: "14px",
|
||||
lineHeight: "160%",
|
||||
color: theme.palette.saleor.main[3]
|
||||
},
|
||||
radioLabelContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
},
|
||||
warehouseName: {
|
||||
maxWidth: "350px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderChangeWarehouseDialog" }
|
||||
);
|
|
@ -12,7 +12,8 @@ import Skeleton from "@saleor/components/Skeleton";
|
|||
import {
|
||||
OrderDetailsFragment,
|
||||
OrderDetailsQuery,
|
||||
OrderStatus
|
||||
OrderStatus,
|
||||
WarehouseFragment
|
||||
} from "@saleor/graphql";
|
||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
|
@ -65,6 +66,7 @@ export interface OrderDetailsPageProps {
|
|||
}>;
|
||||
disabled: boolean;
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
selectedWarehouse?: WarehouseFragment;
|
||||
onOrderLineAdd?: () => void;
|
||||
onOrderLineChange?: (
|
||||
id: string,
|
||||
|
@ -91,6 +93,7 @@ export interface OrderDetailsPageProps {
|
|||
onInvoiceClick(invoiceId: string);
|
||||
onInvoiceGenerate();
|
||||
onInvoiceSend(invoiceId: string);
|
||||
onWarehouseChange?();
|
||||
onSubmit(data: MetadataFormData): SubmitPromise;
|
||||
}
|
||||
|
||||
|
@ -115,6 +118,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
order,
|
||||
shop,
|
||||
saveButtonBarState,
|
||||
selectedWarehouse,
|
||||
onBack,
|
||||
onBillingAddressEdit,
|
||||
onFulfillmentApprove,
|
||||
|
@ -137,6 +141,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
onOrderLineChange,
|
||||
onOrderLineRemove,
|
||||
onShippingMethodEdit,
|
||||
onWarehouseChange,
|
||||
onSubmit
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
@ -243,6 +248,8 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
notAllowedToFulfillUnpaid={notAllowedToFulfillUnpaid}
|
||||
lines={unfulfilled}
|
||||
onFulfill={onOrderFulfill}
|
||||
onWarehouseChange={onWarehouseChange}
|
||||
selectedWarehouse={selectedWarehouse}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
|
|
148
src/orders/components/OrderFulfillLine/OrderFulfillLine.tsx
Normal file
148
src/orders/components/OrderFulfillLine/OrderFulfillLine.tsx
Normal file
|
@ -0,0 +1,148 @@
|
|||
import { TableCell, TableRow, TextField, Typography } from "@material-ui/core";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import {
|
||||
OrderFulfillLineFragment,
|
||||
OrderFulfillStockInput
|
||||
} from "@saleor/graphql";
|
||||
import { FormsetChange, FormsetData } from "@saleor/hooks/useFormset";
|
||||
import { Tooltip, WarningIcon } from "@saleor/macaw-ui";
|
||||
import {
|
||||
getAttributesCaption,
|
||||
getOrderLineAvailableQuantity,
|
||||
getWarehouseStock
|
||||
} from "@saleor/orders/utils/data";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { messages } from "./messages";
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
interface OrderFulfillLineProps {
|
||||
line: OrderFulfillLineFragment;
|
||||
lineIndex: number;
|
||||
warehouseId: string;
|
||||
formsetData: FormsetData<null, OrderFulfillStockInput[]>;
|
||||
formsetChange: FormsetChange<OrderFulfillStockInput[]>;
|
||||
}
|
||||
|
||||
export const OrderFulfillLine: React.FC<OrderFulfillLineProps> = props => {
|
||||
const { line, lineIndex, warehouseId, formsetData, formsetChange } = props;
|
||||
const classes = useStyles();
|
||||
const intl = useIntl();
|
||||
|
||||
const isDeletedVariant = !line?.variant;
|
||||
const isPreorder = !!line.variant?.preorder;
|
||||
const lineFormQuantity = isPreorder
|
||||
? 0
|
||||
: formsetData[lineIndex].value?.[0]?.quantity;
|
||||
|
||||
const overfulfill = lineFormQuantity > line.quantityToFulfill;
|
||||
const warehouseStock = getWarehouseStock(line?.variant?.stocks, warehouseId);
|
||||
const availableQuantity = getOrderLineAvailableQuantity(line, warehouseStock);
|
||||
|
||||
const isStockExceeded = lineFormQuantity > availableQuantity;
|
||||
|
||||
if (!line) {
|
||||
return (
|
||||
<TableRow key={lineIndex}>
|
||||
<TableCellAvatar className={classes.colName}>
|
||||
<Skeleton />
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colSku}>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colStock}>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow key={line.id}>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={line?.thumbnail?.url}
|
||||
badge={
|
||||
isPreorder || !line?.variant ? (
|
||||
<Tooltip
|
||||
variant="warning"
|
||||
title={intl.formatMessage(
|
||||
isPreorder
|
||||
? messages.preorderWarning
|
||||
: messages.deletedVariantWarning
|
||||
)}
|
||||
>
|
||||
<div className={classes.warningIcon}>
|
||||
<WarningIcon />
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
undefined
|
||||
)
|
||||
}
|
||||
>
|
||||
{line.productName}
|
||||
<Typography color="textSecondary" variant="caption">
|
||||
{getAttributesCaption(line.variant?.attributes)}
|
||||
</Typography>
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colSku}>{line.variant?.sku}</TableCell>
|
||||
{isPreorder ? (
|
||||
<TableCell className={classes.colQuantity} />
|
||||
) : (
|
||||
<TableCell
|
||||
className={classes.colQuantity}
|
||||
key={warehouseStock?.id ?? "deletedVariant" + lineIndex}
|
||||
>
|
||||
<TextField
|
||||
type="number"
|
||||
inputProps={{
|
||||
className: classNames(classes.quantityInnerInput, {
|
||||
[classes.quantityInnerInputNoRemaining]: !line.variant
|
||||
?.trackInventory
|
||||
}),
|
||||
min: 0,
|
||||
style: { textAlign: "right" }
|
||||
}}
|
||||
fullWidth
|
||||
value={lineFormQuantity}
|
||||
onChange={event =>
|
||||
formsetChange(line.id, [
|
||||
{
|
||||
quantity: parseInt(event.target.value, 10),
|
||||
warehouse: warehouseId
|
||||
}
|
||||
])
|
||||
}
|
||||
error={overfulfill}
|
||||
variant="outlined"
|
||||
InputProps={{
|
||||
classes: {
|
||||
...(isStockExceeded &&
|
||||
!overfulfill && {
|
||||
notchedOutline: classes.warning
|
||||
})
|
||||
},
|
||||
endAdornment: (
|
||||
<div className={classes.remainingQuantity}>
|
||||
/ {line.quantityToFulfill}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className={classes.colStock} key="total">
|
||||
{isPreorder || isDeletedVariant ? undefined : availableQuantity}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
OrderFulfillLine.displayName = "OrderFulfillLine";
|
||||
export default OrderFulfillLine;
|
2
src/orders/components/OrderFulfillLine/index.ts
Normal file
2
src/orders/components/OrderFulfillLine/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderFulfillLine";
|
||||
export * from "./OrderFulfillLine";
|
13
src/orders/components/OrderFulfillLine/messages.ts
Normal file
13
src/orders/components/OrderFulfillLine/messages.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const messages = defineMessages({
|
||||
preorderWarning: {
|
||||
defaultMessage:
|
||||
"This product is still in preorder. You will be able to fulfill it after it reaches it’s release date",
|
||||
description: "tooltip content when product is in preorder"
|
||||
},
|
||||
deletedVariantWarning: {
|
||||
defaultMessage: "This variant no longer exists. You can still fulfill it.",
|
||||
description: "tooltip content when line's variant has been deleted"
|
||||
}
|
||||
});
|
47
src/orders/components/OrderFulfillLine/styles.ts
Normal file
47
src/orders/components/OrderFulfillLine/styles.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
colStock: {
|
||||
textAlign: "right",
|
||||
width: 180
|
||||
},
|
||||
colName: {
|
||||
width: 250
|
||||
},
|
||||
colQuantity: {
|
||||
textAlign: "right",
|
||||
width: 210
|
||||
},
|
||||
colSku: {
|
||||
textAlign: "right",
|
||||
textOverflow: "ellipsis",
|
||||
width: 150
|
||||
},
|
||||
warningIcon: {
|
||||
color: theme.palette.saleor.warning.mid,
|
||||
marginRight: theme.spacing(2)
|
||||
},
|
||||
error: {
|
||||
color: theme.palette.error.main
|
||||
},
|
||||
warning: {
|
||||
borderColor: theme.palette.saleor.warning.dark + " !important",
|
||||
boxShadow: `0 0 0 3px ${theme.palette.saleor.warning.light}`
|
||||
},
|
||||
quantityInnerInput: {
|
||||
paddingBottom: theme.spacing(2),
|
||||
paddingTop: theme.spacing(2)
|
||||
},
|
||||
quantityInnerInputNoRemaining: {
|
||||
paddingRight: 0
|
||||
},
|
||||
remainingQuantity: {
|
||||
paddingBottom: theme.spacing(2),
|
||||
paddingTop: theme.spacing(2),
|
||||
color: theme.palette.text.secondary,
|
||||
whiteSpace: "nowrap"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderFulfillLine" }
|
||||
);
|
|
@ -14,7 +14,7 @@ const props: OrderFulfillPageProps = {
|
|||
onSubmit: () => undefined,
|
||||
order: orderToFulfill,
|
||||
saveButtonBar: "default",
|
||||
warehouses: warehouseList
|
||||
warehouse: warehouseList[0]
|
||||
};
|
||||
|
||||
storiesOf("Views / Orders / Fulfill order", module)
|
||||
|
@ -25,7 +25,7 @@ storiesOf("Views / Orders / Fulfill order", module)
|
|||
{...props}
|
||||
loading={true}
|
||||
order={undefined}
|
||||
warehouses={undefined}
|
||||
warehouse={undefined}
|
||||
/>
|
||||
))
|
||||
.add("error", () => (
|
||||
|
@ -44,6 +44,4 @@ storiesOf("Views / Orders / Fulfill order", module)
|
|||
]}
|
||||
/>
|
||||
))
|
||||
.add("one warehouse", () => (
|
||||
<OrderFulfillPage {...props} warehouses={warehouseList.slice(0, 1)} />
|
||||
));
|
||||
.add("one warehouse", () => <OrderFulfillPage {...props} />);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
Card,
|
||||
CardActions,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
|
@ -8,19 +7,20 @@ import {
|
|||
TextField,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import { CSSProperties } from "@material-ui/styles";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Container from "@saleor/components/Container";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import Form from "@saleor/components/Form";
|
||||
import { Grid } from "@saleor/components/Grid";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Savebar from "@saleor/components/Savebar";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import {
|
||||
FulfillOrderMutation,
|
||||
OrderErrorCode,
|
||||
OrderFulfillDataQuery,
|
||||
OrderFulfillLineFragment,
|
||||
OrderFulfillStockInput,
|
||||
ShopOrderSettingsFragment,
|
||||
WarehouseFragment
|
||||
|
@ -28,91 +28,25 @@ import {
|
|||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import useFormset, { FormsetData } from "@saleor/hooks/useFormset";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import {
|
||||
Backlink,
|
||||
ConfirmButtonTransitionState,
|
||||
makeStyles
|
||||
} from "@saleor/macaw-ui";
|
||||
import { Backlink, ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import {
|
||||
getToFulfillOrderLines,
|
||||
isStockError
|
||||
getAttributesCaption,
|
||||
getToFulfillOrderLines
|
||||
} from "@saleor/orders/utils/data";
|
||||
import { update } from "@saleor/utils/lists";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import OrderFulfillLine from "../OrderFulfillLine/OrderFulfillLine";
|
||||
import OrderFulfillStockExceededDialog from "../OrderFulfillStockExceededDialog";
|
||||
import { messages } from "./messages";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => {
|
||||
const inputPadding: CSSProperties = {
|
||||
paddingBottom: theme.spacing(2),
|
||||
paddingTop: theme.spacing(2)
|
||||
};
|
||||
|
||||
return {
|
||||
actionBar: {
|
||||
flexDirection: "row",
|
||||
padding: theme.spacing(1, 4)
|
||||
},
|
||||
colName: {
|
||||
width: 250,
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
width: ({ warehouses }: OrderFulfillPageProps) =>
|
||||
warehouses?.length > 3 ? 250 : "auto"
|
||||
},
|
||||
[theme.breakpoints.only("md")]: {
|
||||
width: ({ warehouses }: OrderFulfillPageProps) =>
|
||||
warehouses?.length > 2 ? 250 : "auto"
|
||||
}
|
||||
},
|
||||
colQuantity: {
|
||||
textAlign: "right",
|
||||
width: 210
|
||||
},
|
||||
colQuantityHeader: {
|
||||
textAlign: "right"
|
||||
},
|
||||
colQuantityTotal: {
|
||||
textAlign: "right",
|
||||
width: 180
|
||||
},
|
||||
colSku: {
|
||||
textAlign: "right",
|
||||
textOverflow: "ellipsis",
|
||||
width: 150
|
||||
},
|
||||
error: {
|
||||
color: theme.palette.error.main
|
||||
},
|
||||
full: {
|
||||
fontWeight: 600
|
||||
},
|
||||
quantityInnerInput: {
|
||||
...inputPadding
|
||||
},
|
||||
quantityInnerInputNoRemaining: {
|
||||
paddingRight: 0
|
||||
},
|
||||
remainingQuantity: {
|
||||
...inputPadding,
|
||||
color: theme.palette.text.secondary,
|
||||
whiteSpace: "nowrap"
|
||||
},
|
||||
table: {
|
||||
"&&": {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
{ name: "OrderFulfillPage" }
|
||||
);
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
interface OrderFulfillFormData {
|
||||
sendInfo: boolean;
|
||||
trackingNumber: string;
|
||||
allowStockToBeExceeded: boolean;
|
||||
}
|
||||
export interface OrderFulfillSubmitData extends OrderFulfillFormData {
|
||||
items: FormsetData<null, OrderFulfillStockInput[]>;
|
||||
|
@ -122,14 +56,16 @@ export interface OrderFulfillPageProps {
|
|||
errors: FulfillOrderMutation["orderFulfill"]["errors"];
|
||||
order: OrderFulfillDataQuery["order"];
|
||||
saveButtonBar: ConfirmButtonTransitionState;
|
||||
warehouses: WarehouseFragment[];
|
||||
warehouse: WarehouseFragment;
|
||||
shopSettings?: ShopOrderSettingsFragment;
|
||||
onBack: () => void;
|
||||
onSubmit: (data: OrderFulfillSubmitData) => SubmitPromise;
|
||||
}
|
||||
|
||||
const initialFormData: OrderFulfillFormData = {
|
||||
sendInfo: true
|
||||
sendInfo: true,
|
||||
trackingNumber: "",
|
||||
allowStockToBeExceeded: false
|
||||
};
|
||||
|
||||
const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
||||
|
@ -138,7 +74,7 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
|||
errors,
|
||||
order,
|
||||
saveButtonBar,
|
||||
warehouses,
|
||||
warehouse,
|
||||
shopSettings,
|
||||
onBack,
|
||||
onSubmit
|
||||
|
@ -151,28 +87,50 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
|||
null,
|
||||
OrderFulfillStockInput[]
|
||||
>(
|
||||
getToFulfillOrderLines(order?.lines).map(line => ({
|
||||
(getToFulfillOrderLines(order?.lines) as OrderFulfillLineFragment[]).map(
|
||||
line => ({
|
||||
data: null,
|
||||
id: line.id,
|
||||
label: line.variant?.attributes
|
||||
.map(attribute =>
|
||||
attribute.values
|
||||
.map(attributeValue => attributeValue.name)
|
||||
.join(" , ")
|
||||
label: getAttributesCaption(line?.variant?.attributes),
|
||||
value: line?.variant?.preorder
|
||||
? null
|
||||
: [
|
||||
{
|
||||
quantity: line.quantityToFulfill,
|
||||
warehouse: warehouse?.id
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
.join(" / "),
|
||||
value: line.variant?.stocks?.map(stock => ({
|
||||
quantity: 0,
|
||||
warehouse: stock.warehouse.id
|
||||
}))
|
||||
}))
|
||||
);
|
||||
|
||||
const handleSubmit = (formData: OrderFulfillFormData) =>
|
||||
onSubmit({
|
||||
const [
|
||||
displayStockExceededDialog,
|
||||
setDisplayStockExceededDialog
|
||||
] = React.useState(false);
|
||||
|
||||
const handleSubmit = ({
|
||||
formData,
|
||||
allowStockToBeExceeded
|
||||
}: {
|
||||
formData: OrderFulfillFormData;
|
||||
allowStockToBeExceeded: boolean;
|
||||
}) => {
|
||||
setDisplayStockExceededDialog(false);
|
||||
return onSubmit({
|
||||
...formData,
|
||||
items: formsetData
|
||||
allowStockToBeExceeded,
|
||||
items: formsetData.filter(item => !!item.value)
|
||||
});
|
||||
};
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
errors &&
|
||||
errors.every(err => err.code === OrderErrorCode.INSUFFICIENT_STOCK)
|
||||
) {
|
||||
setDisplayStockExceededDialog(true);
|
||||
}
|
||||
}, [errors]);
|
||||
|
||||
const notAllowedToFulfillUnpaid =
|
||||
shopSettings?.fulfillmentAutoApprove &&
|
||||
|
@ -188,26 +146,21 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
|||
return false;
|
||||
}
|
||||
|
||||
const isAtLeastOneFulfilled = formsetData?.some(({ value }) =>
|
||||
value?.some(({ quantity }) => quantity > 0)
|
||||
const isAtLeastOneFulfilled = formsetData?.some(
|
||||
el => el.value?.[0]?.quantity > 0
|
||||
);
|
||||
|
||||
const areProperlyFulfilled = formsetData?.every(({ id, value }) => {
|
||||
const { lines } = order;
|
||||
|
||||
const { quantityToFulfill } = lines.find(
|
||||
({ id: lineId }) => lineId === id
|
||||
);
|
||||
|
||||
const formQuantityFulfilled = value?.reduce(
|
||||
(result, { quantity }) => result + quantity,
|
||||
0
|
||||
);
|
||||
|
||||
return formQuantityFulfilled <= quantityToFulfill;
|
||||
const overfulfill = formsetData
|
||||
.filter(item => !!item?.value) // this can be removed after preorder is dropped
|
||||
.some(item => {
|
||||
const formQuantityFulfilled = item?.value?.[0]?.quantity;
|
||||
const quantityToFulfill = order?.lines?.find(
|
||||
line => line.id === item.id
|
||||
).quantityToFulfill;
|
||||
return formQuantityFulfilled > quantityToFulfill;
|
||||
});
|
||||
|
||||
return isAtLeastOneFulfilled && areProperlyFulfilled;
|
||||
return !overfulfill && isAtLeastOneFulfilled;
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -224,13 +177,30 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
|||
orderNumber: order?.number
|
||||
})}
|
||||
/>
|
||||
<Form confirmLeave initial={initialFormData} onSubmit={handleSubmit}>
|
||||
<Typography className={classes.warehouseLabel}>
|
||||
<FormattedMessage
|
||||
{...messages.fulfillingFrom}
|
||||
values={{ warehouseName: warehouse?.name }}
|
||||
/>
|
||||
</Typography>
|
||||
<Form
|
||||
confirmLeave
|
||||
initial={initialFormData}
|
||||
onSubmit={formData =>
|
||||
handleSubmit({
|
||||
formData,
|
||||
allowStockToBeExceeded: displayStockExceededDialog
|
||||
})
|
||||
}
|
||||
>
|
||||
{({ change, data, submit }) => (
|
||||
<>
|
||||
<Grid>
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(messages.itemsReadyToShip)}
|
||||
/>
|
||||
{warehouse ? (
|
||||
<ResponsiveTable className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
|
@ -240,231 +210,61 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
|||
<TableCell className={classes.colSku}>
|
||||
<FormattedMessage {...messages.sku} />
|
||||
</TableCell>
|
||||
{warehouses?.map(warehouse => (
|
||||
<TableCell
|
||||
key={warehouse.id}
|
||||
className={classNames(
|
||||
classes.colQuantity,
|
||||
classes.colQuantityHeader
|
||||
)}
|
||||
>
|
||||
{warehouse.name}
|
||||
<FormattedMessage {...messages.quantity} />
|
||||
</TableCell>
|
||||
))}
|
||||
<TableCell className={classes.colQuantityTotal}>
|
||||
<FormattedMessage {...messages.quantityToFulfill} />
|
||||
<TableCell className={classes.colStock}>
|
||||
<FormattedMessage {...messages.stock} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
getToFulfillOrderLines(order?.lines),
|
||||
(
|
||||
line: OrderFulfillDataQuery["order"]["lines"][0],
|
||||
lineIndex
|
||||
) => {
|
||||
if (!line) {
|
||||
return (
|
||||
<TableRow key={lineIndex}>
|
||||
<TableCellAvatar className={classes.colName}>
|
||||
<Skeleton />
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colSku}>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
{warehouses?.map(warehouse => (
|
||||
<TableCell
|
||||
className={classes.colQuantity}
|
||||
key={warehouse.id}
|
||||
>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
))}
|
||||
<TableCell className={classes.colQuantityTotal}>
|
||||
{" "}
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
const remainingQuantity = line.quantityToFulfill;
|
||||
const quantityToFulfill = formsetData[
|
||||
lineIndex
|
||||
].value?.reduce(
|
||||
(quantityToFulfill, lineInput) =>
|
||||
quantityToFulfill + (lineInput.quantity || 0),
|
||||
0
|
||||
);
|
||||
const overfulfill = remainingQuantity < quantityToFulfill;
|
||||
const isPreorder = !!line.variant?.preorder;
|
||||
|
||||
return (
|
||||
<TableRow key={line.id}>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={line?.thumbnail?.url}
|
||||
>
|
||||
{line.productName}
|
||||
<Typography color="textSecondary" variant="caption">
|
||||
{line.variant?.attributes
|
||||
?.map(attribute =>
|
||||
attribute.values
|
||||
.map(attributeValue => attributeValue.name)
|
||||
.join(", ")
|
||||
)
|
||||
?.join(" / ")}
|
||||
</Typography>
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colSku}>
|
||||
{line.variant?.sku}
|
||||
</TableCell>
|
||||
{warehouses?.map(warehouse => {
|
||||
if (isPreorder) {
|
||||
return (
|
||||
<TableCell
|
||||
key="skeleton"
|
||||
className={classNames(
|
||||
classes.colQuantity,
|
||||
classes.error
|
||||
)}
|
||||
(line: OrderFulfillLineFragment, lineIndex) => (
|
||||
<OrderFulfillLine
|
||||
line={line}
|
||||
lineIndex={lineIndex}
|
||||
warehouseId={warehouse?.id}
|
||||
formsetData={formsetData}
|
||||
formsetChange={formsetChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const warehouseStock = line.variant?.stocks?.find(
|
||||
stock => stock.warehouse.id === warehouse.id
|
||||
);
|
||||
const formsetStock = formsetData[
|
||||
lineIndex
|
||||
].value.find(
|
||||
line => line.warehouse === warehouse.id
|
||||
);
|
||||
|
||||
if (!warehouseStock) {
|
||||
return (
|
||||
<TableCell
|
||||
key="skeleton"
|
||||
className={classNames(
|
||||
classes.colQuantity,
|
||||
classes.error
|
||||
)}
|
||||
>
|
||||
<FormattedMessage {...messages.noStock} />
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
|
||||
const warehouseAllocation = line.allocations.find(
|
||||
allocation =>
|
||||
allocation.warehouse.id === warehouse.id
|
||||
);
|
||||
const allocatedQuantityForLine =
|
||||
warehouseAllocation?.quantity || 0;
|
||||
const availableQuantity =
|
||||
warehouseStock.quantity -
|
||||
warehouseStock.quantityAllocated +
|
||||
allocatedQuantityForLine;
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
className={classes.colQuantity}
|
||||
key={warehouseStock.id}
|
||||
>
|
||||
<TextField
|
||||
type="number"
|
||||
inputProps={{
|
||||
className: classNames(
|
||||
classes.quantityInnerInput,
|
||||
{
|
||||
[classes.quantityInnerInputNoRemaining]: !line
|
||||
.variant.trackInventory
|
||||
}
|
||||
),
|
||||
max: (
|
||||
line.variant.trackInventory &&
|
||||
availableQuantity
|
||||
).toString(),
|
||||
min: 0,
|
||||
style: { textAlign: "right" }
|
||||
}}
|
||||
fullWidth
|
||||
value={formsetStock.quantity}
|
||||
onChange={event =>
|
||||
formsetChange(
|
||||
line.id,
|
||||
update(
|
||||
{
|
||||
quantity: parseInt(
|
||||
event.target.value,
|
||||
10
|
||||
),
|
||||
warehouse: warehouse.id
|
||||
},
|
||||
formsetData[lineIndex].value,
|
||||
(a, b) => a.warehouse === b.warehouse
|
||||
)
|
||||
)
|
||||
}
|
||||
error={isStockError(
|
||||
overfulfill,
|
||||
formsetStock,
|
||||
availableQuantity,
|
||||
warehouse,
|
||||
line,
|
||||
errors
|
||||
)}
|
||||
InputProps={{
|
||||
endAdornment: line.variant
|
||||
.trackInventory && (
|
||||
<div
|
||||
className={classes.remainingQuantity}
|
||||
>
|
||||
/ {availableQuantity}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
|
||||
<TableCell
|
||||
className={classes.colQuantityTotal}
|
||||
key="total"
|
||||
>
|
||||
{!isPreorder && (
|
||||
<>
|
||||
<span
|
||||
className={classNames({
|
||||
[classes.error]: overfulfill,
|
||||
[classes.full]:
|
||||
remainingQuantity <= quantityToFulfill
|
||||
})}
|
||||
>
|
||||
{quantityToFulfill}
|
||||
</span>{" "}
|
||||
/ {remainingQuantity}
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card className={classes.shipmentInformationCard}>
|
||||
<Typography className={classes.supportHeader}>
|
||||
<FormattedMessage {...messages.shipmentInformation} />
|
||||
</Typography>
|
||||
<TextField
|
||||
value={data.trackingNumber}
|
||||
name="trackingNumber"
|
||||
label={intl.formatMessage(messages.trackingNumber)}
|
||||
fullWidth
|
||||
onChange={change}
|
||||
/>
|
||||
{shopSettings?.fulfillmentAutoApprove && (
|
||||
<CardActions className={classes.actionBar}>
|
||||
<ControlledCheckbox
|
||||
checked={data.sendInfo}
|
||||
label={intl.formatMessage(messages.sentShipmentDetails)}
|
||||
name="sendInfo"
|
||||
onChange={change}
|
||||
/>
|
||||
</CardActions>
|
||||
)}
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Savebar
|
||||
disabled={!shouldEnableSave()}
|
||||
labels={{
|
||||
|
@ -481,6 +281,15 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
|||
onSubmit={submit}
|
||||
onCancel={onBack}
|
||||
/>
|
||||
<OrderFulfillStockExceededDialog
|
||||
open={displayStockExceededDialog}
|
||||
lines={order?.lines}
|
||||
formsetData={formsetData}
|
||||
warehouseId={warehouse?.id}
|
||||
confirmButtonState={saveButtonBar}
|
||||
onSubmit={submit}
|
||||
onClose={() => setDisplayStockExceededDialog(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
|
|
|
@ -37,6 +37,14 @@ export const messages = defineMessages({
|
|||
defaultMessage: "Quantity to fulfill",
|
||||
description: "quantity of fulfilled products"
|
||||
},
|
||||
quantity: {
|
||||
defaultMessage: "Quantity",
|
||||
description: "Header row quantity label"
|
||||
},
|
||||
stock: {
|
||||
defaultMessage: "Stock",
|
||||
description: "Header row stock label"
|
||||
},
|
||||
noStock: {
|
||||
defaultMessage: "No Stock",
|
||||
description: "no variant stock in warehouse"
|
||||
|
@ -44,5 +52,17 @@ export const messages = defineMessages({
|
|||
sentShipmentDetails: {
|
||||
defaultMessage: "Send shipment details to customer",
|
||||
description: "checkbox label"
|
||||
},
|
||||
shipmentInformation: {
|
||||
defaultMessage: "Shipment information",
|
||||
description: "Shipment information card header"
|
||||
},
|
||||
trackingNumber: {
|
||||
defaultMessage: "Tracking number",
|
||||
description: "Tracking number input label"
|
||||
},
|
||||
fulfillingFrom: {
|
||||
defaultMessage: "Fulfilling from {warehouseName}",
|
||||
description: "Support text under page header"
|
||||
}
|
||||
});
|
||||
|
|
49
src/orders/components/OrderFulfillPage/styles.ts
Normal file
49
src/orders/components/OrderFulfillPage/styles.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
colQuantityHeader: {
|
||||
textAlign: "right"
|
||||
},
|
||||
colStock: {
|
||||
textAlign: "right",
|
||||
width: 180
|
||||
},
|
||||
colName: {
|
||||
width: 250
|
||||
},
|
||||
colQuantity: {
|
||||
textAlign: "right",
|
||||
width: 210
|
||||
},
|
||||
colSku: {
|
||||
textAlign: "right",
|
||||
textOverflow: "ellipsis",
|
||||
width: 150
|
||||
},
|
||||
table: {
|
||||
"&&": {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
},
|
||||
shipmentInformationCard: {
|
||||
padding: theme.spacing(3),
|
||||
alignSelf: "start",
|
||||
display: "grid",
|
||||
gridRowGap: theme.spacing(1)
|
||||
},
|
||||
supportHeader: {
|
||||
textTransform: "uppercase",
|
||||
color: theme.palette.saleor.main[3],
|
||||
fontWeight: 500,
|
||||
letterSpacing: "0.1em",
|
||||
fontSize: "12px",
|
||||
lineHeight: "160%",
|
||||
marginBottom: theme.spacing(2)
|
||||
},
|
||||
warehouseLabel: {
|
||||
marginBottom: theme.spacing(4)
|
||||
}
|
||||
}),
|
||||
{ name: "OrderFulfillPage" }
|
||||
);
|
|
@ -0,0 +1,113 @@
|
|||
import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import { FulfillmentFragment, OrderFulfillLineFragment } from "@saleor/graphql";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import {
|
||||
getFulfillmentFormsetQuantity,
|
||||
getOrderLineAvailableQuantity,
|
||||
OrderFulfillStockInputFormsetData
|
||||
} from "@saleor/orders/utils/data";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import OrderFulfillStockExceededDialogLine from "../OrderFulfillStockExceededDialogLine";
|
||||
import { stockExceededDialogMessages as messages } from "./messages";
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
export interface OrderFulfillStockExceededDialogProps {
|
||||
lines: Array<FulfillmentFragment["lines"][0] | OrderFulfillLineFragment>;
|
||||
open: boolean;
|
||||
formsetData: OrderFulfillStockInputFormsetData;
|
||||
warehouseId: string;
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
onSubmit();
|
||||
onClose();
|
||||
}
|
||||
|
||||
const OrderFulfillStockExceededDialog: React.FC<OrderFulfillStockExceededDialogProps> = props => {
|
||||
const {
|
||||
lines,
|
||||
open,
|
||||
formsetData,
|
||||
warehouseId,
|
||||
confirmButtonState,
|
||||
onClose,
|
||||
onSubmit
|
||||
} = props;
|
||||
|
||||
const intl = useIntl();
|
||||
const classes = useStyles(props);
|
||||
|
||||
const exceededLines = lines?.filter(el => {
|
||||
const line = "orderLine" in el ? el.orderLine : el;
|
||||
const stock = line.variant?.stocks.find(
|
||||
stock => stock.warehouse.id === warehouseId
|
||||
);
|
||||
|
||||
return (
|
||||
getFulfillmentFormsetQuantity(formsetData, line) >
|
||||
getOrderLineAvailableQuantity(line, stock)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionDialog
|
||||
open={open}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onConfirm={onSubmit}
|
||||
onClose={onClose}
|
||||
confirmButtonState={confirmButtonState}
|
||||
maxWidth="sm"
|
||||
confirmButtonLabel={intl.formatMessage(messages.fulfillButton)}
|
||||
>
|
||||
<Typography>{intl.formatMessage(messages.infoLabel)}</Typography>
|
||||
<CardSpacer />
|
||||
<div className={classes.scrollable}>
|
||||
<ResponsiveTable className={classes.table}>
|
||||
{!!lines?.length && (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.colName}>
|
||||
{intl.formatMessage(messages.productLabel)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
{intl.formatMessage(messages.requiredStockLabel)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colWarehouseStock}>
|
||||
{intl.formatMessage(messages.warehouseStockLabel)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
)}
|
||||
|
||||
<TableBody>
|
||||
{renderCollection(exceededLines, line => (
|
||||
<OrderFulfillStockExceededDialogLine
|
||||
key={line?.id}
|
||||
line={line}
|
||||
formsetData={formsetData}
|
||||
warehouseId={warehouseId}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
</div>
|
||||
<CardSpacer />
|
||||
<Typography>{intl.formatMessage(messages.questionLabel)}</Typography>
|
||||
</ActionDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
OrderFulfillStockExceededDialog.displayName = "OrderFulfillStockExceededDialog";
|
||||
export default OrderFulfillStockExceededDialog;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderFulfillStockExceededDialog";
|
||||
export * from "./OrderFulfillStockExceededDialog";
|
|
@ -0,0 +1,41 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const stockExceededDialogMessages = defineMessages({
|
||||
title: {
|
||||
defaultMessage: "Not enough stock",
|
||||
description: "stock exceeded dialog title"
|
||||
},
|
||||
infoLabel: {
|
||||
defaultMessage:
|
||||
"Stock for items shown below are not enough to prepare fulfillment:",
|
||||
description: "stock exceeded dialog description"
|
||||
},
|
||||
questionLabel: {
|
||||
defaultMessage: "Are you sure you want to fulfill those products anyway?",
|
||||
description: "stock exceeded action question label"
|
||||
},
|
||||
cancelButton: {
|
||||
defaultMessage: "Cancel",
|
||||
description: "cancel button label"
|
||||
},
|
||||
fulfillButton: {
|
||||
defaultMessage: "Fulfill anyway",
|
||||
description: "fulfill button label"
|
||||
},
|
||||
productLabel: {
|
||||
defaultMessage: "Product",
|
||||
description: "table header product label"
|
||||
},
|
||||
requiredStockLabel: {
|
||||
defaultMessage: "Required",
|
||||
description: "table header required stock label"
|
||||
},
|
||||
availableStockLabel: {
|
||||
defaultMessage: "Available",
|
||||
description: "table header available stock label"
|
||||
},
|
||||
warehouseStockLabel: {
|
||||
defaultMessage: "Warehouse stock",
|
||||
description: "table header warehouse stock label"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
colName: {
|
||||
width: "auto",
|
||||
margin: "0px"
|
||||
},
|
||||
colQuantity: {
|
||||
textAlign: "right",
|
||||
width: 100,
|
||||
padding: "4px 4px"
|
||||
},
|
||||
colWarehouseStock: {
|
||||
textAlign: "right",
|
||||
width: 150,
|
||||
padding: "4px 24px"
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
label: {
|
||||
margin: theme.spacing(2)
|
||||
},
|
||||
scrollable: {
|
||||
maxHeight: 450,
|
||||
overflow: "scroll"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderFulfillStockExceededDialog" }
|
||||
);
|
|
@ -0,0 +1,59 @@
|
|||
import { TableCell, TableRow, Typography } from "@material-ui/core";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import { FulfillmentFragment, OrderFulfillLineFragment } from "@saleor/graphql";
|
||||
import {
|
||||
getAttributesCaption,
|
||||
getFulfillmentFormsetQuantity,
|
||||
getOrderLineAvailableQuantity,
|
||||
OrderFulfillStockInputFormsetData
|
||||
} from "@saleor/orders/utils/data";
|
||||
import React from "react";
|
||||
|
||||
import { useStyles } from "../OrderFulfillStockExceededDialog/styles";
|
||||
|
||||
export interface OrderFulfillStockExceededDialogLineProps {
|
||||
line: OrderFulfillLineFragment | FulfillmentFragment["lines"][0];
|
||||
warehouseId: string;
|
||||
formsetData: OrderFulfillStockInputFormsetData;
|
||||
}
|
||||
|
||||
const OrderFulfillStockExceededDialogLine: React.FC<OrderFulfillStockExceededDialogLineProps> = props => {
|
||||
const { line: genericLine, warehouseId, formsetData } = props;
|
||||
|
||||
if (!genericLine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const line = "orderLine" in genericLine ? genericLine.orderLine : genericLine;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const stock = line?.variant?.stocks.find(
|
||||
stock => stock.warehouse.id === warehouseId
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow key={line?.id}>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={line?.thumbnail?.url}
|
||||
>
|
||||
{line?.productName}
|
||||
{"attributes" in line.variant && (
|
||||
<Typography color="textSecondary" variant="caption">
|
||||
{getAttributesCaption(line.variant?.attributes)}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
{getFulfillmentFormsetQuantity(formsetData, line)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colWarehouseStock}>
|
||||
{getOrderLineAvailableQuantity(line, stock)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
OrderFulfillStockExceededDialogLine.displayName =
|
||||
"OrderFulfillStockExceededDialogLine";
|
||||
export default OrderFulfillStockExceededDialogLine;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderFulfillStockExceededDialogLine";
|
||||
export * from "./OrderFulfillStockExceededDialogLine";
|
|
@ -8,9 +8,9 @@ import { mergeRepeatedOrderLines } from "@saleor/orders/utils/data";
|
|||
import React from "react";
|
||||
|
||||
import { renderCollection } from "../../../misc";
|
||||
import OrderCardTitle from "../OrderCardTitle";
|
||||
import TableHeader from "../OrderProductsCardElements/OrderProductsCardHeader";
|
||||
import TableLine from "../OrderProductsCardElements/OrderProductsTableRow";
|
||||
import CardTitle from "../OrderReturnPage/OrderReturnRefundItemsCard/CardTitle";
|
||||
import ActionButtons from "./ActionButtons";
|
||||
import ExtraInfoLines from "./ExtraInfoLines";
|
||||
import useStyles from "./styles";
|
||||
|
@ -63,7 +63,7 @@ const OrderFulfilledProductsCard: React.FC<OrderFulfilledProductsCardProps> = pr
|
|||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardTitle
|
||||
<OrderCardTitle
|
||||
withStatus
|
||||
lines={fulfillment?.lines}
|
||||
fulfillmentOrder={fulfillment?.fulfillmentOrder}
|
||||
|
@ -87,7 +87,7 @@ const OrderFulfilledProductsCard: React.FC<OrderFulfilledProductsCardProps> = pr
|
|||
<TableHeader />
|
||||
<TableBody>
|
||||
{renderCollection(getLines(), line => (
|
||||
<TableLine line={line} />
|
||||
<TableLine key={line.id} line={line} />
|
||||
))}
|
||||
</TableBody>
|
||||
<ExtraInfoLines fulfillment={fulfillment} />
|
||||
|
|
|
@ -22,9 +22,9 @@ import { renderCollection } from "@saleor/misc";
|
|||
import React, { CSSProperties } from "react";
|
||||
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import OrderCardTitle from "../../OrderCardTitle";
|
||||
import { FormsetQuantityData, FormsetReplacementData } from "../form";
|
||||
import { getById } from "../utils";
|
||||
import CardTitle from "./CardTitle";
|
||||
import MaximalButton from "./MaximalButton";
|
||||
import ProductErrorCell from "./ProductErrorCell";
|
||||
|
||||
|
@ -120,7 +120,7 @@ const ItemsCard: React.FC<OrderReturnRefundLinesCardProps> = ({
|
|||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
<OrderCardTitle
|
||||
orderNumber={order?.number}
|
||||
lines={lines}
|
||||
fulfillmentOrder={fulfillment?.fulfillmentOrder}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import { Card, CardActions, TableBody, Typography } from "@material-ui/core";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import { OrderLineFragment } from "@saleor/graphql";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import { OrderLineFragment, WarehouseFragment } from "@saleor/graphql";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
import { Button, ChevronIcon, makeStyles } from "@saleor/macaw-ui";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import OrderCardTitle from "../OrderCardTitle";
|
||||
import TableHeader from "../OrderProductsCardElements/OrderProductsCardHeader";
|
||||
import TableLine from "../OrderProductsCardElements/OrderProductsTableRow";
|
||||
import CardTitle from "../OrderReturnPage/OrderReturnRefundItemsCard/CardTitle";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
|
@ -26,6 +27,41 @@ const useStyles = makeStyles(
|
|||
}
|
||||
},
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
toolbar: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
borderRadius: "4px",
|
||||
paddingTop: theme.spacing(1),
|
||||
paddingBottom: theme.spacing(1),
|
||||
paddingRight: theme.spacing(0.5),
|
||||
paddingLeft: theme.spacing(1.5),
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.saleor.active[5],
|
||||
color: theme.palette.saleor.active[1]
|
||||
},
|
||||
"& > div": {
|
||||
minWidth: 0,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
}
|
||||
},
|
||||
cardTitle: {
|
||||
justifyContent: "space-between",
|
||||
"& > div": {
|
||||
"&:first-child": {
|
||||
flex: 0,
|
||||
whiteSpace: "nowrap"
|
||||
},
|
||||
"&:last-child": {
|
||||
flex: "0 1 auto",
|
||||
minWidth: 0,
|
||||
marginLeft: theme.spacing(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
{ name: "OrderUnfulfilledItems" }
|
||||
|
@ -36,6 +72,8 @@ interface OrderUnfulfilledProductsCardProps {
|
|||
notAllowedToFulfillUnpaid: boolean;
|
||||
lines: OrderLineFragment[];
|
||||
onFulfill: () => void;
|
||||
selectedWarehouse: WarehouseFragment;
|
||||
onWarehouseChange: () => null;
|
||||
}
|
||||
|
||||
const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps> = props => {
|
||||
|
@ -43,7 +81,9 @@ const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps>
|
|||
showFulfillmentAction,
|
||||
notAllowedToFulfillUnpaid,
|
||||
lines,
|
||||
onFulfill
|
||||
onFulfill,
|
||||
selectedWarehouse,
|
||||
onWarehouseChange
|
||||
} = props;
|
||||
const classes = useStyles({});
|
||||
|
||||
|
@ -54,19 +94,30 @@ const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps>
|
|||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardTitle withStatus status="unfulfilled" />
|
||||
<OrderCardTitle
|
||||
lines={lines}
|
||||
withStatus
|
||||
status="unfulfilled"
|
||||
className={classes.cardTitle}
|
||||
toolbar={
|
||||
<div className={classes.toolbar} onClick={onWarehouseChange}>
|
||||
<div>{selectedWarehouse?.name ?? <Skeleton />}</div>
|
||||
<ChevronIcon />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<ResponsiveTable className={classes.table}>
|
||||
<TableHeader />
|
||||
<TableBody>
|
||||
{renderCollection(lines, line => (
|
||||
<TableLine isOrderLine line={line} />
|
||||
<TableLine key={line.id} isOrderLine line={line} />
|
||||
))}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
{showFulfillmentAction && (
|
||||
<CardActions>
|
||||
<CardActions className={classes.actions}>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
variant="primary"
|
||||
onClick={onFulfill}
|
||||
disabled={notAllowedToFulfillUnpaid}
|
||||
>
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
PaymentChargeStatusEnum,
|
||||
SearchCustomersQuery,
|
||||
SearchOrderVariantQuery,
|
||||
SearchWarehousesQuery,
|
||||
ShopOrderSettingsFragment,
|
||||
WeightUnitsEnum
|
||||
} from "@saleor/graphql";
|
||||
|
@ -1073,6 +1074,18 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
|||
quantity: 2,
|
||||
quantityFulfilled: 2,
|
||||
quantityToFulfill: 0,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
thumbnail: {
|
||||
__typename: "Image" as "Image",
|
||||
url: placeholder
|
||||
|
@ -1116,7 +1129,33 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
|||
__typename: "ProductVariant",
|
||||
id: "dsfsfuhb",
|
||||
quantityAvailable: 10,
|
||||
preorder: null
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "stock_warehouse1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "stock_warehouse2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
quantity: 1
|
||||
|
@ -1143,6 +1182,18 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
|||
quantity: 2,
|
||||
quantityFulfilled: 2,
|
||||
quantityToFulfill: 0,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
thumbnail: {
|
||||
__typename: "Image" as "Image",
|
||||
url: placeholder
|
||||
|
@ -1186,7 +1237,33 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
|||
__typename: "ProductVariant",
|
||||
id: "dsfsfuhb",
|
||||
quantityAvailable: 10,
|
||||
preorder: null
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "stock_warehouse1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "stock_warehouse2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
quantity: 1
|
||||
|
@ -1221,6 +1298,18 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
|||
quantity: 3,
|
||||
quantityFulfilled: 0,
|
||||
quantityToFulfill: 3,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
thumbnail: {
|
||||
__typename: "Image" as "Image",
|
||||
url: placeholder
|
||||
|
@ -1264,7 +1353,33 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
|||
__typename: "ProductVariant",
|
||||
id: "dsfsfuhb",
|
||||
quantityAvailable: 10,
|
||||
preorder: null
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "stock_warehouse1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "stock_warehouse2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1276,6 +1391,18 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
|||
quantity: 2,
|
||||
quantityFulfilled: 2,
|
||||
quantityToFulfill: 0,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
thumbnail: {
|
||||
__typename: "Image" as "Image",
|
||||
url: placeholder
|
||||
|
@ -1320,7 +1447,33 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
|||
__typename: "ProductVariant",
|
||||
id: "dsfsfuhb",
|
||||
quantityAvailable: 10,
|
||||
preorder: null
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "stock_warehouse1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "stock_warehouse2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -1471,6 +1624,18 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
|
|||
quantity: 2,
|
||||
quantityFulfilled: 0,
|
||||
quantityToFulfill: 2,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
thumbnail: {
|
||||
__typename: "Image" as "Image",
|
||||
url: placeholder
|
||||
|
@ -1514,7 +1679,33 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
|
|||
__typename: "ProductVariant",
|
||||
id: "dsfsfuhb",
|
||||
quantityAvailable: 10,
|
||||
preorder: null
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "stock_warehouse1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "stock_warehouse2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1526,6 +1717,18 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
|
|||
quantity: 2,
|
||||
quantityFulfilled: 0,
|
||||
quantityToFulfill: 2,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
thumbnail: {
|
||||
__typename: "Image" as "Image",
|
||||
url: placeholder
|
||||
|
@ -1569,7 +1772,33 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
|
|||
__typename: "ProductVariant",
|
||||
id: "dsfsfuhb",
|
||||
quantityAvailable: 10,
|
||||
preorder: null
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "stock_warehouse1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "stock_warehouse2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -2088,3 +2317,105 @@ export const shopOrderSettings: ShopOrderSettingsFragment = {
|
|||
fulfillmentAutoApprove: true,
|
||||
fulfillmentAllowUnpaid: true
|
||||
};
|
||||
|
||||
export const warehouseSearch: SearchWarehousesQuery["search"] = {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOmJiZTEwZjk1LTQyYjAtNDRlMS04Yjc5LWU5MjllMmViYTRjMQ==",
|
||||
name: "CyVou-97803",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOjdhOGViNThhLTYwN2QtNGMxNC04ODVmLTBiMWU3ZDcyMTIyNQ==",
|
||||
name: "CyWarehouse72715",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOjY2NWIxZWFmLTU5MDYtNGE0Mi1iYWVkLTc1ODQ3YWNhMWI1NQ==",
|
||||
name: "CyWarehouseCheckout70441",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOjdkNmVmNmFkLWY4NTMtNGVmNS1iMzQ5LTUyY2I2N2U3NmIwZQ==",
|
||||
name: "CyWeightRates-78849",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOjcwZjMyYTUyLWVlODQtNGExYi1iMjgzLTgwYjllMzgyNDlkNg==",
|
||||
name: "EditShipping-82885",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
name: "Europe for click and collect",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
name: "Oceania",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOjNiZDM0YjEyLTllNDktNDMwZC1iM2QyLTRkYmRhMjM1MGUyOQ==",
|
||||
name: "ProductsWithoutSkuInOrder",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOmU4M2U2NjQ2LTFhYjctNGNmNC05N2M4LTFiZjI2NGE2NjQ4Yw==",
|
||||
name: "StocksThreshold",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOmJkMmQ1NDFjLWQwMjMtNDAwNi05YmRjLWZhZTA4OWZlNzZiYg==",
|
||||
name: "UpdateProductsSku59844",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: "V2FyZWhvdXNlOjgzNDMwMzI4LTI2YWItNDNkZS1hNzdhLTVmNGNhMTljMDJhNg==",
|
||||
name: "WithoutShipmentCheckout-4505",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
__typename: "WarehouseCountableEdge"
|
||||
}
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
"WyJXaXRob3V0U2hpcG1lbnRDaGVja291dC00NTA1IiwgIldpdGhvdXRTaGlwbWVudENoZWNrb3V0LTQ1MDUiXQ==",
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: true,
|
||||
startCursor: "WyJDeVZvdS05NzgwMyIsICJDeVZvdS05NzgwMyJd",
|
||||
__typename: "PageInfo"
|
||||
},
|
||||
__typename: "WarehouseCountableConnection"
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
OrderDraftListUrlQueryParams,
|
||||
OrderDraftListUrlSortField,
|
||||
orderFulfillPath,
|
||||
OrderFulfillUrlQueryParams,
|
||||
orderListPath,
|
||||
OrderListUrlQueryParams,
|
||||
OrderListUrlSortField,
|
||||
|
@ -61,9 +62,19 @@ const OrderDetails: React.FC<RouteComponentProps<any>> = ({
|
|||
return <OrderDetailsComponent id={decodeURIComponent(id)} params={params} />;
|
||||
};
|
||||
|
||||
const OrderFulfill: React.FC<RouteComponentProps<any>> = ({ match }) => (
|
||||
<OrderFulfillComponent orderId={decodeURIComponent(match.params.id)} />
|
||||
);
|
||||
const OrderFulfill: React.FC<RouteComponentProps<any>> = ({
|
||||
location,
|
||||
match
|
||||
}) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: OrderFulfillUrlQueryParams = qs;
|
||||
return (
|
||||
<OrderFulfillComponent
|
||||
orderId={decodeURIComponent(match.params.id)}
|
||||
params={params}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const OrderRefund: React.FC<RouteComponentProps<any>> = ({ match }) => (
|
||||
<OrderRefundComponent orderId={decodeURIComponent(match.params.id)} />
|
||||
|
|
|
@ -243,8 +243,16 @@ export const orderFulfillmentUpdateTrackingMutation = gql`
|
|||
`;
|
||||
|
||||
export const orderFulfillmentApproveMutation = gql`
|
||||
mutation OrderFulfillmentApprove($id: ID!, $notifyCustomer: Boolean!) {
|
||||
orderFulfillmentApprove(id: $id, notifyCustomer: $notifyCustomer) {
|
||||
mutation OrderFulfillmentApprove(
|
||||
$id: ID!
|
||||
$notifyCustomer: Boolean!
|
||||
$allowStockToBeExceeded: Boolean
|
||||
) {
|
||||
orderFulfillmentApprove(
|
||||
id: $id
|
||||
notifyCustomer: $notifyCustomer
|
||||
allowStockToBeExceeded: $allowStockToBeExceeded
|
||||
) {
|
||||
errors {
|
||||
...OrderError
|
||||
}
|
||||
|
|
|
@ -130,44 +130,7 @@ export const orderFulfillData = gql`
|
|||
}
|
||||
}
|
||||
lines {
|
||||
id
|
||||
isShippingRequired
|
||||
productName
|
||||
quantity
|
||||
allocations {
|
||||
quantity
|
||||
warehouse {
|
||||
id
|
||||
}
|
||||
}
|
||||
quantityFulfilled
|
||||
quantityToFulfill
|
||||
variant {
|
||||
id
|
||||
name
|
||||
sku
|
||||
preorder {
|
||||
endDate
|
||||
}
|
||||
attributes {
|
||||
values {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
stocks {
|
||||
id
|
||||
warehouse {
|
||||
...Warehouse
|
||||
}
|
||||
quantity
|
||||
quantityAllocated
|
||||
}
|
||||
trackInventory
|
||||
}
|
||||
thumbnail(size: 64) {
|
||||
url
|
||||
}
|
||||
...OrderFulfillLine
|
||||
}
|
||||
number
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ export type OrderUrlDialog =
|
|||
| "cancel"
|
||||
| "cancel-fulfillment"
|
||||
| "capture"
|
||||
| "change-warehouse"
|
||||
| "customer-change"
|
||||
| "edit-customer-addresses"
|
||||
| "edit-billing-address"
|
||||
|
@ -124,6 +125,8 @@ export type OrderUrlDialog =
|
|||
|
||||
export type OrderUrlQueryParams = Dialog<OrderUrlDialog> & SingleAction;
|
||||
|
||||
export type OrderFulfillUrlQueryParams = Partial<{ warehouse: string }>;
|
||||
|
||||
export const orderUrl = (id: string, params?: OrderUrlQueryParams) =>
|
||||
orderPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
|
||||
|
@ -132,8 +135,10 @@ export const orderFulfillPath = (id: string) =>
|
|||
|
||||
export const orderReturnPath = (id: string) => urlJoin(orderPath(id), "return");
|
||||
|
||||
export const orderFulfillUrl = (id: string) =>
|
||||
orderFulfillPath(encodeURIComponent(id));
|
||||
export const orderFulfillUrl = (
|
||||
id: string,
|
||||
params?: OrderFulfillUrlQueryParams
|
||||
) => orderFulfillPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
|
||||
export const orderSettingsPath = urlJoin(orderSectionUrl, "settings");
|
||||
|
||||
|
|
|
@ -521,11 +521,49 @@ describe("Get the total value of all replaced products", () => {
|
|||
{
|
||||
id: "1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
__typename: "ProductVariant"
|
||||
__typename: "ProductVariant",
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
]
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
|
@ -577,10 +615,48 @@ describe("Get the total value of all replaced products", () => {
|
|||
{
|
||||
id: "2",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -633,10 +709,48 @@ describe("Get the total value of all replaced products", () => {
|
|||
{
|
||||
id: "3",
|
||||
isShippingRequired: true,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
|
@ -695,10 +809,48 @@ describe("Get the total value of all replaced products", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -756,10 +908,48 @@ describe("Get the total value of all replaced products", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -817,10 +1007,48 @@ describe("Get the total value of all replaced products", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ3",
|
||||
isShippingRequired: true,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
|
@ -878,10 +1106,48 @@ describe("Get the total value of all replaced products", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -939,10 +1205,48 @@ describe("Get the total value of all replaced products", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -1134,10 +1438,48 @@ describe("Get the total value of all selected products", () => {
|
|||
{
|
||||
id: "1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -1190,10 +1532,48 @@ describe("Get the total value of all selected products", () => {
|
|||
{
|
||||
id: "2",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -1246,10 +1626,48 @@ describe("Get the total value of all selected products", () => {
|
|||
{
|
||||
id: "3",
|
||||
isShippingRequired: true,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
|
@ -1308,10 +1726,48 @@ describe("Get the total value of all selected products", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -1369,10 +1825,48 @@ describe("Get the total value of all selected products", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -1430,10 +1924,48 @@ describe("Get the total value of all selected products", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ3",
|
||||
isShippingRequired: true,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
|
@ -1619,10 +2151,48 @@ describe("Merge repeated order lines of fulfillment lines", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -1680,10 +2250,48 @@ describe("Merge repeated order lines of fulfillment lines", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
|
@ -1741,10 +2349,48 @@ describe("Merge repeated order lines of fulfillment lines", () => {
|
|||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ3",
|
||||
isShippingRequired: true,
|
||||
allocations: [
|
||||
{
|
||||
id: "allocation_test_id",
|
||||
warehouse: {
|
||||
id:
|
||||
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 1,
|
||||
__typename: "Allocation"
|
||||
}
|
||||
],
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
preorder: null,
|
||||
stocks: [
|
||||
{
|
||||
id: "stock_test_id1",
|
||||
warehouse: {
|
||||
name: "warehouse_stock1",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
},
|
||||
{
|
||||
id: "stock_test_id2",
|
||||
warehouse: {
|
||||
name: "warehouse_stock2",
|
||||
id:
|
||||
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
|
||||
__typename: "Warehouse"
|
||||
},
|
||||
quantity: 166,
|
||||
quantityAllocated: 0,
|
||||
__typename: "Stock"
|
||||
}
|
||||
],
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
|
|
|
@ -3,13 +3,15 @@ import {
|
|||
AddressFragment,
|
||||
AddressInput,
|
||||
CountryCode,
|
||||
FulfillmentFragment,
|
||||
FulfillmentStatus,
|
||||
FulfillOrderMutation,
|
||||
OrderDetailsFragment,
|
||||
OrderErrorCode,
|
||||
OrderFulfillDataQuery,
|
||||
OrderFulfillLineFragment,
|
||||
OrderFulfillStockInput,
|
||||
OrderLineFragment,
|
||||
OrderLineStockDataFragment,
|
||||
OrderRefundDataQuery,
|
||||
StockFragment,
|
||||
WarehouseFragment
|
||||
} from "@saleor/graphql";
|
||||
import { FormsetData } from "@saleor/hooks/useFormset";
|
||||
|
@ -36,9 +38,7 @@ export interface OrderLineWithStockWarehouses {
|
|||
};
|
||||
}
|
||||
|
||||
export function getToFulfillOrderLines(
|
||||
lines?: OrderFulfillDataQuery["order"]["lines"]
|
||||
) {
|
||||
export function getToFulfillOrderLines(lines?: OrderLineStockDataFragment[]) {
|
||||
return lines?.filter(line => line.quantityToFulfill > 0) || [];
|
||||
}
|
||||
|
||||
|
@ -276,31 +276,6 @@ export function mergeRepeatedOrderLines(
|
|||
}, Array<OrderDetailsFragment["fulfillments"][0]["lines"][0]>());
|
||||
}
|
||||
|
||||
export const isStockError = (
|
||||
overfulfill: boolean,
|
||||
formsetStock: { quantity: number },
|
||||
availableQuantity: number,
|
||||
warehouse: WarehouseFragment,
|
||||
line: OrderFulfillDataQuery["order"]["lines"][0],
|
||||
errors: FulfillOrderMutation["orderFulfill"]["errors"]
|
||||
) => {
|
||||
if (overfulfill) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isQuantityLargerThanAvailable =
|
||||
line.variant.trackInventory && formsetStock.quantity > availableQuantity;
|
||||
|
||||
const isError = !!errors?.find(
|
||||
err =>
|
||||
err.warehouse === warehouse.id &&
|
||||
err.orderLines.find((id: string) => id === line.id) &&
|
||||
err.code === OrderErrorCode.INSUFFICIENT_STOCK
|
||||
);
|
||||
|
||||
return isQuantityLargerThanAvailable || isError;
|
||||
};
|
||||
|
||||
export function addressToAddressInput<T>(
|
||||
address: T & AddressFragment
|
||||
): AddressInput {
|
||||
|
@ -324,3 +299,83 @@ export const getVariantSearchAddress = (
|
|||
|
||||
return { country: order.channel.defaultCountry.code as CountryCode };
|
||||
};
|
||||
|
||||
export const getAllocatedQuantityForLine = (
|
||||
line: OrderLineStockDataFragment,
|
||||
warehouseId: string
|
||||
) => {
|
||||
const warehouseAllocation = line.allocations.find(
|
||||
allocation => allocation.warehouse.id === warehouseId
|
||||
);
|
||||
return warehouseAllocation?.quantity || 0;
|
||||
};
|
||||
|
||||
export const getOrderLineAvailableQuantity = (
|
||||
line: OrderLineStockDataFragment,
|
||||
stock: StockFragment
|
||||
) => {
|
||||
if (!stock) {
|
||||
return 0;
|
||||
}
|
||||
const allocatedQuantityForLine = getAllocatedQuantityForLine(
|
||||
line,
|
||||
stock.warehouse.id
|
||||
);
|
||||
|
||||
const availableQuantity =
|
||||
stock.quantity - stock.quantityAllocated + allocatedQuantityForLine;
|
||||
|
||||
return availableQuantity;
|
||||
};
|
||||
|
||||
export type OrderFulfillStockInputFormsetData = Array<
|
||||
Pick<FormsetData<null, OrderFulfillStockInput[]>[0], "id" | "value">
|
||||
>;
|
||||
|
||||
export const getFulfillmentFormsetQuantity = (
|
||||
formsetData: OrderFulfillStockInputFormsetData,
|
||||
line: OrderLineStockDataFragment
|
||||
) => formsetData?.find(getById(line.id))?.value?.[0]?.quantity;
|
||||
|
||||
export const getWarehouseStock = (
|
||||
stocks: StockFragment[],
|
||||
warehouseId: string
|
||||
) => stocks?.find(stock => stock.warehouse.id === warehouseId);
|
||||
|
||||
export const isLineAvailableInWarehouse = (
|
||||
line: OrderLineStockDataFragment,
|
||||
warehouse: WarehouseFragment
|
||||
) => {
|
||||
if (!line?.variant?.stocks) {
|
||||
return false;
|
||||
}
|
||||
const stock = getWarehouseStock(line.variant.stocks, warehouse.id);
|
||||
if (stock) {
|
||||
return line.quantityToFulfill <= getOrderLineAvailableQuantity(line, stock);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const transformFuflillmentLinesToStockInputFormsetData = (
|
||||
lines: FulfillmentFragment["lines"],
|
||||
warehouseId: string
|
||||
): OrderFulfillStockInputFormsetData =>
|
||||
lines?.map(line => ({
|
||||
data: null,
|
||||
id: line.orderLine.id,
|
||||
value: [
|
||||
{
|
||||
quantity: line.quantity,
|
||||
warehouse: warehouseId
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
export const getAttributesCaption = (
|
||||
attributes: OrderFulfillLineFragment["variant"]["attributes"]
|
||||
): string =>
|
||||
attributes
|
||||
.map(attribute =>
|
||||
attribute.values.map(attributeValue => attributeValue.name).join(", ")
|
||||
)
|
||||
.join(" / ");
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import {
|
||||
FulfillmentFragment,
|
||||
FulfillmentStatus,
|
||||
OrderDetailsQueryResult,
|
||||
OrderFulfillmentApproveMutation,
|
||||
OrderFulfillmentApproveMutationVariables,
|
||||
OrderUpdateMutation,
|
||||
OrderUpdateMutationVariables,
|
||||
useCustomerAddressesQuery,
|
||||
useWarehouseListQuery
|
||||
useWarehouseListQuery,
|
||||
WarehouseFragment
|
||||
} from "@saleor/graphql";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import OrderCannotCancelOrderDialog from "@saleor/orders/components/OrderCannotCancelOrderDialog";
|
||||
import OrderChangeWarehouseDialog from "@saleor/orders/components/OrderChangeWarehouseDialog";
|
||||
import { OrderCustomerAddressesEditDialogOutput } from "@saleor/orders/components/OrderCustomerAddressesEditDialog/types";
|
||||
import OrderFulfillmentApproveDialog from "@saleor/orders/components/OrderFulfillmentApproveDialog";
|
||||
import OrderFulfillStockExceededDialog from "@saleor/orders/components/OrderFulfillStockExceededDialog";
|
||||
import OrderInvoiceEmailSendDialog from "@saleor/orders/components/OrderInvoiceEmailSendDialog";
|
||||
import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
|
||||
import { transformFuflillmentLinesToStockInputFormsetData } from "@saleor/orders/utils/data";
|
||||
import { PartialMutationProviderOutput } from "@saleor/types";
|
||||
import { mapEdgesToItems } from "@saleor/utils/maps";
|
||||
import React from "react";
|
||||
|
@ -42,11 +49,12 @@ import {
|
|||
OrderUrlQueryParams
|
||||
} from "../../../urls";
|
||||
import { isAnyAddressEditModalOpen } from "../OrderDraftDetails";
|
||||
import { useDefaultWarehouse } from "./useDefaultWarehouse";
|
||||
|
||||
interface OrderNormalDetailsProps {
|
||||
id: string;
|
||||
params: OrderUrlQueryParams;
|
||||
data: any;
|
||||
data: OrderDetailsQueryResult["data"];
|
||||
orderAddNote: any;
|
||||
orderInvoiceRequest: any;
|
||||
handleSubmit: any;
|
||||
|
@ -70,6 +78,10 @@ interface OrderNormalDetailsProps {
|
|||
openModal: any;
|
||||
closeModal: any;
|
||||
}
|
||||
interface ApprovalState {
|
||||
fulfillment: FulfillmentFragment;
|
||||
notifyCustomer: boolean;
|
||||
}
|
||||
|
||||
export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
||||
id,
|
||||
|
@ -96,13 +108,27 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
|||
const shop = data?.shop;
|
||||
const navigate = useNavigator();
|
||||
|
||||
const warehouses = useWarehouseListQuery({
|
||||
const {
|
||||
data: warehousesData,
|
||||
loading: warehousesLoading
|
||||
} = useWarehouseListQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
first: 30
|
||||
}
|
||||
});
|
||||
|
||||
const warehouses = mapEdgesToItems(warehousesData?.warehouses);
|
||||
|
||||
const [fulfillmentWarehouse, setFulfillmentWarehouse] = React.useState<
|
||||
WarehouseFragment
|
||||
>(null);
|
||||
|
||||
useDefaultWarehouse({ warehouses, order, setter: setFulfillmentWarehouse }, [
|
||||
warehousesData,
|
||||
warehousesLoading
|
||||
]);
|
||||
|
||||
const {
|
||||
data: customerAddresses,
|
||||
loading: customerAddressesLoading
|
||||
|
@ -125,6 +151,22 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
|||
|
||||
const handleBack = () => navigate(orderListUrl());
|
||||
|
||||
const [
|
||||
currentApproval,
|
||||
setCurrentApproval
|
||||
] = React.useState<ApprovalState | null>(null);
|
||||
const [stockExceeded, setStockExceeded] = React.useState(false);
|
||||
const approvalErrors =
|
||||
orderFulfillmentApprove.opts.data?.orderFulfillmentApprove.errors || [];
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
approvalErrors.length &&
|
||||
approvalErrors.every(err => err.code === "INSUFFICIENT_STOCK")
|
||||
) {
|
||||
setStockExceeded(true);
|
||||
}
|
||||
}, [approvalErrors]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle
|
||||
|
@ -167,8 +209,11 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
|||
]
|
||||
)}
|
||||
shippingMethods={data?.order?.shippingMethods || []}
|
||||
selectedWarehouse={fulfillmentWarehouse}
|
||||
onOrderCancel={() => openModal("cancel")}
|
||||
onOrderFulfill={() => navigate(orderFulfillUrl(id))}
|
||||
onOrderFulfill={() =>
|
||||
navigate(orderFulfillUrl(id, { warehouse: fulfillmentWarehouse?.id }))
|
||||
}
|
||||
onFulfillmentApprove={fulfillmentId =>
|
||||
navigate(
|
||||
orderUrl(id, {
|
||||
|
@ -213,6 +258,7 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
|||
})
|
||||
}
|
||||
onInvoiceSend={id => openModal("invoice-send", { id })}
|
||||
onWarehouseChange={() => openModal("change-warehouse")}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
<OrderCannotCancelOrderDialog
|
||||
|
@ -279,21 +325,44 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
|||
[]
|
||||
}
|
||||
open={params.action === "approve-fulfillment"}
|
||||
onConfirm={({ notifyCustomer }) =>
|
||||
orderFulfillmentApprove.mutate({
|
||||
onConfirm={({ notifyCustomer }) => {
|
||||
setCurrentApproval({
|
||||
fulfillment: order?.fulfillments.find(getById(params.id)),
|
||||
notifyCustomer
|
||||
});
|
||||
return orderFulfillmentApprove.mutate({
|
||||
id: params.id,
|
||||
notifyCustomer
|
||||
})
|
||||
}
|
||||
});
|
||||
}}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
<OrderFulfillStockExceededDialog
|
||||
lines={currentApproval?.fulfillment.lines}
|
||||
formsetData={transformFuflillmentLinesToStockInputFormsetData(
|
||||
currentApproval?.fulfillment.lines,
|
||||
currentApproval?.fulfillment.warehouse?.id
|
||||
)}
|
||||
open={stockExceeded}
|
||||
warehouseId={currentApproval?.fulfillment.warehouse?.id}
|
||||
onClose={() => setStockExceeded(false)}
|
||||
confirmButtonState="default"
|
||||
onSubmit={() => {
|
||||
setStockExceeded(false);
|
||||
return orderFulfillmentApprove.mutate({
|
||||
id: params.id,
|
||||
notifyCustomer: currentApproval?.notifyCustomer,
|
||||
allowStockToBeExceeded: true
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<OrderFulfillmentCancelDialog
|
||||
confirmButtonState={orderFulfillmentCancel.opts.status}
|
||||
errors={
|
||||
orderFulfillmentCancel.opts.data?.orderFulfillmentCancel.errors || []
|
||||
}
|
||||
open={params.action === "cancel-fulfillment"}
|
||||
warehouses={mapEdgesToItems(warehouses?.data?.warehouses) || []}
|
||||
warehouses={warehouses || []}
|
||||
onConfirm={variables =>
|
||||
orderFulfillmentCancel.mutate({
|
||||
id: params.id,
|
||||
|
@ -325,6 +394,13 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
|||
}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
<OrderChangeWarehouseDialog
|
||||
open={params.action === "change-warehouse"}
|
||||
lines={order?.lines}
|
||||
currentWarehouse={fulfillmentWarehouse}
|
||||
onConfirm={warehouse => setFulfillmentWarehouse(warehouse)}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
<OrderInvoiceEmailSendDialog
|
||||
confirmButtonState={orderInvoiceSend.opts.status}
|
||||
errors={orderInvoiceSend.opts.data?.invoiceSendEmail.errors || []}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { OrderDetailsFragment, WarehouseFragment } from "@saleor/graphql";
|
||||
import {
|
||||
getToFulfillOrderLines,
|
||||
isLineAvailableInWarehouse
|
||||
} from "@saleor/orders/utils/data";
|
||||
import React from "react";
|
||||
|
||||
export interface UseDefaultWarehouseOpts {
|
||||
warehouses: WarehouseFragment[];
|
||||
order: OrderDetailsFragment;
|
||||
setter: React.Dispatch<React.SetStateAction<WarehouseFragment>>;
|
||||
}
|
||||
|
||||
interface WarehousesAvailibility {
|
||||
warehouse: WarehouseFragment;
|
||||
linesAvailable: number;
|
||||
}
|
||||
|
||||
export function useDefaultWarehouse(
|
||||
{ warehouses, order, setter }: UseDefaultWarehouseOpts,
|
||||
deps: unknown[]
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
const warehousesAvailability: WarehousesAvailibility[] = warehouses?.map(
|
||||
warehouse => {
|
||||
if (!order?.lines) {
|
||||
return undefined;
|
||||
}
|
||||
const linesToFulfill = getToFulfillOrderLines(order.lines);
|
||||
|
||||
const linesAvailable = linesToFulfill.filter(line =>
|
||||
isLineAvailableInWarehouse(line, warehouse)
|
||||
).length;
|
||||
|
||||
return {
|
||||
warehouse,
|
||||
linesAvailable
|
||||
};
|
||||
}
|
||||
);
|
||||
const defaultWarehouse = order?.lines
|
||||
? warehousesAvailability?.reduce((prev, curr) =>
|
||||
curr.linesAvailable > prev.linesAvailable ? curr : prev
|
||||
).warehouse
|
||||
: undefined;
|
||||
setter(defaultWarehouse);
|
||||
}, [order, ...deps]);
|
||||
}
|
|
@ -1,43 +1,27 @@
|
|||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import {
|
||||
OrderFulfillDataQuery,
|
||||
useFulfillOrderMutation,
|
||||
useOrderFulfillDataQuery,
|
||||
useOrderFulfillmentUpdateTrackingMutation,
|
||||
useOrderFulfillSettingsQuery,
|
||||
WarehouseClickAndCollectOptionEnum,
|
||||
WarehouseFragment
|
||||
useWarehouseDetailsQuery
|
||||
} from "@saleor/graphql";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { extractMutationErrors } from "@saleor/misc";
|
||||
import OrderFulfillPage from "@saleor/orders/components/OrderFulfillPage";
|
||||
import { orderUrl } from "@saleor/orders/urls";
|
||||
import { getWarehousesFromOrderLines } from "@saleor/orders/utils/data";
|
||||
import { getMutationErrors } from "@saleor/misc";
|
||||
import OrderFulfillPage, {
|
||||
OrderFulfillSubmitData
|
||||
} from "@saleor/orders/components/OrderFulfillPage";
|
||||
import { OrderFulfillUrlQueryParams, orderUrl } from "@saleor/orders/urls";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
export interface OrderFulfillProps {
|
||||
orderId: string;
|
||||
params: OrderFulfillUrlQueryParams;
|
||||
}
|
||||
|
||||
const resolveLocalFulfillment = (
|
||||
order: OrderFulfillDataQuery["order"],
|
||||
orderLineWarehouses: WarehouseFragment[]
|
||||
) => {
|
||||
const deliveryMethod = order?.deliveryMethod;
|
||||
if (
|
||||
deliveryMethod?.__typename === "Warehouse" &&
|
||||
deliveryMethod?.clickAndCollectOption ===
|
||||
WarehouseClickAndCollectOptionEnum.LOCAL
|
||||
) {
|
||||
return orderLineWarehouses?.filter(
|
||||
warehouse => warehouse?.id === deliveryMethod?.id
|
||||
);
|
||||
}
|
||||
return orderLineWarehouses;
|
||||
};
|
||||
|
||||
const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId }) => {
|
||||
const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
@ -54,7 +38,7 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId }) => {
|
|||
}
|
||||
});
|
||||
|
||||
const orderLinesWarehouses = getWarehousesFromOrderLines(data?.order?.lines);
|
||||
const [updateTracking] = useOrderFulfillmentUpdateTrackingMutation();
|
||||
|
||||
const [fulfillOrder, fulfillOrderOpts] = useFulfillOrderMutation({
|
||||
onCompleted: data => {
|
||||
|
@ -71,10 +55,11 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId }) => {
|
|||
}
|
||||
});
|
||||
|
||||
const resolvedOrderLinesWarehouses = resolveLocalFulfillment(
|
||||
data?.order,
|
||||
orderLinesWarehouses
|
||||
);
|
||||
const { data: warehouseData } = useWarehouseDetailsQuery({
|
||||
variables: {
|
||||
id: params?.warehouse
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -100,26 +85,44 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId }) => {
|
|||
loading={loading || settingsLoading || fulfillOrderOpts.loading}
|
||||
errors={fulfillOrderOpts.data?.orderFulfill.errors}
|
||||
onBack={() => navigate(orderUrl(orderId))}
|
||||
onSubmit={formData =>
|
||||
extractMutationErrors(
|
||||
fulfillOrder({
|
||||
onSubmit={async (formData: OrderFulfillSubmitData) => {
|
||||
const res = await fulfillOrder({
|
||||
variables: {
|
||||
input: {
|
||||
lines: formData.items.map(line => ({
|
||||
lines: formData.items
|
||||
.filter(line => !!line?.value)
|
||||
.map(line => ({
|
||||
orderLineId: line.id,
|
||||
stocks: line.value
|
||||
})),
|
||||
notifyCustomer:
|
||||
settings?.shop?.fulfillmentAutoApprove && formData.sendInfo
|
||||
settings?.shop?.fulfillmentAutoApprove && formData.sendInfo,
|
||||
allowStockToBeExceeded: formData.allowStockToBeExceeded
|
||||
},
|
||||
orderId
|
||||
}
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
const fulfillments = res?.data?.orderFulfill?.order?.fulfillments;
|
||||
if (fulfillments && formData.trackingNumber) {
|
||||
updateTracking({
|
||||
variables: {
|
||||
id: fulfillments[fulfillments.length - 1].id,
|
||||
input: {
|
||||
...(formData?.trackingNumber && {
|
||||
trackingNumber: formData.trackingNumber
|
||||
}),
|
||||
notifyCustomer:
|
||||
settings?.shop?.fulfillmentAutoApprove && formData.sendInfo
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return getMutationErrors(res);
|
||||
}}
|
||||
order={data?.order}
|
||||
saveButtonBar="default"
|
||||
warehouses={resolvedOrderLinesWarehouses}
|
||||
saveButtonBar={fulfillOrderOpts.status}
|
||||
warehouse={warehouseData?.warehouse}
|
||||
shopSettings={settings?.shop}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -11,6 +11,7 @@ export const searchWarehouses = gql`
|
|||
search: warehouses(
|
||||
after: $after
|
||||
first: $first
|
||||
sortBy: { direction: ASC, field: NAME }
|
||||
filter: { search: $query }
|
||||
) {
|
||||
edges {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,6 +20,7 @@ const order = orderFixture(placeholderImage);
|
|||
|
||||
const props: Omit<OrderDetailsPageProps, "classes"> = {
|
||||
disabled: false,
|
||||
selectedWarehouse: undefined,
|
||||
onBack: () => undefined,
|
||||
onBillingAddressEdit: undefined,
|
||||
onFulfillmentApprove: () => undefined,
|
||||
|
@ -39,6 +40,7 @@ const props: Omit<OrderDetailsPageProps, "classes"> = {
|
|||
onProductClick: undefined,
|
||||
onProfileView: () => undefined,
|
||||
onShippingAddressEdit: undefined,
|
||||
onWarehouseChange: undefined,
|
||||
onSubmit: () => undefined,
|
||||
order,
|
||||
shop: shopFixture,
|
||||
|
|
Loading…
Reference in a new issue