Feature/order reissue (#910)
* wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Add change to changelog * Remove console.log * Update tests * Extract messages * Add utils functions for selecting only ulfulfilled order lines * Add optional value selection for line item * Update tests * Add optional rendering of unfulfilled items card and refactor a bit * Update displaying of items card title when refunded card * UUpdate utils, form data etc. not to include refunded items when calculating replaced items amount * Uppdate return items card not to display replace buttons for refunded items * Refactor and small fixes after review * Update extracted messages * Fix card title when no fullfilemtn id * wip * Initially stitch returns page. Update types, add mutation * remove unnecessary component display names * Add loading status from form submission & refactor * Add errors from response * Add errors from response and refactor * Remove comments * Add optional error adding when no data from return create request * Update messages * wip * Update snapshots * Remove unnecessary console.log * Add better typing for getParsedLineData function * Update & refactor card title to match cards both in return and order details * Add handling of new statuses to order details cards. Also refactor, and devide order fulfillment card into couple of smaller components * Update messages * Update schema to match api * Update types * Update status label component to match colors with new designs and order details cards * RUpdate and refactor order fulfillment card components to be reusable. Also add replaced status handling * Updayte card title component to handle all cases and statuses * Update oorder unfulfilled items card and order details page, reduce some of the boilerplate * Fix card title types and adjust returns card to match * Update messages * Update snapshots * RUpdate order fulfillment card with subtitles and buttons for returned status * Add onRefund to order fulfillment card * Fix typo and wrong message in card title * Add missing condition in return form submission utils to decice if to refund products * Update fulfillment subtitles row and tests * Update messages * Change naming and locations of OrderFulfillment and items card components * Update messages * U[pdate names of components again to even better ones * Update messages * changelog * Update schema and types so that order history event also includes user first and last name * Add extended timeline event and event header components. Move some of the logic to utils and add way to display links in the event header. * FFix types * Update messages * Change naming of isOfType -> isTimelineEventOfType and refactor extended timeline event messages selection to be less complicated * Add ids and update messages * Add ids and update messages some more * Update storybook decorator to work with react router context in components and tests * Refactor after review * Update messages * Add rredirecting to draft order * Add handling draft creation from replacement * Add related order to order event fragment and update lots and lots of types * Update extended timeline event to match related order type on order history event * Update fixtures * Refactor ExtendedTimelineEvent Co-authored-by: Jakub Majorek <majorek.jakub@gmail.com> * Fix typing * Update messages * Fix missing history event for replacement draft created for replaced products * Update messages * Handle new statuses for returned and partially returned orders * Update messages * update snapshots * BBump empty line to rebuild ci * Change status to proper color * Change replaceable items in return for replace to be auto off instead of on * Add utils functions and make order details menu not show option to return items when there are returnable items in the order * Fix replace checkbox showing when previously hidden and clicked set maximal quantities * Fix return form invalid money values * Add default values to avoid returning of NaN in utils for return amount and refactor * Add ggeneral error alerts * Add eproduct error box component and style. style a lot. * Fixes * Fix lint * Add cannot refund error title + description * Extract messages * Refactor after review * Add better, nicer and fancier imports to product error cell * Use error color from palette in product error cell * Fix max refund when 0 for return * Add ddisable ability to refund products button so it's disabled when 0 products selected * Add class for order return form data parsing and add condition to not do refund when total captured on order is 0 * Update snapshots * Add condition for order lines quantity in order products table row * Fix return amount submit button * Add change to changelog Co-authored-by: Jakub Majorek <majorek.jakub@gmail.com>
This commit is contained in:
parent
b07bb08ade
commit
f0f9fe9b85
83 changed files with 6819 additions and 4215 deletions
|
@ -9,6 +9,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Add shipping methods to translation section - #864 by @marekchoinski
|
||||
- New Miscellaneous and Product refunds - #870 by @orzechdev
|
||||
- Add zip code exclusion - #877 by @dominik-zeglen
|
||||
- Add order reissue
|
||||
- Update quantity column in Inventory part of Product Variant view - #904 by @dominik-zeglen
|
||||
- Add file attributes - #884 by @orzechdev
|
||||
- Add shipping delivery days - #914 by @orzechdev
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
"context": "dialog header",
|
||||
"string": "Cancel Order"
|
||||
},
|
||||
"amount title": {
|
||||
"context": "amount title",
|
||||
"string": "Refunded amount"
|
||||
},
|
||||
"by preposition": {
|
||||
"context": "by preposition",
|
||||
"string": "by"
|
||||
},
|
||||
"configurationMenuAttributes": {
|
||||
"string": "Determine attributes used to create product types"
|
||||
},
|
||||
|
@ -42,6 +50,38 @@
|
|||
"configurationPluginsPages": {
|
||||
"string": "View and update your plugins and their settings."
|
||||
},
|
||||
"event products list title refunded": {
|
||||
"context": "refunded products list title",
|
||||
"string": "Products refunded"
|
||||
},
|
||||
"event products list title replaced": {
|
||||
"context": "replaced products list title",
|
||||
"string": "Products replaced"
|
||||
},
|
||||
"event products list title returned": {
|
||||
"context": "returned products list title",
|
||||
"string": "Products returned"
|
||||
},
|
||||
"event products title draft reissued": {
|
||||
"context": "draft created from replace products list title",
|
||||
"string": "Products replaced"
|
||||
},
|
||||
"event title draft reissued": {
|
||||
"context": "draft created from replace event title",
|
||||
"string": "Draft was reissued from order "
|
||||
},
|
||||
"event title refunded": {
|
||||
"context": "refunded event title",
|
||||
"string": "Products were refunded by "
|
||||
},
|
||||
"event title replaced": {
|
||||
"context": "replaced event title",
|
||||
"string": "Products were replaced by "
|
||||
},
|
||||
"event title returned": {
|
||||
"context": "returned event title",
|
||||
"string": "Products were returned by"
|
||||
},
|
||||
"homeActivityCardHeader": {
|
||||
"context": "header",
|
||||
"string": "Activity"
|
||||
|
@ -336,6 +376,10 @@
|
|||
"context": "unassign product from sale, button",
|
||||
"string": "Unassign"
|
||||
},
|
||||
"shipment refund title": {
|
||||
"context": "shipment refund title",
|
||||
"string": "Shipment was refunded"
|
||||
},
|
||||
"shippingZoneDetailsDialogsDeleteShippingMethod": {
|
||||
"context": "delete shipping method",
|
||||
"string": "Are you sure you want to delete {name}?"
|
||||
|
@ -3164,13 +3208,17 @@
|
|||
"src_dot_orders_dot_components_dot_OrderCustomer_dot_4282475982": {
|
||||
"string": "Billing Address"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_1854613983": {
|
||||
"context": "button",
|
||||
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_cancelOrder": {
|
||||
"context": "cancel button",
|
||||
"string": "Cancel order"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_3086420445": {
|
||||
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_confirmOrder": {
|
||||
"context": "save button",
|
||||
"string": "confirm order"
|
||||
"string": "Confirm order"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_returnOrder": {
|
||||
"context": "return button",
|
||||
"string": "Return / Replace order"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderDraftCancelDialog_dot_1961675716": {
|
||||
"context": "dialog header",
|
||||
|
@ -3306,6 +3354,37 @@
|
|||
"context": "product's sku",
|
||||
"string": "SKU"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_1119771899": {
|
||||
"context": "add tracking button",
|
||||
"string": "Add tracking"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_2211099657": {
|
||||
"context": "edit tracking button",
|
||||
"string": "Edit tracking"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_2845258362": {
|
||||
"context": "refund button",
|
||||
"string": "Refund"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_3254150098": {
|
||||
"string": "Tracking Number: {trackingNumber}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_732594284": {
|
||||
"context": "button",
|
||||
"string": "Cancel Fulfillment"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_fulfilled": {
|
||||
"context": "fulfillment group",
|
||||
"string": "Fulfilled from: "
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_restocked": {
|
||||
"context": "restocked group",
|
||||
"string": "Restocked from: "
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_tracking": {
|
||||
"context": "tracking number",
|
||||
"string": "Tracking Number: {trackingNumber}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillmentCancelDialog_dot_1097287358": {
|
||||
"string": "Are you sure you want to cancel fulfillment? Canceling a fulfillment will restock products at a selected warehouse."
|
||||
},
|
||||
|
@ -3343,71 +3422,10 @@
|
|||
"context": "dialog header",
|
||||
"string": "Add Tracking Code"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_1119771899": {
|
||||
"context": "fulfillment group tracking number",
|
||||
"string": "Add tracking"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_1134347598": {
|
||||
"context": "product price",
|
||||
"string": "Price"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_1895667608": {
|
||||
"context": "product name",
|
||||
"string": "Product"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_2211099657": {
|
||||
"context": "fulfillment group tracking number",
|
||||
"string": "Edit tracking"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_2567258278": {
|
||||
"context": "refunded fulfillment, section header",
|
||||
"string": "Refunded ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_2796503714": {
|
||||
"context": "ordered product quantity",
|
||||
"string": "Quantity"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_3254150098": {
|
||||
"string": "Tracking Number: {trackingNumber}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_3494686506": {
|
||||
"context": "section header",
|
||||
"string": "Fulfilled ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_4039425374": {
|
||||
"context": "cancelled fulfillment, section header",
|
||||
"string": "Cancelled ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_693960049": {
|
||||
"context": "ordered product sku",
|
||||
"string": "SKU"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_732594284": {
|
||||
"context": "button",
|
||||
"string": "Cancel Fulfillment"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_77179533": {
|
||||
"context": "fulfillment group",
|
||||
"string": "Fulfilled from: {warehouseName}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_878013594": {
|
||||
"context": "order line total price",
|
||||
"string": "Total"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_1154330234": {
|
||||
"context": "transaction reference",
|
||||
"string": "Transaction Reference {transactionReference}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_1230178536": {
|
||||
"context": "order history message",
|
||||
"string": "Order address was updated"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_123236698": {
|
||||
"string": "Shipment was refunded"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_1322321687": {
|
||||
"string": "Refunded amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_1463685940": {
|
||||
"context": "order history message",
|
||||
"string": "Order was marked as paid"
|
||||
|
@ -3523,9 +3541,6 @@
|
|||
"context": "order history message",
|
||||
"string": "Payment failed"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_492197448": {
|
||||
"string": "Products refunded"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_493321552": {
|
||||
"context": "order history message",
|
||||
"string": "Order cancel information was sent to customer"
|
||||
|
@ -3546,6 +3561,14 @@
|
|||
"context": "order history message",
|
||||
"string": "Order was cancelled"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_description": {
|
||||
"context": "replacement created order history message description",
|
||||
"string": "was created for replaced products"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_draftNumber": {
|
||||
"context": "replacement created order history message draft number",
|
||||
"string": "Draft #{orderNumber} "
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderInvoiceEmailSendDialog_dot_1821123638": {
|
||||
"string": "Are you sure you want to send this invoice: {invoiceNumber} to the customer?"
|
||||
},
|
||||
|
@ -3729,71 +3752,25 @@
|
|||
"src_dot_orders_dot_components_dot_OrderProductAddDialog_dot_353369701": {
|
||||
"string": "No products matching given query"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_1580639738": {
|
||||
"context": "order refund amount",
|
||||
"string": "Proposed refund amount"
|
||||
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_1134347598": {
|
||||
"context": "product price",
|
||||
"string": "Price"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_1705174606": {
|
||||
"context": "order refund amount",
|
||||
"string": "Max Refund"
|
||||
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_1895667608": {
|
||||
"context": "product name",
|
||||
"string": "Product"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_1734445951": {
|
||||
"context": "order refund amount",
|
||||
"string": "Refund total amount"
|
||||
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_2796503714": {
|
||||
"context": "ordered product quantity",
|
||||
"string": "Quantity"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_2045860028": {
|
||||
"context": "order refund amount",
|
||||
"string": "Authorized Amount"
|
||||
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_693960049": {
|
||||
"context": "ordered product sku",
|
||||
"string": "SKU"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_2854815744": {
|
||||
"context": "order refund amount",
|
||||
"string": "Previously refunded"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_2907874606": {
|
||||
"context": "order refund amount",
|
||||
"string": "Selected products value"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_79173946": {
|
||||
"context": "order refund amount",
|
||||
"string": "Shipment cost"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_120912052": {
|
||||
"context": "order refund amount",
|
||||
"string": "Refunded items can’t be fulfilled"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_159210811": {
|
||||
"string": "Amount must be bigger than 0"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_2256869831": {
|
||||
"context": "section header",
|
||||
"string": "Refunded Amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_2845258362": {
|
||||
"context": "order refund amount, input button",
|
||||
"string": "Refund"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_4033685232": {
|
||||
"string": "Amount cannot be bigger than max refund"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_40513382": {
|
||||
"context": "order refund amount, input button",
|
||||
"string": "Refund {currency} {amount}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_4224226791": {
|
||||
"context": "label",
|
||||
"string": "Automatic Amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_508357513": {
|
||||
"context": "label",
|
||||
"string": "Manual Amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_553737700": {
|
||||
"context": "checkbox",
|
||||
"string": "Refund shipment costs"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_75546233": {
|
||||
"context": "order refund amount, input label",
|
||||
"string": "Amount"
|
||||
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_878013594": {
|
||||
"context": "order line total price",
|
||||
"string": "Total"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundFulfilledProducts_dot_1134347598": {
|
||||
"context": "tabel column header",
|
||||
|
@ -3838,6 +3815,86 @@
|
|||
"context": "page header with order number",
|
||||
"string": "Order #{orderNumber}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_2256869831": {
|
||||
"context": "section header",
|
||||
"string": "Refunded Amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_40513382": {
|
||||
"context": "order refund amount, input button",
|
||||
"string": "Refund {currency} {amount}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_4224226791": {
|
||||
"context": "label",
|
||||
"string": "Automatic Amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_508357513": {
|
||||
"context": "label",
|
||||
"string": "Manual Amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_553737700": {
|
||||
"context": "checkbox",
|
||||
"string": "Refund shipment costs"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_amountTooBig": {
|
||||
"context": "Amount error message",
|
||||
"string": "Amount cannot be bigger than max refund"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_amountTooSmall": {
|
||||
"context": "Amount error message",
|
||||
"string": "Amount must be bigger than 0"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_authorizedAmount": {
|
||||
"context": "order refund amount",
|
||||
"string": "Authorized Amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_label": {
|
||||
"context": "order refund amount, input label",
|
||||
"string": "Amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_maxRefund": {
|
||||
"context": "order refund amount",
|
||||
"string": "Max Refund"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_previouslyRefunded": {
|
||||
"context": "order refund amount",
|
||||
"string": "Previously refunded"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_proposedRefundAmount": {
|
||||
"context": "order refund amount",
|
||||
"string": "Proposed refund amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_refundButton": {
|
||||
"context": "order refund amount button",
|
||||
"string": "Refund"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_refundCannotBeFulfilled": {
|
||||
"context": "order refund subtitle",
|
||||
"string": "Refunded items can't be fulfilled"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_refundTotalAmount": {
|
||||
"context": "order refund amount",
|
||||
"string": "Refund total amount"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_replacedProductsValue": {
|
||||
"context": "order refund amount",
|
||||
"string": "Replaced Products Value"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_returnButton": {
|
||||
"context": "order return amount button",
|
||||
"string": "Return & Replace products"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_returnCannotBeFulfilled": {
|
||||
"context": "order return subtitle",
|
||||
"string": "Returned items can't be fulfilled"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_selectedProductsValue": {
|
||||
"context": "order refund amount",
|
||||
"string": "Selected Products Value"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_shipmentCost": {
|
||||
"context": "order refund amount",
|
||||
"string": "Shipment Cost"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderRefundUnfulfilledProducts_dot_1134347598": {
|
||||
"context": "tabel column header",
|
||||
"string": "Price"
|
||||
|
@ -3885,6 +3942,82 @@
|
|||
"context": "refund type",
|
||||
"string": "Refund Products"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_1134347598": {
|
||||
"context": "table column header",
|
||||
"string": "Price"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_1784788864": {
|
||||
"context": "table column header",
|
||||
"string": "Return"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_1895667608": {
|
||||
"context": "table column header",
|
||||
"string": "Product"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_2049070632": {
|
||||
"context": "table column header",
|
||||
"string": "Replace"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_3988345170": {
|
||||
"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_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"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_titleFulfilled": {
|
||||
"context": "section header",
|
||||
"string": "Fulfillment - #{fulfilmentId}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_titleUnfulfilled": {
|
||||
"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_appTitle": {
|
||||
"context": "page header with order number",
|
||||
"string": "Order #{orderNumber}"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_pageTitle": {
|
||||
"context": "page header",
|
||||
"string": "Order no. {orderNumber} - Replace/Return"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderSettingsPage_dot_1149215359": {
|
||||
"context": "header",
|
||||
"string": "Order settings"
|
||||
|
@ -3908,34 +4041,10 @@
|
|||
"context": "dialog header",
|
||||
"string": "Edit Shipping Method"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_1134347598": {
|
||||
"context": "product unit price",
|
||||
"string": "Price"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_1895667608": {
|
||||
"context": "product name",
|
||||
"string": "Product"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_2095687440": {
|
||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledProductsCard_dot_2095687440": {
|
||||
"context": "button",
|
||||
"string": "Fulfill"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_2796503714": {
|
||||
"context": "ordered products",
|
||||
"string": "Quantity"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_2886647373": {
|
||||
"context": "section header",
|
||||
"string": "Unfulfilled ({quantity})"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_693960049": {
|
||||
"context": "ordered product sku",
|
||||
"string": "SKU"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_878013594": {
|
||||
"context": "order line total price",
|
||||
"string": "Total"
|
||||
},
|
||||
"src_dot_orders_dot_views_dot_OrderDetails_dot_1039259580": {
|
||||
"string": "We’re generating the invoice you requested. Please wait a couple of moments"
|
||||
},
|
||||
|
@ -4025,6 +4134,18 @@
|
|||
"context": "order refunded success message",
|
||||
"string": "Refunded Items"
|
||||
},
|
||||
"src_dot_orders_dot_views_dot_OrderReturn_dot_cannotRefundDescription": {
|
||||
"context": "order return error description when cannot refund",
|
||||
"string": "We’ve encountered a problem while refunding the products. Product’s were not refunded. Please try again."
|
||||
},
|
||||
"src_dot_orders_dot_views_dot_OrderReturn_dot_cannotRefundTitle": {
|
||||
"context": "order return error title when cannot refund",
|
||||
"string": "Couldn't refund products"
|
||||
},
|
||||
"src_dot_orders_dot_views_dot_OrderReturn_dot_successAlert": {
|
||||
"context": "order returned success message",
|
||||
"string": "Successfully returned products!"
|
||||
},
|
||||
"src_dot_pageTypes": {
|
||||
"context": "page types section name",
|
||||
"string": "Page Types"
|
||||
|
@ -4271,6 +4392,10 @@
|
|||
"context": "payment status",
|
||||
"string": "Partially refunded"
|
||||
},
|
||||
"src_dot_partiallyReturned": {
|
||||
"context": "order status",
|
||||
"string": "Partially returned"
|
||||
},
|
||||
"src_dot_permissionGroups": {
|
||||
"context": "permission groups section name",
|
||||
"string": "Permission Groups"
|
||||
|
@ -5306,6 +5431,10 @@
|
|||
"src_dot_requiredField": {
|
||||
"string": "This field is required"
|
||||
},
|
||||
"src_dot_returned": {
|
||||
"context": "order status",
|
||||
"string": "Returned"
|
||||
},
|
||||
"src_dot_sales": {
|
||||
"context": "sales section name",
|
||||
"string": "Sales"
|
||||
|
|
|
@ -2006,9 +2006,21 @@ type FulfillmentRefundProducts {
|
|||
orderErrors: [OrderError!]!
|
||||
}
|
||||
|
||||
type FulfillmentReturnProducts {
|
||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||
returnFulfillment: Fulfillment
|
||||
replaceFulfillment: Fulfillment
|
||||
order: Order
|
||||
replaceOrder: Order
|
||||
orderErrors: [OrderError!]!
|
||||
}
|
||||
|
||||
enum FulfillmentStatus {
|
||||
FULFILLED
|
||||
REFUNDED
|
||||
RETURNED
|
||||
REPLACED
|
||||
REFUNDED_AND_RETURNED
|
||||
CANCELED
|
||||
}
|
||||
|
||||
|
@ -2662,6 +2674,7 @@ type Mutation {
|
|||
orderFulfillmentCancel(id: ID!, input: FulfillmentCancelInput!): FulfillmentCancel
|
||||
orderFulfillmentUpdateTracking(id: ID!, input: FulfillmentUpdateTrackingInput!): FulfillmentUpdateTracking
|
||||
orderFulfillmentRefundProducts(input: OrderRefundProductsInput!, order: ID!): FulfillmentRefundProducts
|
||||
orderFulfillmentReturnProducts(input: OrderReturnProductsInput!, order: ID!): FulfillmentReturnProducts
|
||||
orderMarkAsPaid(id: ID!, transactionReference: String): OrderMarkAsPaid
|
||||
orderRefund(amount: PositiveDecimal!, id: ID!): OrderRefund
|
||||
orderUpdate(id: ID!, input: OrderUpdateInput!): OrderUpdate
|
||||
|
@ -2959,8 +2972,7 @@ enum OrderErrorCode {
|
|||
UNIQUE
|
||||
VOID_INACTIVE_PAYMENT
|
||||
ZERO_QUANTITY
|
||||
INVALID_REFUND_QUANTITY
|
||||
CANNOT_REFUND_FULFILLMENT_LINE
|
||||
INVALID_QUANTITY
|
||||
INSUFFICIENT_STOCK
|
||||
DUPLICATED_INPUT_ITEM
|
||||
NOT_AVAILABLE_IN_CHANNEL
|
||||
|
@ -2988,6 +3000,7 @@ type OrderEvent implements Node {
|
|||
warehouse: Warehouse
|
||||
transactionReference: String
|
||||
shippingCostsIncluded: Boolean
|
||||
relatedOrder: Order
|
||||
}
|
||||
|
||||
type OrderEventCountableConnection {
|
||||
|
@ -3021,6 +3034,7 @@ enum OrderEventsEmailsEnum {
|
|||
|
||||
enum OrderEventsEnum {
|
||||
DRAFT_CREATED
|
||||
DRAFT_CREATED_FROM_REPLACE
|
||||
DRAFT_ADDED_PRODUCTS
|
||||
DRAFT_REMOVED_PRODUCTS
|
||||
PLACED
|
||||
|
@ -3029,6 +3043,7 @@ enum OrderEventsEnum {
|
|||
CANCELED
|
||||
ORDER_MARKED_AS_PAID
|
||||
ORDER_FULLY_PAID
|
||||
ORDER_REPLACEMENT_CREATED
|
||||
UPDATED_ADDRESS
|
||||
EMAIL_SENT
|
||||
CONFIRMED
|
||||
|
@ -3046,6 +3061,8 @@ enum OrderEventsEnum {
|
|||
FULFILLMENT_RESTOCKED_ITEMS
|
||||
FULFILLMENT_FULFILLED_ITEMS
|
||||
FULFILLMENT_REFUNDED
|
||||
FULFILLMENT_RETURNED
|
||||
FULFILLMENT_REPLACED
|
||||
TRACKING_UPDATED
|
||||
NOTE_ADDED
|
||||
OTHER
|
||||
|
@ -3139,6 +3156,26 @@ input OrderRefundProductsInput {
|
|||
includeShippingCosts: Boolean = false
|
||||
}
|
||||
|
||||
input OrderReturnFulfillmentLineInput {
|
||||
fulfillmentLineId: ID!
|
||||
quantity: Int!
|
||||
replace: Boolean = false
|
||||
}
|
||||
|
||||
input OrderReturnLineInput {
|
||||
orderLineId: ID!
|
||||
quantity: Int!
|
||||
replace: Boolean = false
|
||||
}
|
||||
|
||||
input OrderReturnProductsInput {
|
||||
orderLines: [OrderReturnLineInput!]
|
||||
fulfillmentLines: [OrderReturnFulfillmentLineInput!]
|
||||
amountToRefund: PositiveDecimal
|
||||
includeShippingCosts: Boolean = false
|
||||
refund: Boolean = false
|
||||
}
|
||||
|
||||
type OrderSettings {
|
||||
automaticallyConfirmAllNewOrders: Boolean!
|
||||
}
|
||||
|
@ -3181,6 +3218,8 @@ enum OrderStatus {
|
|||
UNCONFIRMED
|
||||
UNFULFILLED
|
||||
PARTIALLY_FULFILLED
|
||||
PARTIALLY_RETURNED
|
||||
RETURNED
|
||||
FULFILLED
|
||||
CANCELED
|
||||
}
|
||||
|
|
|
@ -19,11 +19,14 @@ const useStyles = makeStyles(
|
|||
};
|
||||
|
||||
return {
|
||||
alertDot: {
|
||||
"&:before": { backgroundColor: yellow[500], ...dot }
|
||||
},
|
||||
errorDot: {
|
||||
"&:before": { backgroundColor: theme.palette.error.main, ...dot }
|
||||
},
|
||||
neutralDot: {
|
||||
"&:before": { backgroundColor: yellow[500], ...dot }
|
||||
"&:before": { backgroundColor: grey[300], ...dot }
|
||||
},
|
||||
root: {
|
||||
display: "inline-block",
|
||||
|
@ -35,9 +38,6 @@ const useStyles = makeStyles(
|
|||
},
|
||||
successDot: {
|
||||
"&:before": { backgroundColor: theme.palette.primary.main, ...dot }
|
||||
},
|
||||
unspecifiedDot: {
|
||||
"&:before": { backgroundColor: grey[500], ...dot }
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -47,7 +47,7 @@ const useStyles = makeStyles(
|
|||
interface StatusLabelProps {
|
||||
className?: string;
|
||||
label: string | React.ReactNode;
|
||||
status: "success" | "neutral" | "unspecified" | "error" | string;
|
||||
status: "success" | "alert" | "neutral" | "error" | string;
|
||||
typographyProps?: TypographyProps;
|
||||
}
|
||||
|
||||
|
@ -62,8 +62,8 @@ const StatusLabel: React.FC<StatusLabelProps> = props => {
|
|||
[classes.root]: true,
|
||||
[className]: true,
|
||||
[classes.successDot]: status === "success",
|
||||
[classes.alertDot]: status === "alert",
|
||||
[classes.neutralDot]: status === "neutral",
|
||||
[classes.unspecifiedDot]: status === "unspecified",
|
||||
[classes.errorDot]: status === "error"
|
||||
})}
|
||||
>
|
||||
|
|
|
@ -77,7 +77,7 @@ export const light: IThemeColors = {
|
|||
default: "#616161"
|
||||
},
|
||||
divider: "#EAEAEA",
|
||||
error: "#C22D74",
|
||||
error: "#FE6D76",
|
||||
font: {
|
||||
button: "#FFFFFF",
|
||||
default: "#3D3D3D",
|
||||
|
|
|
@ -4,30 +4,12 @@ import ExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import { DateTime } from "../Date";
|
||||
import TimelineEventHeader, { TitleElement } from "./TimelineEventHeader";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
container: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing(),
|
||||
marginLeft: theme.spacing(3),
|
||||
width: "100%"
|
||||
},
|
||||
date: {
|
||||
color: theme.typography.caption.color
|
||||
},
|
||||
dateExpander: {
|
||||
color: theme.typography.caption.color,
|
||||
position: "absolute",
|
||||
right: 0
|
||||
},
|
||||
dot: {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
borderRadius: "100%",
|
||||
|
@ -37,27 +19,27 @@ const useStyles = makeStyles(
|
|||
top: 6,
|
||||
width: 8
|
||||
},
|
||||
expanded: {},
|
||||
noExpander: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
width: "100%"
|
||||
},
|
||||
panel: {
|
||||
"&$expanded": {
|
||||
margin: 0
|
||||
"& .MuiExpansionPanelDetails-root": {
|
||||
padding: 0,
|
||||
paddingTop: theme.spacing(2)
|
||||
},
|
||||
"&.Mui-expanded": {
|
||||
borderColor: "red",
|
||||
margin: 0,
|
||||
minHeight: 0
|
||||
},
|
||||
"&:before": {
|
||||
display: "none"
|
||||
},
|
||||
background: "none",
|
||||
display: "",
|
||||
margin: 0,
|
||||
minHeight: 0,
|
||||
width: "100%"
|
||||
},
|
||||
panelExpander: {
|
||||
"&$expanded": {
|
||||
margin: 0,
|
||||
"&.MuiExpansionPanelSummary-root.Mui-expanded": {
|
||||
minHeight: 0
|
||||
},
|
||||
"&> .MuiExpansionPanelSummary-content": {
|
||||
|
@ -65,9 +47,12 @@ const useStyles = makeStyles(
|
|||
},
|
||||
"&> .MuiExpansionPanelSummary-expandIcon": {
|
||||
padding: 0,
|
||||
position: "absolute",
|
||||
right: theme.spacing(18)
|
||||
},
|
||||
margin: 0
|
||||
margin: 0,
|
||||
minHeight: 0,
|
||||
padding: 0
|
||||
},
|
||||
root: {
|
||||
"&:last-child:after": {
|
||||
|
@ -82,27 +67,24 @@ const useStyles = makeStyles(
|
|||
alignItems: "center",
|
||||
display: "flex",
|
||||
marginBottom: theme.spacing(3),
|
||||
marginTop: 0,
|
||||
position: "relative",
|
||||
width: "100%"
|
||||
},
|
||||
secondaryTitle: {
|
||||
color: "#9e9e9e",
|
||||
fontSize: 14,
|
||||
marginTop: theme.spacing(2)
|
||||
}
|
||||
}),
|
||||
{ name: "TimelineEvent" }
|
||||
);
|
||||
|
||||
interface TimelineEventProps {
|
||||
export interface TimelineEventProps {
|
||||
children?: React.ReactNode;
|
||||
date: string;
|
||||
secondaryTitle?: string;
|
||||
title: string;
|
||||
title?: string;
|
||||
titleElements?: TitleElement[];
|
||||
}
|
||||
|
||||
export const TimelineEvent: React.FC<TimelineEventProps> = props => {
|
||||
const { children, date, secondaryTitle, title } = props;
|
||||
const { children, date, secondaryTitle, title, titleElements } = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
|
@ -110,39 +92,28 @@ export const TimelineEvent: React.FC<TimelineEventProps> = props => {
|
|||
<div className={classes.root}>
|
||||
<span className={classes.dot} />
|
||||
{children ? (
|
||||
<ExpansionPanel
|
||||
className={classNames(classes.panel, classes.expanded)}
|
||||
elevation={0}
|
||||
>
|
||||
<ExpansionPanel className={classes.panel} elevation={0}>
|
||||
<ExpansionPanelSummary
|
||||
className={classNames(classes.panelExpander, classes.expanded)}
|
||||
className={classes.panelExpander}
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
>
|
||||
<Typography>{title}</Typography>
|
||||
<Typography className={classes.dateExpander}>
|
||||
<DateTime date={date} />
|
||||
</Typography>
|
||||
<TimelineEventHeader
|
||||
title={title}
|
||||
date={date}
|
||||
titleElements={titleElements}
|
||||
/>
|
||||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<Typography>{children}</Typography>
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
) : (
|
||||
<div className={classes.container}>
|
||||
<div className={classes.noExpander}>
|
||||
<Typography>{title}</Typography>
|
||||
<Typography className={classes.date}>
|
||||
<DateTime date={date} />
|
||||
</Typography>
|
||||
</div>
|
||||
{secondaryTitle && (
|
||||
<div className={classes.noExpander}>
|
||||
<Typography className={classes.secondaryTitle}>
|
||||
{secondaryTitle}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<TimelineEventHeader
|
||||
title={title}
|
||||
titleElements={titleElements}
|
||||
secondaryTitle={secondaryTitle}
|
||||
date={date}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
93
src/components/Timeline/TimelineEventHeader.tsx
Normal file
93
src/components/Timeline/TimelineEventHeader.tsx
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import React from "react";
|
||||
|
||||
import { DateTime } from "../Date";
|
||||
import Link from "../Link";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
container: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
width: "100%"
|
||||
},
|
||||
date: {
|
||||
color: theme.typography.caption.color,
|
||||
paddingLeft: 24
|
||||
},
|
||||
elementsContainer: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap"
|
||||
},
|
||||
secondaryTitle: {
|
||||
color: "#9e9e9e",
|
||||
fontSize: 14,
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
titleElement: {
|
||||
marginRight: "0.5rem"
|
||||
}
|
||||
}),
|
||||
{ name: "TimelineEventHeader" }
|
||||
);
|
||||
|
||||
export interface TitleElement {
|
||||
text: string;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
export interface TimelineEventHeaderProps {
|
||||
title?: string;
|
||||
date: string;
|
||||
titleElements?: TitleElement[];
|
||||
secondaryTitle?: string;
|
||||
}
|
||||
|
||||
export const TimelineEventHeader: React.FC<TimelineEventHeaderProps> = props => {
|
||||
const { title, date, titleElements, secondaryTitle } = props;
|
||||
const navigate = useNavigator();
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
{title && <Typography>{title}</Typography>}
|
||||
{titleElements && (
|
||||
<div className={classes.elementsContainer}>
|
||||
{titleElements.map(({ text, link }) => {
|
||||
if (link) {
|
||||
return (
|
||||
<Link
|
||||
className={classes.titleElement}
|
||||
onClick={() => navigate(link)}
|
||||
>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography className={classes.titleElement}>{text}</Typography>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Typography className={classes.date}>
|
||||
<DateTime date={date} />
|
||||
</Typography>
|
||||
{secondaryTitle && (
|
||||
<Typography className={classes.secondaryTitle}>
|
||||
{secondaryTitle}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineEventHeader;
|
|
@ -12,6 +12,10 @@ export const fragmentOrderEvent = gql`
|
|||
email
|
||||
emailType
|
||||
invoiceNumber
|
||||
relatedOrder {
|
||||
id
|
||||
number
|
||||
}
|
||||
message
|
||||
quantity
|
||||
transactionReference
|
||||
|
@ -19,6 +23,8 @@ export const fragmentOrderEvent = gql`
|
|||
user {
|
||||
id
|
||||
email
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
lines {
|
||||
quantity
|
||||
|
|
|
@ -42,10 +42,18 @@ export interface OrderDetailsFragment_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderDetailsFragment_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderDetailsFragment_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderDetailsFragment_events_lines_orderLine {
|
||||
|
@ -70,6 +78,7 @@ export interface OrderDetailsFragment_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderDetailsFragment_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -8,10 +8,18 @@ import { OrderEventsEmailsEnum, OrderEventsEnum } from "./../../types/globalType
|
|||
// GraphQL fragment: OrderEventFragment
|
||||
// ====================================================
|
||||
|
||||
export interface OrderEventFragment_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderEventFragment_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderEventFragment_lines_orderLine {
|
||||
|
@ -36,6 +44,7 @@ export interface OrderEventFragment {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderEventFragment_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
25
src/icons/ErrorExclamationCircle.tsx
Normal file
25
src/icons/ErrorExclamationCircle.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
|
||||
import React from "react";
|
||||
|
||||
const ErrorExclamationCircle = createSvgIcon(
|
||||
<>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="12" cy="12" r="12" fill="#FE6D76" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12.75 6H11.25V15H12.75V6ZM12.75 16.5H11.25V18H12.75V16.5Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</>,
|
||||
"ErrorExclamationCircle"
|
||||
);
|
||||
|
||||
export default ErrorExclamationCircle;
|
18
src/misc.ts
18
src/misc.ts
|
@ -136,6 +136,10 @@ export const orderStatusMessages = defineMessages({
|
|||
defaultMessage: "Partially fulfilled",
|
||||
description: "order status"
|
||||
},
|
||||
partiallyReturned: {
|
||||
defaultMessage: "Partially returned",
|
||||
description: "order status"
|
||||
},
|
||||
readyToCapture: {
|
||||
defaultMessage: "Ready to capture",
|
||||
description: "order status"
|
||||
|
@ -144,6 +148,10 @@ export const orderStatusMessages = defineMessages({
|
|||
defaultMessage: "Ready to fulfill",
|
||||
description: "order status"
|
||||
},
|
||||
returned: {
|
||||
defaultMessage: "Returned",
|
||||
description: "order status"
|
||||
},
|
||||
unconfirmed: {
|
||||
defaultMessage: "Unconfirmed",
|
||||
description: "order status"
|
||||
|
@ -189,6 +197,16 @@ export const transformOrderStatus = (
|
|||
localized: intl.formatMessage(orderStatusMessages.unconfirmed),
|
||||
status: StatusType.NEUTRAL
|
||||
};
|
||||
case OrderStatus.PARTIALLY_RETURNED:
|
||||
return {
|
||||
localized: intl.formatMessage(orderStatusMessages.partiallyReturned),
|
||||
status: StatusType.NEUTRAL
|
||||
};
|
||||
case OrderStatus.RETURNED:
|
||||
return {
|
||||
localized: intl.formatMessage(orderStatusMessages.returned),
|
||||
status: StatusType.NEUTRAL
|
||||
};
|
||||
}
|
||||
return {
|
||||
localized: status,
|
||||
|
|
|
@ -19,19 +19,20 @@ import { UserPermissionProps } from "@saleor/types";
|
|||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { maybe } from "../../../misc";
|
||||
import { OrderStatus } from "../../../types/globalTypes";
|
||||
import { OrderDetails_order } from "../../types/OrderDetails";
|
||||
import OrderCustomer from "../OrderCustomer";
|
||||
import OrderCustomerNote from "../OrderCustomerNote";
|
||||
import OrderFulfillment from "../OrderFulfillment";
|
||||
import OrderFulfilledProductsCard from "../OrderFulfilledProductsCard";
|
||||
import OrderHistory, { FormData as HistoryFormData } from "../OrderHistory";
|
||||
import OrderInvoiceList from "../OrderInvoiceList";
|
||||
import OrderPayment from "../OrderPayment/OrderPayment";
|
||||
import OrderUnfulfilledItems from "../OrderUnfulfilledItems/OrderUnfulfilledItems";
|
||||
import OrderUnfulfilledProductsCard from "../OrderUnfulfilledProductsCard";
|
||||
import Title from "./Title";
|
||||
import { filteredConditionalItems, hasAnyItemsReplaceable } from "./utils";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
|
@ -75,12 +76,28 @@ export interface OrderDetailsPageProps extends UserPermissionProps {
|
|||
onOrderCancel();
|
||||
onNoteAdd(data: HistoryFormData);
|
||||
onProfileView();
|
||||
onOrderReturn();
|
||||
onInvoiceClick(invoiceId: string);
|
||||
onInvoiceGenerate();
|
||||
onInvoiceSend(invoiceId: string);
|
||||
onSubmit(data: MetadataFormData): SubmitPromise;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
cancelOrder: {
|
||||
defaultMessage: "Cancel order",
|
||||
description: "cancel button"
|
||||
},
|
||||
confirmOrder: {
|
||||
defaultMessage: "Confirm order",
|
||||
description: "save button"
|
||||
},
|
||||
returnOrder: {
|
||||
defaultMessage: "Return / Replace order",
|
||||
description: "return button"
|
||||
}
|
||||
});
|
||||
|
||||
const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
||||
const {
|
||||
disabled,
|
||||
|
@ -103,6 +120,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
onInvoiceClick,
|
||||
onInvoiceGenerate,
|
||||
onInvoiceSend,
|
||||
onOrderReturn,
|
||||
onSubmit
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
@ -140,10 +158,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
|
||||
const saveLabel =
|
||||
order?.status === OrderStatus.UNCONFIRMED
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "confirm order",
|
||||
description: "save button"
|
||||
})
|
||||
? intl.formatMessage(messages.confirmOrder)
|
||||
: undefined;
|
||||
|
||||
const allowSave = (hasChanged: boolean) => {
|
||||
|
@ -154,6 +169,23 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
return disabled;
|
||||
};
|
||||
|
||||
const selectCardMenuItems = filteredConditionalItems([
|
||||
{
|
||||
item: {
|
||||
label: intl.formatMessage(messages.cancelOrder),
|
||||
onSelect: onOrderCancel
|
||||
},
|
||||
shouldExist: canCancel
|
||||
},
|
||||
{
|
||||
item: {
|
||||
label: intl.formatMessage(messages.returnOrder),
|
||||
onSelect: onOrderReturn
|
||||
},
|
||||
shouldExist: hasAnyItemsReplaceable(order)
|
||||
}
|
||||
]);
|
||||
|
||||
return (
|
||||
<Form initial={initial} onSubmit={handleSubmit}>
|
||||
{({ change, data, hasChanged, submit }) => {
|
||||
|
@ -169,19 +201,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
inline
|
||||
title={<Title order={order} />}
|
||||
>
|
||||
{canCancel && (
|
||||
<CardMenu
|
||||
menuItems={[
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Cancel order",
|
||||
description: "button"
|
||||
}),
|
||||
onSelect: onOrderCancel
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<CardMenu menuItems={selectCardMenuItems} />
|
||||
</PageHeader>
|
||||
<div className={classes.date}>
|
||||
{order && order.created ? (
|
||||
|
@ -194,36 +214,26 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
</div>
|
||||
<Grid>
|
||||
<div>
|
||||
{unfulfilled.length > 0 && (
|
||||
<OrderUnfulfilledItems
|
||||
canFulfill={canFulfill}
|
||||
lines={unfulfilled}
|
||||
onFulfill={onOrderFulfill}
|
||||
/>
|
||||
)}
|
||||
{renderCollection(
|
||||
maybe(() => order.fulfillments),
|
||||
(fulfillment, fulfillmentIndex) => (
|
||||
<React.Fragment
|
||||
key={maybe(() => fulfillment.id, "loading")}
|
||||
>
|
||||
{!(
|
||||
unfulfilled.length === 0 && fulfillmentIndex === 0
|
||||
) && <CardSpacer />}
|
||||
<OrderFulfillment
|
||||
fulfillment={fulfillment}
|
||||
orderNumber={maybe(() => order.number)}
|
||||
onOrderFulfillmentCancel={() =>
|
||||
onFulfillmentCancel(fulfillment.id)
|
||||
}
|
||||
onTrackingCodeAdd={() =>
|
||||
onFulfillmentTrackingNumberUpdate(fulfillment.id)
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
<CardSpacer />
|
||||
<OrderUnfulfilledProductsCard
|
||||
canFulfill={canFulfill}
|
||||
lines={unfulfilled}
|
||||
onFulfill={onOrderFulfill}
|
||||
/>
|
||||
{order?.fulfillments?.map(fulfillment => (
|
||||
<React.Fragment key={fulfillment.id}>
|
||||
<OrderFulfilledProductsCard
|
||||
fulfillment={fulfillment}
|
||||
orderNumber={order.number}
|
||||
onOrderFulfillmentCancel={() =>
|
||||
onFulfillmentCancel(fulfillment.id)
|
||||
}
|
||||
onTrackingCodeAdd={() =>
|
||||
onFulfillmentTrackingNumberUpdate(fulfillment.id)
|
||||
}
|
||||
onRefund={onPaymentRefund}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<OrderPayment
|
||||
order={order}
|
||||
onCapture={onPaymentCapture}
|
||||
|
@ -281,5 +291,6 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
OrderDetailsPage.displayName = "OrderDetailsPage";
|
||||
export default OrderDetailsPage;
|
||||
|
|
44
src/orders/components/OrderDetailsPage/utils.test.tsx
Normal file
44
src/orders/components/OrderDetailsPage/utils.test.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { filteredConditionalItems } from "./utils";
|
||||
|
||||
describe("filteredConditionalItems", () => {
|
||||
it("should return empty [] when no items has shouldExist set to true", () => {
|
||||
const items = [
|
||||
{
|
||||
item: { id: "#1" },
|
||||
shouldExist: false
|
||||
},
|
||||
{
|
||||
item: { id: "#2" },
|
||||
shouldExist: false
|
||||
},
|
||||
{
|
||||
item: { id: "#3" },
|
||||
shouldExist: false
|
||||
}
|
||||
];
|
||||
|
||||
expect(filteredConditionalItems(items)).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return only items that has shouldExist set to true", () => {
|
||||
const items = [
|
||||
{
|
||||
item: { id: "#1" },
|
||||
shouldExist: false
|
||||
},
|
||||
{
|
||||
item: { id: "#2" },
|
||||
shouldExist: true
|
||||
},
|
||||
{
|
||||
item: { id: "#3" },
|
||||
shouldExist: true
|
||||
}
|
||||
];
|
||||
|
||||
expect(filteredConditionalItems(items)).toEqual([
|
||||
{ id: "#2" },
|
||||
{ id: "#3" }
|
||||
]);
|
||||
});
|
||||
});
|
26
src/orders/components/OrderDetailsPage/utils.ts
Normal file
26
src/orders/components/OrderDetailsPage/utils.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
||||
|
||||
import {
|
||||
getFulfilledFulfillemnts,
|
||||
getUnfulfilledLines
|
||||
} from "../OrderReturnPage/utils";
|
||||
|
||||
export const hasAnyItemsReplaceable = (order?: OrderDetails_order) => {
|
||||
if (!order) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasAnyUnfulfilledItems = getUnfulfilledLines(order).length > 0;
|
||||
|
||||
const hasAnyFulfilmentsToReturn = getFulfilledFulfillemnts(order).length > 0;
|
||||
|
||||
return hasAnyUnfulfilledItems || hasAnyFulfilmentsToReturn;
|
||||
};
|
||||
|
||||
export interface ConditionalItem {
|
||||
shouldExist: boolean;
|
||||
item: any;
|
||||
}
|
||||
|
||||
export const filteredConditionalItems = (items: ConditionalItem[]) =>
|
||||
items.filter(({ shouldExist }) => shouldExist).map(({ item }) => item);
|
|
@ -0,0 +1,64 @@
|
|||
import { Button, CardActions } from "@material-ui/core";
|
||||
import { FulfillmentStatus } from "@saleor/types/globalTypes";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
interface AcionButtonsProps {
|
||||
status: FulfillmentStatus;
|
||||
trackingNumber?: string;
|
||||
onTrackingCodeAdd();
|
||||
onRefund();
|
||||
}
|
||||
|
||||
const statusesToShow = [
|
||||
FulfillmentStatus.FULFILLED,
|
||||
FulfillmentStatus.RETURNED
|
||||
];
|
||||
|
||||
const ActionButtons: React.FC<AcionButtonsProps> = ({
|
||||
status,
|
||||
onTrackingCodeAdd,
|
||||
trackingNumber,
|
||||
onRefund
|
||||
}) => {
|
||||
const hasTrackingNumber = !!trackingNumber;
|
||||
|
||||
if (!statusesToShow.includes(status)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (status === FulfillmentStatus.RETURNED) {
|
||||
return (
|
||||
<CardActions>
|
||||
<Button color="primary" onClick={onRefund}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Refund"
|
||||
description="refund button"
|
||||
/>
|
||||
</Button>
|
||||
</CardActions>
|
||||
);
|
||||
}
|
||||
|
||||
return hasTrackingNumber ? (
|
||||
<CardActions>
|
||||
<Button color="primary" onClick={onTrackingCodeAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit tracking"
|
||||
description="edit tracking button"
|
||||
/>
|
||||
</Button>
|
||||
</CardActions>
|
||||
) : (
|
||||
<CardActions>
|
||||
<Button color="primary" onClick={onTrackingCodeAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add tracking"
|
||||
description="add tracking button"
|
||||
/>
|
||||
</Button>
|
||||
</CardActions>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionButtons;
|
|
@ -0,0 +1,103 @@
|
|||
import { makeStyles, TableCell, TableRow, Typography } from "@material-ui/core";
|
||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
||||
import { FulfillmentStatus } from "@saleor/types/globalTypes";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { OrderDetails_order_fulfillments } from "../../types/OrderDetails";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
infoLabel: {
|
||||
display: "inline-block"
|
||||
},
|
||||
infoLabelWithMargin: {
|
||||
marginBottom: theme.spacing()
|
||||
},
|
||||
infoRow: {
|
||||
padding: theme.spacing(2, 3)
|
||||
}
|
||||
}),
|
||||
{ name: "ExtraInfoLines" }
|
||||
);
|
||||
|
||||
const messages = defineMessages({
|
||||
fulfilled: {
|
||||
defaultMessage: "Fulfilled from: ",
|
||||
description: "fulfillment group"
|
||||
},
|
||||
restocked: {
|
||||
defaultMessage: "Restocked from: ",
|
||||
description: "restocked group"
|
||||
},
|
||||
tracking: {
|
||||
defaultMessage: "Tracking Number: {trackingNumber}",
|
||||
description: "tracking number"
|
||||
}
|
||||
});
|
||||
|
||||
const NUMBER_OF_COLUMNS = 5;
|
||||
|
||||
interface ExtraInfoLinesProps {
|
||||
fulfillment?: OrderDetails_order_fulfillments;
|
||||
}
|
||||
|
||||
const ExtraInfoLines: React.FC<ExtraInfoLinesProps> = ({ fulfillment }) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
if (!fulfillment || !fulfillment?.warehouse || !fulfillment?.trackingNumber) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { warehouse, trackingNumber, status } = fulfillment;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell className={classes.infoRow} colSpan={NUMBER_OF_COLUMNS}>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
{warehouse && (
|
||||
<>
|
||||
{intl.formatMessage(
|
||||
status === FulfillmentStatus.RETURNED
|
||||
? messages.restocked
|
||||
: messages.fulfilled
|
||||
)}
|
||||
<Typography
|
||||
className={classNames(classes.infoLabel, {
|
||||
[classes.infoLabelWithMargin]: !!trackingNumber
|
||||
})}
|
||||
color="textPrimary"
|
||||
variant="body2"
|
||||
>
|
||||
{getStringOrPlaceholder(warehouse?.name)}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
{trackingNumber && (
|
||||
<FormattedMessage
|
||||
defaultMessage="Tracking Number: {trackingNumber}"
|
||||
values={{
|
||||
trackingNumber: (
|
||||
<Typography
|
||||
className={classes.infoLabel}
|
||||
color="textPrimary"
|
||||
variant="body2"
|
||||
>
|
||||
{trackingNumber}
|
||||
</Typography>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtraInfoLines;
|
|
@ -0,0 +1,114 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import CardMenu from "@saleor/components/CardMenu";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import { mergeRepeatedOrderLines } from "@saleor/orders/utils/data";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { FulfillmentStatus } from "../../../types/globalTypes";
|
||||
import { OrderDetails_order_fulfillments } from "../../types/OrderDetails";
|
||||
import TableHeader from "../OrderProductsCardElements/OrderProductsCardHeader";
|
||||
import TableLine from "../OrderProductsCardElements/OrderProductsTableRow";
|
||||
import CardTitle from "../OrderReturnPage/OrderReturnRefundItemsCard/CardTitle";
|
||||
import ActionButtons from "./ActionButtons";
|
||||
import ExtraInfoLines from "./ExtraInfoLines";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
() => ({
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderFulfillment" }
|
||||
);
|
||||
|
||||
interface OrderFulfilledProductsCardProps {
|
||||
fulfillment: OrderDetails_order_fulfillments;
|
||||
orderNumber?: string;
|
||||
onOrderFulfillmentCancel: () => void;
|
||||
onTrackingCodeAdd: () => void;
|
||||
onRefund: () => void;
|
||||
}
|
||||
|
||||
const OrderFulfilledProductsCard: React.FC<OrderFulfilledProductsCardProps> = props => {
|
||||
const {
|
||||
fulfillment,
|
||||
orderNumber,
|
||||
onOrderFulfillmentCancel,
|
||||
onTrackingCodeAdd,
|
||||
onRefund
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
if (!fulfillment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getLines = () => {
|
||||
const statusesToMergeLines = [
|
||||
FulfillmentStatus.REFUNDED,
|
||||
FulfillmentStatus.REFUNDED_AND_RETURNED,
|
||||
FulfillmentStatus.RETURNED,
|
||||
FulfillmentStatus.REPLACED
|
||||
];
|
||||
|
||||
if (statusesToMergeLines.includes(fulfillment?.status)) {
|
||||
return mergeRepeatedOrderLines(fulfillment.lines);
|
||||
}
|
||||
|
||||
return fulfillment?.lines || [];
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardTitle
|
||||
withStatus
|
||||
lines={fulfillment?.lines}
|
||||
fulfillmentOrder={fulfillment?.fulfillmentOrder}
|
||||
status={fulfillment?.status}
|
||||
orderNumber={orderNumber}
|
||||
toolbar={
|
||||
maybe(() => fulfillment.status) === FulfillmentStatus.FULFILLED && (
|
||||
<CardMenu
|
||||
menuItems={[
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Cancel Fulfillment",
|
||||
description: "button"
|
||||
}),
|
||||
onSelect: onOrderFulfillmentCancel
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ResponsiveTable className={classes.table}>
|
||||
<TableHeader />
|
||||
<TableBody>
|
||||
{renderCollection(getLines(), line => (
|
||||
<TableLine line={line} />
|
||||
))}
|
||||
</TableBody>
|
||||
<ExtraInfoLines fulfillment={fulfillment} />
|
||||
</ResponsiveTable>
|
||||
<ActionButtons
|
||||
status={fulfillment?.status}
|
||||
trackingNumber={fulfillment?.trackingNumber}
|
||||
onTrackingCodeAdd={onTrackingCodeAdd}
|
||||
onRefund={onRefund}
|
||||
/>
|
||||
</Card>
|
||||
<CardSpacer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderFulfilledProductsCard;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderFulfilledProductsCard";
|
||||
export * from "./OrderFulfilledProductsCard";
|
|
@ -1,333 +0,0 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardActions from "@material-ui/core/CardActions";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CardMenu from "@saleor/components/CardMenu";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Money from "@saleor/components/Money";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import { mergeRepeatedOrderLines } from "@saleor/orders/utils/data";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { getStringOrPlaceholder, maybe, renderCollection } from "../../../misc";
|
||||
import { FulfillmentStatus } from "../../../types/globalTypes";
|
||||
import { OrderDetails_order_fulfillments } from "../../types/OrderDetails";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
clickableRow: {
|
||||
cursor: "pointer"
|
||||
},
|
||||
colName: {
|
||||
width: "auto"
|
||||
},
|
||||
colNameLabel: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
colPrice: {
|
||||
textAlign: "right",
|
||||
width: 120
|
||||
},
|
||||
colQuantity: {
|
||||
textAlign: "center",
|
||||
width: 120
|
||||
},
|
||||
colSku: {
|
||||
textAlign: "right",
|
||||
textOverflow: "ellipsis",
|
||||
width: 120
|
||||
},
|
||||
colTotal: {
|
||||
textAlign: "right",
|
||||
width: 120
|
||||
},
|
||||
infoLabel: {
|
||||
display: "inline-block"
|
||||
},
|
||||
infoLabelWithMargin: {
|
||||
marginBottom: theme.spacing()
|
||||
},
|
||||
infoRow: {
|
||||
padding: theme.spacing(2, 3)
|
||||
},
|
||||
orderNumber: {
|
||||
display: "inline",
|
||||
marginLeft: theme.spacing(1)
|
||||
},
|
||||
statusBar: {
|
||||
paddingTop: 0
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderFulfillment" }
|
||||
);
|
||||
|
||||
interface OrderFulfillmentProps {
|
||||
fulfillment: OrderDetails_order_fulfillments;
|
||||
orderNumber: string;
|
||||
onOrderFulfillmentCancel: () => void;
|
||||
onTrackingCodeAdd: () => void;
|
||||
}
|
||||
|
||||
const numberOfColumns = 5;
|
||||
|
||||
const OrderFulfillment: React.FC<OrderFulfillmentProps> = props => {
|
||||
const {
|
||||
fulfillment,
|
||||
orderNumber,
|
||||
onOrderFulfillmentCancel,
|
||||
onTrackingCodeAdd
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const lines =
|
||||
fulfillment?.status === FulfillmentStatus.REFUNDED
|
||||
? mergeRepeatedOrderLines(fulfillment?.lines)
|
||||
: fulfillment?.lines;
|
||||
const status = maybe(() => fulfillment.status);
|
||||
const quantity = lines
|
||||
? lines.map(line => line.quantity).reduce((prev, curr) => prev + curr, 0)
|
||||
: "...";
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={
|
||||
!!lines ? (
|
||||
<StatusLabel
|
||||
label={
|
||||
<>
|
||||
{status === FulfillmentStatus.FULFILLED
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Fulfilled ({quantity})",
|
||||
description: "section header"
|
||||
},
|
||||
{
|
||||
quantity
|
||||
}
|
||||
)
|
||||
: status === FulfillmentStatus.REFUNDED
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Refunded ({quantity})",
|
||||
description: "refunded fulfillment, section header"
|
||||
},
|
||||
{
|
||||
quantity
|
||||
}
|
||||
)
|
||||
: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Cancelled ({quantity})",
|
||||
description: "cancelled fulfillment, section header"
|
||||
},
|
||||
{
|
||||
quantity
|
||||
}
|
||||
)}
|
||||
<Typography className={classes.orderNumber} variant="body1">
|
||||
{maybe(
|
||||
() => `#${orderNumber}-${fulfillment.fulfillmentOrder}`
|
||||
)}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
status={
|
||||
status === FulfillmentStatus.FULFILLED
|
||||
? "success"
|
||||
: status === FulfillmentStatus.REFUNDED
|
||||
? "unspecified"
|
||||
: "error"
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
}
|
||||
toolbar={
|
||||
maybe(() => fulfillment.status) === FulfillmentStatus.FULFILLED && (
|
||||
<CardMenu
|
||||
menuItems={[
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Cancel Fulfillment",
|
||||
description: "button"
|
||||
}),
|
||||
onSelect: onOrderFulfillmentCancel
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ResponsiveTable className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameLabel}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Product"
|
||||
description="product name"
|
||||
/>
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSku}>
|
||||
<FormattedMessage
|
||||
defaultMessage="SKU"
|
||||
description="ordered product sku"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Quantity"
|
||||
description="ordered product quantity"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Price"
|
||||
description="product price"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTotal}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Total"
|
||||
description="order line total price"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(lines, line => (
|
||||
<TableRow
|
||||
className={!!line ? classes.clickableRow : undefined}
|
||||
hover={!!line}
|
||||
key={maybe(() => line.id)}
|
||||
>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => line.orderLine.thumbnail.url)}
|
||||
>
|
||||
{maybe(() => line.orderLine.productName) || <Skeleton />}
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colSku}>
|
||||
{line?.orderLine.productSku || <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
{line?.quantity || <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
{maybe(() => line.orderLine.unitPrice.gross) ? (
|
||||
<Money money={line.orderLine.unitPrice.gross} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTotal}>
|
||||
{maybe(
|
||||
() => line.quantity * line.orderLine.unitPrice.gross.amount
|
||||
) ? (
|
||||
<Money
|
||||
money={{
|
||||
amount:
|
||||
line.quantity * line.orderLine.unitPrice.gross.amount,
|
||||
currency: line.orderLine.unitPrice.gross.currency
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{(fulfillment?.warehouse || fulfillment?.trackingNumber) && (
|
||||
<TableRow>
|
||||
<TableCell className={classes.infoRow} colSpan={numberOfColumns}>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
{fulfillment?.warehouse && (
|
||||
<FormattedMessage
|
||||
defaultMessage="Fulfilled from: {warehouseName}"
|
||||
description="fulfillment group"
|
||||
values={{
|
||||
warehouseName: (
|
||||
<Typography
|
||||
className={classNames(classes.infoLabel, {
|
||||
[classes.infoLabelWithMargin]: !!fulfillment?.trackingNumber
|
||||
})}
|
||||
color="textPrimary"
|
||||
variant="body2"
|
||||
>
|
||||
{getStringOrPlaceholder(
|
||||
fulfillment?.warehouse?.name
|
||||
)}
|
||||
</Typography>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
{fulfillment?.trackingNumber && (
|
||||
<FormattedMessage
|
||||
defaultMessage="Tracking Number: {trackingNumber}"
|
||||
values={{
|
||||
trackingNumber: (
|
||||
<Typography
|
||||
className={classes.infoLabel}
|
||||
color="textPrimary"
|
||||
variant="body2"
|
||||
>
|
||||
{fulfillment.trackingNumber}
|
||||
</Typography>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
{status === FulfillmentStatus.FULFILLED && !fulfillment.trackingNumber && (
|
||||
<CardActions>
|
||||
<Button color="primary" onClick={onTrackingCodeAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add tracking"
|
||||
description="fulfillment group tracking number"
|
||||
/>
|
||||
</Button>
|
||||
</CardActions>
|
||||
)}
|
||||
{status === FulfillmentStatus.FULFILLED && fulfillment.trackingNumber && (
|
||||
<CardActions>
|
||||
<Button color="primary" onClick={onTrackingCodeAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit tracking"
|
||||
description="fulfillment group tracking number"
|
||||
/>
|
||||
</Button>
|
||||
</CardActions>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
OrderFulfillment.displayName = "OrderFulfillment";
|
||||
export default OrderFulfillment;
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from "./OrderFulfillment";
|
||||
export * from "./OrderFulfillment";
|
211
src/orders/components/OrderHistory/ExtendedTimelineEvent.tsx
Normal file
211
src/orders/components/OrderHistory/ExtendedTimelineEvent.tsx
Normal file
|
@ -0,0 +1,211 @@
|
|||
import { makeStyles, Typography } from "@material-ui/core";
|
||||
import Money from "@saleor/components/Money";
|
||||
import { TimelineEvent } from "@saleor/components/Timeline";
|
||||
import { orderUrl } from "@saleor/orders/urls";
|
||||
import { staffMemberDetailsUrl } from "@saleor/staff/urls";
|
||||
import { OrderEventsEnum } from "@saleor/types/globalTypes";
|
||||
import classNames from "classnames";
|
||||
import camelCase from "lodash/camelCase";
|
||||
import React from "react";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
import { OrderDetails_order_events } from "../../types/OrderDetails";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
eventSubtitle: {
|
||||
marginBottom: theme.spacing(0.5),
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
header: {
|
||||
fontWeight: 500,
|
||||
marginBottom: theme.spacing(1)
|
||||
},
|
||||
linesTableCell: {
|
||||
paddingRight: theme.spacing(3)
|
||||
},
|
||||
root: { marginTop: theme.spacing(4) },
|
||||
topSpacer: {
|
||||
marginTop: theme.spacing(3)
|
||||
},
|
||||
user: {
|
||||
marginBottom: theme.spacing(1)
|
||||
}
|
||||
}),
|
||||
{ name: "OrderHistory" }
|
||||
);
|
||||
|
||||
export const productTitles = defineMessages({
|
||||
draftCreatedFromReplace: {
|
||||
defaultMessage: "Products replaced",
|
||||
description: "draft created from replace products list title",
|
||||
id: "event products title draft reissued"
|
||||
},
|
||||
fulfillmentRefunded: {
|
||||
defaultMessage: "Products refunded",
|
||||
description: "refunded products list title",
|
||||
id: "event products list title refunded"
|
||||
},
|
||||
fulfillmentReplaced: {
|
||||
defaultMessage: "Products replaced",
|
||||
description: "replaced products list title",
|
||||
id: "event products list title replaced"
|
||||
},
|
||||
fulfillmentReturned: {
|
||||
defaultMessage: "Products returned",
|
||||
description: "returned products list title",
|
||||
id: "event products list title returned"
|
||||
}
|
||||
});
|
||||
|
||||
export const titles = defineMessages({
|
||||
draftCreatedFromReplace: {
|
||||
defaultMessage: "Draft was reissued from order ",
|
||||
description: "draft created from replace event title",
|
||||
id: "event title draft reissued"
|
||||
},
|
||||
fulfillmentRefunded: {
|
||||
defaultMessage: "Products were refunded by ",
|
||||
description: "refunded event title",
|
||||
id: "event title refunded"
|
||||
},
|
||||
fulfillmentReplaced: {
|
||||
defaultMessage: "Products were replaced by ",
|
||||
description: "replaced event title",
|
||||
id: "event title replaced"
|
||||
},
|
||||
fulfillmentReturned: {
|
||||
defaultMessage: "Products were returned by",
|
||||
description: "returned event title",
|
||||
id: "event title returned"
|
||||
}
|
||||
});
|
||||
|
||||
export const messages = defineMessages({
|
||||
by: {
|
||||
defaultMessage: "by",
|
||||
description: "by preposition",
|
||||
id: "by preposition"
|
||||
},
|
||||
refundedAmount: {
|
||||
defaultMessage: "Refunded amount",
|
||||
description: "amount title",
|
||||
id: "amount title"
|
||||
},
|
||||
refundedShipment: {
|
||||
defaultMessage: "Shipment was refunded",
|
||||
description: "shipment refund title",
|
||||
id: "shipment refund title"
|
||||
}
|
||||
});
|
||||
|
||||
interface ExtendedTimelineEventProps {
|
||||
event: OrderDetails_order_events;
|
||||
orderCurrency: string;
|
||||
}
|
||||
|
||||
const ExtendedTimelineEvent: React.FC<ExtendedTimelineEventProps> = ({
|
||||
event,
|
||||
orderCurrency
|
||||
}) => {
|
||||
const {
|
||||
id,
|
||||
date,
|
||||
type,
|
||||
user,
|
||||
lines,
|
||||
amount,
|
||||
shippingCostsIncluded,
|
||||
relatedOrder
|
||||
} = event;
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
||||
const eventTypeInCamelCase = camelCase(type);
|
||||
|
||||
const employeeName = `${user.firstName} ${user.lastName}`;
|
||||
|
||||
const titleElements = {
|
||||
by: { text: intl.formatMessage(messages.by) },
|
||||
employeeName: { link: staffMemberDetailsUrl(user.id), text: employeeName },
|
||||
orderNumber: {
|
||||
link: orderUrl(relatedOrder?.id),
|
||||
text: `#${relatedOrder?.number}`
|
||||
},
|
||||
title: { text: intl.formatMessage(titles[eventTypeInCamelCase]) }
|
||||
};
|
||||
|
||||
const selectTitleElements = () => {
|
||||
const { title, by, employeeName, orderNumber } = titleElements;
|
||||
|
||||
switch (type) {
|
||||
case OrderEventsEnum.DRAFT_CREATED_FROM_REPLACE: {
|
||||
return [title, orderNumber, by, employeeName];
|
||||
}
|
||||
default: {
|
||||
return [title, employeeName];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TimelineEvent date={date} titleElements={selectTitleElements()} key={id}>
|
||||
{lines && (
|
||||
<>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
className={classes.eventSubtitle}
|
||||
>
|
||||
{intl.formatMessage(productTitles[eventTypeInCamelCase])}
|
||||
</Typography>
|
||||
<table>
|
||||
<tbody>
|
||||
{lines.map(({ orderLine, quantity }) => (
|
||||
<tr key={orderLine.id}>
|
||||
<td className={classes.linesTableCell}>
|
||||
{orderLine.productName}
|
||||
</td>
|
||||
<td className={classes.linesTableCell}>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
{orderLine.variantName}
|
||||
</Typography>
|
||||
</td>
|
||||
<td className={classes.linesTableCell}>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
{`qty: ${quantity}`}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{(amount || amount === 0) && (
|
||||
<>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
className={classNames(classes.eventSubtitle, classes.topSpacer)}
|
||||
>
|
||||
{intl.formatMessage(messages.refundedAmount)}
|
||||
</Typography>
|
||||
<Money
|
||||
money={{
|
||||
amount,
|
||||
currency: orderCurrency
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{shippingCostsIncluded && (
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.refundedShipment)}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</TimelineEvent>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtendedTimelineEvent;
|
|
@ -2,28 +2,32 @@ import { makeStyles } from "@material-ui/core/styles";
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import Form from "@saleor/components/Form";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import Money from "@saleor/components/Money";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import {
|
||||
Timeline,
|
||||
TimelineAddNote,
|
||||
TimelineEvent,
|
||||
TimelineEventProps,
|
||||
TimelineNote
|
||||
} from "@saleor/components/Timeline";
|
||||
import React from "react";
|
||||
import { FormattedMessage, IntlShape, useIntl } from "react-intl";
|
||||
|
||||
import { TitleElement } from "@saleor/components/Timeline/TimelineEventHeader";
|
||||
import { OrderDetails_order_events } from "@saleor/orders/types/OrderDetails";
|
||||
import { orderUrl } from "@saleor/orders/urls";
|
||||
import {
|
||||
OrderEventsEmailsEnum,
|
||||
OrderEventsEnum
|
||||
} from "../../../types/globalTypes";
|
||||
import { OrderDetails_order_events } from "../../types/OrderDetails";
|
||||
} from "@saleor/types/globalTypes";
|
||||
import React from "react";
|
||||
import { defineMessages } from "react-intl";
|
||||
import { FormattedMessage, IntlShape, useIntl } from "react-intl";
|
||||
|
||||
export interface FormData {
|
||||
message: string;
|
||||
}
|
||||
import ExtendedTimelineEvent from "./ExtendedTimelineEvent";
|
||||
import { getEventSecondaryTitle, isTimelineEventOfType } from "./utils";
|
||||
|
||||
const getEventMessage = (event: OrderDetails_order_events, intl: IntlShape) => {
|
||||
export const getEventMessage = (
|
||||
event: OrderDetails_order_events,
|
||||
intl: IntlShape
|
||||
) => {
|
||||
switch (event.type) {
|
||||
case OrderEventsEnum.CANCELED:
|
||||
return intl.formatMessage({
|
||||
|
@ -247,6 +251,21 @@ const getEventMessage = (event: OrderDetails_order_events, intl: IntlShape) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const replacementCreatedMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage: "was created for replaced products",
|
||||
description: "replacement created order history message description"
|
||||
},
|
||||
draftNumber: {
|
||||
defaultMessage: "Draft #{orderNumber} ",
|
||||
description: "replacement created order history message draft number"
|
||||
}
|
||||
});
|
||||
|
||||
export interface FormData {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
eventSubtitle: {
|
||||
|
@ -279,6 +298,45 @@ const OrderHistory: React.FC<OrderHistoryProps> = props => {
|
|||
|
||||
const intl = useIntl();
|
||||
|
||||
const getTimelineEventTitleProps = (
|
||||
event: OrderDetails_order_events
|
||||
): Partial<TimelineEventProps> => {
|
||||
const { type, message } = event;
|
||||
|
||||
const title = isTimelineEventOfType("rawMessage", type)
|
||||
? message
|
||||
: getEventMessage(event, intl);
|
||||
|
||||
if (isTimelineEventOfType("secondaryTitle", type)) {
|
||||
return {
|
||||
secondaryTitle: intl.formatMessage(...getEventSecondaryTitle(event)),
|
||||
title
|
||||
};
|
||||
}
|
||||
|
||||
return { title };
|
||||
};
|
||||
|
||||
const getTitleElements = (
|
||||
event: OrderDetails_order_events
|
||||
): TitleElement[] => {
|
||||
const { type, relatedOrder } = event;
|
||||
|
||||
switch (type) {
|
||||
case OrderEventsEnum.ORDER_REPLACEMENT_CREATED: {
|
||||
return [
|
||||
{
|
||||
link: orderUrl(relatedOrder?.id),
|
||||
text: intl.formatMessage(replacementCreatedMessages.draftNumber, {
|
||||
orderNumber: relatedOrder?.number
|
||||
})
|
||||
},
|
||||
{ text: intl.formatMessage(replacementCreatedMessages.description) }
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Typography className={classes.header} color="textSecondary">
|
||||
|
@ -301,106 +359,42 @@ const OrderHistory: React.FC<OrderHistoryProps> = props => {
|
|||
.slice()
|
||||
.reverse()
|
||||
.map(event => {
|
||||
if (event.type === OrderEventsEnum.NOTE_ADDED) {
|
||||
const { id, user, date, message, type } = event;
|
||||
|
||||
if (isTimelineEventOfType("note", type)) {
|
||||
return (
|
||||
<TimelineNote
|
||||
date={event.date}
|
||||
user={event.user}
|
||||
message={event.message}
|
||||
key={event.id}
|
||||
date={date}
|
||||
user={user}
|
||||
message={message}
|
||||
key={id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (event.type === OrderEventsEnum.ORDER_MARKED_AS_PAID) {
|
||||
if (isTimelineEventOfType("extendable", type)) {
|
||||
return (
|
||||
<TimelineEvent
|
||||
date={event.date}
|
||||
title={getEventMessage(event, intl)}
|
||||
secondaryTitle={intl.formatMessage(
|
||||
{
|
||||
defaultMessage:
|
||||
"Transaction Reference {transactionReference}",
|
||||
description: "transaction reference"
|
||||
},
|
||||
{ transactionReference: event.transactionReference }
|
||||
)}
|
||||
key={event.id}
|
||||
<ExtendedTimelineEvent
|
||||
event={event}
|
||||
orderCurrency={orderCurrency}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (event.type === OrderEventsEnum.FULFILLMENT_REFUNDED) {
|
||||
|
||||
if (isTimelineEventOfType("linked", type)) {
|
||||
return (
|
||||
<TimelineEvent
|
||||
date={event.date}
|
||||
title={getEventMessage(event, intl)}
|
||||
key={event.id}
|
||||
>
|
||||
{event.lines && (
|
||||
<>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
className={classes.eventSubtitle}
|
||||
>
|
||||
<FormattedMessage defaultMessage="Products refunded" />
|
||||
</Typography>
|
||||
<table>
|
||||
<tbody>
|
||||
{event.lines.map(line => (
|
||||
<tr key={line.orderLine.id}>
|
||||
<td className={classes.linesTableCell}>
|
||||
{line.orderLine.productName}
|
||||
</td>
|
||||
<td className={classes.linesTableCell}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
>
|
||||
{line.orderLine.variantName}
|
||||
</Typography>
|
||||
</td>
|
||||
<td className={classes.linesTableCell}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
>
|
||||
{`qty: ${line.quantity}`}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
className={classes.eventSubtitle}
|
||||
>
|
||||
<FormattedMessage defaultMessage="Refunded amount" />
|
||||
</Typography>
|
||||
{(event.amount || event.amount === 0) && (
|
||||
<Money
|
||||
money={{
|
||||
amount: event.amount,
|
||||
currency: orderCurrency
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{event.shippingCostsIncluded && (
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="Shipment was refunded" />
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</TimelineEvent>
|
||||
titleElements={getTitleElements(event)}
|
||||
key={id}
|
||||
date={date}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TimelineEvent
|
||||
date={event.date}
|
||||
title={getEventMessage(event, intl)}
|
||||
key={event.id}
|
||||
{...getTimelineEventTitleProps(event)}
|
||||
key={id}
|
||||
date={date}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
41
src/orders/components/OrderHistory/utils.tsx
Normal file
41
src/orders/components/OrderHistory/utils.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { OrderDetails_order_events } from "@saleor/orders/types/OrderDetails";
|
||||
import { OrderEventsEnum } from "@saleor/types/globalTypes";
|
||||
import { MessageDescriptor } from "react-intl";
|
||||
|
||||
export const getEventSecondaryTitle = (
|
||||
event: OrderDetails_order_events
|
||||
): [MessageDescriptor, any?] => {
|
||||
switch (event.type) {
|
||||
case OrderEventsEnum.ORDER_MARKED_AS_PAID: {
|
||||
return [
|
||||
{
|
||||
defaultMessage: "Transaction Reference {transactionReference}",
|
||||
description: "transaction reference",
|
||||
id: "transaction-reference-order-history"
|
||||
},
|
||||
{ transactionReference: event.transactionReference }
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const timelineEventTypes = {
|
||||
extendable: [
|
||||
OrderEventsEnum.FULFILLMENT_REFUNDED,
|
||||
OrderEventsEnum.FULFILLMENT_REPLACED,
|
||||
OrderEventsEnum.FULFILLMENT_RETURNED,
|
||||
OrderEventsEnum.DRAFT_CREATED_FROM_REPLACE
|
||||
],
|
||||
linked: [OrderEventsEnum.ORDER_REPLACEMENT_CREATED],
|
||||
note: [OrderEventsEnum.NOTE_ADDED],
|
||||
rawMessage: [
|
||||
OrderEventsEnum.OTHER,
|
||||
OrderEventsEnum.EXTERNAL_SERVICE_NOTIFICATION
|
||||
],
|
||||
secondaryTitle: [OrderEventsEnum.ORDER_MARKED_AS_PAID]
|
||||
};
|
||||
|
||||
export const isTimelineEventOfType = (
|
||||
type: "extendable" | "secondaryTitle" | "rawMessage" | "note" | "linked",
|
||||
eventType: OrderEventsEnum
|
||||
) => !!timelineEventTypes[type]?.includes(eventType);
|
|
@ -0,0 +1,95 @@
|
|||
import { makeStyles, TableCell, TableHead, TableRow } from "@material-ui/core";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
clickableRow: {
|
||||
cursor: "pointer"
|
||||
},
|
||||
colName: {
|
||||
textAlign: "left",
|
||||
width: "auto"
|
||||
},
|
||||
colPrice: {
|
||||
textAlign: "right",
|
||||
width: 120
|
||||
},
|
||||
colQuantity: {
|
||||
textAlign: "center",
|
||||
width: 120
|
||||
},
|
||||
colSku: {
|
||||
textAlign: "right",
|
||||
textOverflow: "ellipsis",
|
||||
width: 120
|
||||
},
|
||||
colTotal: {
|
||||
textAlign: "right",
|
||||
width: 120
|
||||
},
|
||||
infoLabel: {
|
||||
display: "inline-block"
|
||||
},
|
||||
infoLabelWithMargin: {
|
||||
marginBottom: theme.spacing()
|
||||
},
|
||||
infoRow: {
|
||||
padding: theme.spacing(2, 3)
|
||||
},
|
||||
orderNumber: {
|
||||
display: "inline",
|
||||
marginLeft: theme.spacing(1)
|
||||
},
|
||||
statusBar: {
|
||||
paddingTop: 0
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
}),
|
||||
{ name: "TableHeader" }
|
||||
);
|
||||
|
||||
const TableHeader = () => {
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Product"
|
||||
description="product name"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSku}>
|
||||
<FormattedMessage
|
||||
defaultMessage="SKU"
|
||||
description="ordered product sku"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Quantity"
|
||||
description="ordered product quantity"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Price"
|
||||
description="product price"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTotal}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Total"
|
||||
description="order line total price"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableHeader;
|
|
@ -0,0 +1,129 @@
|
|||
import { makeStyles, TableCell, TableRow } from "@material-ui/core";
|
||||
import Money from "@saleor/components/Money";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import {
|
||||
OrderDetails_order_fulfillments_lines,
|
||||
OrderDetails_order_lines
|
||||
} from "@saleor/orders/types/OrderDetails";
|
||||
import React from "react";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
clickableRow: {
|
||||
cursor: "pointer"
|
||||
},
|
||||
colName: {
|
||||
width: "auto"
|
||||
},
|
||||
colNameLabel: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
colPrice: {
|
||||
textAlign: "right",
|
||||
width: 120
|
||||
},
|
||||
colQuantity: {
|
||||
textAlign: "center",
|
||||
width: 120
|
||||
},
|
||||
colSku: {
|
||||
textAlign: "right",
|
||||
textOverflow: "ellipsis",
|
||||
width: 120
|
||||
},
|
||||
colTotal: {
|
||||
textAlign: "right",
|
||||
width: 120
|
||||
},
|
||||
infoLabel: {
|
||||
display: "inline-block"
|
||||
},
|
||||
infoLabelWithMargin: {
|
||||
marginBottom: theme.spacing()
|
||||
},
|
||||
infoRow: {
|
||||
padding: theme.spacing(2, 3)
|
||||
},
|
||||
orderNumber: {
|
||||
display: "inline",
|
||||
marginLeft: theme.spacing(1)
|
||||
},
|
||||
statusBar: {
|
||||
paddingTop: 0
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
}),
|
||||
{ name: "TableLine" }
|
||||
);
|
||||
|
||||
interface TableLineProps {
|
||||
line: OrderDetails_order_fulfillments_lines | OrderDetails_order_lines;
|
||||
isOrderLine?: boolean;
|
||||
}
|
||||
|
||||
const TableLine: React.FC<TableLineProps> = ({
|
||||
line: lineData,
|
||||
isOrderLine = false
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const { quantity, quantityFulfilled } = lineData as OrderDetails_order_lines;
|
||||
|
||||
if (!lineData) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
const line = isOrderLine
|
||||
? ({
|
||||
...lineData,
|
||||
orderLine: lineData
|
||||
} as OrderDetails_order_fulfillments_lines)
|
||||
: (lineData as OrderDetails_order_fulfillments_lines);
|
||||
|
||||
const quantityToDisplay = isOrderLine
|
||||
? quantity - quantityFulfilled
|
||||
: quantity;
|
||||
|
||||
return (
|
||||
<TableRow className={classes.clickableRow} hover key={line.id}>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => line.orderLine.thumbnail.url)}
|
||||
>
|
||||
{maybe(() => line.orderLine.productName) || <Skeleton />}
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colSku}>
|
||||
{line?.orderLine.productSku || <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
{quantityToDisplay || <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
{maybe(() => line.orderLine.unitPrice.gross) ? (
|
||||
<Money money={line.orderLine.unitPrice.gross} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTotal}>
|
||||
{maybe(() => line.quantity * line.orderLine.unitPrice.gross.amount) ? (
|
||||
<Money
|
||||
money={{
|
||||
amount: line.quantity * line.orderLine.unitPrice.gross.amount,
|
||||
currency: line.orderLine.unitPrice.gross.currency
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableLine;
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./OrderRefundAmount";
|
||||
export { default } from "./OrderRefundAmount";
|
|
@ -1,153 +0,0 @@
|
|||
import { makeStyles } from "@material-ui/core";
|
||||
import Money, { IMoney } from "@saleor/components/Money";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
highlightedRow: {
|
||||
fontWeight: 600
|
||||
},
|
||||
root: {
|
||||
...theme.typography.body1,
|
||||
lineHeight: 1.9,
|
||||
width: "100%"
|
||||
},
|
||||
textRight: {
|
||||
minWidth: 30,
|
||||
textAlign: "right"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderRefundAmountValues" }
|
||||
);
|
||||
|
||||
export interface OrderRefundAmountValuesProps {
|
||||
authorizedAmount: IMoney;
|
||||
shipmentCost?: IMoney;
|
||||
selectedProductsValue?: IMoney;
|
||||
previouslyRefunded: IMoney;
|
||||
maxRefund: IMoney;
|
||||
proposedRefundAmount?: IMoney;
|
||||
refundTotalAmount?: IMoney;
|
||||
}
|
||||
|
||||
const OrderRefundAmountValues: React.FC<OrderRefundAmountValuesProps> = ({
|
||||
authorizedAmount,
|
||||
shipmentCost,
|
||||
selectedProductsValue,
|
||||
previouslyRefunded,
|
||||
maxRefund,
|
||||
proposedRefundAmount,
|
||||
refundTotalAmount
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<table className={classes.root}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
defaultMessage="Authorized Amount"
|
||||
description="order refund amount"
|
||||
/>
|
||||
</td>
|
||||
<td className={classes.textRight}>
|
||||
{authorizedAmount?.amount !== undefined ? (
|
||||
<Money money={authorizedAmount} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
{shipmentCost?.amount !== undefined && (
|
||||
<tr>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
defaultMessage="Shipment cost"
|
||||
description="order refund amount"
|
||||
/>
|
||||
</td>
|
||||
<td className={classes.textRight}>
|
||||
<Money money={shipmentCost} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{selectedProductsValue?.amount !== undefined && (
|
||||
<tr>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
defaultMessage="Selected products value"
|
||||
description="order refund amount"
|
||||
/>
|
||||
</td>
|
||||
<td className={classes.textRight}>
|
||||
<Money money={selectedProductsValue} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
defaultMessage="Previously refunded"
|
||||
description="order refund amount"
|
||||
/>
|
||||
</td>
|
||||
<td className={classes.textRight}>
|
||||
{previouslyRefunded?.amount !== undefined ? (
|
||||
<>
|
||||
<Money money={previouslyRefunded} />
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr className={classes.highlightedRow}>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
defaultMessage="Max Refund"
|
||||
description="order refund amount"
|
||||
/>
|
||||
</td>
|
||||
<td className={classes.textRight}>
|
||||
{maxRefund?.amount !== undefined ? (
|
||||
<Money money={maxRefund} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
{proposedRefundAmount?.amount !== undefined && (
|
||||
<tr className={classes.highlightedRow}>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
defaultMessage="Proposed refund amount"
|
||||
description="order refund amount"
|
||||
/>
|
||||
</td>
|
||||
<td className={classes.textRight}>
|
||||
<Money money={proposedRefundAmount} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{refundTotalAmount?.amount !== undefined && (
|
||||
<tr className={classes.highlightedRow}>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
defaultMessage="Refund total amount"
|
||||
description="order refund amount"
|
||||
/>
|
||||
</td>
|
||||
<td className={classes.textRight}>
|
||||
<Money money={refundTotalAmount} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
OrderRefundAmountValues.displayName = "OrderRefundAmountValues";
|
||||
export default OrderRefundAmountValues;
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./OrderRefundAmountValues";
|
||||
export { default } from "./OrderRefundAmountValues";
|
|
@ -12,8 +12,12 @@ import React from "react";
|
|||
import { useIntl } from "react-intl";
|
||||
|
||||
import OrderRefund from "../OrderRefund";
|
||||
import OrderRefundAmount from "../OrderRefundAmount";
|
||||
import OrderRefundFulfilledProducts from "../OrderRefundFulfilledProducts";
|
||||
import OrderRefundAmount from "../OrderRefundReturnAmount";
|
||||
import {
|
||||
getMiscellaneousAmountValues,
|
||||
getRefundProductsAmountValues
|
||||
} from "../OrderRefundReturnAmount/utils";
|
||||
import OrderRefundUnfulfilledProducts from "../OrderRefundUnfulfilledProducts";
|
||||
import OrderRefundForm, {
|
||||
OrderRefundSubmitData,
|
||||
|
@ -55,91 +59,104 @@ const OrderRefundPage: React.FC<OrderRefundPageProps> = props => {
|
|||
defaultType={defaultType}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ data, handlers, change, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{order?.number
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Order #{orderNumber}",
|
||||
description: "page header with order number"
|
||||
},
|
||||
{
|
||||
orderNumber: order.number
|
||||
}
|
||||
)
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Order",
|
||||
{({ data, handlers, change, submit }) => {
|
||||
const isProductRefund = data.type === OrderRefundType.PRODUCTS;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{order?.number
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Order #{orderNumber}",
|
||||
description: "page header with order number"
|
||||
},
|
||||
{
|
||||
orderNumber: order.number
|
||||
}
|
||||
)
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Order",
|
||||
description: "page header"
|
||||
})}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Order no. {orderNumber} - Refund",
|
||||
description: "page header"
|
||||
})}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Order no. {orderNumber} - Refund",
|
||||
description: "page header"
|
||||
},
|
||||
{
|
||||
orderNumber: order?.number
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<Grid>
|
||||
<div>
|
||||
<OrderRefund data={data} disabled={disabled} onChange={change} />
|
||||
{data.type === OrderRefundType.PRODUCTS && (
|
||||
<>
|
||||
{unfulfilledLines?.length > 0 && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
<OrderRefundUnfulfilledProducts
|
||||
unfulfilledLines={unfulfilledLines}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
onRefundedProductQuantityChange={
|
||||
handlers.changeRefundedProductQuantity
|
||||
}
|
||||
onSetMaximalQuantities={
|
||||
handlers.setMaximalRefundedProductQuantities
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{renderCollection(fulfilledFulfillemnts, fulfillment => (
|
||||
<React.Fragment key={fulfillment?.id}>
|
||||
<CardSpacer />
|
||||
<OrderRefundFulfilledProducts
|
||||
fulfillment={fulfillment}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
orderNumber={order?.number}
|
||||
onRefundedProductQuantityChange={
|
||||
handlers.changeRefundedFulfilledProductQuantity
|
||||
}
|
||||
onSetMaximalQuantities={() =>
|
||||
handlers.setMaximalRefundedFulfilledProductQuantities(
|
||||
fulfillment?.id
|
||||
)
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
},
|
||||
{
|
||||
orderNumber: order?.number
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<OrderRefundAmount
|
||||
data={data}
|
||||
order={order}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
onRefund={submit}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</Container>
|
||||
)}
|
||||
/>
|
||||
<Grid>
|
||||
<div>
|
||||
<OrderRefund
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
onChange={change}
|
||||
/>
|
||||
{isProductRefund && (
|
||||
<>
|
||||
{unfulfilledLines?.length > 0 && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
<OrderRefundUnfulfilledProducts
|
||||
unfulfilledLines={unfulfilledLines}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
onRefundedProductQuantityChange={
|
||||
handlers.changeRefundedProductQuantity
|
||||
}
|
||||
onSetMaximalQuantities={
|
||||
handlers.setMaximalRefundedProductQuantities
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{renderCollection(fulfilledFulfillemnts, fulfillment => (
|
||||
<React.Fragment key={fulfillment?.id}>
|
||||
<CardSpacer />
|
||||
<OrderRefundFulfilledProducts
|
||||
fulfillment={fulfillment}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
orderNumber={order?.number}
|
||||
onRefundedProductQuantityChange={
|
||||
handlers.changeRefundedFulfilledProductQuantity
|
||||
}
|
||||
onSetMaximalQuantities={() =>
|
||||
handlers.setMaximalRefundedFulfilledProductQuantities(
|
||||
fulfillment?.id
|
||||
)
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<OrderRefundAmount
|
||||
amountData={
|
||||
isProductRefund
|
||||
? getRefundProductsAmountValues(order, data)
|
||||
: getMiscellaneousAmountValues(order)
|
||||
}
|
||||
data={data}
|
||||
order={order}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
onRefund={submit}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</OrderRefundForm>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,91 +9,22 @@ import CardSpacer from "@saleor/components/CardSpacer";
|
|||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import { IMoney } from "@saleor/components/Money";
|
||||
import PriceField from "@saleor/components/PriceField";
|
||||
import { OrderErrorFragment } from "@saleor/fragments/types/OrderErrorFragment";
|
||||
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
||||
import { OrderRefundData_order } from "@saleor/orders/types/OrderRefundData";
|
||||
import {
|
||||
getAllFulfillmentLinesPriceSum,
|
||||
getPreviouslyRefundedPrice,
|
||||
getRefundedLinesPriceSum
|
||||
} from "@saleor/orders/utils/data";
|
||||
import { getFormErrors } from "@saleor/utils/errors";
|
||||
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import OrderRefundAmountValues, {
|
||||
OrderRefundAmountValuesProps
|
||||
} from "../OrderRefundAmountValues";
|
||||
import {
|
||||
OrderRefundAmountCalculationMode,
|
||||
OrderRefundFormData,
|
||||
OrderRefundType
|
||||
} from "../OrderRefundPage/form";
|
||||
|
||||
const getMiscellaneousAmountValues = (
|
||||
order: OrderRefundData_order
|
||||
): OrderRefundAmountValuesProps => {
|
||||
const authorizedAmount = order?.total?.gross;
|
||||
const previouslyRefunded = getPreviouslyRefundedPrice(order);
|
||||
const maxRefund = order?.totalCaptured;
|
||||
|
||||
return {
|
||||
authorizedAmount,
|
||||
maxRefund,
|
||||
previouslyRefunded
|
||||
};
|
||||
};
|
||||
|
||||
const getProductsAmountValues = (
|
||||
order: OrderRefundData_order,
|
||||
data: OrderRefundFormData
|
||||
): OrderRefundAmountValuesProps => {
|
||||
const authorizedAmount = order?.total?.gross;
|
||||
const shipmentCost =
|
||||
authorizedAmount?.currency &&
|
||||
(order?.shippingPrice?.gross || {
|
||||
amount: 0,
|
||||
currency: authorizedAmount?.currency
|
||||
});
|
||||
const previouslyRefunded = getPreviouslyRefundedPrice(order);
|
||||
const maxRefund = order?.totalCaptured;
|
||||
const refundedLinesSum = getRefundedLinesPriceSum(
|
||||
order?.lines,
|
||||
data.refundedProductQuantities
|
||||
);
|
||||
const allFulfillmentLinesSum = getAllFulfillmentLinesPriceSum(
|
||||
order?.fulfillments,
|
||||
data.refundedFulfilledProductQuantities
|
||||
);
|
||||
const allLinesSum = refundedLinesSum + allFulfillmentLinesSum;
|
||||
const calculatedTotalAmount = data.refundShipmentCosts
|
||||
? allLinesSum + shipmentCost?.amount
|
||||
: allLinesSum;
|
||||
const selectedProductsValue = authorizedAmount?.currency && {
|
||||
amount: allLinesSum,
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
const proposedRefundAmount = authorizedAmount?.currency && {
|
||||
amount: calculatedTotalAmount,
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
const refundTotalAmount = authorizedAmount?.currency && {
|
||||
amount: calculatedTotalAmount,
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
|
||||
return {
|
||||
authorizedAmount,
|
||||
maxRefund,
|
||||
previouslyRefunded,
|
||||
proposedRefundAmount,
|
||||
refundTotalAmount,
|
||||
selectedProductsValue,
|
||||
shipmentCost
|
||||
};
|
||||
};
|
||||
import { OrderReturnFormData } from "../OrderReturnPage/form";
|
||||
import OrderRefundAmountValues, {
|
||||
OrderRefundAmountValuesProps
|
||||
} from "./OrderRefundReturnAmountValues";
|
||||
import RefundAmountInput from "./RefundAmountInput";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
|
@ -124,80 +55,54 @@ const useStyles = makeStyles(
|
|||
{ name: "OrderRefundAmount" }
|
||||
);
|
||||
|
||||
interface RefundAmountInputProps {
|
||||
data: OrderRefundFormData;
|
||||
maxRefund: IMoney;
|
||||
currencySymbol: string;
|
||||
amountTooSmall: boolean;
|
||||
amountTooBig: boolean;
|
||||
disabled: boolean;
|
||||
errors: OrderErrorFragment[];
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
}
|
||||
|
||||
const RefundAmountInput: React.FC<RefundAmountInputProps> = props => {
|
||||
const {
|
||||
data,
|
||||
maxRefund,
|
||||
amountTooSmall,
|
||||
amountTooBig,
|
||||
currencySymbol,
|
||||
disabled,
|
||||
errors,
|
||||
onChange
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
const classes = useStyles(props);
|
||||
|
||||
const formErrors = getFormErrors(["amount"], errors);
|
||||
|
||||
return (
|
||||
<PriceField
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
currencySymbol={currencySymbol}
|
||||
name={"amount" as keyof FormData}
|
||||
value={data.amount}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Amount",
|
||||
description: "order refund amount, input label"
|
||||
})}
|
||||
className={classes.priceField}
|
||||
InputProps={{ inputProps: { max: maxRefund?.amount } }}
|
||||
inputProps={{
|
||||
"data-test": "amountInput",
|
||||
max: maxRefund?.amount
|
||||
}}
|
||||
error={!!formErrors.amount || amountTooSmall || amountTooBig}
|
||||
hint={
|
||||
getOrderErrorMessage(formErrors.amount, intl) ||
|
||||
(amountTooSmall &&
|
||||
intl.formatMessage({
|
||||
defaultMessage: "Amount must be bigger than 0"
|
||||
})) ||
|
||||
(amountTooBig &&
|
||||
intl.formatMessage({
|
||||
defaultMessage: "Amount cannot be bigger than max refund"
|
||||
}))
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const messages = defineMessages({
|
||||
refundButton: {
|
||||
defaultMessage: "Refund",
|
||||
description: "order refund amount button"
|
||||
},
|
||||
refundCannotBeFulfilled: {
|
||||
defaultMessage: "Refunded items can't be fulfilled",
|
||||
description: "order refund subtitle"
|
||||
},
|
||||
returnButton: {
|
||||
defaultMessage: "Return & Replace products",
|
||||
description: "order return amount button"
|
||||
},
|
||||
returnCannotBeFulfilled: {
|
||||
defaultMessage: "Returned items can't be fulfilled",
|
||||
description: "order return subtitle"
|
||||
}
|
||||
});
|
||||
|
||||
interface OrderRefundAmountProps {
|
||||
data: OrderRefundFormData;
|
||||
order: OrderRefundData_order;
|
||||
data: OrderRefundFormData | OrderReturnFormData;
|
||||
order: OrderRefundData_order | OrderDetails_order;
|
||||
disabled: boolean;
|
||||
disableSubmitButton?: boolean;
|
||||
isReturn?: boolean;
|
||||
errors: OrderErrorFragment[];
|
||||
amountData: OrderRefundAmountValuesProps;
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
onRefund: () => void;
|
||||
}
|
||||
|
||||
const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
||||
const { data, order, disabled, errors, onChange, onRefund } = props;
|
||||
const {
|
||||
data,
|
||||
order,
|
||||
disabled,
|
||||
errors,
|
||||
onChange,
|
||||
onRefund,
|
||||
isReturn = false,
|
||||
amountData,
|
||||
disableSubmitButton
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
|
||||
const { type = OrderRefundType.PRODUCTS } = data as OrderRefundFormData;
|
||||
|
||||
const amountCurrency = order?.total?.gross?.currency;
|
||||
|
||||
const {
|
||||
|
@ -207,14 +112,12 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
proposedRefundAmount,
|
||||
refundTotalAmount,
|
||||
selectedProductsValue,
|
||||
shipmentCost
|
||||
} =
|
||||
data.type === OrderRefundType.PRODUCTS
|
||||
? getProductsAmountValues(order, data)
|
||||
: getMiscellaneousAmountValues(order);
|
||||
shipmentCost,
|
||||
replacedProductsValue
|
||||
} = amountData;
|
||||
|
||||
const selectedRefundAmount =
|
||||
data.type === OrderRefundType.PRODUCTS &&
|
||||
type === OrderRefundType.PRODUCTS &&
|
||||
data.amountCalculationMode === OrderRefundAmountCalculationMode.AUTOMATIC
|
||||
? refundTotalAmount?.amount
|
||||
: data.amount;
|
||||
|
@ -222,8 +125,9 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
const isAmountTooSmall = selectedRefundAmount && selectedRefundAmount <= 0;
|
||||
const isAmountTooBig = selectedRefundAmount > maxRefund?.amount;
|
||||
|
||||
const disableRefundButton =
|
||||
!selectedRefundAmount || isAmountTooSmall || isAmountTooBig;
|
||||
const disableRefundButton = isReturn
|
||||
? disableSubmitButton || isAmountTooSmall || isAmountTooBig
|
||||
: !selectedRefundAmount || isAmountTooBig || isAmountTooSmall;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
@ -234,7 +138,7 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
{data.type === OrderRefundType.PRODUCTS && (
|
||||
{type === OrderRefundType.PRODUCTS && (
|
||||
<RadioGroup
|
||||
value={data.amountCalculationMode}
|
||||
onChange={onChange}
|
||||
|
@ -261,6 +165,7 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
name="refundShipmentCosts"
|
||||
onChange={onChange}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<OrderRefundAmountValues
|
||||
authorizedAmount={authorizedAmount}
|
||||
previouslyRefunded={previouslyRefunded}
|
||||
|
@ -268,8 +173,8 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
selectedProductsValue={selectedProductsValue}
|
||||
refundTotalAmount={refundTotalAmount}
|
||||
shipmentCost={data.refundShipmentCosts && shipmentCost}
|
||||
replacedProductsValue={replacedProductsValue}
|
||||
/>
|
||||
<CardSpacer />
|
||||
</>
|
||||
)}
|
||||
<Hr className={classes.hr} />
|
||||
|
@ -302,9 +207,10 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
selectedProductsValue={selectedProductsValue}
|
||||
proposedRefundAmount={proposedRefundAmount}
|
||||
shipmentCost={data.refundShipmentCosts && shipmentCost}
|
||||
replacedProductsValue={replacedProductsValue}
|
||||
/>
|
||||
<RefundAmountInput
|
||||
data={data}
|
||||
data={data as OrderRefundFormData}
|
||||
maxRefund={maxRefund}
|
||||
amountTooSmall={isAmountTooSmall}
|
||||
amountTooBig={isAmountTooBig}
|
||||
|
@ -317,7 +223,7 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
)}
|
||||
</RadioGroup>
|
||||
)}
|
||||
{data.type === OrderRefundType.MISCELLANEOUS && (
|
||||
{type === OrderRefundType.MISCELLANEOUS && (
|
||||
<>
|
||||
<OrderRefundAmountValues
|
||||
authorizedAmount={authorizedAmount}
|
||||
|
@ -325,7 +231,7 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
maxRefund={maxRefund}
|
||||
/>
|
||||
<RefundAmountInput
|
||||
data={data}
|
||||
data={data as OrderRefundFormData}
|
||||
maxRefund={maxRefund}
|
||||
amountTooSmall={isAmountTooSmall}
|
||||
amountTooBig={isAmountTooBig}
|
||||
|
@ -337,17 +243,16 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
</>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
size="large"
|
||||
onClick={onRefund}
|
||||
className={classes.refundButton}
|
||||
disabled={disabled || disableRefundButton}
|
||||
disabled={disableRefundButton}
|
||||
data-test="submit"
|
||||
>
|
||||
{!disableRefundButton ? (
|
||||
{!disableRefundButton && !isReturn ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Refund {currency} {amount}"
|
||||
description="order refund amount, input button"
|
||||
|
@ -357,10 +262,9 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Refund"
|
||||
description="order refund amount, input button"
|
||||
/>
|
||||
intl.formatMessage(
|
||||
isReturn ? messages.returnButton : messages.refundButton
|
||||
)
|
||||
)}
|
||||
</Button>
|
||||
<Typography
|
||||
|
@ -368,10 +272,11 @@ const OrderRefundAmount: React.FC<OrderRefundAmountProps> = props => {
|
|||
color="textSecondary"
|
||||
className={classes.refundCaution}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Refunded items can’t be fulfilled"
|
||||
description="order refund amount"
|
||||
/>
|
||||
{intl.formatMessage(
|
||||
isReturn
|
||||
? messages.returnCannotBeFulfilled
|
||||
: messages.refundCannotBeFulfilled
|
||||
)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
|
@ -0,0 +1,133 @@
|
|||
import { makeStyles } from "@material-ui/core";
|
||||
import Money, { IMoney } from "@saleor/components/Money";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import classNames from "classnames";
|
||||
import { reduce } from "lodash";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { defineMessages } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
container: {
|
||||
...theme.typography.body1,
|
||||
lineHeight: 1.9,
|
||||
width: "100%"
|
||||
},
|
||||
highlightedRow: {
|
||||
fontWeight: 600
|
||||
},
|
||||
row: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing(2),
|
||||
textAlign: "right"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderRefundAmountValues" }
|
||||
);
|
||||
|
||||
export interface OrderRefundAmountValuesProps {
|
||||
authorizedAmount: IMoney;
|
||||
shipmentCost?: IMoney;
|
||||
selectedProductsValue?: IMoney;
|
||||
previouslyRefunded: IMoney;
|
||||
maxRefund: IMoney;
|
||||
proposedRefundAmount?: IMoney;
|
||||
replacedProductsValue?: IMoney;
|
||||
refundTotalAmount?: IMoney;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
authorizedAmount: {
|
||||
defaultMessage: "Authorized Amount",
|
||||
description: "order refund amount"
|
||||
},
|
||||
maxRefund: {
|
||||
defaultMessage: "Max Refund",
|
||||
description: "order refund amount"
|
||||
},
|
||||
previouslyRefunded: {
|
||||
defaultMessage: "Previously refunded",
|
||||
description: "order refund amount"
|
||||
},
|
||||
proposedRefundAmount: {
|
||||
defaultMessage: "Proposed refund amount",
|
||||
description: "order refund amount"
|
||||
},
|
||||
refundTotalAmount: {
|
||||
defaultMessage: "Refund total amount",
|
||||
description: "order refund amount"
|
||||
},
|
||||
replacedProductsValue: {
|
||||
defaultMessage: "Replaced Products Value",
|
||||
description: "order refund amount"
|
||||
},
|
||||
selectedProductsValue: {
|
||||
defaultMessage: "Selected Products Value",
|
||||
description: "order refund amount"
|
||||
},
|
||||
shipmentCost: {
|
||||
defaultMessage: "Shipment Cost",
|
||||
description: "order refund amount"
|
||||
}
|
||||
});
|
||||
|
||||
const OrderRefundAmountValues: React.FC<OrderRefundAmountValuesProps> = props => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
const orderedKeys: Array<keyof OrderRefundAmountValuesProps> = [
|
||||
"authorizedAmount",
|
||||
"shipmentCost",
|
||||
"selectedProductsValue",
|
||||
"previouslyRefunded",
|
||||
"replacedProductsValue",
|
||||
"maxRefund",
|
||||
"refundTotalAmount"
|
||||
];
|
||||
|
||||
const highlightedItems: Array<keyof OrderRefundAmountValuesProps> = [
|
||||
"maxRefund",
|
||||
"refundTotalAmount"
|
||||
];
|
||||
|
||||
const items = reduce(
|
||||
orderedKeys,
|
||||
(result, key) => {
|
||||
const value = props[key];
|
||||
|
||||
if (!value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return [
|
||||
...result,
|
||||
{ data: value, highlighted: highlightedItems.includes(key), key }
|
||||
];
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
{items.map(({ key, data, highlighted }) => (
|
||||
<div
|
||||
className={classNames(classes.row, {
|
||||
[classes.highlightedRow]: highlighted
|
||||
})}
|
||||
key={key}
|
||||
>
|
||||
{intl.formatMessage(messages[key])}
|
||||
<div>
|
||||
{data?.amount !== undefined ? <Money money={data} /> : <Skeleton />}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
OrderRefundAmountValues.displayName = "OrderRefundAmountValues";
|
||||
export default OrderRefundAmountValues;
|
|
@ -0,0 +1,108 @@
|
|||
import { makeStyles } from "@material-ui/core";
|
||||
import { IMoney } from "@saleor/components/Money";
|
||||
import PriceField from "@saleor/components/PriceField";
|
||||
import { OrderErrorFragment } from "@saleor/fragments/types/OrderErrorFragment";
|
||||
import { getFormErrors } from "@saleor/utils/errors";
|
||||
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||
import React from "react";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
import { OrderRefundFormData } from "../OrderRefundPage/form";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
hr: {
|
||||
margin: theme.spacing(1, 0)
|
||||
},
|
||||
maxRefundRow: {
|
||||
fontWeight: 600
|
||||
},
|
||||
priceField: {
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
refundButton: {
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
refundCaution: {
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
root: {
|
||||
...theme.typography.body1,
|
||||
lineHeight: 1.9,
|
||||
width: "100%"
|
||||
},
|
||||
textRight: {
|
||||
textAlign: "right"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderRefundAmount" }
|
||||
);
|
||||
|
||||
interface RefundAmountInputProps {
|
||||
data: OrderRefundFormData;
|
||||
maxRefund: IMoney;
|
||||
currencySymbol: string;
|
||||
amountTooSmall: boolean;
|
||||
amountTooBig: boolean;
|
||||
disabled: boolean;
|
||||
errors: OrderErrorFragment[];
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
amountTooBig: {
|
||||
defaultMessage: "Amount cannot be bigger than max refund",
|
||||
description: "Amount error message"
|
||||
},
|
||||
amountTooSmall: {
|
||||
defaultMessage: "Amount must be bigger than 0",
|
||||
description: "Amount error message"
|
||||
},
|
||||
label: {
|
||||
defaultMessage: "Amount",
|
||||
description: "order refund amount, input label"
|
||||
}
|
||||
});
|
||||
|
||||
const RefundAmountInput: React.FC<RefundAmountInputProps> = props => {
|
||||
const {
|
||||
data,
|
||||
maxRefund,
|
||||
amountTooSmall,
|
||||
amountTooBig,
|
||||
currencySymbol,
|
||||
disabled,
|
||||
errors,
|
||||
onChange
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
const classes = useStyles(props);
|
||||
const formErrors = getFormErrors(["amount"], errors);
|
||||
|
||||
const isError = !!formErrors.amount || amountTooSmall || amountTooBig;
|
||||
|
||||
return (
|
||||
<PriceField
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
currencySymbol={currencySymbol}
|
||||
name={"amount" as keyof FormData}
|
||||
value={data.amount}
|
||||
label={intl.formatMessage(messages.label)}
|
||||
className={classes.priceField}
|
||||
InputProps={{ inputProps: { max: maxRefund?.amount } }}
|
||||
inputProps={{
|
||||
"data-test": "amountInput",
|
||||
max: maxRefund?.amount
|
||||
}}
|
||||
error={isError}
|
||||
hint={
|
||||
getOrderErrorMessage(formErrors.amount, intl) ||
|
||||
(amountTooSmall && intl.formatMessage(messages.amountTooSmall)) ||
|
||||
(amountTooBig && intl.formatMessage(messages.amountTooBig))
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default RefundAmountInput;
|
2
src/orders/components/OrderRefundReturnAmount/index.ts
Normal file
2
src/orders/components/OrderRefundReturnAmount/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./OrderRefundReturnAmount";
|
||||
export { default } from "./OrderRefundReturnAmount";
|
203
src/orders/components/OrderRefundReturnAmount/utils.ts
Normal file
203
src/orders/components/OrderRefundReturnAmount/utils.ts
Normal file
|
@ -0,0 +1,203 @@
|
|||
import { IMoney } from "@saleor/components/Money";
|
||||
import { FormsetData } from "@saleor/hooks/useFormset";
|
||||
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
||||
import { OrderRefundData_order } from "@saleor/orders/types/OrderRefundData";
|
||||
import {
|
||||
getAllFulfillmentLinesPriceSum,
|
||||
getPreviouslyRefundedPrice,
|
||||
getRefundedLinesPriceSum,
|
||||
getReplacedProductsAmount,
|
||||
getReturnSelectedProductsAmount
|
||||
} from "@saleor/orders/utils/data";
|
||||
|
||||
import { OrderRefundFormData } from "../OrderRefundPage/form";
|
||||
import { LineItemData, OrderReturnFormData } from "../OrderReturnPage/form";
|
||||
import { OrderRefundAmountValuesProps } from "./OrderRefundReturnAmountValues";
|
||||
|
||||
export const getMiscellaneousAmountValues = (
|
||||
order: OrderRefundData_order
|
||||
): OrderRefundAmountValuesProps => {
|
||||
const authorizedAmount = order?.total?.gross;
|
||||
const previouslyRefunded = getPreviouslyRefundedPrice(order);
|
||||
const maxRefund = order?.totalCaptured;
|
||||
|
||||
return {
|
||||
authorizedAmount,
|
||||
maxRefund,
|
||||
previouslyRefunded
|
||||
};
|
||||
};
|
||||
|
||||
const getAuthorizedAmount = (order: OrderRefundData_order) =>
|
||||
order?.total?.gross;
|
||||
|
||||
const getShipmentCost = (order: OrderRefundData_order) =>
|
||||
getAuthorizedAmount(order)?.currency &&
|
||||
(order?.shippingPrice?.gross || {
|
||||
amount: 0,
|
||||
currency: getAuthorizedAmount(order)?.currency
|
||||
});
|
||||
|
||||
const getMaxRefund = (order: OrderRefundData_order) => order?.totalCaptured;
|
||||
|
||||
export const getProductsAmountValues = (
|
||||
order: OrderRefundData_order,
|
||||
fulfilledItemsQuantities: FormsetData<null | LineItemData, string | number>,
|
||||
unfulfilledItemsQuantities: FormsetData<null | LineItemData, string | number>,
|
||||
shipmentCosts
|
||||
): OrderRefundAmountValuesProps => {
|
||||
const authorizedAmount = getAuthorizedAmount(order);
|
||||
const shipmentCost = getShipmentCost(order);
|
||||
|
||||
const previouslyRefunded = getPreviouslyRefundedPrice(order);
|
||||
const maxRefund = getMaxRefund(order);
|
||||
const refundedLinesSum = getRefundedLinesPriceSum(
|
||||
order?.lines,
|
||||
unfulfilledItemsQuantities as FormsetData<null, string>
|
||||
);
|
||||
const allFulfillmentLinesSum = getAllFulfillmentLinesPriceSum(
|
||||
order?.fulfillments,
|
||||
fulfilledItemsQuantities as FormsetData<null, string>
|
||||
);
|
||||
const allLinesSum = refundedLinesSum + allFulfillmentLinesSum;
|
||||
const calculatedTotalAmount = getCalculatedTotalAmount({
|
||||
allLinesSum,
|
||||
maxRefund,
|
||||
previouslyRefunded,
|
||||
shipmentCost,
|
||||
shipmentCosts
|
||||
});
|
||||
|
||||
const selectedProductsValue = authorizedAmount?.currency && {
|
||||
amount: allLinesSum,
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
|
||||
const proposedRefundAmount = authorizedAmount?.currency && {
|
||||
amount: calculatedTotalAmount,
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
const refundTotalAmount = authorizedAmount?.currency && {
|
||||
amount: calculatedTotalAmount,
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
|
||||
return {
|
||||
authorizedAmount,
|
||||
maxRefund,
|
||||
previouslyRefunded,
|
||||
proposedRefundAmount,
|
||||
refundTotalAmount,
|
||||
selectedProductsValue,
|
||||
shipmentCost
|
||||
};
|
||||
};
|
||||
|
||||
const getCalculatedTotalAmount = ({
|
||||
shipmentCost,
|
||||
shipmentCosts,
|
||||
allLinesSum,
|
||||
maxRefund
|
||||
}: {
|
||||
shipmentCost: IMoney;
|
||||
shipmentCosts: IMoney;
|
||||
allLinesSum: number;
|
||||
previouslyRefunded: IMoney;
|
||||
maxRefund: IMoney;
|
||||
}) => {
|
||||
if (maxRefund?.amount === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const shipmentCostValue = shipmentCost ? shipmentCost.amount : 0;
|
||||
|
||||
const calculatedTotalAmount = shipmentCosts
|
||||
? allLinesSum + shipmentCostValue
|
||||
: allLinesSum;
|
||||
|
||||
return calculatedTotalAmount;
|
||||
};
|
||||
|
||||
const getReturnTotalAmount = ({
|
||||
selectedProductsValue,
|
||||
refundShipmentCosts,
|
||||
order,
|
||||
maxRefund
|
||||
}: {
|
||||
order: OrderDetails_order;
|
||||
selectedProductsValue: IMoney;
|
||||
refundShipmentCosts: boolean;
|
||||
maxRefund: IMoney;
|
||||
}) => {
|
||||
if (maxRefund?.amount === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (refundShipmentCosts) {
|
||||
const totalValue =
|
||||
selectedProductsValue?.amount + getShipmentCost(order)?.amount;
|
||||
return totalValue || 0;
|
||||
}
|
||||
|
||||
return selectedProductsValue?.amount || 0;
|
||||
};
|
||||
|
||||
export const getReturnProductsAmountValues = (
|
||||
order: OrderDetails_order,
|
||||
formData: OrderReturnFormData
|
||||
) => {
|
||||
const authorizedAmount = getAuthorizedAmount(order);
|
||||
|
||||
const {
|
||||
fulfiledItemsQuantities,
|
||||
unfulfiledItemsQuantities,
|
||||
refundShipmentCosts
|
||||
} = formData;
|
||||
|
||||
const replacedProductsValue = authorizedAmount?.currency && {
|
||||
amount: getReplacedProductsAmount(order, formData),
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
|
||||
const selectedProductsValue = authorizedAmount?.currency && {
|
||||
amount: getReturnSelectedProductsAmount(order, formData),
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
|
||||
const refundTotalAmount = authorizedAmount?.currency && {
|
||||
amount: getReturnTotalAmount({
|
||||
maxRefund: getMaxRefund(order),
|
||||
order,
|
||||
refundShipmentCosts,
|
||||
selectedProductsValue
|
||||
}),
|
||||
currency: authorizedAmount.currency
|
||||
};
|
||||
|
||||
return {
|
||||
...getProductsAmountValues(
|
||||
order,
|
||||
fulfiledItemsQuantities,
|
||||
unfulfiledItemsQuantities,
|
||||
refundShipmentCosts
|
||||
),
|
||||
refundTotalAmount,
|
||||
replacedProductsValue,
|
||||
selectedProductsValue
|
||||
};
|
||||
};
|
||||
|
||||
export const getRefundProductsAmountValues = (
|
||||
order: OrderRefundData_order,
|
||||
{
|
||||
refundedFulfilledProductQuantities,
|
||||
refundShipmentCosts,
|
||||
refundedProductQuantities
|
||||
}: OrderRefundFormData
|
||||
) =>
|
||||
getProductsAmountValues(
|
||||
order,
|
||||
refundedFulfilledProductQuantities,
|
||||
refundedProductQuantities,
|
||||
refundShipmentCosts
|
||||
);
|
129
src/orders/components/OrderReturnPage/OrderReturnPage.tsx
Normal file
129
src/orders/components/OrderReturnPage/OrderReturnPage.tsx
Normal file
|
@ -0,0 +1,129 @@
|
|||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import Container from "@saleor/components/Container";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import { OrderErrorFragment } from "@saleor/fragments/types/OrderErrorFragment";
|
||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
||||
import React from "react";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
import OrderAmount from "../OrderRefundReturnAmount";
|
||||
import { getReturnProductsAmountValues } from "../OrderRefundReturnAmount/utils";
|
||||
import OrderRefundForm, { OrderRefundSubmitData } from "./form";
|
||||
import ItemsCard from "./OrderReturnRefundItemsCard/ReturnItemsCard";
|
||||
import {
|
||||
getFulfilledFulfillemnts,
|
||||
getParsedFulfiledLines,
|
||||
getUnfulfilledLines
|
||||
} from "./utils";
|
||||
|
||||
const messages = defineMessages({
|
||||
appTitle: {
|
||||
defaultMessage: "Order #{orderNumber}",
|
||||
description: "page header with order number"
|
||||
},
|
||||
pageTitle: {
|
||||
defaultMessage: "Order no. {orderNumber} - Replace/Return",
|
||||
description: "page header"
|
||||
}
|
||||
});
|
||||
|
||||
export interface OrderReturnPageProps {
|
||||
order: OrderDetails_order;
|
||||
loading: boolean;
|
||||
errors?: OrderErrorFragment[];
|
||||
onBack: () => void;
|
||||
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise;
|
||||
}
|
||||
|
||||
const OrderRefundPage: React.FC<OrderReturnPageProps> = props => {
|
||||
const { order, loading, errors = [], onBack, onSubmit } = props;
|
||||
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<OrderRefundForm order={order} onSubmit={onSubmit}>
|
||||
{({ data, handlers, change, submit }) => {
|
||||
const { fulfiledItemsQuantities, unfulfiledItemsQuantities } = data;
|
||||
|
||||
const hasAnyItemsSelected =
|
||||
fulfiledItemsQuantities.some(({ value }) => !!value) ||
|
||||
unfulfiledItemsQuantities.some(({ value }) => !!value);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(messages.appTitle, {
|
||||
orderNumber: order?.number
|
||||
})}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage(messages.pageTitle, {
|
||||
orderNumber: order?.number
|
||||
})}
|
||||
/>
|
||||
<Grid>
|
||||
<div>
|
||||
{!!data.unfulfiledItemsQuantities.length && (
|
||||
<>
|
||||
<ItemsCard
|
||||
errors={errors}
|
||||
order={order}
|
||||
lines={getUnfulfilledLines(order)}
|
||||
itemsQuantities={data.unfulfiledItemsQuantities}
|
||||
itemsSelections={data.itemsToBeReplaced}
|
||||
onChangeQuantity={handlers.changeUnfulfiledItemsQuantity}
|
||||
onSetMaxQuantity={
|
||||
handlers.handleSetMaximalUnfulfiledItemsQuantities
|
||||
}
|
||||
onChangeSelected={handlers.changeItemsToBeReplaced}
|
||||
/>
|
||||
<CardSpacer />
|
||||
</>
|
||||
)}
|
||||
{renderCollection(
|
||||
getFulfilledFulfillemnts(order),
|
||||
({ id, lines }) => (
|
||||
<React.Fragment key={id}>
|
||||
<ItemsCard
|
||||
errors={errors}
|
||||
order={order}
|
||||
fulfilmentId={id}
|
||||
lines={getParsedFulfiledLines(lines)}
|
||||
itemsQuantities={data.fulfiledItemsQuantities}
|
||||
itemsSelections={data.itemsToBeReplaced}
|
||||
onChangeQuantity={handlers.changeFulfiledItemsQuantity}
|
||||
onSetMaxQuantity={handlers.handleSetMaximalFulfiledItemsQuantities(
|
||||
id
|
||||
)}
|
||||
onChangeSelected={handlers.changeItemsToBeReplaced}
|
||||
/>
|
||||
<CardSpacer />
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<OrderAmount
|
||||
isReturn
|
||||
amountData={getReturnProductsAmountValues(order, data)}
|
||||
data={data}
|
||||
order={order}
|
||||
disableSubmitButton={!hasAnyItemsSelected}
|
||||
disabled={loading}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
onRefund={submit}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</OrderRefundForm>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderRefundPage;
|
|
@ -0,0 +1,135 @@
|
|||
import { makeStyles, Typography } from "@material-ui/core";
|
||||
import DefaultCardTitle from "@saleor/components/CardTitle";
|
||||
import { StatusType } from "@saleor/components/StatusChip/types";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import { FulfillmentStatus } from "@saleor/types/globalTypes";
|
||||
import camelCase from "lodash/camelCase";
|
||||
import React from "react";
|
||||
import { defineMessages } from "react-intl";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
orderNumber: {
|
||||
display: "inline",
|
||||
marginLeft: theme.spacing(1)
|
||||
}
|
||||
}),
|
||||
{ name: "CardTitle" }
|
||||
);
|
||||
|
||||
const messages = defineMessages({
|
||||
cancelled: {
|
||||
defaultMessage: "Cancelled ({quantity})",
|
||||
description: "cancelled fulfillment, section header"
|
||||
},
|
||||
fulfilled: {
|
||||
defaultMessage: "Fulfilled ({quantity})",
|
||||
description: "section header"
|
||||
},
|
||||
refunded: {
|
||||
defaultMessage: "Refunded ({quantity})",
|
||||
description: "refunded fulfillment, section header"
|
||||
},
|
||||
refundedAndReturned: {
|
||||
defaultMessage: "Refunded and Returned ({quantity})",
|
||||
description: "cancelled fulfillment, section header"
|
||||
},
|
||||
replaced: {
|
||||
defaultMessage: "Replaced ({quantity})",
|
||||
description: "refunded fulfillment, section header"
|
||||
},
|
||||
returned: {
|
||||
defaultMessage: "Returned ({quantity})",
|
||||
description: "refunded fulfillment, section header"
|
||||
},
|
||||
unfulfilled: {
|
||||
defaultMessage: "Unfulfilled",
|
||||
description: "section header"
|
||||
}
|
||||
});
|
||||
|
||||
type CardTitleStatus = FulfillmentStatus | "unfulfilled";
|
||||
|
||||
type CardTitleLines = Array<{
|
||||
quantity: number;
|
||||
}>;
|
||||
|
||||
interface CardTitleProps {
|
||||
lines?: CardTitleLines;
|
||||
fulfillmentOrder?: number;
|
||||
status: CardTitleStatus;
|
||||
toolbar?: React.ReactNode;
|
||||
orderNumber?: string;
|
||||
withStatus?: boolean;
|
||||
}
|
||||
|
||||
const selectStatus = (status: CardTitleStatus) => {
|
||||
switch (status) {
|
||||
case FulfillmentStatus.FULFILLED:
|
||||
return StatusType.SUCCESS;
|
||||
case FulfillmentStatus.REFUNDED:
|
||||
return StatusType.NEUTRAL;
|
||||
case FulfillmentStatus.RETURNED:
|
||||
return StatusType.NEUTRAL;
|
||||
case FulfillmentStatus.REPLACED:
|
||||
return StatusType.NEUTRAL;
|
||||
case FulfillmentStatus.REFUNDED_AND_RETURNED:
|
||||
return StatusType.NEUTRAL;
|
||||
case FulfillmentStatus.CANCELED:
|
||||
return StatusType.ERROR;
|
||||
default:
|
||||
return StatusType.ALERT;
|
||||
}
|
||||
};
|
||||
|
||||
const CardTitle: React.FC<CardTitleProps> = ({
|
||||
lines = [],
|
||||
fulfillmentOrder,
|
||||
status,
|
||||
orderNumber = "",
|
||||
withStatus = false,
|
||||
toolbar
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
const fulfillmentName =
|
||||
orderNumber && fulfillmentOrder
|
||||
? `#${orderNumber}-${fulfillmentOrder}`
|
||||
: "";
|
||||
|
||||
const messageForStatus = messages[camelCase(status)] || messages.unfulfilled;
|
||||
|
||||
const totalQuantity = lines.reduce(
|
||||
(resultQuantity, { quantity }) => resultQuantity + quantity,
|
||||
0
|
||||
);
|
||||
|
||||
const title = (
|
||||
<>
|
||||
{intl.formatMessage(messageForStatus, {
|
||||
fulfillmentName,
|
||||
quantity: totalQuantity
|
||||
})}
|
||||
<Typography className={classes.orderNumber} variant="body1">
|
||||
{fulfillmentName}
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DefaultCardTitle
|
||||
toolbar={toolbar}
|
||||
title={
|
||||
withStatus ? (
|
||||
<StatusLabel label={title} status={selectStatus(status)} />
|
||||
) : (
|
||||
title
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardTitle;
|
|
@ -0,0 +1,39 @@
|
|||
import { Button, makeStyles } from "@material-ui/core";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
button: {
|
||||
letterSpacing: 2,
|
||||
marginBottom: theme.spacing(1),
|
||||
marginTop: theme.spacing(3),
|
||||
padding: 0
|
||||
}
|
||||
}),
|
||||
{ name: "MaximalButton" }
|
||||
);
|
||||
|
||||
interface MaximalButtonProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const MaximalButton: React.FC<MaximalButtonProps> = ({ onClick }) => {
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={classes.button}
|
||||
color="primary"
|
||||
onClick={onClick}
|
||||
data-test="setMaximalQuantityUnfulfilledButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Set maximal quantities"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default MaximalButton;
|
|
@ -0,0 +1,100 @@
|
|||
import Popper from "@material-ui/core/Popper";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ErrorExclamationCircleIcon from "@saleor/icons/ErrorExclamationCircle";
|
||||
import React, { useState } from "react";
|
||||
import { defineMessages } from "react-intl";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
container: {
|
||||
position: "relative"
|
||||
},
|
||||
errorBox: {
|
||||
backgroundColor: theme.palette.error.main,
|
||||
borderRadius: 8,
|
||||
marginRight: theme.spacing(3),
|
||||
padding: theme.spacing(2, 3),
|
||||
width: 280,
|
||||
zIndex: 1000
|
||||
},
|
||||
errorText: {
|
||||
color: "white",
|
||||
fontSize: 14
|
||||
},
|
||||
errorTextHighlighted: {
|
||||
color: theme.palette.error.main,
|
||||
fontSize: 12,
|
||||
marginRight: theme.spacing(1)
|
||||
},
|
||||
titleContainer: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end"
|
||||
}
|
||||
}),
|
||||
{ name: "ProductErrorCell" }
|
||||
);
|
||||
|
||||
const messages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"This product is no longer in database so it can’t be replaced, nor returned",
|
||||
description: "product no longer exists error description"
|
||||
},
|
||||
title: {
|
||||
defaultMessage: "Product no longer exists",
|
||||
description: "product no longer exists error title"
|
||||
}
|
||||
});
|
||||
|
||||
interface ProductErrorCellProps {
|
||||
hasVariant: boolean;
|
||||
}
|
||||
|
||||
const ProductErrorCell: React.FC<ProductErrorCellProps> = ({ hasVariant }) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
const popperAnchorRef = React.useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const [showErrorBox, setShowErrorBox] = useState<boolean>(false);
|
||||
|
||||
if (hasVariant) {
|
||||
return <TableCell />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
align="right"
|
||||
className={classes.container}
|
||||
ref={popperAnchorRef}
|
||||
>
|
||||
<div
|
||||
className={classes.titleContainer}
|
||||
onMouseEnter={() => setShowErrorBox(true)}
|
||||
onMouseLeave={() => setShowErrorBox(false)}
|
||||
>
|
||||
<Typography className={classes.errorTextHighlighted}>
|
||||
{intl.formatMessage(messages.title)}
|
||||
</Typography>
|
||||
<ErrorExclamationCircleIcon />
|
||||
</div>
|
||||
<Popper
|
||||
placement="bottom-end"
|
||||
open={showErrorBox}
|
||||
anchorEl={popperAnchorRef.current}
|
||||
>
|
||||
<div className={classes.errorBox}>
|
||||
<Typography className={classes.errorText}>
|
||||
{intl.formatMessage(messages.description)}
|
||||
</Typography>
|
||||
</div>
|
||||
</Popper>
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductErrorCell;
|
|
@ -0,0 +1,262 @@
|
|||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
Checkbox,
|
||||
makeStyles,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import Money from "@saleor/components/Money";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import { OrderErrorFragment } from "@saleor/fragments/types/OrderErrorFragment";
|
||||
import { FormsetChange } from "@saleor/hooks/useFormset";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import {
|
||||
OrderDetails_order,
|
||||
OrderDetails_order_lines
|
||||
} from "@saleor/orders/types/OrderDetails";
|
||||
import React, { CSSProperties } from "react";
|
||||
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { FormsetQuantityData, FormsetReplacementData } from "../form";
|
||||
import { getById } from "../utils";
|
||||
import CardTitle from "./CardTitle";
|
||||
import MaximalButton from "./MaximalButton";
|
||||
import ProductErrorCell from "./ProductErrorCell";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => {
|
||||
const inputPadding: CSSProperties = {
|
||||
paddingBottom: theme.spacing(2),
|
||||
paddingTop: theme.spacing(2)
|
||||
};
|
||||
|
||||
return {
|
||||
cartContent: {
|
||||
paddingBottom: 0,
|
||||
paddingTop: 0
|
||||
},
|
||||
|
||||
notice: {
|
||||
marginBottom: theme.spacing(1),
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
|
||||
quantityInnerInput: {
|
||||
...inputPadding
|
||||
},
|
||||
quantityInnerInputNoRemaining: {
|
||||
paddingRight: 0
|
||||
},
|
||||
remainingQuantity: {
|
||||
...inputPadding,
|
||||
color: theme.palette.text.secondary,
|
||||
whiteSpace: "nowrap"
|
||||
},
|
||||
setMaximalQuantityButton: {
|
||||
marginBottom: theme.spacing(1),
|
||||
marginTop: theme.spacing(2),
|
||||
padding: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
{ name: "ItemsCard" }
|
||||
);
|
||||
|
||||
const messages = defineMessages({
|
||||
improperValue: {
|
||||
defaultMessage: "Improper value",
|
||||
description: "error message"
|
||||
},
|
||||
|
||||
titleFulfilled: {
|
||||
defaultMessage: "Fulfillment - #{fulfilmentId}",
|
||||
description: "section header"
|
||||
},
|
||||
titleUnfulfilled: {
|
||||
defaultMessage: "Unfulfilled Items",
|
||||
description: "section header"
|
||||
}
|
||||
});
|
||||
|
||||
interface OrderReturnRefundLinesCardProps {
|
||||
onChangeQuantity: FormsetChange<number>;
|
||||
fulfilmentId?: string;
|
||||
canReplace?: boolean;
|
||||
errors: OrderErrorFragment[];
|
||||
lines: OrderDetails_order_lines[];
|
||||
order: OrderDetails_order;
|
||||
itemsSelections: FormsetReplacementData;
|
||||
itemsQuantities: FormsetQuantityData;
|
||||
onChangeSelected: FormsetChange<boolean>;
|
||||
onSetMaxQuantity();
|
||||
}
|
||||
|
||||
const ItemsCard: React.FC<OrderReturnRefundLinesCardProps> = ({
|
||||
lines,
|
||||
onSetMaxQuantity,
|
||||
onChangeQuantity,
|
||||
onChangeSelected,
|
||||
itemsSelections,
|
||||
itemsQuantities,
|
||||
fulfilmentId,
|
||||
order
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
||||
const handleChangeQuantity = (id: string) => (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => onChangeQuantity(id, parseInt(event.target.value, 10));
|
||||
|
||||
const fulfillment = order?.fulfillments.find(getById(fulfilmentId));
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
orderNumber={order?.number}
|
||||
lines={lines}
|
||||
fulfillmentOrder={fulfillment?.fulfillmentOrder}
|
||||
status={fulfillment?.status}
|
||||
/>
|
||||
<CardContent className={classes.cartContent}>
|
||||
<MaximalButton onClick={onSetMaxQuantity} />
|
||||
</CardContent>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<FormattedMessage
|
||||
defaultMessage="Product"
|
||||
description="table column header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell />
|
||||
<TableCell align="right">
|
||||
<FormattedMessage
|
||||
defaultMessage="Price"
|
||||
description="table column header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<FormattedMessage
|
||||
defaultMessage="Return"
|
||||
description="table column header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<FormattedMessage
|
||||
defaultMessage="Replace"
|
||||
description="table column header"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
lines,
|
||||
line => {
|
||||
const {
|
||||
quantity,
|
||||
quantityFulfilled,
|
||||
id,
|
||||
thumbnail,
|
||||
unitPrice,
|
||||
productName,
|
||||
variant
|
||||
} = line;
|
||||
const isValueError = false;
|
||||
const isRefunded = itemsQuantities.find(getById(id)).data
|
||||
.isRefunded;
|
||||
const isReplacable = !!variant && !isRefunded;
|
||||
const isReturnable = !!variant;
|
||||
const lineQuantity = fulfilmentId
|
||||
? quantity
|
||||
: quantity - quantityFulfilled;
|
||||
const isSelected = itemsSelections.find(getById(id))?.value;
|
||||
const currentQuantity = itemsQuantities.find(getById(id))?.value;
|
||||
const anyLineWithoutVariant = lines.some(
|
||||
({ variant }) => !variant
|
||||
);
|
||||
const productNameCellWidth = anyLineWithoutVariant
|
||||
? "30%"
|
||||
: "50%";
|
||||
|
||||
return (
|
||||
<TableRow key={id}>
|
||||
<TableCellAvatar
|
||||
thumbnail={thumbnail?.url}
|
||||
style={{ width: productNameCellWidth }}
|
||||
>
|
||||
{productName || <Skeleton />}
|
||||
</TableCellAvatar>
|
||||
<ProductErrorCell hasVariant={isReturnable} />
|
||||
<TableCell align="right">
|
||||
<Money
|
||||
money={{
|
||||
amount: unitPrice.gross.amount,
|
||||
currency: unitPrice.gross.currency
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{isReturnable && (
|
||||
<TextField
|
||||
type="number"
|
||||
inputProps={{
|
||||
className: classes.quantityInnerInput,
|
||||
"data-test": "quantityInput",
|
||||
"data-test-id": id,
|
||||
max: lineQuantity.toString(),
|
||||
min: 0,
|
||||
style: { textAlign: "right" }
|
||||
}}
|
||||
fullWidth
|
||||
value={currentQuantity}
|
||||
onChange={handleChangeQuantity(id)}
|
||||
InputProps={{
|
||||
endAdornment: lineQuantity && (
|
||||
<div className={classes.remainingQuantity}>
|
||||
/ {lineQuantity}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
error={isValueError}
|
||||
helperText={
|
||||
isValueError &&
|
||||
intl.formatMessage(messages.improperValue)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{isReplacable && (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={() => onChangeSelected(id, !isSelected)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemsCard;
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./ReturnItemsCard";
|
||||
export { default } from "./ReturnItemsCard";
|
233
src/orders/components/OrderReturnPage/form.tsx
Normal file
233
src/orders/components/OrderReturnPage/form.tsx
Normal file
|
@ -0,0 +1,233 @@
|
|||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import useFormset, {
|
||||
FormsetChange,
|
||||
FormsetData
|
||||
} from "@saleor/hooks/useFormset";
|
||||
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
||||
import { FulfillmentStatus } from "@saleor/types/globalTypes";
|
||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { OrderRefundAmountCalculationMode } from "../OrderRefundPage/form";
|
||||
import {
|
||||
getById,
|
||||
getLineItem,
|
||||
getOrderUnfulfilledLines,
|
||||
getParsedLineData,
|
||||
getParsedLineDataForFulfillmentStatus
|
||||
} from "./utils";
|
||||
|
||||
export interface LineItemOptions<T> {
|
||||
initialValue: T;
|
||||
isFulfillment?: boolean;
|
||||
isRefunded?: boolean;
|
||||
}
|
||||
|
||||
export interface LineItemData {
|
||||
isFulfillment: boolean;
|
||||
isRefunded: boolean;
|
||||
}
|
||||
|
||||
export type FormsetQuantityData = FormsetData<LineItemData, number>;
|
||||
export type FormsetReplacementData = FormsetData<LineItemData, boolean>;
|
||||
|
||||
export interface OrderReturnData {
|
||||
amount: number;
|
||||
refundShipmentCosts: boolean;
|
||||
amountCalculationMode: OrderRefundAmountCalculationMode;
|
||||
}
|
||||
|
||||
export interface OrderReturnHandlers {
|
||||
changeFulfiledItemsQuantity: FormsetChange<number>;
|
||||
changeUnfulfiledItemsQuantity: FormsetChange<number>;
|
||||
changeItemsToBeReplaced: FormsetChange<boolean>;
|
||||
handleSetMaximalFulfiledItemsQuantities;
|
||||
handleSetMaximalUnfulfiledItemsQuantities;
|
||||
}
|
||||
|
||||
export interface OrderReturnFormData extends OrderReturnData {
|
||||
itemsToBeReplaced: FormsetReplacementData;
|
||||
fulfiledItemsQuantities: FormsetQuantityData;
|
||||
unfulfiledItemsQuantities: FormsetQuantityData;
|
||||
}
|
||||
|
||||
export type OrderRefundSubmitData = OrderReturnFormData;
|
||||
|
||||
export interface UseOrderRefundFormResult {
|
||||
change: FormChange;
|
||||
hasChanged: boolean;
|
||||
data: OrderReturnFormData;
|
||||
handlers: OrderReturnHandlers;
|
||||
submit: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
interface OrderReturnProps {
|
||||
children: (props: UseOrderRefundFormResult) => React.ReactNode;
|
||||
order: OrderDetails_order;
|
||||
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise;
|
||||
}
|
||||
|
||||
const getOrderRefundPageFormData = (): OrderReturnData => ({
|
||||
amount: undefined,
|
||||
amountCalculationMode: OrderRefundAmountCalculationMode.AUTOMATIC,
|
||||
refundShipmentCosts: false
|
||||
});
|
||||
|
||||
function useOrderReturnForm(
|
||||
order: OrderDetails_order,
|
||||
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise
|
||||
): UseOrderRefundFormResult {
|
||||
const form = useForm(getOrderRefundPageFormData());
|
||||
const [hasChanged, setHasChanged] = useState(false);
|
||||
|
||||
const handleChange: FormChange = (event, cb) => {
|
||||
form.change(event, cb);
|
||||
};
|
||||
|
||||
const unfulfiledItemsQuantites = useFormset<LineItemData, number>(
|
||||
getOrderUnfulfilledLines(order).map(getParsedLineData({ initialValue: 0 }))
|
||||
);
|
||||
|
||||
const getItemsFulfilled = () => {
|
||||
const commonOptions = {
|
||||
initialValue: 0,
|
||||
isFulfillment: true
|
||||
};
|
||||
|
||||
const refundedFulfilmentsItems = getParsedLineDataForFulfillmentStatus(
|
||||
order,
|
||||
FulfillmentStatus.REFUNDED,
|
||||
{ ...commonOptions, isRefunded: true }
|
||||
);
|
||||
|
||||
const fulfilledFulfillmentsItems = getParsedLineDataForFulfillmentStatus(
|
||||
order,
|
||||
FulfillmentStatus.FULFILLED,
|
||||
commonOptions
|
||||
);
|
||||
|
||||
return refundedFulfilmentsItems.concat(fulfilledFulfillmentsItems);
|
||||
};
|
||||
|
||||
const fulfiledItemsQuatities = useFormset<LineItemData, number>(
|
||||
getItemsFulfilled()
|
||||
);
|
||||
|
||||
const getItemsToBeReplaced = () => {
|
||||
if (!order) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const orderLinesItems = getOrderUnfulfilledLines(order).map(
|
||||
getParsedLineData({ initialValue: false })
|
||||
);
|
||||
|
||||
const refundedFulfilmentsItems = getParsedLineDataForFulfillmentStatus(
|
||||
order,
|
||||
FulfillmentStatus.REFUNDED,
|
||||
{ initialValue: false, isFulfillment: true }
|
||||
);
|
||||
|
||||
const fulfilledFulfillmentsItems = getParsedLineDataForFulfillmentStatus(
|
||||
order,
|
||||
FulfillmentStatus.FULFILLED,
|
||||
{ initialValue: false, isFulfillment: true }
|
||||
);
|
||||
|
||||
return [
|
||||
...orderLinesItems,
|
||||
...refundedFulfilmentsItems,
|
||||
...fulfilledFulfillmentsItems
|
||||
];
|
||||
};
|
||||
|
||||
const itemsToBeReplaced = useFormset<LineItemData, boolean>(
|
||||
getItemsToBeReplaced()
|
||||
);
|
||||
|
||||
const handleSetMaximalUnfulfiledItemsQuantities = () => {
|
||||
const newQuantities: FormsetQuantityData = unfulfiledItemsQuantites.data.map(
|
||||
({ id }) => {
|
||||
const line = order.lines.find(getById(id));
|
||||
const initialValue = line.quantity - line.quantityFulfilled;
|
||||
|
||||
return getLineItem(line, { initialValue });
|
||||
}
|
||||
);
|
||||
|
||||
triggerChange();
|
||||
unfulfiledItemsQuantites.set(newQuantities);
|
||||
};
|
||||
|
||||
const handleSetMaximalFulfiledItemsQuantities = (
|
||||
fulfillmentId: string
|
||||
) => () => {
|
||||
const { lines } = order.fulfillments.find(getById(fulfillmentId));
|
||||
|
||||
const newQuantities: FormsetQuantityData = fulfiledItemsQuatities.data.map(
|
||||
item => {
|
||||
const line = lines.find(getById(item.id));
|
||||
|
||||
if (!line) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return getLineItem(line, {
|
||||
initialValue: line.quantity,
|
||||
isRefunded: item.data.isRefunded
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
triggerChange();
|
||||
fulfiledItemsQuatities.set(newQuantities);
|
||||
};
|
||||
|
||||
const data: OrderReturnFormData = {
|
||||
fulfiledItemsQuantities: fulfiledItemsQuatities.data,
|
||||
itemsToBeReplaced: itemsToBeReplaced.data,
|
||||
unfulfiledItemsQuantities: unfulfiledItemsQuantites.data,
|
||||
...form.data
|
||||
};
|
||||
|
||||
const submit = () => handleFormSubmit(data, onSubmit, setHasChanged);
|
||||
|
||||
const triggerChange = () => setHasChanged(true);
|
||||
|
||||
function handleHandlerChange<T>(callback: (id: string, value: T) => void) {
|
||||
return (id: string, value: T) => {
|
||||
triggerChange();
|
||||
callback(id, value);
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
change: handleChange,
|
||||
data,
|
||||
handlers: {
|
||||
changeFulfiledItemsQuantity: handleHandlerChange(
|
||||
fulfiledItemsQuatities.change
|
||||
),
|
||||
changeItemsToBeReplaced: handleHandlerChange(itemsToBeReplaced.change),
|
||||
changeUnfulfiledItemsQuantity: handleHandlerChange(
|
||||
unfulfiledItemsQuantites.change
|
||||
),
|
||||
handleSetMaximalFulfiledItemsQuantities,
|
||||
handleSetMaximalUnfulfiledItemsQuantities
|
||||
},
|
||||
hasChanged,
|
||||
submit
|
||||
};
|
||||
}
|
||||
|
||||
const OrderReturnForm: React.FC<OrderReturnProps> = ({
|
||||
children,
|
||||
order,
|
||||
onSubmit
|
||||
}) => {
|
||||
const props = useOrderReturnForm(order, onSubmit);
|
||||
|
||||
return <form>{children(props)}</form>;
|
||||
};
|
||||
|
||||
export default OrderReturnForm;
|
2
src/orders/components/OrderReturnPage/index.ts
Normal file
2
src/orders/components/OrderReturnPage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./OrderReturnPage";
|
||||
export { default } from "./OrderReturnPage";
|
93
src/orders/components/OrderReturnPage/utils.tsx
Normal file
93
src/orders/components/OrderReturnPage/utils.tsx
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { OrderDetailsFragment_fulfillments_lines } from "@saleor/fragments/types/OrderDetailsFragment";
|
||||
import {
|
||||
OrderDetails_order,
|
||||
OrderDetails_order_fulfillments
|
||||
} from "@saleor/orders/types/OrderDetails";
|
||||
import { FulfillmentStatus } from "@saleor/types/globalTypes";
|
||||
|
||||
import { LineItemOptions } from "./form";
|
||||
|
||||
const fulfiledStatuses = [
|
||||
FulfillmentStatus.FULFILLED,
|
||||
FulfillmentStatus.REFUNDED
|
||||
];
|
||||
|
||||
export const getOrderUnfulfilledLines = (order: OrderDetails_order) =>
|
||||
order?.lines.filter(line => line.quantityFulfilled !== line.quantity) || [];
|
||||
|
||||
export const getFulfilledFulfillment = fulfillment =>
|
||||
fulfiledStatuses.includes(fulfillment.status);
|
||||
|
||||
export const getFulfilledFulfillemnts = (order?: OrderDetails_order) =>
|
||||
order?.fulfillments.filter(getFulfilledFulfillment) || [];
|
||||
|
||||
export const getUnfulfilledLines = (order?: OrderDetails_order) =>
|
||||
order?.lines.filter(line => line.quantity !== line.quantityFulfilled) || [];
|
||||
|
||||
export const getAllOrderFulfilledLines = (order?: OrderDetails_order) =>
|
||||
getFulfilledFulfillemnts(order).reduce(
|
||||
(result, { lines }) => [...result, ...getParsedFulfiledLines(lines)],
|
||||
[]
|
||||
);
|
||||
|
||||
export function getLineItem<T>(
|
||||
{ id }: { id: string },
|
||||
{
|
||||
initialValue,
|
||||
isFulfillment = false,
|
||||
isRefunded = false
|
||||
}: LineItemOptions<T>
|
||||
) {
|
||||
return {
|
||||
data: { isFulfillment, isRefunded },
|
||||
id,
|
||||
label: null,
|
||||
value: initialValue
|
||||
};
|
||||
}
|
||||
|
||||
export function getParsedLineData<T>({
|
||||
initialValue,
|
||||
isFulfillment = false,
|
||||
isRefunded = false
|
||||
}: LineItemOptions<T>) {
|
||||
return (item: { id: string }) =>
|
||||
getLineItem(item, { initialValue, isFulfillment, isRefunded });
|
||||
}
|
||||
|
||||
export function getParsedLineDataForFulfillmentStatus<T>(
|
||||
order: OrderDetails_order,
|
||||
fulfillmentStatus: FulfillmentStatus,
|
||||
lineItemOptions: LineItemOptions<T>
|
||||
) {
|
||||
return getParsedLinesOfFulfillments(
|
||||
getFulfillmentsWithStatus(order, fulfillmentStatus)
|
||||
).map(getParsedLineData(lineItemOptions));
|
||||
}
|
||||
|
||||
export const getFulfillmentsWithStatus = (
|
||||
order: OrderDetails_order,
|
||||
fulfillmentStatus: FulfillmentStatus
|
||||
) =>
|
||||
order?.fulfillments.filter(({ status }) => status === fulfillmentStatus) ||
|
||||
[];
|
||||
|
||||
export const getParsedLinesOfFulfillments = (
|
||||
fullfillments: OrderDetails_order_fulfillments[]
|
||||
) =>
|
||||
fullfillments.reduce(
|
||||
(result, { lines }) => [...result, ...getParsedFulfiledLines(lines)],
|
||||
[]
|
||||
);
|
||||
|
||||
export const getParsedFulfiledLines = (
|
||||
lines: OrderDetailsFragment_fulfillments_lines[]
|
||||
) =>
|
||||
lines.map(({ id, quantity, orderLine }) => ({
|
||||
...orderLine,
|
||||
id,
|
||||
quantity
|
||||
}));
|
||||
|
||||
export const getById = (idToCompare: string) => (obj: { id: string }) =>
|
||||
obj.id === idToCompare;
|
|
@ -1,192 +0,0 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardActions from "@material-ui/core/CardActions";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Money from "@saleor/components/Money";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { maybe } from "../../../misc";
|
||||
import { OrderDetails_order_lines } from "../../types/OrderDetails";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
{
|
||||
clickableRow: {
|
||||
cursor: "pointer"
|
||||
},
|
||||
colName: {
|
||||
paddingLeft: 0,
|
||||
width: "auto"
|
||||
},
|
||||
colNameLabel: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
colPrice: {
|
||||
textAlign: "right",
|
||||
width: 120
|
||||
},
|
||||
colQuantity: {
|
||||
textAlign: "center",
|
||||
width: 120
|
||||
},
|
||||
colSku: {
|
||||
textAlign: "right",
|
||||
textOverflow: "ellipsis",
|
||||
width: 120
|
||||
},
|
||||
colTotal: {
|
||||
textAlign: "right",
|
||||
width: 120
|
||||
},
|
||||
statusBar: {
|
||||
paddingTop: 0
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
},
|
||||
{ name: "OrderUnfulfilledItems" }
|
||||
);
|
||||
|
||||
interface OrderUnfulfilledItemsProps {
|
||||
canFulfill: boolean;
|
||||
lines: OrderDetails_order_lines[];
|
||||
onFulfill: () => void;
|
||||
}
|
||||
|
||||
const OrderUnfulfilledItems: React.FC<OrderUnfulfilledItemsProps> = props => {
|
||||
const { canFulfill, lines, onFulfill } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={
|
||||
<StatusLabel
|
||||
label={intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Unfulfilled ({quantity})",
|
||||
description: "section header"
|
||||
},
|
||||
{
|
||||
quantity: lines
|
||||
.map(line => line.quantity - line.quantityFulfilled)
|
||||
.reduce((prev, curr) => prev + curr, 0)
|
||||
}
|
||||
)}
|
||||
status="error"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ResponsiveTable className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameLabel}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Product"
|
||||
description="product name"
|
||||
/>
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSku}>
|
||||
<FormattedMessage
|
||||
defaultMessage="SKU"
|
||||
description="ordered product sku"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Quantity"
|
||||
description="ordered products"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Price"
|
||||
description="product unit price"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTotal}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Total"
|
||||
description="order line total price"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{lines.map(line => (
|
||||
<TableRow
|
||||
className={!!line ? classes.clickableRow : undefined}
|
||||
hover={!!line}
|
||||
key={maybe(() => line.id)}
|
||||
>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => line.thumbnail.url)}
|
||||
>
|
||||
{maybe(() => line.productName) || <Skeleton />}
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colSku}>
|
||||
{line?.productSku || <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
{maybe(() => line.quantity - line.quantityFulfilled) || (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
{maybe(() => line.unitPrice.gross) ? (
|
||||
<Money money={line.unitPrice.gross} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTotal}>
|
||||
{maybe(
|
||||
() =>
|
||||
(line.quantity - line.quantityFulfilled) *
|
||||
line.unitPrice.gross.amount
|
||||
) ? (
|
||||
<Money
|
||||
money={{
|
||||
amount:
|
||||
(line.quantity - line.quantityFulfilled) *
|
||||
line.unitPrice.gross.amount,
|
||||
currency: line.unitPrice.gross.currency
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
{canFulfill && (
|
||||
<CardActions>
|
||||
<Button variant="text" color="primary" onClick={onFulfill}>
|
||||
<FormattedMessage defaultMessage="Fulfill" description="button" />
|
||||
</Button>
|
||||
</CardActions>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
OrderUnfulfilledItems.displayName = "OrderUnfulfilledItems";
|
||||
export default OrderUnfulfilledItems;
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from "./OrderUnfulfilledItems";
|
||||
export * from "./OrderUnfulfilledItems";
|
|
@ -0,0 +1,64 @@
|
|||
import { makeStyles, TableBody } from "@material-ui/core";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardActions from "@material-ui/core/CardActions";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { OrderDetails_order_lines } from "../../types/OrderDetails";
|
||||
import TableHeader from "../OrderProductsCardElements/OrderProductsCardHeader";
|
||||
import TableLine from "../OrderProductsCardElements/OrderProductsTableRow";
|
||||
import CardTitle from "../OrderReturnPage/OrderReturnRefundItemsCard/CardTitle";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
() => ({
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
}),
|
||||
{ name: "OrderUnfulfilledItems" }
|
||||
);
|
||||
|
||||
interface OrderUnfulfilledProductsCardProps {
|
||||
canFulfill: boolean;
|
||||
lines: OrderDetails_order_lines[];
|
||||
onFulfill: () => void;
|
||||
}
|
||||
|
||||
const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps> = props => {
|
||||
const { canFulfill, lines, onFulfill } = props;
|
||||
const classes = useStyles({});
|
||||
|
||||
if (!lines.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardTitle withStatus status="unfulfilled" />
|
||||
<ResponsiveTable className={classes.table}>
|
||||
<TableHeader />
|
||||
<TableBody>
|
||||
{renderCollection(lines, line => (
|
||||
<TableLine isOrderLine line={line} />
|
||||
))}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
{canFulfill && (
|
||||
<CardActions>
|
||||
<Button variant="text" color="primary" onClick={onFulfill}>
|
||||
<FormattedMessage defaultMessage="Fulfill" description="button" />
|
||||
</Button>
|
||||
</CardActions>
|
||||
)}
|
||||
</Card>
|
||||
<CardSpacer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderUnfulfilledProductsCard;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderUnfulfilledProductsCard";
|
||||
export * from "./OrderUnfulfilledProductsCard";
|
|
@ -834,13 +834,16 @@ export const order = (placeholder: string): OrderDetails_order => ({
|
|||
lines: [],
|
||||
message: null,
|
||||
quantity: 1,
|
||||
relatedOrder: null,
|
||||
shippingCostsIncluded: false,
|
||||
transactionReference: "123",
|
||||
type: OrderEventsEnum.FULFILLMENT_FULFILLED_ITEMS,
|
||||
user: {
|
||||
__typename: "User",
|
||||
email: "admin@example.com",
|
||||
id: "QWRkcmVzczoxNQ=="
|
||||
firstName: "John",
|
||||
id: "QWRkcmVzczoxNQ==",
|
||||
lastName: "Doe"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -875,13 +878,16 @@ export const order = (placeholder: string): OrderDetails_order => ({
|
|||
],
|
||||
message: null,
|
||||
quantity: 1,
|
||||
relatedOrder: null,
|
||||
shippingCostsIncluded: true,
|
||||
transactionReference: "123",
|
||||
type: OrderEventsEnum.FULFILLMENT_REFUNDED,
|
||||
user: {
|
||||
__typename: "User",
|
||||
email: "admin@example.com",
|
||||
id: "QWRkcmVzczoxNQ=="
|
||||
firstName: "Jane",
|
||||
id: "QWRkcmVzczoxNQ==",
|
||||
lastName: "Doe"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -895,6 +901,7 @@ export const order = (placeholder: string): OrderDetails_order => ({
|
|||
lines: [],
|
||||
message: "This is note",
|
||||
quantity: null,
|
||||
relatedOrder: null,
|
||||
shippingCostsIncluded: false,
|
||||
transactionReference: "124",
|
||||
type: OrderEventsEnum.NOTE_ADDED,
|
||||
|
@ -911,6 +918,7 @@ export const order = (placeholder: string): OrderDetails_order => ({
|
|||
lines: [],
|
||||
message: "This is note",
|
||||
quantity: null,
|
||||
relatedOrder: null,
|
||||
shippingCostsIncluded: false,
|
||||
transactionReference: "125",
|
||||
type: OrderEventsEnum.NOTE_ADDED,
|
||||
|
@ -927,6 +935,7 @@ export const order = (placeholder: string): OrderDetails_order => ({
|
|||
lines: [],
|
||||
message: "Note from external service",
|
||||
quantity: null,
|
||||
relatedOrder: null,
|
||||
shippingCostsIncluded: false,
|
||||
transactionReference: "126",
|
||||
type: OrderEventsEnum.EXTERNAL_SERVICE_NOTIFICATION,
|
||||
|
@ -943,6 +952,7 @@ export const order = (placeholder: string): OrderDetails_order => ({
|
|||
lines: [],
|
||||
message: null,
|
||||
quantity: null,
|
||||
relatedOrder: null,
|
||||
shippingCostsIncluded: false,
|
||||
transactionReference: "127",
|
||||
type: OrderEventsEnum.EMAIL_SENT,
|
||||
|
@ -959,6 +969,7 @@ export const order = (placeholder: string): OrderDetails_order => ({
|
|||
lines: [],
|
||||
message: null,
|
||||
quantity: null,
|
||||
relatedOrder: null,
|
||||
shippingCostsIncluded: false,
|
||||
transactionReference: "128",
|
||||
type: OrderEventsEnum.EMAIL_SENT,
|
||||
|
@ -975,6 +986,7 @@ export const order = (placeholder: string): OrderDetails_order => ({
|
|||
lines: [],
|
||||
message: null,
|
||||
quantity: null,
|
||||
relatedOrder: null,
|
||||
shippingCostsIncluded: false,
|
||||
transactionReference: "129",
|
||||
type: OrderEventsEnum.PAYMENT_AUTHORIZED,
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
OrderListUrlSortField,
|
||||
orderPath,
|
||||
orderRefundPath,
|
||||
orderReturnPath,
|
||||
orderSettingsPath,
|
||||
OrderUrlQueryParams
|
||||
} from "./urls";
|
||||
|
@ -24,6 +25,7 @@ import OrderDraftListComponent from "./views/OrderDraftList";
|
|||
import OrderFulfillComponent from "./views/OrderFulfill";
|
||||
import OrderListComponent from "./views/OrderList";
|
||||
import OrderRefundComponent from "./views/OrderRefund";
|
||||
import OrderReturnComponent from "./views/OrderReturn";
|
||||
import OrderSettings from "./views/OrderSettings";
|
||||
|
||||
const OrderList: React.FC<RouteComponentProps<any>> = ({ location }) => {
|
||||
|
@ -71,6 +73,10 @@ const OrderRefund: React.FC<RouteComponentProps<any>> = ({ match }) => (
|
|||
<OrderRefundComponent orderId={decodeURIComponent(match.params.id)} />
|
||||
);
|
||||
|
||||
const OrderReturn: React.FC<RouteComponentProps<any>> = ({ match }) => (
|
||||
<OrderReturnComponent orderId={decodeURIComponent(match.params.id)} />
|
||||
);
|
||||
|
||||
const Component = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
|
@ -82,6 +88,7 @@ const Component = () => {
|
|||
<Route exact path={orderDraftListPath} component={OrderDraftList} />
|
||||
<Route exact path={orderListPath} component={OrderList} />
|
||||
<Route path={orderFulfillPath(":id")} component={OrderFulfill} />
|
||||
<Route path={orderReturnPath(":id")} component={OrderReturn} />
|
||||
<Route path={orderRefundPath(":id")} component={OrderRefund} />
|
||||
<Route path={orderPath(":id")} component={OrderDetails} />
|
||||
</Switch>
|
||||
|
|
|
@ -14,6 +14,10 @@ import makeMutation from "@saleor/hooks/makeMutation";
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedMutation } from "../mutations";
|
||||
import {
|
||||
FulfillmentReturnProducts,
|
||||
FulfillmentReturnProductsVariables
|
||||
} from "./types/FulfillmentReturnProducts";
|
||||
import { FulfillOrder, FulfillOrderVariables } from "./types/FulfillOrder";
|
||||
import {
|
||||
InvoiceEmailSend,
|
||||
|
@ -172,6 +176,32 @@ const orderDraftFinalizeMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const orderReturnCreateMutation = gql`
|
||||
${orderErrorFragment}
|
||||
mutation FulfillmentReturnProducts(
|
||||
$id: ID!
|
||||
$input: OrderReturnProductsInput!
|
||||
) {
|
||||
orderFulfillmentReturnProducts(input: $input, order: $id) {
|
||||
errors: orderErrors {
|
||||
...OrderErrorFragment
|
||||
}
|
||||
order {
|
||||
id
|
||||
}
|
||||
replaceOrder {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useOrderReturnCreateMutation = makeMutation<
|
||||
FulfillmentReturnProducts,
|
||||
FulfillmentReturnProductsVariables
|
||||
>(orderReturnCreateMutation);
|
||||
|
||||
export const TypedOrderDraftFinalizeMutation = TypedMutation<
|
||||
OrderDraftFinalize,
|
||||
OrderDraftFinalizeVariables
|
||||
|
@ -191,6 +221,7 @@ const orderRefundMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useOrderRefundMutation = makeMutation<
|
||||
OrderRefund,
|
||||
OrderRefundVariables
|
||||
|
|
|
@ -157,6 +157,10 @@ export const TypedOrderDetailsQuery = TypedQuery<
|
|||
OrderDetailsVariables
|
||||
>(orderDetailsQuery);
|
||||
|
||||
export const useOrderQuery = makeQuery<OrderDetails, OrderDetailsVariables>(
|
||||
orderDetailsQuery
|
||||
);
|
||||
|
||||
export const searchOrderVariant = gql`
|
||||
query SearchOrderVariant($first: Int!, $query: String!, $after: String) {
|
||||
search: products(first: $first, after: $after, filter: { search: $query }) {
|
||||
|
|
|
@ -50,10 +50,18 @@ export interface FulfillOrder_orderFulfill_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface FulfillOrder_orderFulfill_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface FulfillOrder_orderFulfill_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface FulfillOrder_orderFulfill_order_events_lines_orderLine {
|
||||
|
@ -78,6 +86,7 @@ export interface FulfillOrder_orderFulfill_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: FulfillOrder_orderFulfill_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
41
src/orders/types/FulfillmentReturnProducts.ts
Normal file
41
src/orders/types/FulfillmentReturnProducts.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { OrderReturnProductsInput, OrderErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: FulfillmentReturnProducts
|
||||
// ====================================================
|
||||
|
||||
export interface FulfillmentReturnProducts_orderFulfillmentReturnProducts_errors {
|
||||
__typename: "OrderError";
|
||||
code: OrderErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface FulfillmentReturnProducts_orderFulfillmentReturnProducts_order {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface FulfillmentReturnProducts_orderFulfillmentReturnProducts_replaceOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface FulfillmentReturnProducts_orderFulfillmentReturnProducts {
|
||||
__typename: "FulfillmentReturnProducts";
|
||||
errors: FulfillmentReturnProducts_orderFulfillmentReturnProducts_errors[];
|
||||
order: FulfillmentReturnProducts_orderFulfillmentReturnProducts_order | null;
|
||||
replaceOrder: FulfillmentReturnProducts_orderFulfillmentReturnProducts_replaceOrder | null;
|
||||
}
|
||||
|
||||
export interface FulfillmentReturnProducts {
|
||||
orderFulfillmentReturnProducts: FulfillmentReturnProducts_orderFulfillmentReturnProducts | null;
|
||||
}
|
||||
|
||||
export interface FulfillmentReturnProductsVariables {
|
||||
id: string;
|
||||
input: OrderReturnProductsInput;
|
||||
}
|
|
@ -14,10 +14,18 @@ export interface OrderAddNote_orderAddNote_errors {
|
|||
field: string | null;
|
||||
}
|
||||
|
||||
export interface OrderAddNote_orderAddNote_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderAddNote_orderAddNote_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderAddNote_orderAddNote_order_events_lines_orderLine {
|
||||
|
@ -42,6 +50,7 @@ export interface OrderAddNote_orderAddNote_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderAddNote_orderAddNote_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderCancel_orderCancel_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderCancel_orderCancel_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderCancel_orderCancel_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderCancel_orderCancel_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderCancel_orderCancel_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderCancel_orderCancel_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderCapture_orderCapture_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderCapture_orderCapture_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderCapture_orderCapture_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderCapture_orderCapture_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderCapture_orderCapture_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderCapture_orderCapture_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderConfirm_orderConfirm_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderConfirm_orderConfirm_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderConfirm_orderConfirm_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -42,10 +42,18 @@ export interface OrderDetails_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderDetails_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderDetails_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderDetails_order_events_lines_orderLine {
|
||||
|
@ -70,6 +78,7 @@ export interface OrderDetails_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderDetails_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderDraftCancel_draftOrderDelete_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderDraftCancel_draftOrderDelete_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderDraftCancel_draftOrderDelete_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderDraftCancel_draftOrderDelete_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderDraftCancel_draftOrderDelete_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderDraftCancel_draftOrderDelete_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderDraftFinalize_draftOrderComplete_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderDraftFinalize_draftOrderComplete_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderDraftFinalize_draftOrderComplete_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderDraftFinalize_draftOrderComplete_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderDraftFinalize_draftOrderComplete_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderDraftFinalize_draftOrderComplete_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderDraftUpdate_draftOrderUpdate_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderDraftUpdate_draftOrderUpdate_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderDraftUpdate_draftOrderUpdate_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderDraftUpdate_draftOrderUpdate_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderDraftUpdate_draftOrderUpdate_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderDraftUpdate_draftOrderUpdate_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderFulfillmentCancel_orderFulfillmentCancel_order_billingAddr
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentCancel_orderFulfillmentCancel_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentCancel_orderFulfillmentCancel_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentCancel_orderFulfillmentCancel_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderFulfillmentCancel_orderFulfillmentCancel_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderFulfillmentCancel_orderFulfillmentCancel_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -113,10 +113,18 @@ export interface OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_o
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_order_events_lines_orderLine {
|
||||
|
@ -141,6 +149,7 @@ export interface OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_o
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_o
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_o
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderLineDelete_draftOrderLineDelete_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderLineDelete_draftOrderLineDelete_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderLineDelete_draftOrderLineDelete_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderLineDelete_draftOrderLineDelete_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderLineDelete_draftOrderLineDelete_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderLineDelete_draftOrderLineDelete_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderLineUpdate_draftOrderLineUpdate_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderLineUpdate_draftOrderLineUpdate_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderLineUpdate_draftOrderLineUpdate_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderLineUpdate_draftOrderLineUpdate_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderLineUpdate_draftOrderLineUpdate_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderLineUpdate_draftOrderLineUpdate_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderLinesAdd_draftOrderLinesCreate_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderLinesAdd_draftOrderLinesCreate_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderLinesAdd_draftOrderLinesCreate_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderLinesAdd_draftOrderLinesCreate_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderLinesAdd_draftOrderLinesCreate_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderLinesAdd_draftOrderLinesCreate_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderMarkAsPaid_orderMarkAsPaid_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderMarkAsPaid_orderMarkAsPaid_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderMarkAsPaid_orderMarkAsPaid_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderMarkAsPaid_orderMarkAsPaid_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderMarkAsPaid_orderMarkAsPaid_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderMarkAsPaid_orderMarkAsPaid_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderRefund_orderRefund_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderRefund_orderRefund_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderRefund_orderRefund_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderRefund_orderRefund_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderRefund_orderRefund_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderRefund_orderRefund_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderUpdate_orderUpdate_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderUpdate_orderUpdate_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderUpdate_orderUpdate_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderUpdate_orderUpdate_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderUpdate_orderUpdate_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderUpdate_orderUpdate_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -48,10 +48,18 @@ export interface OrderVoid_orderVoid_order_billingAddress {
|
|||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderVoid_orderVoid_order_events_relatedOrder {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
number: string | null;
|
||||
}
|
||||
|
||||
export interface OrderVoid_orderVoid_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface OrderVoid_orderVoid_order_events_lines_orderLine {
|
||||
|
@ -76,6 +84,7 @@ export interface OrderVoid_orderVoid_order_events {
|
|||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
relatedOrder: OrderVoid_orderVoid_order_events_relatedOrder | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
transactionReference: string | null;
|
||||
|
|
|
@ -99,6 +99,7 @@ export const orderDraftListUrl = (
|
|||
};
|
||||
|
||||
export const orderPath = (id: string) => urlJoin(orderSectionUrl, id);
|
||||
|
||||
export type OrderUrlDialog =
|
||||
| "add-order-line"
|
||||
| "cancel"
|
||||
|
@ -112,17 +113,26 @@ export type OrderUrlDialog =
|
|||
| "mark-paid"
|
||||
| "void"
|
||||
| "invoice-send";
|
||||
|
||||
export type OrderUrlQueryParams = Dialog<OrderUrlDialog> & SingleAction;
|
||||
|
||||
export const orderUrl = (id: string, params?: OrderUrlQueryParams) =>
|
||||
orderPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
|
||||
export const orderFulfillPath = (id: string) =>
|
||||
urlJoin(orderPath(id), "fulfill");
|
||||
|
||||
export const orderReturnPath = (id: string) => urlJoin(orderPath(id), "return");
|
||||
|
||||
export const orderFulfillUrl = (id: string) =>
|
||||
orderFulfillPath(encodeURIComponent(id));
|
||||
|
||||
export const orderSettingsPath = urlJoin(orderSectionUrl, "settings");
|
||||
|
||||
export const orderRefundPath = (id: string) => urlJoin(orderPath(id), "refund");
|
||||
|
||||
export const orderRefundUrl = (id: string) =>
|
||||
orderRefundPath(encodeURIComponent(id));
|
||||
|
||||
export const orderReturnUrl = (id: string) =>
|
||||
orderReturnPath(encodeURIComponent(id));
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
/* eslint-disable sort-keys */
|
||||
import { FormsetData } from "@saleor/hooks/useFormset";
|
||||
import { FulfillmentStatus } from "@saleor/types/globalTypes";
|
||||
import {
|
||||
FulfillmentStatus,
|
||||
OrderStatus,
|
||||
PaymentChargeStatusEnum
|
||||
} from "@saleor/types/globalTypes";
|
||||
|
||||
import { OrderDetails_order_fulfillments_lines } from "../types/OrderDetails";
|
||||
import { LineItemData } from "../components/OrderReturnPage/form";
|
||||
import {
|
||||
OrderDetails_order,
|
||||
OrderDetails_order_fulfillments_lines,
|
||||
OrderDetails_order_lines
|
||||
} from "../types/OrderDetails";
|
||||
import {
|
||||
OrderRefundData_order_fulfillments,
|
||||
OrderRefundData_order_lines
|
||||
|
@ -11,10 +20,53 @@ import {
|
|||
getAllFulfillmentLinesPriceSum,
|
||||
getPreviouslyRefundedPrice,
|
||||
getRefundedLinesPriceSum,
|
||||
getReplacedProductsAmount,
|
||||
getReturnSelectedProductsAmount,
|
||||
mergeRepeatedOrderLines,
|
||||
OrderWithTotalAndTotalCaptured
|
||||
} from "./data";
|
||||
|
||||
const orderBase: OrderDetails_order = {
|
||||
__typename: "Order",
|
||||
actions: [],
|
||||
availableShippingMethods: [],
|
||||
canFinalize: true,
|
||||
channel: null,
|
||||
billingAddress: {
|
||||
__typename: "Address",
|
||||
city: "Port Danielshire",
|
||||
cityArea: "",
|
||||
companyName: "",
|
||||
country: {
|
||||
__typename: "CountryDisplay",
|
||||
code: "SE",
|
||||
country: "Szwecja"
|
||||
},
|
||||
countryArea: "",
|
||||
firstName: "Elizabeth",
|
||||
id: "QWRkcmVzczoy",
|
||||
lastName: "Vaughn",
|
||||
phone: "",
|
||||
postalCode: "52203",
|
||||
streetAddress1: "419 Ruiz Orchard Apt. 199",
|
||||
streetAddress2: ""
|
||||
},
|
||||
created: "2018-09-11T09:37:30.124154+00:00",
|
||||
id: "T3JkZXI6MTk=",
|
||||
number: "19",
|
||||
paymentStatus: PaymentChargeStatusEnum.FULLY_CHARGED,
|
||||
status: OrderStatus.FULFILLED,
|
||||
// @ts-ignore
|
||||
total: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 1215.89,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe("Get previously refunded price", () => {
|
||||
it("is able to calculate refunded price from order", () => {
|
||||
const order: OrderWithTotalAndTotalCaptured = {
|
||||
|
@ -397,6 +449,762 @@ describe("Get get all fulfillment lines price sum", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("Get the total value of all replaced products", () => {
|
||||
it("sums up correctly", () => {
|
||||
const unfulfilledLines: OrderDetails_order_lines[] = [
|
||||
{
|
||||
id: "1",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 2,
|
||||
quantityFulfilled: 2,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 10,
|
||||
quantityFulfilled: 2,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
isShippingRequired: true,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
productSku: "29810068",
|
||||
quantity: 6,
|
||||
quantityFulfilled: 1,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 2.5,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 2.5,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleordemoproduct_cl_boot06_1-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
}
|
||||
];
|
||||
|
||||
const fulfilledLines: OrderDetails_order_fulfillments_lines[] = [
|
||||
{
|
||||
id: "4",
|
||||
quantity: 1,
|
||||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 20,
|
||||
quantityFulfilled: 6,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
__typename: "FulfillmentLine"
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
quantity: 1,
|
||||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 25,
|
||||
quantityFulfilled: 8,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
__typename: "FulfillmentLine"
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
quantity: 1,
|
||||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ3",
|
||||
isShippingRequired: true,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
productSku: "29810068",
|
||||
quantity: 10,
|
||||
quantityFulfilled: 3,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 2.5,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 2.5,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleordemoproduct_cl_boot06_1-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
__typename: "FulfillmentLine"
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
quantity: 1,
|
||||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 20,
|
||||
quantityFulfilled: 6,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
__typename: "FulfillmentLine"
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
quantity: 1,
|
||||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 25,
|
||||
quantityFulfilled: 8,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
__typename: "FulfillmentLine"
|
||||
}
|
||||
];
|
||||
|
||||
const unfulfiledItemsQuantities: FormsetData<LineItemData, number> = [
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "1",
|
||||
label: null,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "2",
|
||||
label: null,
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "3",
|
||||
label: null,
|
||||
value: 1
|
||||
}
|
||||
];
|
||||
|
||||
const fulfiledItemsQuantities: FormsetData<LineItemData, number> = [
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "4",
|
||||
label: null,
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "5",
|
||||
label: null,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "6",
|
||||
label: null,
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: true },
|
||||
id: "7",
|
||||
label: null,
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: true },
|
||||
id: "8",
|
||||
label: null,
|
||||
value: 3
|
||||
}
|
||||
];
|
||||
|
||||
const itemsToBeReplaced: FormsetData<LineItemData, boolean> = [
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "1",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "2",
|
||||
label: null,
|
||||
value: false
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "3",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "4",
|
||||
label: null,
|
||||
value: false
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "5",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "6",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: true },
|
||||
id: "7",
|
||||
label: null,
|
||||
value: false
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: true },
|
||||
id: "8",
|
||||
label: null,
|
||||
value: true
|
||||
}
|
||||
];
|
||||
|
||||
const totalValue = getReplacedProductsAmount(
|
||||
{
|
||||
...orderBase,
|
||||
lines: unfulfilledLines,
|
||||
fulfillments: [
|
||||
{
|
||||
id: "#1",
|
||||
fulfillmentOrder: 1,
|
||||
status: FulfillmentStatus.FULFILLED,
|
||||
warehouse: null,
|
||||
trackingNumber: "",
|
||||
lines: fulfilledLines,
|
||||
__typename: "Fulfillment"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
itemsToBeReplaced,
|
||||
unfulfiledItemsQuantities,
|
||||
fulfiledItemsQuantities
|
||||
}
|
||||
);
|
||||
|
||||
expect(totalValue).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Get the total value of all selected products", () => {
|
||||
it("sums up correctly", () => {
|
||||
const unfulfilledLines: OrderDetails_order_lines[] = [
|
||||
{
|
||||
id: "1",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 2,
|
||||
quantityFulfilled: 2,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 10,
|
||||
quantityFulfilled: 2,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
isShippingRequired: true,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
productSku: "29810068",
|
||||
quantity: 6,
|
||||
quantityFulfilled: 1,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 2.5,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 2.5,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleordemoproduct_cl_boot06_1-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
}
|
||||
];
|
||||
|
||||
const fulfilledLines: OrderDetails_order_fulfillments_lines[] = [
|
||||
{
|
||||
id: "4",
|
||||
quantity: 1,
|
||||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 20,
|
||||
quantityFulfilled: 6,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
__typename: "FulfillmentLine"
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
quantity: 1,
|
||||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ1",
|
||||
isShippingRequired: false,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzE3",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "Lake Tunes",
|
||||
productSku: "lake-tunes-mp3",
|
||||
quantity: 25,
|
||||
quantityFulfilled: 8,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 9.99,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleor-digital-03_2-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
__typename: "FulfillmentLine"
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
quantity: 1,
|
||||
orderLine: {
|
||||
id: "T3JkZXJMaW5lOjQ3",
|
||||
isShippingRequired: true,
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6Mjg2",
|
||||
quantityAvailable: 50,
|
||||
__typename: "ProductVariant"
|
||||
},
|
||||
productName: "T-shirt",
|
||||
productSku: "29810068",
|
||||
quantity: 10,
|
||||
quantityFulfilled: 3,
|
||||
unitPrice: {
|
||||
gross: {
|
||||
amount: 2.5,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
net: {
|
||||
amount: 2.5,
|
||||
currency: "USD",
|
||||
__typename: "Money"
|
||||
},
|
||||
__typename: "TaxedMoney"
|
||||
},
|
||||
thumbnail: {
|
||||
url:
|
||||
"http://localhost:8000/media/__sized__/products/saleordemoproduct_cl_boot06_1-thumbnail-255x255.png",
|
||||
__typename: "Image"
|
||||
},
|
||||
__typename: "OrderLine"
|
||||
},
|
||||
__typename: "FulfillmentLine"
|
||||
}
|
||||
];
|
||||
|
||||
const unfulfiledItemsQuantities: FormsetData<null, number> = [
|
||||
{
|
||||
data: null,
|
||||
id: "1",
|
||||
label: null,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
id: "2",
|
||||
label: null,
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
id: "3",
|
||||
label: null,
|
||||
value: 1
|
||||
}
|
||||
];
|
||||
|
||||
const fulfiledItemsQuantities: FormsetData<null, number> = [
|
||||
{
|
||||
data: null,
|
||||
id: "4",
|
||||
label: null,
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
id: "5",
|
||||
label: null,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
id: "6",
|
||||
label: null,
|
||||
value: 3
|
||||
}
|
||||
];
|
||||
|
||||
const itemsToBeReplaced: FormsetData<LineItemData, boolean> = [
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "1",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "2",
|
||||
label: null,
|
||||
value: false
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: false, isRefunded: false },
|
||||
id: "3",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "4",
|
||||
label: null,
|
||||
value: false
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "5",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: false },
|
||||
id: "6",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: true },
|
||||
id: "7",
|
||||
label: null,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
data: { isFulfillment: true, isRefunded: true },
|
||||
id: "8",
|
||||
label: null,
|
||||
value: true
|
||||
}
|
||||
];
|
||||
|
||||
const totalValue = getReturnSelectedProductsAmount(
|
||||
{
|
||||
...orderBase,
|
||||
lines: unfulfilledLines,
|
||||
fulfillments: [
|
||||
{
|
||||
id: "#1",
|
||||
fulfillmentOrder: 1,
|
||||
status: FulfillmentStatus.FULFILLED,
|
||||
warehouse: null,
|
||||
trackingNumber: "",
|
||||
lines: fulfilledLines,
|
||||
__typename: "Fulfillment"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
itemsToBeReplaced,
|
||||
unfulfiledItemsQuantities,
|
||||
fulfiledItemsQuantities
|
||||
}
|
||||
);
|
||||
|
||||
expect(totalValue).toBe(59.94);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Merge repeated order lines of fulfillment lines", () => {
|
||||
it("is able to merge repeated order lines and sum their quantities", () => {
|
||||
const lines: OrderDetails_order_fulfillments_lines[] = [
|
||||
|
|
|
@ -1,7 +1,19 @@
|
|||
import { IMoney, subtractMoney } from "@saleor/components/Money";
|
||||
import { FormsetData } from "@saleor/hooks/useFormset";
|
||||
|
||||
import { OrderDetails_order_fulfillments_lines } from "../types/OrderDetails";
|
||||
import {
|
||||
LineItemData,
|
||||
OrderReturnFormData
|
||||
} from "../components/OrderReturnPage/form";
|
||||
import {
|
||||
getAllOrderFulfilledLines,
|
||||
getById
|
||||
} from "../components/OrderReturnPage/utils";
|
||||
import {
|
||||
OrderDetails_order,
|
||||
OrderDetails_order_fulfillments_lines,
|
||||
OrderDetails_order_lines
|
||||
} from "../types/OrderDetails";
|
||||
import {
|
||||
OrderRefundData_order,
|
||||
OrderRefundData_order_fulfillments,
|
||||
|
@ -23,9 +35,130 @@ export function getPreviouslyRefundedPrice(
|
|||
);
|
||||
}
|
||||
|
||||
const getItemPriceAndQuantity = ({
|
||||
orderLines,
|
||||
itemsQuantities,
|
||||
id
|
||||
}: {
|
||||
orderLines: OrderDetails_order_lines[];
|
||||
itemsQuantities: FormsetData<LineItemData, number>;
|
||||
id: string;
|
||||
}) => {
|
||||
const { unitPrice } = orderLines.find(getById(id));
|
||||
const selectedQuantity = itemsQuantities.find(getById(id))?.value;
|
||||
|
||||
return { selectedQuantity, unitPrice };
|
||||
};
|
||||
|
||||
const selectItemPriceAndQuantity = (
|
||||
order: OrderDetails_order,
|
||||
{
|
||||
fulfiledItemsQuantities,
|
||||
unfulfiledItemsQuantities
|
||||
}: Partial<OrderReturnFormData>,
|
||||
id: string,
|
||||
isFulfillment: boolean
|
||||
) =>
|
||||
isFulfillment
|
||||
? getItemPriceAndQuantity({
|
||||
id,
|
||||
itemsQuantities: fulfiledItemsQuantities,
|
||||
orderLines: getAllOrderFulfilledLines(order)
|
||||
})
|
||||
: getItemPriceAndQuantity({
|
||||
id,
|
||||
itemsQuantities: unfulfiledItemsQuantities,
|
||||
orderLines: order.lines
|
||||
});
|
||||
|
||||
export const getReplacedProductsAmount = (
|
||||
order: OrderDetails_order,
|
||||
{
|
||||
itemsToBeReplaced,
|
||||
unfulfiledItemsQuantities,
|
||||
fulfiledItemsQuantities
|
||||
}: Partial<OrderReturnFormData>
|
||||
) => {
|
||||
if (!order || !itemsToBeReplaced.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return itemsToBeReplaced.reduce(
|
||||
(
|
||||
resultAmount: number,
|
||||
{ id, value: isItemToBeReplaced, data: { isFulfillment, isRefunded } }
|
||||
) => {
|
||||
if (!isItemToBeReplaced || isRefunded) {
|
||||
return resultAmount;
|
||||
}
|
||||
|
||||
const { unitPrice, selectedQuantity } = selectItemPriceAndQuantity(
|
||||
order,
|
||||
{ fulfiledItemsQuantities, unfulfiledItemsQuantities },
|
||||
id,
|
||||
isFulfillment
|
||||
);
|
||||
|
||||
return resultAmount + unitPrice?.gross?.amount * selectedQuantity;
|
||||
},
|
||||
0
|
||||
);
|
||||
};
|
||||
|
||||
export const getReturnSelectedProductsAmount = (
|
||||
order: OrderDetails_order,
|
||||
{ itemsToBeReplaced, unfulfiledItemsQuantities, fulfiledItemsQuantities }
|
||||
) => {
|
||||
if (!order) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const unfulfilledItemsValue = getPartialProductsValue({
|
||||
itemsQuantities: unfulfiledItemsQuantities,
|
||||
itemsToBeReplaced,
|
||||
orderLines: order.lines
|
||||
});
|
||||
|
||||
const fulfiledItemsValue = getPartialProductsValue({
|
||||
itemsQuantities: fulfiledItemsQuantities,
|
||||
itemsToBeReplaced,
|
||||
orderLines: getAllOrderFulfilledLines(order)
|
||||
});
|
||||
|
||||
return unfulfilledItemsValue + fulfiledItemsValue;
|
||||
};
|
||||
|
||||
const getPartialProductsValue = ({
|
||||
orderLines,
|
||||
itemsQuantities,
|
||||
itemsToBeReplaced
|
||||
}: {
|
||||
itemsToBeReplaced: FormsetData<LineItemData, boolean>;
|
||||
itemsQuantities: FormsetData<LineItemData, number>;
|
||||
orderLines: OrderDetails_order_lines[];
|
||||
}) =>
|
||||
itemsQuantities.reduce((resultAmount, { id, value: quantity }) => {
|
||||
const {
|
||||
value: isItemToBeReplaced,
|
||||
data: { isRefunded }
|
||||
} = itemsToBeReplaced.find(getById(id));
|
||||
|
||||
if (quantity < 1 || isItemToBeReplaced || isRefunded) {
|
||||
return resultAmount;
|
||||
}
|
||||
|
||||
const { selectedQuantity, unitPrice } = getItemPriceAndQuantity({
|
||||
id,
|
||||
itemsQuantities,
|
||||
orderLines
|
||||
});
|
||||
|
||||
return resultAmount + unitPrice.gross.amount * selectedQuantity;
|
||||
}, 0);
|
||||
|
||||
export function getRefundedLinesPriceSum(
|
||||
lines: OrderRefundData_order_lines[],
|
||||
refundedProductQuantities: FormsetData<null, string>
|
||||
refundedProductQuantities: FormsetData<null, string | number>
|
||||
): number {
|
||||
return lines?.reduce((sum, line) => {
|
||||
const refundedLine = refundedProductQuantities.find(
|
||||
|
@ -37,7 +170,7 @@ export function getRefundedLinesPriceSum(
|
|||
|
||||
export function getAllFulfillmentLinesPriceSum(
|
||||
fulfillments: OrderRefundData_order_fulfillments[],
|
||||
refundedFulfilledProductQuantities: FormsetData<null, string>
|
||||
refundedFulfilledProductQuantities: FormsetData<null, string | number>
|
||||
): number {
|
||||
return fulfillments?.reduce((sum, fulfillment) => {
|
||||
const fulfilmentLinesSum = fulfillment?.lines.reduce((sum, line) => {
|
||||
|
|
|
@ -50,6 +50,7 @@ import {
|
|||
orderFulfillUrl,
|
||||
orderListUrl,
|
||||
orderRefundUrl,
|
||||
orderReturnPath,
|
||||
orderUrl,
|
||||
OrderUrlDialog,
|
||||
OrderUrlQueryParams
|
||||
|
@ -227,6 +228,7 @@ export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
|
|||
)}
|
||||
/>
|
||||
<OrderDetailsPage
|
||||
onOrderReturn={() => navigate(orderReturnPath(id))}
|
||||
disabled={
|
||||
updateMetadataOpts.loading ||
|
||||
updatePrivateMetadataOpts.loading
|
||||
|
|
113
src/orders/views/OrderReturn/OrderReturn.tsx
Normal file
113
src/orders/views/OrderReturn/OrderReturn.tsx
Normal file
|
@ -0,0 +1,113 @@
|
|||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import OrderReturnPage from "@saleor/orders/components/OrderReturnPage";
|
||||
import { OrderReturnFormData } from "@saleor/orders/components/OrderReturnPage/form";
|
||||
import { useOrderReturnCreateMutation } from "@saleor/orders/mutations";
|
||||
import { useOrderQuery } from "@saleor/orders/queries";
|
||||
import { orderUrl } from "@saleor/orders/urls";
|
||||
import { OrderErrorCode } from "@saleor/types/globalTypes";
|
||||
import React from "react";
|
||||
import { defineMessages } from "react-intl";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import ReturnFormDataParser from "./utils";
|
||||
|
||||
export const messages = defineMessages({
|
||||
cannotRefundDescription: {
|
||||
defaultMessage:
|
||||
"We’ve encountered a problem while refunding the products. Product’s were not refunded. Please try again.",
|
||||
description: "order return error description when cannot refund"
|
||||
},
|
||||
cannotRefundTitle: {
|
||||
defaultMessage: "Couldn't refund products",
|
||||
description: "order return error title when cannot refund"
|
||||
},
|
||||
successAlert: {
|
||||
defaultMessage: "Successfully returned products!",
|
||||
description: "order returned success message"
|
||||
}
|
||||
});
|
||||
|
||||
interface OrderReturnProps {
|
||||
orderId: string;
|
||||
}
|
||||
|
||||
const OrderReturn: React.FC<OrderReturnProps> = ({ orderId }) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const { data, loading } = useOrderQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
id: orderId
|
||||
}
|
||||
});
|
||||
|
||||
const [returnCreate, returnCreateOpts] = useOrderReturnCreateMutation({
|
||||
onCompleted: ({
|
||||
orderFulfillmentReturnProducts: { errors, replaceOrder }
|
||||
}) => {
|
||||
if (!errors.length) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(messages.successAlert)
|
||||
});
|
||||
|
||||
navigateToOrder(replaceOrder?.id);
|
||||
}
|
||||
|
||||
if (errors[0].code === OrderErrorCode.CANNOT_REFUND) {
|
||||
notify({
|
||||
autohide: 5000,
|
||||
status: "error",
|
||||
text: intl.formatMessage(messages.cannotRefundDescription),
|
||||
title: intl.formatMessage(messages.cannotRefundTitle)
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
notify({
|
||||
autohide: 5000,
|
||||
status: "error",
|
||||
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleSubmit = async (formData: OrderReturnFormData) => {
|
||||
if (!data?.order) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await returnCreate({
|
||||
variables: {
|
||||
id: data.order.id,
|
||||
input: new ReturnFormDataParser(data.order, formData).getParsedData()
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
data: { orderFulfillmentReturnProducts }
|
||||
} = result;
|
||||
|
||||
return orderFulfillmentReturnProducts.errors;
|
||||
};
|
||||
|
||||
const navigateToOrder = (id?: string) => navigate(orderUrl(id || orderId));
|
||||
|
||||
return (
|
||||
<OrderReturnPage
|
||||
errors={returnCreateOpts.data?.orderFulfillmentReturnProducts.errors}
|
||||
order={data?.order}
|
||||
loading={loading || returnCreateOpts.loading}
|
||||
onSubmit={handleSubmit}
|
||||
onBack={() => navigateToOrder()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
OrderReturn.displayName = "OrderReturn";
|
||||
export default OrderReturn;
|
2
src/orders/views/OrderReturn/index.tsx
Normal file
2
src/orders/views/OrderReturn/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./OrderReturn";
|
||||
export { default } from "./OrderReturn";
|
102
src/orders/views/OrderReturn/utils.tsx
Normal file
102
src/orders/views/OrderReturn/utils.tsx
Normal file
|
@ -0,0 +1,102 @@
|
|||
/* eslint-disable @typescript-eslint/member-ordering */
|
||||
import { OrderRefundAmountCalculationMode } from "@saleor/orders/components/OrderRefundPage/form";
|
||||
import {
|
||||
FormsetQuantityData,
|
||||
OrderReturnFormData
|
||||
} from "@saleor/orders/components/OrderReturnPage/form";
|
||||
import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
|
||||
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
||||
import {
|
||||
OrderReturnFulfillmentLineInput,
|
||||
OrderReturnLineInput,
|
||||
OrderReturnProductsInput
|
||||
} from "@saleor/types/globalTypes";
|
||||
|
||||
class ReturnFormDataParser {
|
||||
private order: OrderDetails_order;
|
||||
private formData: OrderReturnFormData;
|
||||
|
||||
constructor(order: OrderDetails_order, formData: OrderReturnFormData) {
|
||||
this.order = order;
|
||||
this.formData = formData;
|
||||
}
|
||||
|
||||
public getParsedData = (): OrderReturnProductsInput => {
|
||||
const {
|
||||
fulfiledItemsQuantities,
|
||||
unfulfiledItemsQuantities,
|
||||
refundShipmentCosts
|
||||
} = this.formData;
|
||||
|
||||
const fulfillmentLines = this.getParsedLineData<
|
||||
OrderReturnFulfillmentLineInput
|
||||
>(fulfiledItemsQuantities, "fulfillmentLineId");
|
||||
|
||||
const orderLines = this.getParsedLineData<OrderReturnLineInput>(
|
||||
unfulfiledItemsQuantities,
|
||||
"orderLineId"
|
||||
);
|
||||
|
||||
return {
|
||||
amountToRefund: this.getAmountToRefund(),
|
||||
fulfillmentLines,
|
||||
includeShippingCosts: refundShipmentCosts,
|
||||
orderLines,
|
||||
refund: this.getShouldRefund(orderLines, fulfillmentLines)
|
||||
};
|
||||
};
|
||||
|
||||
private getAmountToRefund = (): number | undefined =>
|
||||
this.formData.amountCalculationMode ===
|
||||
OrderRefundAmountCalculationMode.MANUAL
|
||||
? this.formData.amount
|
||||
: undefined;
|
||||
|
||||
private getParsedLineData = function<
|
||||
T extends OrderReturnFulfillmentLineInput | OrderReturnLineInput
|
||||
>(
|
||||
itemsQuantities: FormsetQuantityData,
|
||||
idKey: "fulfillmentLineId" | "orderLineId"
|
||||
): T[] {
|
||||
const { itemsToBeReplaced } = this.formData;
|
||||
|
||||
return itemsQuantities.reduce((result, { value: quantity, id }) => {
|
||||
if (!quantity) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const shouldReplace = !!itemsToBeReplaced.find(getById(id))?.value;
|
||||
|
||||
return [
|
||||
...result,
|
||||
({ [idKey]: id, quantity, replace: shouldReplace } as unknown) as T
|
||||
];
|
||||
}, []);
|
||||
};
|
||||
|
||||
private getShouldRefund = (
|
||||
orderLines: OrderReturnLineInput[],
|
||||
fulfillmentLines: OrderReturnFulfillmentLineInput[]
|
||||
) => {
|
||||
if (!this.order.totalCaptured?.amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!!this.getAmountToRefund()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
orderLines.some(ReturnFormDataParser.isLineRefundable) ||
|
||||
fulfillmentLines.some(ReturnFormDataParser.isLineRefundable)
|
||||
);
|
||||
};
|
||||
|
||||
private static isLineRefundable = function<
|
||||
T extends OrderReturnLineInput | OrderReturnFulfillmentLineInput
|
||||
>({ replace }: T) {
|
||||
return !replace;
|
||||
};
|
||||
}
|
||||
|
||||
export default ReturnFormDataParser;
|
|
@ -1,11 +1,13 @@
|
|||
import { Locale, RawLocaleProvider } from "@saleor/components/Locale";
|
||||
import React from "react";
|
||||
import { IntlProvider } from "react-intl";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
import { Provider as DateProvider } from "../components/Date/DateContext";
|
||||
import MessageManagerProvider from "../components/messages";
|
||||
import ThemeProvider from "../components/Theme";
|
||||
import { TimezoneProvider } from "../components/Timezone";
|
||||
import { APP_MOUNT_URI } from "../config";
|
||||
|
||||
export const Decorator = storyFn => (
|
||||
<IntlProvider defaultLocale={Locale.EN} locale={Locale.EN}>
|
||||
|
@ -18,15 +20,17 @@ export const Decorator = storyFn => (
|
|||
<DateProvider value={+new Date("2018-08-07T14:30:44+00:00")}>
|
||||
<TimezoneProvider value="America/New_York">
|
||||
<ThemeProvider isDefaultDark={false}>
|
||||
<MessageManagerProvider>
|
||||
<div
|
||||
style={{
|
||||
padding: 24
|
||||
}}
|
||||
>
|
||||
{storyFn()}
|
||||
</div>
|
||||
</MessageManagerProvider>
|
||||
<BrowserRouter basename={APP_MOUNT_URI}>
|
||||
<MessageManagerProvider>
|
||||
<div
|
||||
style={{
|
||||
padding: 24
|
||||
}}
|
||||
>
|
||||
{storyFn()}
|
||||
</div>
|
||||
</MessageManagerProvider>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
</TimezoneProvider>
|
||||
</DateProvider>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -30,6 +30,7 @@ const props: Omit<OrderDetailsPageProps, "classes"> = {
|
|||
onNoteAdd: undefined,
|
||||
onOrderCancel: undefined,
|
||||
onOrderFulfill: undefined,
|
||||
onOrderReturn: () => undefined,
|
||||
onPaymentCapture: undefined,
|
||||
onPaymentPaid: undefined,
|
||||
onPaymentRefund: undefined,
|
||||
|
|
|
@ -451,6 +451,9 @@ export enum FulfillmentStatus {
|
|||
CANCELED = "CANCELED",
|
||||
FULFILLED = "FULFILLED",
|
||||
REFUNDED = "REFUNDED",
|
||||
REFUNDED_AND_RETURNED = "REFUNDED_AND_RETURNED",
|
||||
REPLACED = "REPLACED",
|
||||
RETURNED = "RETURNED",
|
||||
}
|
||||
|
||||
export enum InvoiceErrorCode {
|
||||
|
@ -564,7 +567,6 @@ export enum OrderErrorCode {
|
|||
CANNOT_CANCEL_ORDER = "CANNOT_CANCEL_ORDER",
|
||||
CANNOT_DELETE = "CANNOT_DELETE",
|
||||
CANNOT_REFUND = "CANNOT_REFUND",
|
||||
CANNOT_REFUND_FULFILLMENT_LINE = "CANNOT_REFUND_FULFILLMENT_LINE",
|
||||
CAPTURE_INACTIVE_PAYMENT = "CAPTURE_INACTIVE_PAYMENT",
|
||||
CHANNEL_INACTIVE = "CHANNEL_INACTIVE",
|
||||
DUPLICATED_INPUT_ITEM = "DUPLICATED_INPUT_ITEM",
|
||||
|
@ -572,7 +574,7 @@ export enum OrderErrorCode {
|
|||
GRAPHQL_ERROR = "GRAPHQL_ERROR",
|
||||
INSUFFICIENT_STOCK = "INSUFFICIENT_STOCK",
|
||||
INVALID = "INVALID",
|
||||
INVALID_REFUND_QUANTITY = "INVALID_REFUND_QUANTITY",
|
||||
INVALID_QUANTITY = "INVALID_QUANTITY",
|
||||
NOT_AVAILABLE_IN_CHANNEL = "NOT_AVAILABLE_IN_CHANNEL",
|
||||
NOT_EDITABLE = "NOT_EDITABLE",
|
||||
NOT_FOUND = "NOT_FOUND",
|
||||
|
@ -607,13 +609,16 @@ export enum OrderEventsEnum {
|
|||
CONFIRMED = "CONFIRMED",
|
||||
DRAFT_ADDED_PRODUCTS = "DRAFT_ADDED_PRODUCTS",
|
||||
DRAFT_CREATED = "DRAFT_CREATED",
|
||||
DRAFT_CREATED_FROM_REPLACE = "DRAFT_CREATED_FROM_REPLACE",
|
||||
DRAFT_REMOVED_PRODUCTS = "DRAFT_REMOVED_PRODUCTS",
|
||||
EMAIL_SENT = "EMAIL_SENT",
|
||||
EXTERNAL_SERVICE_NOTIFICATION = "EXTERNAL_SERVICE_NOTIFICATION",
|
||||
FULFILLMENT_CANCELED = "FULFILLMENT_CANCELED",
|
||||
FULFILLMENT_FULFILLED_ITEMS = "FULFILLMENT_FULFILLED_ITEMS",
|
||||
FULFILLMENT_REFUNDED = "FULFILLMENT_REFUNDED",
|
||||
FULFILLMENT_REPLACED = "FULFILLMENT_REPLACED",
|
||||
FULFILLMENT_RESTOCKED_ITEMS = "FULFILLMENT_RESTOCKED_ITEMS",
|
||||
FULFILLMENT_RETURNED = "FULFILLMENT_RETURNED",
|
||||
INVOICE_GENERATED = "INVOICE_GENERATED",
|
||||
INVOICE_REQUESTED = "INVOICE_REQUESTED",
|
||||
INVOICE_SENT = "INVOICE_SENT",
|
||||
|
@ -621,6 +626,7 @@ export enum OrderEventsEnum {
|
|||
NOTE_ADDED = "NOTE_ADDED",
|
||||
ORDER_FULLY_PAID = "ORDER_FULLY_PAID",
|
||||
ORDER_MARKED_AS_PAID = "ORDER_MARKED_AS_PAID",
|
||||
ORDER_REPLACEMENT_CREATED = "ORDER_REPLACEMENT_CREATED",
|
||||
OTHER = "OTHER",
|
||||
OVERSOLD_ITEMS = "OVERSOLD_ITEMS",
|
||||
PAYMENT_AUTHORIZED = "PAYMENT_AUTHORIZED",
|
||||
|
@ -651,6 +657,8 @@ export enum OrderStatus {
|
|||
DRAFT = "DRAFT",
|
||||
FULFILLED = "FULFILLED",
|
||||
PARTIALLY_FULFILLED = "PARTIALLY_FULFILLED",
|
||||
PARTIALLY_RETURNED = "PARTIALLY_RETURNED",
|
||||
RETURNED = "RETURNED",
|
||||
UNCONFIRMED = "UNCONFIRMED",
|
||||
UNFULFILLED = "UNFULFILLED",
|
||||
}
|
||||
|
@ -1377,6 +1385,26 @@ export interface OrderRefundProductsInput {
|
|||
includeShippingCosts?: boolean | null;
|
||||
}
|
||||
|
||||
export interface OrderReturnFulfillmentLineInput {
|
||||
fulfillmentLineId: string;
|
||||
quantity: number;
|
||||
replace?: boolean | null;
|
||||
}
|
||||
|
||||
export interface OrderReturnLineInput {
|
||||
orderLineId: string;
|
||||
quantity: number;
|
||||
replace?: boolean | null;
|
||||
}
|
||||
|
||||
export interface OrderReturnProductsInput {
|
||||
orderLines?: OrderReturnLineInput[] | null;
|
||||
fulfillmentLines?: OrderReturnFulfillmentLineInput[] | null;
|
||||
amountToRefund?: any | null;
|
||||
includeShippingCosts?: boolean | null;
|
||||
refund?: boolean | null;
|
||||
}
|
||||
|
||||
export interface OrderSettingsUpdateInput {
|
||||
automaticallyConfirmAllNewOrders: boolean;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue