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

View file

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

View file

@ -1227,6 +1227,7 @@ export const OrderLineFragmentDoc = gql`
quantity
warehouse {
id
name
}
}
variant {
@ -1474,9 +1475,11 @@ export const OrderFulfillLineFragmentDoc = gql`
productName
quantity
allocations {
id
quantity
warehouse {
id
name
}
}
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 { 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 Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
@ -10,8 +10,8 @@ import OrderChangeWarehouseDialog, { OrderChangeWarehouseDialogProps } from ".";
const props: OrderChangeWarehouseDialogProps = {
open: true,
lines: order("abc").lines,
currentWarehouse: null,
line: fulfillOrderLine("abc"),
currentWarehouseId: null,
onConfirm: () => null,
onClose: () => null,
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,4 +12,9 @@ export const messages = defineMessages({
id: "8vQGO0",
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,
},
colName: {
width: 250,
width: 220,
},
colQuantity: {
textAlign: "right",
@ -16,7 +16,11 @@ export const useStyles = makeStyles(
colSku: {
textAlign: "right",
textOverflow: "ellipsis",
width: 150,
width: 100,
},
colWarehouse: {
width: 200,
textAlign: "right",
},
warningIcon: {
color: theme.palette.saleor.warning.mid,
@ -42,6 +46,23 @@ export const useStyles = makeStyles(
color: theme.palette.text.secondary,
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" },
);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,10 +2,8 @@ import { Card, CardActions, TableBody, Typography } from "@material-ui/core";
import { Button } from "@saleor/components/Button";
import CardSpacer from "@saleor/components/CardSpacer";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import { OrderLineFragment, WarehouseFragment } from "@saleor/graphql";
import { OrderLineFragment } from "@saleor/graphql";
import { commonMessages } from "@saleor/intl";
import { ChevronIcon, IconButton } from "@saleor/macaw-ui";
import { renderCollection } from "@saleor/misc";
import React from "react";
import { FormattedMessage } from "react-intl";
@ -20,8 +18,6 @@ interface OrderUnfulfilledProductsCardProps {
notAllowedToFulfillUnpaid: boolean;
lines: OrderLineFragment[];
onFulfill: () => void;
selectedWarehouse: WarehouseFragment;
onWarehouseChange: () => null;
}
const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps> = props => {
@ -30,8 +26,6 @@ const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps>
notAllowedToFulfillUnpaid,
lines,
onFulfill,
selectedWarehouse,
onWarehouseChange,
} = props;
const classes = useStyles();
@ -47,18 +41,6 @@ const OrderUnfulfilledProductsCard: React.FC<OrderUnfulfilledProductsCardProps>
withStatus
status="unfulfilled"
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}>
<TableHeader />

View file

@ -8,6 +8,7 @@ import {
OrderDetailsQuery,
OrderEventsEmailsEnum,
OrderEventsEnum,
OrderFulfillLineFragment,
OrderListQuery,
OrderSettingsFragment,
OrderStatus,
@ -1078,6 +1079,7 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
{
id: "allocation_test_id",
warehouse: {
name: "US Warehouse",
id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse",
@ -1186,6 +1188,7 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
{
id: "allocation_test_id",
warehouse: {
name: "US Warehouse",
id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse",
@ -1302,6 +1305,7 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
{
id: "allocation_test_id",
warehouse: {
name: "US Warehouse",
id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse",
@ -1395,6 +1399,7 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
{
id: "allocation_test_id",
warehouse: {
name: "US Warehouse",
id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse",
@ -1628,6 +1633,7 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
{
id: "allocation_test_id",
warehouse: {
name: "US Warehouse",
id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse",
@ -1721,6 +1727,7 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
{
id: "allocation_test_id",
warehouse: {
name: "US Warehouse",
id:
"V2FyZWhvdXNlOjk1NWY0ZDk2LWRmNTAtNGY0Zi1hOTM4LWM5MTYzYTA4YTViNg==",
__typename: "Warehouse",
@ -1869,6 +1876,7 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
user: null,
userEmail: null,
});
export const flatOrders = orders.map(order => ({
...order,
orderStatus: transformOrderStatus(order.status, {
@ -1878,6 +1886,70 @@ export const flatOrders = orders.map(order => ({
formatMessage: (message: MessageDescriptor) => message.defaultMessage,
} 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 = [
{ id: "p1", name: "Product 1: variant 1", sku: "12345", stockQuantity: 3 },
{ 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 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) =>
orderPath(encodeURIComponent(id)) + "?" + stringifyQs(params);

View file

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

View file

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

View file

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

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 {
useFulfillOrderMutation,
useOrderFulfillDataQuery,
useOrderFulfillmentUpdateTrackingMutation,
useOrderFulfillSettingsQuery,
useWarehouseDetailsQuery,
} from "@saleor/graphql";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
@ -12,7 +10,13 @@ import { getMutationErrors } from "@saleor/misc";
import OrderFulfillPage, {
OrderFulfillSubmitData,
} 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 { useIntl } from "react-intl";
@ -26,6 +30,11 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
const notify = useNotifier();
const intl = useIntl();
const [openModal, closeModal] = createDialogActionHandlers<
OrderFulfillUrlDialog,
OrderFulfillUrlQueryParams
>(navigate, params => orderFulfillUrl(orderId, params), params);
const {
data: settings,
loading: settingsLoading,
@ -38,8 +47,6 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
},
});
const [updateTracking] = useOrderFulfillmentUpdateTrackingMutation();
const [fulfillOrder, fulfillOrderOpts] = useFulfillOrderMutation({
onCompleted: data => {
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 (
<>
<WindowTitle
@ -85,6 +86,7 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId, params }) => {
}
/>
<OrderFulfillPage
params={params}
loading={loading || settingsLoading || fulfillOrderOpts.loading}
errors={fulfillOrderOpts.data?.orderFulfill.errors}
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);
}}
order={data?.order}
saveButtonBar={fulfillOrderOpts.status}
warehouse={warehouseData?.warehouse}
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"> = {
disabled: false,
selectedWarehouse: undefined,
onBillingAddressEdit: undefined,
onFulfillmentApprove: () => undefined,
onFulfillmentCancel: () => undefined,
@ -39,7 +38,6 @@ const props: Omit<OrderDetailsPageProps, "classes"> = {
onProductClick: undefined,
onProfileView: () => undefined,
onShippingAddressEdit: undefined,
onWarehouseChange: undefined,
onSubmit: () => undefined,
order,
shop: shopFixture,