2020-04-20 09:37:32 +00:00
|
|
|
import React from "react";
|
|
|
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
|
|
import Card from "@material-ui/core/Card";
|
2020-04-20 16:23:54 +00:00
|
|
|
import CardActions from "@material-ui/core/CardActions";
|
2020-04-20 09:37:32 +00:00
|
|
|
import TableBody from "@material-ui/core/TableBody";
|
|
|
|
import TableCell from "@material-ui/core/TableCell";
|
|
|
|
import TableHead from "@material-ui/core/TableHead";
|
|
|
|
import TableRow from "@material-ui/core/TableRow";
|
|
|
|
import TextField from "@material-ui/core/TextField";
|
|
|
|
import classNames from "classnames";
|
2020-04-20 16:23:54 +00:00
|
|
|
import Typography from "@material-ui/core/Typography";
|
2020-04-20 09:37:32 +00:00
|
|
|
|
|
|
|
import useFormset, { FormsetData } from "@saleor/hooks/useFormset";
|
2020-04-24 11:56:28 +00:00
|
|
|
import { OrderFulfillStockInput } from "@saleor/types/globalTypes";
|
2020-04-20 09:37:32 +00:00
|
|
|
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
|
2020-04-20 16:23:54 +00:00
|
|
|
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
2020-04-20 09:37:32 +00:00
|
|
|
import Container from "@saleor/components/Container";
|
|
|
|
import PageHeader from "@saleor/components/PageHeader";
|
|
|
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
|
|
|
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
|
|
|
import Form from "@saleor/components/Form";
|
2020-04-24 11:56:28 +00:00
|
|
|
import {
|
|
|
|
OrderFulfillData_order,
|
|
|
|
OrderFulfillData_order_lines
|
|
|
|
} from "@saleor/orders/types/OrderFulfillData";
|
2020-04-20 09:37:32 +00:00
|
|
|
import CardTitle from "@saleor/components/CardTitle";
|
|
|
|
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
|
|
|
import makeStyles from "@material-ui/core/styles/makeStyles";
|
2020-04-20 16:23:54 +00:00
|
|
|
import { update } from "@saleor/utils/lists";
|
|
|
|
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
2020-04-20 17:18:20 +00:00
|
|
|
import { renderCollection } from "@saleor/misc";
|
|
|
|
import Skeleton from "@saleor/components/Skeleton";
|
2020-04-20 17:24:54 +00:00
|
|
|
import AppHeader from "@saleor/components/AppHeader";
|
2020-04-20 09:37:32 +00:00
|
|
|
|
|
|
|
const useStyles = makeStyles(
|
|
|
|
theme => ({
|
2020-04-20 16:23:54 +00:00
|
|
|
actionBar: {
|
|
|
|
flexDirection: "row",
|
|
|
|
paddingLeft: theme.spacing(2) + 2
|
|
|
|
},
|
2020-04-20 09:37:32 +00:00
|
|
|
colName: {
|
2020-04-24 11:56:28 +00:00
|
|
|
width: 250
|
2020-04-20 09:37:32 +00:00
|
|
|
},
|
|
|
|
colQuantity: {
|
2020-04-20 16:23:54 +00:00
|
|
|
width: 210
|
2020-04-20 09:37:32 +00:00
|
|
|
},
|
|
|
|
colQuantityContent: {
|
|
|
|
alignItems: "center",
|
|
|
|
display: "inline-flex"
|
|
|
|
},
|
2020-04-24 11:56:28 +00:00
|
|
|
colQuantityHeader: {
|
|
|
|
textAlign: "right"
|
|
|
|
},
|
2020-04-20 09:37:32 +00:00
|
|
|
colQuantityTotal: {
|
|
|
|
textAlign: "right",
|
|
|
|
width: 180
|
|
|
|
},
|
|
|
|
colSku: {
|
|
|
|
textAlign: "right",
|
2020-04-24 11:56:28 +00:00
|
|
|
width: 150
|
2020-04-20 09:37:32 +00:00
|
|
|
},
|
|
|
|
error: {
|
|
|
|
color: theme.palette.error.main
|
|
|
|
},
|
2020-04-20 16:23:54 +00:00
|
|
|
full: {
|
|
|
|
fontWeight: 600
|
|
|
|
},
|
|
|
|
quantityInnerInput: {
|
|
|
|
padding: "16px 0 14px 12px"
|
|
|
|
},
|
2020-04-20 09:37:32 +00:00
|
|
|
quantityInput: {
|
2020-04-20 16:23:54 +00:00
|
|
|
width: 100
|
2020-04-20 09:37:32 +00:00
|
|
|
},
|
|
|
|
remainingQuantity: {
|
2020-04-20 16:23:54 +00:00
|
|
|
marginLeft: theme.spacing()
|
2020-04-20 09:37:32 +00:00
|
|
|
},
|
|
|
|
table: {
|
|
|
|
"&&": {
|
|
|
|
tableLayout: "fixed"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
{ name: "OrderFulfillPage" }
|
|
|
|
);
|
|
|
|
|
|
|
|
interface OrderFulfillFormData {
|
|
|
|
sendInfo: boolean;
|
|
|
|
}
|
|
|
|
interface OrderFulfillSubmitData extends OrderFulfillFormData {
|
2020-04-24 11:56:28 +00:00
|
|
|
items: FormsetData<null, OrderFulfillStockInput[]>;
|
2020-04-20 09:37:32 +00:00
|
|
|
}
|
|
|
|
export interface OrderFulfillPageProps {
|
|
|
|
disabled: boolean;
|
|
|
|
order: OrderFulfillData_order;
|
|
|
|
saveButtonBar: ConfirmButtonTransitionState;
|
|
|
|
warehouses: WarehouseFragment[];
|
2020-04-20 17:18:20 +00:00
|
|
|
onBack: () => void;
|
2020-04-20 09:37:32 +00:00
|
|
|
onSubmit: (data: OrderFulfillSubmitData) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
const initialFormData: OrderFulfillFormData = {
|
|
|
|
sendInfo: true
|
|
|
|
};
|
|
|
|
|
2020-04-24 11:56:28 +00:00
|
|
|
function getRemainingQuantity(line: OrderFulfillData_order_lines): number {
|
|
|
|
return line.quantity - line.quantityFulfilled;
|
|
|
|
}
|
|
|
|
|
2020-04-20 09:37:32 +00:00
|
|
|
const OrderFulfillPage: React.FC<OrderFulfillPageProps> = ({
|
|
|
|
disabled,
|
|
|
|
order,
|
|
|
|
saveButtonBar,
|
|
|
|
warehouses,
|
|
|
|
onBack,
|
|
|
|
onSubmit
|
|
|
|
}) => {
|
|
|
|
const intl = useIntl();
|
|
|
|
const classes = useStyles({});
|
|
|
|
|
2020-04-20 16:23:54 +00:00
|
|
|
const { change: formsetChange, data: formsetData } = useFormset<
|
|
|
|
null,
|
2020-04-24 11:56:28 +00:00
|
|
|
OrderFulfillStockInput[]
|
2020-04-20 16:23:54 +00:00
|
|
|
>(
|
2020-04-24 11:56:28 +00:00
|
|
|
order?.lines
|
|
|
|
.filter(line => getRemainingQuantity(line) > 0)
|
|
|
|
.map(line => ({
|
|
|
|
data: null,
|
|
|
|
id: line.id,
|
|
|
|
label: line.variant.attributes
|
|
|
|
.map(attribute =>
|
|
|
|
attribute.values
|
|
|
|
.map(attributeValue => attributeValue.name)
|
|
|
|
.join(" , ")
|
|
|
|
)
|
|
|
|
.join(" / "),
|
|
|
|
value: line.variant.stocks.map(stock => ({
|
|
|
|
quantity: 0,
|
|
|
|
warehouse: stock.warehouse.id
|
|
|
|
}))
|
2020-04-20 09:37:32 +00:00
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
|
|
|
const handleSubmit = (formData: OrderFulfillFormData) =>
|
|
|
|
onSubmit({
|
|
|
|
...formData,
|
|
|
|
items: formsetData
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Container>
|
2020-04-20 17:24:54 +00:00
|
|
|
<AppHeader onBack={onBack}>
|
|
|
|
{order?.number
|
|
|
|
? intl.formatMessage(
|
|
|
|
{
|
|
|
|
defaultMessage: "Order #{orderNumber}",
|
|
|
|
description: "page header with order number"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
orderNumber: order.number
|
|
|
|
}
|
|
|
|
)
|
|
|
|
: intl.formatMessage({
|
|
|
|
defaultMessage: "Order",
|
|
|
|
description: "page header"
|
|
|
|
})}
|
|
|
|
</AppHeader>
|
2020-04-20 09:37:32 +00:00
|
|
|
<PageHeader
|
|
|
|
title={intl.formatMessage(
|
|
|
|
{
|
|
|
|
defaultMessage: "Order no. {orderNumber} - Add Fulfillment",
|
|
|
|
description: "page header"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
orderNumber: order?.number
|
|
|
|
}
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
<Form initial={initialFormData} onSubmit={handleSubmit}>
|
|
|
|
{({ change, data, submit }) => (
|
|
|
|
<>
|
|
|
|
<Card>
|
|
|
|
<CardTitle
|
|
|
|
title={intl.formatMessage({
|
|
|
|
defaultMessage: "Items ready to ship",
|
|
|
|
description: "header"
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
<ResponsiveTable className={classes.table}>
|
|
|
|
<TableHead>
|
|
|
|
<TableRow>
|
|
|
|
<TableCell className={classes.colName}>
|
2020-04-20 16:23:54 +00:00
|
|
|
<FormattedMessage defaultMessage="Product name" />
|
2020-04-20 09:37:32 +00:00
|
|
|
</TableCell>
|
|
|
|
<TableCell className={classes.colSku}>
|
|
|
|
<FormattedMessage
|
|
|
|
defaultMessage="SKU"
|
|
|
|
description="product's sku"
|
|
|
|
/>
|
|
|
|
</TableCell>
|
2020-04-20 17:18:20 +00:00
|
|
|
{warehouses?.map(warehouse => (
|
2020-04-20 09:37:32 +00:00
|
|
|
<TableCell
|
|
|
|
key={warehouse.id}
|
2020-04-24 11:56:28 +00:00
|
|
|
className={classNames(
|
|
|
|
classes.colQuantity,
|
|
|
|
classes.colQuantityHeader
|
|
|
|
)}
|
2020-04-20 09:37:32 +00:00
|
|
|
>
|
|
|
|
{warehouse.name}
|
|
|
|
</TableCell>
|
|
|
|
))}
|
|
|
|
<TableCell className={classes.colQuantityTotal}>
|
|
|
|
<FormattedMessage
|
|
|
|
defaultMessage="Quantity to fulfill"
|
|
|
|
description="quantity of fulfilled products"
|
|
|
|
/>
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
</TableHead>
|
|
|
|
<TableBody>
|
2020-04-24 11:56:28 +00:00
|
|
|
{renderCollection(
|
|
|
|
order?.lines.filter(line => getRemainingQuantity(line) > 0),
|
|
|
|
(line, lineIndex) => {
|
|
|
|
if (!line) {
|
|
|
|
return (
|
|
|
|
<TableRow>
|
|
|
|
<TableCellAvatar className={classes.colName}>
|
|
|
|
<Skeleton />
|
|
|
|
</TableCellAvatar>
|
|
|
|
<TableCell className={classes.colSku}>
|
2020-04-20 17:18:20 +00:00
|
|
|
<Skeleton />
|
|
|
|
</TableCell>
|
2020-04-24 11:56:28 +00:00
|
|
|
{warehouses?.map(warehouse => (
|
2020-04-20 09:37:32 +00:00
|
|
|
<TableCell
|
2020-04-24 11:56:28 +00:00
|
|
|
className={classes.colQuantity}
|
|
|
|
key={warehouse.id}
|
2020-04-20 09:37:32 +00:00
|
|
|
>
|
2020-04-24 11:56:28 +00:00
|
|
|
<Skeleton />
|
2020-04-20 09:37:32 +00:00
|
|
|
</TableCell>
|
2020-04-24 11:56:28 +00:00
|
|
|
))}
|
|
|
|
<TableCell className={classes.colQuantityTotal}>
|
|
|
|
{" "}
|
|
|
|
<Skeleton />
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const remainingQuantity = getRemainingQuantity(line);
|
|
|
|
const quantityToFulfill = formsetData[
|
|
|
|
lineIndex
|
|
|
|
].value.reduce(
|
|
|
|
(quantityToFulfill, lineInput) =>
|
|
|
|
quantityToFulfill + (lineInput.quantity || 0),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
const overfulfill = remainingQuantity < quantityToFulfill;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<TableRow key={line.id}>
|
|
|
|
<TableCellAvatar
|
|
|
|
className={classes.colName}
|
|
|
|
thumbnail={line?.thumbnail?.url}
|
|
|
|
>
|
|
|
|
{line.productName}
|
|
|
|
<Typography color="textSecondary" variant="caption">
|
|
|
|
{line.variant.attributes
|
|
|
|
.map(attribute =>
|
|
|
|
attribute.values
|
|
|
|
.map(attributeValue => attributeValue.name)
|
|
|
|
.join(", ")
|
|
|
|
)
|
|
|
|
.join(" / ")}
|
|
|
|
</Typography>
|
|
|
|
</TableCellAvatar>
|
|
|
|
<TableCell className={classes.colSku}>
|
|
|
|
{line.variant.sku}
|
|
|
|
</TableCell>
|
|
|
|
{warehouses?.map(warehouse => {
|
|
|
|
const warehouseStock = line.variant.stocks.find(
|
|
|
|
stock => stock.warehouse.id === warehouse.id
|
2020-04-20 09:37:32 +00:00
|
|
|
);
|
2020-04-24 11:56:28 +00:00
|
|
|
const formsetStock = formsetData[
|
|
|
|
lineIndex
|
|
|
|
].value.find(
|
|
|
|
line => line.warehouse === warehouse.id
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!warehouseStock) {
|
|
|
|
return (
|
|
|
|
<TableCell
|
|
|
|
className={classNames(
|
|
|
|
classes.colQuantity,
|
|
|
|
classes.error
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<FormattedMessage
|
|
|
|
defaultMessage="No Stock"
|
|
|
|
description="no variant stock in warehouse"
|
|
|
|
/>
|
|
|
|
</TableCell>
|
|
|
|
);
|
|
|
|
}
|
2020-04-20 09:37:32 +00:00
|
|
|
|
2020-04-24 11:56:28 +00:00
|
|
|
const availableQuantity =
|
|
|
|
warehouseStock.quantity -
|
|
|
|
warehouseStock.quantityAllocated;
|
2020-04-21 11:13:39 +00:00
|
|
|
|
2020-04-24 11:56:28 +00:00
|
|
|
return (
|
|
|
|
<TableCell className={classes.colQuantity}>
|
|
|
|
<div className={classes.colQuantityContent}>
|
|
|
|
<TextField
|
|
|
|
type="number"
|
|
|
|
inputProps={{
|
|
|
|
className: classes.quantityInnerInput,
|
|
|
|
max: warehouseStock.quantity,
|
|
|
|
min: 0,
|
|
|
|
style: { textAlign: "right" }
|
|
|
|
}}
|
|
|
|
className={classes.quantityInput}
|
|
|
|
value={formsetStock.quantity}
|
|
|
|
onChange={event =>
|
|
|
|
formsetChange(
|
|
|
|
line.id,
|
|
|
|
update(
|
|
|
|
{
|
|
|
|
quantity: parseInt(
|
|
|
|
event.target.value,
|
|
|
|
10
|
|
|
|
),
|
|
|
|
warehouse: warehouse.id
|
|
|
|
},
|
|
|
|
formsetData[lineIndex].value,
|
|
|
|
(a, b) => a.warehouse === b.warehouse
|
|
|
|
)
|
2020-04-20 16:23:54 +00:00
|
|
|
)
|
2020-04-24 11:56:28 +00:00
|
|
|
}
|
|
|
|
error={
|
|
|
|
overfulfill ||
|
|
|
|
formsetStock.quantity > availableQuantity
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
<div className={classes.remainingQuantity}>
|
|
|
|
/ {availableQuantity}
|
|
|
|
</div>
|
2020-04-20 09:37:32 +00:00
|
|
|
</div>
|
2020-04-24 11:56:28 +00:00
|
|
|
</TableCell>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
<TableCell className={classes.colQuantityTotal}>
|
|
|
|
<span
|
|
|
|
className={classNames({
|
|
|
|
[classes.error]: overfulfill,
|
|
|
|
[classes.full]:
|
|
|
|
remainingQuantity <= quantityToFulfill
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
{quantityToFulfill}
|
|
|
|
</span>{" "}
|
|
|
|
/ {remainingQuantity}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
)}
|
2020-04-20 09:37:32 +00:00
|
|
|
</TableBody>
|
|
|
|
</ResponsiveTable>
|
2020-04-20 16:23:54 +00:00
|
|
|
<CardActions className={classes.actionBar}>
|
|
|
|
<ControlledCheckbox
|
|
|
|
checked={data.sendInfo}
|
|
|
|
label={intl.formatMessage({
|
|
|
|
defaultMessage: "Send shipment details to customer",
|
|
|
|
description: "checkbox"
|
|
|
|
})}
|
|
|
|
name="sendInfo"
|
|
|
|
onChange={change}
|
|
|
|
/>
|
|
|
|
</CardActions>
|
2020-04-20 09:37:32 +00:00
|
|
|
</Card>
|
|
|
|
<SaveButtonBar
|
|
|
|
disabled={disabled}
|
2020-04-20 16:23:54 +00:00
|
|
|
labels={{
|
|
|
|
save: intl.formatMessage({
|
|
|
|
defaultMessage: "Fulfill",
|
|
|
|
description: "fulfill order, button"
|
|
|
|
})
|
|
|
|
}}
|
2020-04-20 09:37:32 +00:00
|
|
|
state={saveButtonBar}
|
|
|
|
onSave={submit}
|
|
|
|
onCancel={onBack}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</Form>
|
|
|
|
</Container>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
OrderFulfillPage.displayName = "OrderFulfillPage";
|
|
|
|
export default OrderFulfillPage;
|