Move fulfillment warehouse selection to fulfilll page (#2199)

* Move order fulfillment warehouse selection to fulfill page

* Use modal to select warehouse on fulfill order page

* Remove tracking number input from fulfill order page

* Update visual structure of fulfill order page

* Fix fulfill order page styles

* Update order fulfill utils
This commit is contained in:
Dawid 2022-08-02 14:57:18 +02:00 committed by GitHub
parent 804e14768b
commit 2c97b2da41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1432 additions and 2060 deletions

View file

@ -2602,6 +2602,10 @@
"context": "header", "context": "header",
"string": "Edit Media" "string": "Edit Media"
}, },
"Ila7WO": {
"context": "change warehouse dialog description",
"string": "Choose warehouse from which you want to fulfill {productName}"
},
"ImTelT": { "ImTelT": {
"context": "card subtitle", "context": "card subtitle",
"string": "Select warehouses that will be used in this channel. You can assign warehouses to multiple channels." "string": "Select warehouses that will be used in this channel. You can assign warehouses to multiple channels."
@ -2642,6 +2646,10 @@
"J0UdxG": { "J0UdxG": {
"string": "Skip pricing for now" "string": "Skip pricing for now"
}, },
"J0lNnk": {
"context": "column label",
"string": "Warehouse"
},
"J3uE0t": { "J3uE0t": {
"context": "section header", "context": "section header",
"string": "Attribute Values" "string": "Attribute Values"
@ -3628,10 +3636,6 @@
"context": "product field", "context": "product field",
"string": "Charge Taxes" "string": "Charge Taxes"
}, },
"QWCh6/": {
"context": "change warehouse dialog description",
"string": "Choose warehouse you want to fulfill this order from"
},
"QY7FSs": { "QY7FSs": {
"context": "button", "context": "button",
"string": "create product type" "string": "create product type"
@ -3660,10 +3664,6 @@
"context": "years after label", "context": "years after label",
"string": "years after issue" "string": "years after issue"
}, },
"QfKQx3": {
"context": "warehouse label when multiple products are unavailable",
"string": "{productCount} products are unavailable at this location"
},
"QiN4hv": { "QiN4hv": {
"context": "active", "context": "active",
"string": "Active" "string": "Active"
@ -4369,6 +4369,10 @@
"W5SK5c": { "W5SK5c": {
"string": "Select content type" "string": "Select content type"
}, },
"W5hb5H": {
"context": "warehouse input",
"string": "Warehouse"
},
"W8i2Ez": { "W8i2Ez": {
"context": "product field", "context": "product field",
"string": "Name" "string": "Name"
@ -5692,6 +5696,10 @@
"context": "button", "context": "button",
"string": "Set as default billing address" "string": "Set as default billing address"
}, },
"hLSgWj": {
"context": "warehouse label number available of products",
"string": "{productCount} available at this location"
},
"hLUYBt": { "hLUYBt": {
"context": "payment status", "context": "payment status",
"string": "Pending" "string": "Pending"
@ -6571,10 +6579,6 @@
"oYGfnY": { "oYGfnY": {
"string": "ZIP / Postal code" "string": "ZIP / Postal code"
}, },
"oiaUni": {
"context": "Support text under page header",
"string": "Fulfilling from {warehouseName}"
},
"oiuwOl": { "oiuwOl": {
"context": "button", "context": "button",
"string": "Assign" "string": "Assign"
@ -7622,10 +7626,6 @@
"context": "ProductTypeDeleteWarningDialog title", "context": "ProductTypeDeleteWarningDialog title",
"string": "Delete product {selectedTypesCount,plural,one{type} other{types}}" "string": "Delete product {selectedTypesCount,plural,one{type} other{types}}"
}, },
"x4WAC7": {
"context": "warehouse label when one product is unavailable",
"string": "{productName} is unavailable at this location"
},
"x8V/xS": { "x8V/xS": {
"context": "attribute visibility in storefront", "context": "attribute visibility in storefront",
"string": "Public" "string": "Public"
@ -7923,10 +7923,6 @@
"context": "attribute type", "context": "attribute type",
"string": "Content Attribute" "string": "Content Attribute"
}, },
"zbrHAw": {
"context": "Tracking number input label",
"string": "Tracking number"
},
"zjHH6b": { "zjHH6b": {
"context": "sale start date", "context": "sale start date",
"string": "Started" "string": "Started"

View file

@ -79,6 +79,7 @@ export const fragmentOrderLine = gql`
quantity quantity
warehouse { warehouse {
id id
name
} }
} }
variant { variant {
@ -342,9 +343,11 @@ export const fragmentOrderFulfillLine = gql`
productName productName
quantity quantity
allocations { allocations {
id
quantity quantity
warehouse { warehouse {
id id
name
} }
} }
quantityFulfilled quantityFulfilled

View file

@ -1227,6 +1227,7 @@ export const OrderLineFragmentDoc = gql`
quantity quantity
warehouse { warehouse {
id id
name
} }
} }
variant { variant {
@ -1474,9 +1475,11 @@ export const OrderFulfillLineFragmentDoc = gql`
productName productName
quantity quantity
allocations { allocations {
id
quantity quantity
warehouse { warehouse {
id id
name
} }
} }
quantityFulfilled quantityFulfilled

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
import { MockedProvider, MockedResponse } from "@apollo/client/testing"; import { MockedProvider, MockedResponse } from "@apollo/client/testing";
import { allPermissions } from "@saleor/hooks/makeQuery"; import { allPermissions } from "@saleor/hooks/makeQuery";
import { order, warehouseSearch } from "@saleor/orders/fixtures"; import { fulfillOrderLine, warehouseSearch } from "@saleor/orders/fixtures";
import { searchWarehouses } from "@saleor/searches/useWarehouseSearch"; import { searchWarehouses } from "@saleor/searches/useWarehouseSearch";
import Decorator from "@saleor/storybook/Decorator"; import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
@ -10,8 +10,8 @@ import OrderChangeWarehouseDialog, { OrderChangeWarehouseDialogProps } from ".";
const props: OrderChangeWarehouseDialogProps = { const props: OrderChangeWarehouseDialogProps = {
open: true, open: true,
lines: order("abc").lines, line: fulfillOrderLine("abc"),
currentWarehouse: null, currentWarehouseId: null,
onConfirm: () => null, onConfirm: () => null,
onClose: () => null, onClose: () => null,
}; };

View file

@ -13,7 +13,7 @@ import {
} from "@material-ui/core"; } from "@material-ui/core";
import Debounce from "@saleor/components/Debounce"; import Debounce from "@saleor/components/Debounce";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import { OrderLineFragment, WarehouseFragment } from "@saleor/graphql"; import { OrderFulfillLineFragment, WarehouseFragment } from "@saleor/graphql";
import { buttonMessages } from "@saleor/intl"; import { buttonMessages } from "@saleor/intl";
import { import {
Button, Button,
@ -25,7 +25,7 @@ import {
SearchIcon, SearchIcon,
useElementScroll, useElementScroll,
} from "@saleor/macaw-ui"; } from "@saleor/macaw-ui";
import { isLineAvailableInWarehouse } from "@saleor/orders/utils/data"; import { getLineAvailableQuantityInWarehouse } from "@saleor/orders/utils/data";
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch"; import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
import { mapEdgesToItems } from "@saleor/utils/maps"; import { mapEdgesToItems } from "@saleor/utils/maps";
import React from "react"; import React from "react";
@ -37,16 +37,16 @@ import { useStyles } from "./styles";
export interface OrderChangeWarehouseDialogProps { export interface OrderChangeWarehouseDialogProps {
open: boolean; open: boolean;
lines: OrderLineFragment[]; line: OrderFulfillLineFragment;
currentWarehouse: WarehouseFragment; currentWarehouseId: string;
onConfirm: (warehouse: WarehouseFragment) => void; onConfirm: (warehouse: WarehouseFragment) => void;
onClose(); onClose();
} }
export const OrderChangeWarehouseDialog: React.FC<OrderChangeWarehouseDialogProps> = ({ export const OrderChangeWarehouseDialog: React.FC<OrderChangeWarehouseDialogProps> = ({
open, open,
lines, line,
currentWarehouse, currentWarehouseId,
onConfirm, onConfirm,
onClose, onClose,
}) => { }) => {
@ -63,10 +63,10 @@ export const OrderChangeWarehouseDialog: React.FC<OrderChangeWarehouseDialogProp
>(null); >(null);
React.useEffect(() => { React.useEffect(() => {
if (currentWarehouse?.id) { if (currentWarehouseId) {
setSelectedWarehouseId(currentWarehouse.id); setSelectedWarehouseId(currentWarehouseId);
} }
}, [currentWarehouse]); }, [currentWarehouseId]);
const { result: warehousesOpts, loadMore, search } = useWarehouseSearch({ const { result: warehousesOpts, loadMore, search } = useWarehouseSearch({
variables: { variables: {
@ -103,7 +103,12 @@ export const OrderChangeWarehouseDialog: React.FC<OrderChangeWarehouseDialogProp
</DialogHeader> </DialogHeader>
<DialogContent className={classes.container}> <DialogContent className={classes.container}>
<FormattedMessage {...messages.dialogDescription} /> <FormattedMessage
{...messages.dialogDescription}
values={{
productName: line?.productName,
}}
/>
<Debounce debounceFn={search}> <Debounce debounceFn={search}>
{debounceSearchChange => { {debounceSearchChange => {
const handleSearchChange = ( const handleSearchChange = (
@ -144,15 +149,19 @@ export const OrderChangeWarehouseDialog: React.FC<OrderChangeWarehouseDialogProp
<DialogTable ref={setAnchor}> <DialogTable ref={setAnchor}>
{filteredWarehouses ? ( {filteredWarehouses ? (
<RadioGroup value={selectedWarehouseId} onChange={handleChange}> <RadioGroup
value={selectedWarehouseId}
onChange={handleChange}
className={classes.tableBody}
>
{filteredWarehouses.map(warehouse => { {filteredWarehouses.map(warehouse => {
const unavailableLines = lines?.filter( const lineQuantityInWarehouse = getLineAvailableQuantityInWarehouse(
line => !isLineAvailableInWarehouse(line, warehouse), line,
warehouse,
); );
const someLinesUnavailable = unavailableLines?.length > 0;
return ( return (
<TableRow key={warehouse.id}> <TableRow key={warehouse.id}>
<TableCell> <TableCell className={classes.tableCell}>
<FormControlLabel <FormControlLabel
value={warehouse.id} value={warehouse.id}
control={<Radio color="primary" />} control={<Radio color="primary" />}
@ -161,28 +170,20 @@ export const OrderChangeWarehouseDialog: React.FC<OrderChangeWarehouseDialogProp
<span className={classes.warehouseName}> <span className={classes.warehouseName}>
{warehouse.name} {warehouse.name}
</span> </span>
{someLinesUnavailable && ( <Typography className={classes.supportText}>
<Typography className={classes.supportText}> <FormattedMessage
{unavailableLines.length === 1 {...messages.productAvailability}
? intl.formatMessage( values={{
messages.productUnavailable, productCount: lineQuantityInWarehouse,
{ }}
productName: />
unavailableLines[0].productName, </Typography>
},
)
: intl.formatMessage(
messages.multipleProductsUnavailable,
{ productCount: unavailableLines.length },
)}
</Typography>
)}
</div> </div>
} }
/> />
{currentWarehouse?.id === warehouse?.id && ( {currentWarehouseId === warehouse?.id && (
<Typography className={classes.helpText}> <Typography className={classes.helpText}>
{intl.formatMessage(messages.currentSelection)} <FormattedMessage {...messages.currentSelection} />
</Typography> </Typography>
)} )}
</TableCell> </TableCell>

View file

@ -7,8 +7,9 @@ export const changeWarehouseDialogMessages = defineMessages({
description: "change warehouse dialog title", description: "change warehouse dialog title",
}, },
dialogDescription: { dialogDescription: {
defaultMessage: "Choose warehouse you want to fulfill this order from", defaultMessage:
id: "QWCh6/", "Choose warehouse from which you want to fulfill {productName}",
id: "Ila7WO",
description: "change warehouse dialog description", description: "change warehouse dialog description",
}, },
searchFieldPlaceholder: { searchFieldPlaceholder: {
@ -21,15 +22,10 @@ export const changeWarehouseDialogMessages = defineMessages({
id: "Epm41J", id: "Epm41J",
description: "change warehouse dialog warehouse list label", description: "change warehouse dialog warehouse list label",
}, },
productUnavailable: { productAvailability: {
defaultMessage: "{productName} is unavailable at this location", defaultMessage: "{productCount} available at this location",
id: "x4WAC7", id: "hLSgWj",
description: "warehouse label when one product is unavailable", description: "warehouse label number available of products",
},
multipleProductsUnavailable: {
defaultMessage: "{productCount} products are unavailable at this location",
id: "QfKQx3",
description: "warehouse label when multiple products are unavailable",
}, },
currentSelection: { currentSelection: {
defaultMessage: "currently selected", defaultMessage: "currently selected",

View file

@ -41,6 +41,15 @@ export const useStyles = makeStyles(
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
}, },
tableBody: {
display: "table",
width: "100%",
},
tableCell: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
},
}), }),
{ name: "OrderChangeWarehouseDialog" }, { name: "OrderChangeWarehouseDialog" },
); );

View file

@ -19,7 +19,6 @@ import {
OrderDetailsFragment, OrderDetailsFragment,
OrderDetailsQuery, OrderDetailsQuery,
OrderStatus, OrderStatus,
WarehouseFragment,
} from "@saleor/graphql"; } from "@saleor/graphql";
import { SubmitPromise } from "@saleor/hooks/useForm"; import { SubmitPromise } from "@saleor/hooks/useForm";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -70,7 +69,6 @@ export interface OrderDetailsPageProps {
}>; }>;
disabled: boolean; disabled: boolean;
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
selectedWarehouse?: WarehouseFragment;
onOrderLineAdd?: () => void; onOrderLineAdd?: () => void;
onOrderLineChange?: ( onOrderLineChange?: (
id: string, id: string,
@ -96,7 +94,6 @@ export interface OrderDetailsPageProps {
onInvoiceClick(invoiceId: string); onInvoiceClick(invoiceId: string);
onInvoiceGenerate(); onInvoiceGenerate();
onInvoiceSend(invoiceId: string); onInvoiceSend(invoiceId: string);
onWarehouseChange?();
onSubmit(data: MetadataFormData): SubmitPromise; onSubmit(data: MetadataFormData): SubmitPromise;
} }
@ -124,7 +121,6 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
order, order,
shop, shop,
saveButtonBarState, saveButtonBarState,
selectedWarehouse,
onBillingAddressEdit, onBillingAddressEdit,
onFulfillmentApprove, onFulfillmentApprove,
onFulfillmentCancel, onFulfillmentCancel,
@ -146,7 +142,6 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
onOrderLineChange, onOrderLineChange,
onOrderLineRemove, onOrderLineRemove,
onShippingMethodEdit, onShippingMethodEdit,
onWarehouseChange,
onSubmit, onSubmit,
} = props; } = props;
const classes = useStyles(props); const classes = useStyles(props);
@ -268,8 +263,6 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
notAllowedToFulfillUnpaid={notAllowedToFulfillUnpaid} notAllowedToFulfillUnpaid={notAllowedToFulfillUnpaid}
lines={unfulfilled} lines={unfulfilled}
onFulfill={onOrderFulfill} onFulfill={onOrderFulfill}
onWarehouseChange={onWarehouseChange}
selectedWarehouse={selectedWarehouse}
/> />
) : ( ) : (
<> <>

View file

@ -1,16 +1,19 @@
import { TableCell, TableRow, TextField, Typography } from "@material-ui/core"; import { TableCell, TableRow, TextField, Typography } from "@material-ui/core";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import TableCellAvatar from "@saleor/components/TableCellAvatar"; import TableCellAvatar from "@saleor/components/TableCellAvatar";
import { import { OrderFulfillLineFragment } from "@saleor/graphql";
OrderFulfillLineFragment,
OrderFulfillStockInput,
} from "@saleor/graphql";
import { FormsetChange, FormsetData } from "@saleor/hooks/useFormset"; import { FormsetChange, FormsetData } from "@saleor/hooks/useFormset";
import { Tooltip, WarningIcon } from "@saleor/macaw-ui"; import {
ChevronIcon,
IconButton,
Tooltip,
WarningIcon,
} from "@saleor/macaw-ui";
import { import {
getAttributesCaption, getAttributesCaption,
getOrderLineAvailableQuantity, getOrderLineAvailableQuantity,
getWarehouseStock, getWarehouseStock,
OrderFulfillLineFormData,
} from "@saleor/orders/utils/data"; } from "@saleor/orders/utils/data";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
@ -22,13 +25,19 @@ import { useStyles } from "./styles";
interface OrderFulfillLineProps { interface OrderFulfillLineProps {
line: OrderFulfillLineFragment; line: OrderFulfillLineFragment;
lineIndex: number; lineIndex: number;
warehouseId: string; formsetData: FormsetData<null, OrderFulfillLineFormData[]>;
formsetData: FormsetData<null, OrderFulfillStockInput[]>; formsetChange: FormsetChange<OrderFulfillLineFormData[]>;
formsetChange: FormsetChange<OrderFulfillStockInput[]>; onWarehouseChange: () => void;
} }
export const OrderFulfillLine: React.FC<OrderFulfillLineProps> = props => { export const OrderFulfillLine: React.FC<OrderFulfillLineProps> = props => {
const { line, lineIndex, warehouseId, formsetData, formsetChange } = props; const {
line,
lineIndex,
formsetData,
formsetChange,
onWarehouseChange,
} = props;
const classes = useStyles(); const classes = useStyles();
const intl = useIntl(); const intl = useIntl();
@ -37,14 +46,19 @@ export const OrderFulfillLine: React.FC<OrderFulfillLineProps> = props => {
const lineFormQuantity = isPreorder const lineFormQuantity = isPreorder
? 0 ? 0
: formsetData[lineIndex]?.value?.[0]?.quantity; : formsetData[lineIndex]?.value?.[0]?.quantity;
const lineFormWarehouse = formsetData[lineIndex]?.value?.[0]?.warehouse;
const overfulfill = lineFormQuantity > line.quantityToFulfill; const overfulfill = lineFormQuantity > line.quantityToFulfill;
const warehouseStock = getWarehouseStock(line?.variant?.stocks, warehouseId);
const warehouseStock = getWarehouseStock(
line?.variant?.stocks,
lineFormWarehouse?.id,
);
const availableQuantity = getOrderLineAvailableQuantity(line, warehouseStock); const availableQuantity = getOrderLineAvailableQuantity(line, warehouseStock);
const isStockExceeded = lineFormQuantity > availableQuantity; const isStockExceeded = lineFormQuantity > availableQuantity;
if (!line) { if (!line || !lineFormWarehouse) {
return ( return (
<TableRow key={lineIndex}> <TableRow key={lineIndex}>
<TableCellAvatar className={classes.colName}> <TableCellAvatar className={classes.colName}>
@ -59,6 +73,9 @@ export const OrderFulfillLine: React.FC<OrderFulfillLineProps> = props => {
<TableCell className={classes.colStock}> <TableCell className={classes.colStock}>
<Skeleton /> <Skeleton />
</TableCell> </TableCell>
<TableCell className={classes.colWarehouse}>
<Skeleton />
</TableCell>
</TableRow> </TableRow>
); );
} }
@ -116,7 +133,7 @@ export const OrderFulfillLine: React.FC<OrderFulfillLineProps> = props => {
formsetChange(line.id, [ formsetChange(line.id, [
{ {
quantity: parseInt(event.target.value, 10), quantity: parseInt(event.target.value, 10),
warehouse: warehouseId, warehouse: lineFormWarehouse,
}, },
]) ])
} }
@ -141,6 +158,20 @@ export const OrderFulfillLine: React.FC<OrderFulfillLineProps> = props => {
<TableCell className={classes.colStock} key="total"> <TableCell className={classes.colStock} key="total">
{isPreorder || isDeletedVariant ? undefined : availableQuantity} {isPreorder || isDeletedVariant ? undefined : availableQuantity}
</TableCell> </TableCell>
<TableCell className={classes.colWarehouse}>
<IconButton
onClick={onWarehouseChange}
className={classes.warehouseButton}
data-test-id="select-warehouse-button"
>
<div className={classes.warehouseButtonContent}>
<div className={classes.warehouseButtonContentText}>
{lineFormWarehouse?.name ?? <Skeleton />}
</div>
<ChevronIcon />
</div>
</IconButton>
</TableCell>
</TableRow> </TableRow>
); );
}; };

View file

@ -12,4 +12,9 @@ export const messages = defineMessages({
id: "8vQGO0", id: "8vQGO0",
description: "tooltip content when line's variant has been deleted", description: "tooltip content when line's variant has been deleted",
}, },
warehouse: {
defaultMessage: "Warehouse",
id: "W5hb5H",
description: "warehouse input",
},
}); });

View file

@ -7,7 +7,7 @@ export const useStyles = makeStyles(
width: 180, width: 180,
}, },
colName: { colName: {
width: 250, width: 220,
}, },
colQuantity: { colQuantity: {
textAlign: "right", textAlign: "right",
@ -16,7 +16,11 @@ export const useStyles = makeStyles(
colSku: { colSku: {
textAlign: "right", textAlign: "right",
textOverflow: "ellipsis", textOverflow: "ellipsis",
width: 150, width: 100,
},
colWarehouse: {
width: 200,
textAlign: "right",
}, },
warningIcon: { warningIcon: {
color: theme.palette.saleor.warning.mid, color: theme.palette.saleor.warning.mid,
@ -42,6 +46,23 @@ export const useStyles = makeStyles(
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
whiteSpace: "nowrap", whiteSpace: "nowrap",
}, },
warehouseButton: {
padding: theme.spacing(1.5),
width: "100%",
justifyContent: "right",
border: `1px solid ${theme.palette.divider}`,
},
warehouseButtonContent: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
cursor: "pointer",
},
warehouseButtonContentText: {
overflow: "hidden",
textOverflow: "ellipsis",
},
}), }),
{ name: "OrderFulfillLine" }, { name: "OrderFulfillLine" },
); );

View file

@ -8,24 +8,21 @@ import { orderToFulfill } from "./fixtures";
import OrderFulfillPage, { OrderFulfillPageProps } from "./OrderFulfillPage"; import OrderFulfillPage, { OrderFulfillPageProps } from "./OrderFulfillPage";
const props: OrderFulfillPageProps = { const props: OrderFulfillPageProps = {
params: {},
errors: [], errors: [],
loading: false, loading: false,
onSubmit: () => undefined, onSubmit: () => undefined,
order: orderToFulfill, order: orderToFulfill,
saveButtonBar: "default", saveButtonBar: "default",
warehouse: warehouseList[0], openModal: () => undefined,
closeModal: () => undefined,
}; };
storiesOf("Views / Orders / Fulfill order", module) storiesOf("Views / Orders / Fulfill order", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <OrderFulfillPage {...props} />) .add("default", () => <OrderFulfillPage {...props} />)
.add("loading", () => ( .add("loading", () => (
<OrderFulfillPage <OrderFulfillPage {...props} loading={true} order={undefined} />
{...props}
loading={true}
order={undefined}
warehouse={undefined}
/>
)) ))
.add("error", () => ( .add("error", () => (
<OrderFulfillPage <OrderFulfillPage

View file

@ -1,18 +1,17 @@
import { import {
Card, Card,
CardContent,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableRow, TableRow,
TextField,
Typography,
} from "@material-ui/core"; } from "@material-ui/core";
import { Backlink } from "@saleor/components/Backlink"; import { Backlink } from "@saleor/components/Backlink";
import CardSpacer from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
import { Grid } from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import ResponsiveTable from "@saleor/components/ResponsiveTable"; import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Savebar from "@saleor/components/Savebar"; import Savebar from "@saleor/components/Savebar";
@ -24,7 +23,6 @@ import {
OrderFulfillLineFragment, OrderFulfillLineFragment,
OrderFulfillStockInput, OrderFulfillStockInput,
ShopOrderSettingsFragment, ShopOrderSettingsFragment,
WarehouseFragment,
} from "@saleor/graphql"; } from "@saleor/graphql";
import { SubmitPromise } from "@saleor/hooks/useForm"; import { SubmitPromise } from "@saleor/hooks/useForm";
import useFormset, { FormsetData } from "@saleor/hooks/useFormset"; import useFormset, { FormsetData } from "@saleor/hooks/useFormset";
@ -32,10 +30,17 @@ import useNavigator from "@saleor/hooks/useNavigator";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui"; import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
import { renderCollection } from "@saleor/misc"; import { renderCollection } from "@saleor/misc";
import { orderUrl } from "@saleor/orders/urls"; import OrderChangeWarehouseDialog from "@saleor/orders/components/OrderChangeWarehouseDialog";
import {
OrderFulfillUrlDialog,
OrderFulfillUrlQueryParams,
orderUrl,
} from "@saleor/orders/urls";
import { import {
getAttributesCaption, getAttributesCaption,
getLineAllocationWithHighestQuantity,
getToFulfillOrderLines, getToFulfillOrderLines,
OrderFulfillLineFormData,
} from "@saleor/orders/utils/data"; } from "@saleor/orders/utils/data";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
@ -48,37 +53,42 @@ import { useStyles } from "./styles";
interface OrderFulfillFormData { interface OrderFulfillFormData {
sendInfo: boolean; sendInfo: boolean;
trackingNumber: string;
allowStockToBeExceeded: boolean; allowStockToBeExceeded: boolean;
} }
export interface OrderFulfillSubmitData extends OrderFulfillFormData { export interface OrderFulfillSubmitData extends OrderFulfillFormData {
items: FormsetData<null, OrderFulfillStockInput[]>; items: FormsetData<null, OrderFulfillStockInput[]>;
} }
export interface OrderFulfillPageProps { export interface OrderFulfillPageProps {
params: OrderFulfillUrlQueryParams;
loading: boolean; loading: boolean;
errors: FulfillOrderMutation["orderFulfill"]["errors"]; errors: FulfillOrderMutation["orderFulfill"]["errors"];
order: OrderFulfillDataQuery["order"]; order: OrderFulfillDataQuery["order"];
saveButtonBar: ConfirmButtonTransitionState; saveButtonBar: ConfirmButtonTransitionState;
warehouse: WarehouseFragment;
shopSettings?: ShopOrderSettingsFragment; shopSettings?: ShopOrderSettingsFragment;
onSubmit: (data: OrderFulfillSubmitData) => SubmitPromise; onSubmit: (data: OrderFulfillSubmitData) => SubmitPromise;
openModal: (
action: OrderFulfillUrlDialog,
params?: OrderFulfillUrlQueryParams,
) => void;
closeModal: () => void;
} }
const initialFormData: OrderFulfillFormData = { const initialFormData: OrderFulfillFormData = {
sendInfo: true, sendInfo: true,
trackingNumber: "",
allowStockToBeExceeded: false, allowStockToBeExceeded: false,
}; };
const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => { const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
const { const {
params,
loading, loading,
errors, errors,
order, order,
saveButtonBar, saveButtonBar,
warehouse,
shopSettings, shopSettings,
onSubmit, onSubmit,
openModal,
closeModal,
} = props; } = props;
const intl = useIntl(); const intl = useIntl();
@ -87,22 +97,28 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
const { change: formsetChange, data: formsetData } = useFormset< const { change: formsetChange, data: formsetData } = useFormset<
null, null,
OrderFulfillStockInput[] OrderFulfillLineFormData[]
>( >(
(getToFulfillOrderLines(order?.lines) as OrderFulfillLineFragment[]).map( (getToFulfillOrderLines(order?.lines) as OrderFulfillLineFragment[]).map(
line => ({ line => {
data: null, const highestQuantityAllocation = getLineAllocationWithHighestQuantity(
id: line.id, line,
label: getAttributesCaption(line?.variant?.attributes), );
value: line?.variant?.preorder
? null return {
: [ data: null,
{ id: line.id,
quantity: line.quantityToFulfill, label: getAttributesCaption(line?.variant?.attributes),
warehouse: warehouse?.id, value: line?.variant?.preorder
}, ? null
], : [
}), {
quantity: line.quantityToFulfill,
warehouse: highestQuantityAllocation?.warehouse,
},
],
};
},
), ),
); );
@ -122,7 +138,15 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
return onSubmit({ return onSubmit({
...formData, ...formData,
allowStockToBeExceeded, allowStockToBeExceeded,
items: formsetData.filter(item => !!item.value), items: formsetData
.filter(item => !!item.value)
.map(item => ({
...item,
value: item.value.map(value => ({
quantity: value.quantity,
warehouse: value.warehouse.id,
})),
})),
}); });
}; };
React.useEffect(() => { React.useEffect(() => {
@ -166,43 +190,37 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
}; };
return ( return (
<Container> <>
<Backlink href={orderUrl(order?.id)}> <Container>
{order?.number <Backlink href={orderUrl(order?.id)}>
? intl.formatMessage(messages.headerOrderNumber, { {order?.number
orderNumber: order.number, ? intl.formatMessage(messages.headerOrderNumber, {
}) orderNumber: order.number,
: intl.formatMessage(messages.headerOrder)} })
</Backlink> : intl.formatMessage(messages.headerOrder)}
<PageHeader </Backlink>
title={intl.formatMessage(messages.headerOrderNumberAddFulfillment, { <PageHeader
orderNumber: order?.number, title={intl.formatMessage(messages.headerOrderNumberAddFulfillment, {
})} orderNumber: order?.number,
/> })}
<Typography className={classes.warehouseLabel}>
<FormattedMessage
{...messages.fulfillingFrom}
values={{ warehouseName: warehouse?.name }}
/> />
</Typography> <Form
<Form confirmLeave
confirmLeave initial={initialFormData}
initial={initialFormData} onSubmit={formData =>
onSubmit={formData => handleSubmit({
handleSubmit({ formData,
formData, allowStockToBeExceeded: displayStockExceededDialog,
allowStockToBeExceeded: displayStockExceededDialog, })
}) }
} >
> {({ change, data, submit }) => (
{({ change, data, submit }) => ( <>
<>
<Grid>
<Card> <Card>
<CardTitle <CardTitle
title={intl.formatMessage(messages.itemsReadyToShip)} title={intl.formatMessage(messages.itemsReadyToShip)}
/> />
{warehouse ? ( {order ? (
<ResponsiveTable className={classes.table}> <ResponsiveTable className={classes.table}>
<TableHead> <TableHead>
<TableRow> <TableRow>
@ -223,80 +241,108 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
<TableCell className={classes.colStock}> <TableCell className={classes.colStock}>
<FormattedMessage {...messages.stock} /> <FormattedMessage {...messages.stock} />
</TableCell> </TableCell>
<TableCell className={classes.colWarehouse}>
<FormattedMessage {...messages.warehouse} />
</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{renderCollection( {renderCollection(
getToFulfillOrderLines(order?.lines), getToFulfillOrderLines(order.lines),
(line: OrderFulfillLineFragment, lineIndex) => ( (line: OrderFulfillLineFragment, lineIndex) => (
<OrderFulfillLine <OrderFulfillLine
key={line.id} key={line.id}
line={line} line={line}
lineIndex={lineIndex} lineIndex={lineIndex}
warehouseId={warehouse?.id}
formsetData={formsetData} formsetData={formsetData}
formsetChange={formsetChange} formsetChange={formsetChange}
onWarehouseChange={() =>
openModal("change-warehouse", {
lineId: line.id,
warehouseId:
formsetData[lineIndex]?.value?.[0]?.warehouse
.id,
})
}
/> />
), ),
)} )}
</TableBody> </TableBody>
</ResponsiveTable> </ResponsiveTable>
) : ( ) : (
<Skeleton /> <CardContent>
<Skeleton />
</CardContent>
)} )}
</Card> </Card>
<Card className={classes.shipmentInformationCard}> <CardSpacer />
<Typography className={classes.supportHeader}>
<FormattedMessage {...messages.shipmentInformation} /> {shopSettings?.fulfillmentAutoApprove && (
</Typography> <Card>
<TextField <CardTitle
value={data.trackingNumber} title={intl.formatMessage(messages.shipmentInformation)}
name="trackingNumber"
label={intl.formatMessage(messages.trackingNumber)}
fullWidth
onChange={change}
/>
{shopSettings?.fulfillmentAutoApprove && (
<ControlledCheckbox
checked={data.sendInfo}
label={intl.formatMessage(messages.sentShipmentDetails)}
name="sendInfo"
onChange={change}
/> />
)} <CardContent>
</Card> <ControlledCheckbox
</Grid> checked={data.sendInfo}
label={intl.formatMessage(messages.sentShipmentDetails)}
name="sendInfo"
onChange={change}
/>
</CardContent>
</Card>
)}
<Savebar <Savebar
disabled={!shouldEnableSave()} disabled={!shouldEnableSave()}
labels={{ labels={{
confirm: shopSettings?.fulfillmentAutoApprove confirm: shopSettings?.fulfillmentAutoApprove
? intl.formatMessage(messages.submitFulfillment) ? intl.formatMessage(messages.submitFulfillment)
: intl.formatMessage(messages.submitPrepareFulfillment), : intl.formatMessage(messages.submitPrepareFulfillment),
}} }}
state={saveButtonBar} state={saveButtonBar}
tooltips={{ tooltips={{
confirm: confirm:
notAllowedToFulfillUnpaid && notAllowedToFulfillUnpaid &&
intl.formatMessage(commonMessages.cannotFullfillUnpaidOrder), intl.formatMessage(
}} commonMessages.cannotFullfillUnpaidOrder,
onSubmit={submit} ),
onCancel={() => navigate(orderUrl(order?.id))} }}
/> onSubmit={submit}
<OrderFulfillStockExceededDialog onCancel={() => navigate(orderUrl(order?.id))}
open={displayStockExceededDialog} />
lines={order?.lines} <OrderFulfillStockExceededDialog
formsetData={formsetData} open={displayStockExceededDialog}
warehouseId={warehouse?.id} lines={order?.lines}
confirmButtonState={saveButtonBar} formsetData={formsetData}
onSubmit={submit} confirmButtonState={saveButtonBar}
onClose={() => setDisplayStockExceededDialog(false)} onSubmit={submit}
/> onClose={() => setDisplayStockExceededDialog(false)}
</> />
)} </>
</Form> )}
</Container> </Form>
</Container>
<OrderChangeWarehouseDialog
open={params.action === "change-warehouse"}
line={order?.lines.find(line => line.id === params.lineId)}
currentWarehouseId={params.warehouseId}
onConfirm={warehouse => {
const lineFormQuantity = formsetData.find(
item => item.id === params.lineId,
)?.value?.[0]?.quantity;
formsetChange(params.lineId, [
{
quantity: lineFormQuantity,
warehouse,
},
]);
}}
onClose={closeModal}
/>
</>
); );
}; };

View file

@ -71,14 +71,9 @@ export const messages = defineMessages({
id: "lF+VJQ", id: "lF+VJQ",
description: "Shipment information card header", description: "Shipment information card header",
}, },
trackingNumber: { warehouse: {
defaultMessage: "Tracking number", defaultMessage: "Warehouse",
id: "zbrHAw", id: "J0lNnk",
description: "Tracking number input label", description: "column label",
},
fulfillingFrom: {
defaultMessage: "Fulfilling from {warehouseName}",
id: "oiaUni",
description: "Support text under page header",
}, },
}); });

View file

@ -10,7 +10,7 @@ export const useStyles = makeStyles(
width: 180, width: 180,
}, },
colName: { colName: {
width: 250, width: 220,
}, },
colQuantity: { colQuantity: {
textAlign: "right", textAlign: "right",
@ -19,7 +19,11 @@ export const useStyles = makeStyles(
colSku: { colSku: {
textAlign: "right", textAlign: "right",
textOverflow: "ellipsis", textOverflow: "ellipsis",
width: 150, width: 100,
},
colWarehouse: {
textAlign: "right",
width: 200,
}, },
table: { table: {
"&&": { "&&": {
@ -41,9 +45,6 @@ export const useStyles = makeStyles(
lineHeight: "160%", lineHeight: "160%",
marginBottom: theme.spacing(2), marginBottom: theme.spacing(2),
}, },
warehouseLabel: {
marginBottom: theme.spacing(4),
},
}), }),
{ name: "OrderFulfillPage" }, { name: "OrderFulfillPage" },
); );

View file

@ -14,7 +14,7 @@ import { renderCollection } from "@saleor/misc";
import { import {
getFulfillmentFormsetQuantity, getFulfillmentFormsetQuantity,
getOrderLineAvailableQuantity, getOrderLineAvailableQuantity,
OrderFulfillStockInputFormsetData, OrderFulfillStockFormsetData,
} from "@saleor/orders/utils/data"; } from "@saleor/orders/utils/data";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -26,8 +26,7 @@ import { useStyles } from "./styles";
export interface OrderFulfillStockExceededDialogProps { export interface OrderFulfillStockExceededDialogProps {
lines: Array<FulfillmentFragment["lines"][0] | OrderFulfillLineFragment>; lines: Array<FulfillmentFragment["lines"][0] | OrderFulfillLineFragment>;
open: boolean; open: boolean;
formsetData: OrderFulfillStockInputFormsetData; formsetData: OrderFulfillStockFormsetData;
warehouseId: string;
confirmButtonState: ConfirmButtonTransitionState; confirmButtonState: ConfirmButtonTransitionState;
onSubmit(); onSubmit();
onClose(); onClose();
@ -38,7 +37,6 @@ const OrderFulfillStockExceededDialog: React.FC<OrderFulfillStockExceededDialogP
lines, lines,
open, open,
formsetData, formsetData,
warehouseId,
confirmButtonState, confirmButtonState,
onClose, onClose,
onSubmit, onSubmit,
@ -49,8 +47,10 @@ const OrderFulfillStockExceededDialog: React.FC<OrderFulfillStockExceededDialogP
const exceededLines = lines?.filter(el => { const exceededLines = lines?.filter(el => {
const line = "orderLine" in el ? el.orderLine : el; const line = "orderLine" in el ? el.orderLine : el;
const lineFormWarehouse = formsetData?.find(item => item.id === el.id)
?.value?.[0]?.warehouse;
const stock = line.variant?.stocks.find( const stock = line.variant?.stocks.find(
stock => stock.warehouse.id === warehouseId, stock => stock.warehouse.id === lineFormWarehouse?.id,
); );
return ( return (
@ -91,14 +91,20 @@ const OrderFulfillStockExceededDialog: React.FC<OrderFulfillStockExceededDialogP
)} )}
<TableBody> <TableBody>
{renderCollection(exceededLines, line => ( {renderCollection(exceededLines, line => {
<OrderFulfillStockExceededDialogLine const lineFormWarehouse = formsetData?.find(
key={line?.id} item => item.id === line.id,
line={line} )?.value?.[0]?.warehouse;
formsetData={formsetData}
warehouseId={warehouseId} return (
/> <OrderFulfillStockExceededDialogLine
))} key={line?.id}
line={line}
formsetData={formsetData}
warehouseId={lineFormWarehouse?.id}
/>
);
})}
</TableBody> </TableBody>
</ResponsiveTable> </ResponsiveTable>
</div> </div>

View file

@ -5,7 +5,7 @@ import {
getAttributesCaption, getAttributesCaption,
getFulfillmentFormsetQuantity, getFulfillmentFormsetQuantity,
getOrderLineAvailableQuantity, getOrderLineAvailableQuantity,
OrderFulfillStockInputFormsetData, OrderFulfillStockFormsetData,
} from "@saleor/orders/utils/data"; } from "@saleor/orders/utils/data";
import React from "react"; import React from "react";
@ -14,23 +14,24 @@ import { useStyles } from "../OrderFulfillStockExceededDialog/styles";
export interface OrderFulfillStockExceededDialogLineProps { export interface OrderFulfillStockExceededDialogLineProps {
line: OrderFulfillLineFragment | FulfillmentFragment["lines"][0]; line: OrderFulfillLineFragment | FulfillmentFragment["lines"][0];
warehouseId: string; warehouseId: string;
formsetData: OrderFulfillStockInputFormsetData; formsetData: OrderFulfillStockFormsetData;
} }
const OrderFulfillStockExceededDialogLine: React.FC<OrderFulfillStockExceededDialogLineProps> = props => { const OrderFulfillStockExceededDialogLine: React.FC<OrderFulfillStockExceededDialogLineProps> = props => {
const { line: genericLine, warehouseId, formsetData } = props; const { line: genericLine, warehouseId, formsetData } = props;
const line = "orderLine" in genericLine ? genericLine.orderLine : genericLine;
const classes = useStyles(props); const classes = useStyles(props);
const stock = line?.variant?.stocks.find(
stock => stock.warehouse.id === warehouseId,
);
if (!genericLine) { if (!genericLine) {
return null; return null;
} }
const line = "orderLine" in genericLine ? genericLine.orderLine : genericLine;
const stock = line?.variant?.stocks.find(
stock => stock.warehouse.id === warehouseId,
);
return ( return (
<TableRow key={line?.id}> <TableRow key={line?.id}>
<TableCellAvatar <TableCellAvatar

View file

@ -2,10 +2,8 @@ import { Card, CardActions, TableBody, Typography } from "@material-ui/core";
import { Button } from "@saleor/components/Button"; import { Button } from "@saleor/components/Button";
import CardSpacer from "@saleor/components/CardSpacer"; import CardSpacer from "@saleor/components/CardSpacer";
import ResponsiveTable from "@saleor/components/ResponsiveTable"; import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton"; import { OrderLineFragment } from "@saleor/graphql";
import { OrderLineFragment, WarehouseFragment } from "@saleor/graphql";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { ChevronIcon, IconButton } from "@saleor/macaw-ui";
import { renderCollection } from "@saleor/misc"; import { renderCollection } from "@saleor/misc";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
@ -20,8 +18,6 @@ interface OrderUnfulfilledProductsCardProps {
notAllowedToFulfillUnpaid: boolean; notAllowedToFulfillUnpaid: boolean;
lines: OrderLineFragment[]; lines: OrderLineFragment[];
onFulfill: () => void; onFulfill: () => void;
selectedWarehouse: WarehouseFragment;
onWarehouseChange: () => null;
} }
const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps> = props => { const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps> = props => {
@ -30,8 +26,6 @@ const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps>
notAllowedToFulfillUnpaid, notAllowedToFulfillUnpaid,
lines, lines,
onFulfill, onFulfill,
selectedWarehouse,
onWarehouseChange,
} = props; } = props;
const classes = useStyles(); const classes = useStyles();
@ -47,18 +41,6 @@ const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps>
withStatus withStatus
status="unfulfilled" status="unfulfilled"
className={classes.cardTitle} className={classes.cardTitle}
toolbar={
<IconButton
onClick={onWarehouseChange}
className={classes.toolbarButton}
data-test-id="select-warehouse-button"
>
<div className={classes.toolbarButtonContent}>
<div>{selectedWarehouse?.name ?? <Skeleton />}</div>
<ChevronIcon />
</div>
</IconButton>
}
/> />
<ResponsiveTable className={classes.table}> <ResponsiveTable className={classes.table}>
<TableHeader /> <TableHeader />

View file

@ -8,6 +8,7 @@ import {
OrderDetailsQuery, OrderDetailsQuery,
OrderEventsEmailsEnum, OrderEventsEmailsEnum,
OrderEventsEnum, OrderEventsEnum,
OrderFulfillLineFragment,
OrderListQuery, OrderListQuery,
OrderSettingsFragment, OrderSettingsFragment,
OrderStatus, OrderStatus,
@ -1078,6 +1079,7 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1186,6 +1188,7 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1302,6 +1305,7 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1395,6 +1399,7 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1628,6 +1633,7 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1721,6 +1727,7 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1869,6 +1876,7 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
user: null, user: null,
userEmail: null, userEmail: null,
}); });
export const flatOrders = orders.map(order => ({ export const flatOrders = orders.map(order => ({
...order, ...order,
orderStatus: transformOrderStatus(order.status, { orderStatus: transformOrderStatus(order.status, {
@ -1878,6 +1886,70 @@ export const flatOrders = orders.map(order => ({
formatMessage: (message: MessageDescriptor) => message.defaultMessage, formatMessage: (message: MessageDescriptor) => message.defaultMessage,
} as any), } as any),
})); }));
export const fulfillOrderLine = (
placeholderImage: string,
): OrderFulfillLineFragment => ({
__typename: "OrderLine",
id: "T3JkZXJMaW5lOjIz",
isShippingRequired: false,
productName: "Williams, Garcia and Walker (XS)",
quantity: 2,
quantityFulfilled: 2,
quantityToFulfill: 0,
allocations: [
{
id: "allocation_test_id",
warehouse: {
name: "US Warehouse",
id: "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse",
},
quantity: 1,
__typename: "Allocation",
},
],
thumbnail: {
__typename: "Image" as "Image",
url: placeholderImage,
},
variant: {
__typename: "ProductVariant",
id: "dsfsfuhb",
name: "Williams, Garcia and Walker (XS)",
sku: "5-1337",
attributes: [],
trackInventory: true,
preorder: null,
stocks: [
{
id: "stock_test_id1",
warehouse: {
name: "stock_warehouse1",
id:
"V2FyZWhvdXNlOjc4OGUyMGRlLTlmYTAtNDI5My1iZDk2LWUwM2RjY2RhMzc0ZQ==",
__typename: "Warehouse",
},
quantity: 166,
quantityAllocated: 0,
__typename: "Stock",
},
{
id: "stock_test_id2",
warehouse: {
name: "stock_warehouse2",
id:
"V2FyZWhvdXNlOjczYzI0OGNmLTliNzAtNDlmMi1hMDRlLTM4ZTYxMmQ5MDYwMQ==",
__typename: "Warehouse",
},
quantity: 166,
quantityAllocated: 0,
__typename: "Stock",
},
],
},
});
export const variants = [ export const variants = [
{ id: "p1", name: "Product 1: variant 1", sku: "12345", stockQuantity: 3 }, { id: "p1", name: "Product 1: variant 1", sku: "12345", stockQuantity: 3 },
{ id: "p2", name: "Product 1: variant 2", sku: "12346", stockQuantity: 1 }, { id: "p2", name: "Product 1: variant 2", sku: "12346", stockQuantity: 1 },

View file

@ -126,7 +126,11 @@ export type OrderUrlDialog =
export type OrderUrlQueryParams = Dialog<OrderUrlDialog> & SingleAction; export type OrderUrlQueryParams = Dialog<OrderUrlDialog> & SingleAction;
export type OrderFulfillUrlQueryParams = Partial<{ warehouse: string }>; export type OrderFulfillUrlFiltersType = "warehouseId" | "lineId";
export type OrderFulfillUrlFilters = Filters<OrderFulfillUrlFiltersType>;
export type OrderFulfillUrlDialog = "change-warehouse";
export type OrderFulfillUrlQueryParams = Dialog<OrderFulfillUrlDialog> &
OrderFulfillUrlFilters;
export const orderUrl = (id: string, params?: OrderUrlQueryParams) => export const orderUrl = (id: string, params?: OrderUrlQueryParams) =>
orderPath(encodeURIComponent(id)) + "?" + stringifyQs(params); orderPath(encodeURIComponent(id)) + "?" + stringifyQs(params);

View file

@ -525,6 +525,7 @@ describe("Get the total value of all replaced products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -619,6 +620,7 @@ describe("Get the total value of all replaced products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -713,6 +715,7 @@ describe("Get the total value of all replaced products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -813,6 +816,7 @@ describe("Get the total value of all replaced products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -912,6 +916,7 @@ describe("Get the total value of all replaced products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1011,6 +1016,7 @@ describe("Get the total value of all replaced products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1110,6 +1116,7 @@ describe("Get the total value of all replaced products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1209,6 +1216,7 @@ describe("Get the total value of all replaced products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1442,6 +1450,7 @@ describe("Get the total value of all selected products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1536,6 +1545,7 @@ describe("Get the total value of all selected products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1630,6 +1640,7 @@ describe("Get the total value of all selected products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1730,6 +1741,7 @@ describe("Get the total value of all selected products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1829,6 +1841,7 @@ describe("Get the total value of all selected products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -1928,6 +1941,7 @@ describe("Get the total value of all selected products", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -2155,6 +2169,7 @@ describe("Merge repeated order lines of fulfillment lines", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -2254,6 +2269,7 @@ describe("Merge repeated order lines of fulfillment lines", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",
@ -2353,6 +2369,7 @@ describe("Merge repeated order lines of fulfillment lines", () => {
{ {
id: "allocation_test_id", id: "allocation_test_id",
warehouse: { warehouse: {
name: "US Warehouse",
id: id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==", "V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse", __typename: "Warehouse",

View file

@ -7,7 +7,6 @@ import {
FulfillmentStatus, FulfillmentStatus,
OrderDetailsFragment, OrderDetailsFragment,
OrderFulfillLineFragment, OrderFulfillLineFragment,
OrderFulfillStockInput,
OrderLineFragment, OrderLineFragment,
OrderLineStockDataFragment, OrderLineStockDataFragment,
OrderRefundDataQuery, OrderRefundDataQuery,
@ -328,12 +327,17 @@ export const getOrderLineAvailableQuantity = (
return availableQuantity; return availableQuantity;
}; };
export type OrderFulfillStockInputFormsetData = Array< export interface OrderFulfillLineFormData {
Pick<FormsetData<null, OrderFulfillStockInput[]>[0], "id" | "value"> quantity: number;
warehouse: WarehouseFragment;
}
export type OrderFulfillStockFormsetData = Array<
Pick<FormsetData<null, OrderFulfillLineFormData[]>[0], "id" | "value">
>; >;
export const getFulfillmentFormsetQuantity = ( export const getFulfillmentFormsetQuantity = (
formsetData: OrderFulfillStockInputFormsetData, formsetData: OrderFulfillStockFormsetData,
line: OrderLineStockDataFragment, line: OrderLineStockDataFragment,
) => formsetData?.find(getById(line.id))?.value?.[0]?.quantity; ) => formsetData?.find(getById(line.id))?.value?.[0]?.quantity;
@ -343,9 +347,9 @@ export const getWarehouseStock = (
) => stocks?.find(stock => stock.warehouse.id === warehouseId); ) => stocks?.find(stock => stock.warehouse.id === warehouseId);
export const isLineAvailableInWarehouse = ( export const isLineAvailableInWarehouse = (
line: OrderLineStockDataFragment, line: OrderFulfillLineFragment | OrderLineStockDataFragment,
warehouse: WarehouseFragment, warehouse: WarehouseFragment,
) => { ): boolean => {
if (!line?.variant?.stocks) { if (!line?.variant?.stocks) {
return false; return false;
} }
@ -356,17 +360,59 @@ export const isLineAvailableInWarehouse = (
return false; return false;
}; };
export const transformFuflillmentLinesToStockInputFormsetData = ( export const getLineAvailableQuantityInWarehouse = (
line: OrderFulfillLineFragment,
warehouse: WarehouseFragment,
): number => {
if (!line?.variant?.stocks) {
return 0;
}
const stock = getWarehouseStock(line.variant.stocks, warehouse.id);
if (stock) {
return getOrderLineAvailableQuantity(line, stock);
}
return 0;
};
export const getLineAllocationWithHighestQuantity = (
line: OrderFulfillLineFragment,
): OrderFulfillLineFragment["allocations"][number] | undefined =>
line.allocations.reduce((prevAllocation, allocation) => {
if (!prevAllocation || prevAllocation.quantity < allocation.quantity) {
return allocation;
}
return prevAllocation;
}, null);
export const getWarehouseWithHighestAvailableQuantity = (
lines?: OrderLineFragment[],
): WarehouseFragment | undefined => {
let highestAvailableQuantity = 0;
return lines?.reduce(
(selectedWarehouse, line) =>
line.allocations.reduce((warehouse, allocation) => {
if (allocation.quantity > highestAvailableQuantity) {
highestAvailableQuantity = allocation.quantity;
return allocation.warehouse;
}
return warehouse;
}, selectedWarehouse),
null as WarehouseFragment,
);
};
export const transformFuflillmentLinesToStockFormsetData = (
lines: FulfillmentFragment["lines"], lines: FulfillmentFragment["lines"],
warehouseId: string, warehouse: WarehouseFragment,
): OrderFulfillStockInputFormsetData => ): OrderFulfillStockFormsetData =>
lines?.map(line => ({ lines?.map(line => ({
data: null, data: null,
id: line.orderLine.id, id: line.orderLine.id,
value: [ value: [
{ {
quantity: line.quantity, quantity: line.quantity,
warehouse: warehouseId, warehouse,
}, },
], ],
})); }));

View file

@ -9,17 +9,15 @@ import {
OrderUpdateMutationVariables, OrderUpdateMutationVariables,
useCustomerAddressesQuery, useCustomerAddressesQuery,
useWarehouseListQuery, useWarehouseListQuery,
WarehouseFragment,
} from "@saleor/graphql"; } from "@saleor/graphql";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import OrderCannotCancelOrderDialog from "@saleor/orders/components/OrderCannotCancelOrderDialog"; import OrderCannotCancelOrderDialog from "@saleor/orders/components/OrderCannotCancelOrderDialog";
import OrderChangeWarehouseDialog from "@saleor/orders/components/OrderChangeWarehouseDialog";
import { OrderCustomerAddressesEditDialogOutput } from "@saleor/orders/components/OrderCustomerAddressesEditDialog/types"; import { OrderCustomerAddressesEditDialogOutput } from "@saleor/orders/components/OrderCustomerAddressesEditDialog/types";
import OrderFulfillmentApproveDialog from "@saleor/orders/components/OrderFulfillmentApproveDialog"; import OrderFulfillmentApproveDialog from "@saleor/orders/components/OrderFulfillmentApproveDialog";
import OrderFulfillStockExceededDialog from "@saleor/orders/components/OrderFulfillStockExceededDialog"; import OrderFulfillStockExceededDialog from "@saleor/orders/components/OrderFulfillStockExceededDialog";
import OrderInvoiceEmailSendDialog from "@saleor/orders/components/OrderInvoiceEmailSendDialog"; import OrderInvoiceEmailSendDialog from "@saleor/orders/components/OrderInvoiceEmailSendDialog";
import { getById } from "@saleor/orders/components/OrderReturnPage/utils"; import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
import { transformFuflillmentLinesToStockInputFormsetData } from "@saleor/orders/utils/data"; import { transformFuflillmentLinesToStockFormsetData } from "@saleor/orders/utils/data";
import { PartialMutationProviderOutput } from "@saleor/types"; import { PartialMutationProviderOutput } from "@saleor/types";
import { mapEdgesToItems } from "@saleor/utils/maps"; import { mapEdgesToItems } from "@saleor/utils/maps";
import React from "react"; import React from "react";
@ -48,7 +46,6 @@ import {
OrderUrlQueryParams, OrderUrlQueryParams,
} from "../../../urls"; } from "../../../urls";
import { isAnyAddressEditModalOpen } from "../OrderDraftDetails"; import { isAnyAddressEditModalOpen } from "../OrderDraftDetails";
import { useDefaultWarehouse } from "./useDefaultWarehouse";
interface OrderNormalDetailsProps { interface OrderNormalDetailsProps {
id: string; id: string;
@ -107,10 +104,7 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
const shop = data?.shop; const shop = data?.shop;
const navigate = useNavigator(); const navigate = useNavigator();
const { const { data: warehousesData } = useWarehouseListQuery({
data: warehousesData,
loading: warehousesLoading,
} = useWarehouseListQuery({
displayLoader: true, displayLoader: true,
variables: { variables: {
first: 30, first: 30,
@ -119,15 +113,6 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
const warehouses = mapEdgesToItems(warehousesData?.warehouses); const warehouses = mapEdgesToItems(warehousesData?.warehouses);
const [fulfillmentWarehouse, setFulfillmentWarehouse] = React.useState<
WarehouseFragment
>(null);
useDefaultWarehouse({ warehouses, order, setter: setFulfillmentWarehouse }, [
warehousesData,
warehousesLoading,
]);
const { const {
data: customerAddresses, data: customerAddresses,
loading: customerAddressesLoading, loading: customerAddressesLoading,
@ -206,11 +191,8 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
], ],
)} )}
shippingMethods={data?.order?.shippingMethods || []} shippingMethods={data?.order?.shippingMethods || []}
selectedWarehouse={fulfillmentWarehouse}
onOrderCancel={() => openModal("cancel")} onOrderCancel={() => openModal("cancel")}
onOrderFulfill={() => onOrderFulfill={() => navigate(orderFulfillUrl(id))}
navigate(orderFulfillUrl(id, { warehouse: fulfillmentWarehouse?.id }))
}
onFulfillmentApprove={fulfillmentId => onFulfillmentApprove={fulfillmentId =>
navigate( navigate(
orderUrl(id, { orderUrl(id, {
@ -255,7 +237,6 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
}) })
} }
onInvoiceSend={id => openModal("invoice-send", { id })} onInvoiceSend={id => openModal("invoice-send", { id })}
onWarehouseChange={() => openModal("change-warehouse")}
onSubmit={handleSubmit} onSubmit={handleSubmit}
/> />
<OrderCannotCancelOrderDialog <OrderCannotCancelOrderDialog
@ -336,12 +317,11 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
/> />
<OrderFulfillStockExceededDialog <OrderFulfillStockExceededDialog
lines={currentApproval?.fulfillment.lines} lines={currentApproval?.fulfillment.lines}
formsetData={transformFuflillmentLinesToStockInputFormsetData( formsetData={transformFuflillmentLinesToStockFormsetData(
currentApproval?.fulfillment.lines, currentApproval?.fulfillment.lines,
currentApproval?.fulfillment.warehouse?.id, currentApproval?.fulfillment.warehouse,
)} )}
open={stockExceeded} open={stockExceeded}
warehouseId={currentApproval?.fulfillment.warehouse?.id}
onClose={() => setStockExceeded(false)} onClose={() => setStockExceeded(false)}
confirmButtonState="default" confirmButtonState="default"
onSubmit={() => { onSubmit={() => {
@ -391,13 +371,6 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
} }
onClose={closeModal} onClose={closeModal}
/> />
<OrderChangeWarehouseDialog
open={params.action === "change-warehouse"}
lines={order?.lines}
currentWarehouse={fulfillmentWarehouse}
onConfirm={warehouse => setFulfillmentWarehouse(warehouse)}
onClose={closeModal}
/>
<OrderInvoiceEmailSendDialog <OrderInvoiceEmailSendDialog
confirmButtonState={orderInvoiceSend.opts.status} confirmButtonState={orderInvoiceSend.opts.status}
errors={orderInvoiceSend.opts.data?.invoiceSendEmail.errors || []} errors={orderInvoiceSend.opts.data?.invoiceSendEmail.errors || []}

View file

@ -1,48 +0,0 @@
import { OrderDetailsFragment, WarehouseFragment } from "@saleor/graphql";
import {
getToFulfillOrderLines,
isLineAvailableInWarehouse,
} from "@saleor/orders/utils/data";
import React from "react";
export interface UseDefaultWarehouseOpts {
warehouses: WarehouseFragment[];
order: OrderDetailsFragment;
setter: React.Dispatch<React.SetStateAction<WarehouseFragment>>;
}
interface WarehousesAvailibility {
warehouse: WarehouseFragment;
linesAvailable: number;
}
export function useDefaultWarehouse(
{ warehouses, order, setter }: UseDefaultWarehouseOpts,
deps: unknown[],
) {
React.useEffect(() => {
const warehousesAvailability: WarehousesAvailibility[] = warehouses?.map(
warehouse => {
if (!order?.lines) {
return undefined;
}
const linesToFulfill = getToFulfillOrderLines(order.lines);
const linesAvailable = linesToFulfill.filter(line =>
isLineAvailableInWarehouse(line, warehouse),
).length;
return {
warehouse,
linesAvailable,
};
},
);
const defaultWarehouse = order?.lines
? warehousesAvailability?.reduce((prev, curr) =>
curr.linesAvailable > prev.linesAvailable ? curr : prev,
).warehouse
: undefined;
setter(defaultWarehouse);
}, [order, ...deps]);
}

View file

@ -2,9 +2,7 @@ import { WindowTitle } from "@saleor/components/WindowTitle";
import { import {
useFulfillOrderMutation, useFulfillOrderMutation,
useOrderFulfillDataQuery, useOrderFulfillDataQuery,
useOrderFulfillmentUpdateTrackingMutation,
useOrderFulfillSettingsQuery, useOrderFulfillSettingsQuery,
useWarehouseDetailsQuery,
} from "@saleor/graphql"; } from "@saleor/graphql";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
@ -12,7 +10,13 @@ import { getMutationErrors } from "@saleor/misc";
import OrderFulfillPage, { import OrderFulfillPage, {
OrderFulfillSubmitData, OrderFulfillSubmitData,
} from "@saleor/orders/components/OrderFulfillPage"; } from "@saleor/orders/components/OrderFulfillPage";
import { OrderFulfillUrlQueryParams, orderUrl } from "@saleor/orders/urls"; import {
orderFulfillUrl,
OrderFulfillUrlDialog,
OrderFulfillUrlQueryParams,
orderUrl,
} from "@saleor/orders/urls";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -26,6 +30,11 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl(); const intl = useIntl();
const [openModal, closeModal] = createDialogActionHandlers<
OrderFulfillUrlDialog,
OrderFulfillUrlQueryParams
>(navigate, params => orderFulfillUrl(orderId, params), params);
const { const {
data: settings, data: settings,
loading: settingsLoading, loading: settingsLoading,
@ -38,8 +47,6 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
}, },
}); });
const [updateTracking] = useOrderFulfillmentUpdateTrackingMutation();
const [fulfillOrder, fulfillOrderOpts] = useFulfillOrderMutation({ const [fulfillOrder, fulfillOrderOpts] = useFulfillOrderMutation({
onCompleted: data => { onCompleted: data => {
if (data.orderFulfill.errors.length === 0) { if (data.orderFulfill.errors.length === 0) {
@ -56,12 +63,6 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
}, },
}); });
const { data: warehouseData } = useWarehouseDetailsQuery({
variables: {
id: params?.warehouse,
},
});
return ( return (
<> <>
<WindowTitle <WindowTitle
@ -85,6 +86,7 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
} }
/> />
<OrderFulfillPage <OrderFulfillPage
params={params}
loading={loading || settingsLoading || fulfillOrderOpts.loading} loading={loading || settingsLoading || fulfillOrderOpts.loading}
errors={fulfillOrderOpts.data?.orderFulfill.errors} errors={fulfillOrderOpts.data?.orderFulfill.errors}
onSubmit={async (formData: OrderFulfillSubmitData) => { onSubmit={async (formData: OrderFulfillSubmitData) => {
@ -105,27 +107,13 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
}, },
}); });
const fulfillments = res?.data?.orderFulfill?.order?.fulfillments;
if (fulfillments && formData.trackingNumber) {
updateTracking({
variables: {
id: fulfillments[fulfillments.length - 1].id,
input: {
...(formData?.trackingNumber && {
trackingNumber: formData.trackingNumber,
}),
notifyCustomer:
settings?.shop?.fulfillmentAutoApprove && formData.sendInfo,
},
},
});
}
return getMutationErrors(res); return getMutationErrors(res);
}} }}
order={data?.order} order={data?.order}
saveButtonBar={fulfillOrderOpts.status} saveButtonBar={fulfillOrderOpts.status}
warehouse={warehouseData?.warehouse}
shopSettings={settings?.shop} shopSettings={settings?.shop}
openModal={openModal}
closeModal={closeModal}
/> />
</> </>
); );

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,6 @@ const order = orderFixture(placeholderImage);
const props: Omit<OrderDetailsPageProps, "classes"> = { const props: Omit<OrderDetailsPageProps, "classes"> = {
disabled: false, disabled: false,
selectedWarehouse: undefined,
onBillingAddressEdit: undefined, onBillingAddressEdit: undefined,
onFulfillmentApprove: () => undefined, onFulfillmentApprove: () => undefined,
onFulfillmentCancel: () => undefined, onFulfillmentCancel: () => undefined,
@ -39,7 +38,6 @@ const props: Omit<OrderDetailsPageProps, "classes"> = {
onProductClick: undefined, onProductClick: undefined,
onProfileView: () => undefined, onProfileView: () => undefined,
onShippingAddressEdit: undefined, onShippingAddressEdit: undefined,
onWarehouseChange: undefined,
onSubmit: () => undefined, onSubmit: () => undefined,
order, order,
shop: shopFixture, shop: shopFixture,