skyouc
2025-04-29 c3bd7262ff89c7594ec368f76ea910e6212769af
1. 出库单新增修改优化
13个文件已修改
3个文件已添加
1738 ■■■■■ 已修改文件
rsf-admin/src/i18n/en.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/zh.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/components/DictSelect.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/orders/outStock/AsnOrderModal.jsx 643 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/orders/outStock/AsnWareModal.jsx 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/orders/outStock/OutOrderCreate.jsx 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/orders/outStock/OutOrderList.jsx 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/orders/outStock/SelectMatnrModal.jsx 613 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/interceptor/severlet/CustomParameterFilter.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/OutStockService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/en.js
@@ -56,6 +56,7 @@
            expand: 'Expand',
            expandAll: 'Expand All',
            collapse: 'Collapse',
            newAddMats: 'New Mats',
            collapseAll: 'Collapse All',
            scope: 'Assign',
            import: {
rsf-admin/src/i18n/zh.js
@@ -56,6 +56,7 @@
            expand: '展开',
            expandAll: '全部展开',
            collapse: '折叠',
            newAddMats: '新增物料',
            collapseAll: '全部折叠',
            scope: '权限',
            inputPlaceholder: '站点可填多个,中间以英文逗号区分(,)',
rsf-admin/src/page/components/DictSelect.jsx
@@ -44,6 +44,7 @@
            <Select
                labelId="demo-select-small-label"
                value={validValue}
                variant="filled"
                onChange={handleChange}
                size='small'
            >
rsf-admin/src/page/orders/outStock/AsnOrderModal.jsx
New file
@@ -0,0 +1,643 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
    CreateBase,
    useTranslate,
    TextInput,
    NumberInput,
    BooleanInput,
    DateInput,
    SaveButton,
    SelectInput,
    ReferenceInput,
    ReferenceArrayInput,
    AutocompleteInput,
    Toolbar,
    required,
    useDataProvider,
    useNotify,
    Form,
    useCreateController,
    useListContext,
    useRefresh,
} from 'react-admin';
import {
    Dialog,
    DialogActions,
    DialogContent,
    DialogTitle,
    Stack,
    Grid,
    TextField,
    Box,
    Button,
    Paper,
    TableContainer,
    Table,
    TableHead,
    TableBody,
    TableRow,
    TableCell,
    Tooltip,
    IconButton,
    styled,
    Select,
    MenuItem
} from '@mui/material';
import DialogCloseButton from "../../components/DialogCloseButton";
import StatusSelectInput from "../../components/StatusSelectInput";
import ConfirmButton from "../../components/ConfirmButton";
import AsnWareModal from "./AsnWareModal";
import { useForm, Controller, useWatch, FormProvider, useFormContext } from "react-hook-form";
import SaveIcon from '@mui/icons-material/Save';
import request from '@/utils/request';
import { Add, Edit, Delete } from '@mui/icons-material';
import _, { set } from 'lodash';
import { DataGrid, useGridApiRef } from '@mui/x-data-grid';
import DictionarySelect from "../../components/DictionarySelect";
import DictSelect from "../../components/DictSelect";
import "./asnOrder.css";
const AsnOrderModal = (props) => {
    const { open, setOpen, asnId, billReload } = props;
    const translate = useTranslate();
    const notify = useNotify();
    const refresh = useRefresh();
    const [disabled, setDisabled] = useState(false)
    const [createDialog, setCreateDialog] = useState(false);
    const tableRef = useRef();
    useEffect(() => {
        if (open && asnId !== 0) {
            requestGetHead()
            requestGetBody()
        }
        setDisabled(false)
    }, [open])
    const handleClose = (event, reason) => {
        if (reason !== "backdropClick") {
            setOpen(false);
            refresh();
            setFormData({ type: '', wkType: '' })
            setTableData([])
        }
    };
    const [formData, setFormData] = useState({
        type: '',
        wkType: '',
        poCode: '',
        logisNo: '',
        arrTime: ''
    });
    const [tabelData, setTableData] = useState([]);
    const handleChange = (value, name) => {
        setFormData((prevData) => ({
            ...prevData,
            [name]: value
        }));
        console.log(formData);
    };
    const resetData = () => {
        setFormData({
            type: '',
            wkType: '',
            poCode: '',
            logisNo: '',
            arrTime: ''
        })
        setTableData([])
    }
    const setFinally = () => {
        const rows = tableRef.current.state.editRows;
        for (const key in rows) {
            const find = tabelData.find(item => item.matnrId === +key);
            find.anfme = rows[key].anfme.value;
        }
        setTableData([...tabelData]);
    }
    const handleSubmit = async () => {
        setFinally()
        setDisabled(true)
        if (asnId === 0) {
            const parmas = {
                "orders": formData,
                "items": tabelData,
            }
            const res = await request.post(`/asnOrder/items/save`, parmas);
            if (res?.data?.code === 200) {
                setOpen(false);
                refresh();
                billReload?.current()
                resetData()
            } else {
                notify(res.data.msg);
            }
        } else {
            const parmas = {
                "orders": formData,
                "items": tabelData,
            }
            const res = await request.post(`/asnOrder/items/update`, parmas);
            if (res?.data?.code === 200) {
                setOpen(false);
                refresh();
                billReload?.current()
                resetData()
            } else {
                notify(res.data.msg);
            }
        }
        setDisabled(false)
    };
    const handleDelete = async () => {
        const res = await request.post(`/asnOrder/remove/${asnId}`);
        if (res?.data?.code === 200) {
            setOpen(false);
            refresh();
        } else {
            notify(res.data.msg);
        }
    };
    const requestGetHead = async () => {
        const res = await request.get(`/asnOrder/${asnId}`);
        if (res?.data?.code === 200) {
            setFormData(res.data.data)
        } else {
            notify(res.data.msg);
        }
    }
    const requestGetBody = async () => {
        const res = await request.post(`/asnOrderItem/page`, { asnId });
        if (res?.data?.code === 200) {
            setTableData(res.data.data.records)
        } else {
            notify(res.data.msg);
        }
    }
    const [selectedRows, setSelectedRows] = useState([]);
    const handleDeleteItem = () => {
        const newTableData = _.filter(tabelData, (item) => !selectedRows.includes(item.matnrId));
        setTableData(newTableData);
    }
    return (
        <>
            <Dialog
                open={open}
                onClose={handleClose}
                aria-labelledby="form-dialog-title"
                aria-hidden
                fullWidth
                disableRestoreFocus
                maxWidth="lg"   // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
            >
                <DialogTitle id="form-dialog-title" sx={{
                    position: 'sticky',
                    top: 0,
                    backgroundColor: 'background.paper',
                    zIndex: 1000
                }}>
                    {translate('create.title')}
                    <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
                        <DialogCloseButton onClose={handleClose} />
                    </Box>
                </DialogTitle>
                <DialogContent sx={{ mt: 2 }}>
                    <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                        <Form defaultValues={formData}>
                            <Grid container spacing={2}>
                                <Grid item md={3}>
                                    <DictSelect
                                        label={translate("table.field.asnOrder.type")}
                                        value={formData.type}
                                        onChange={(e) => handleChange(e.target.value, 'type')}
                                        dictTypeCode="sys_order_type"
                                        required
                                    />
                                </Grid>
                                <Grid item md={3}>
                                    <DictSelect
                                        label={translate("table.field.asnOrder.wkType")}
                                        value={formData.wkType}
                                        onChange={(e) => handleChange(e.target.value, 'wkType')}
                                        dictTypeCode="sys_business_type"
                                        required
                                    />
                                </Grid>
                                <Grid item md={3}>
                                    <TextField
                                        label={translate("table.field.asnOrder.poCode")}
                                        value={formData.poCode}
                                        onChange={(e) => handleChange(e.target.value, 'poCode')}
                                    />
                                </Grid>
                                <Grid item md={3}>
                                    <TextField
                                        label={translate("table.field.asnOrder.logisNo")}
                                        value={formData.logisNo}
                                        onChange={(e) => handleChange(e.target.value, 'logisNo')}
                                    />
                                </Grid>
                                <Grid item md={3}>
                                    {/* <TextField
                                        label={translate("table.field.asnOrder.arrTime")}
                                        value={formData.arrTime}
                                        onChange={(e) => handleChange(e.target.value, 'arrTime')}
                                    /> */}
                                    <DateInput
                                        source="arrTime"
                                        label="table.field.asnOrder.arrTime"
                                        value={formData.arrTime}
                                        onChange={(e) => handleChange(e.target.value, 'arrTime')}
                                    />
                                </Grid>
                            </Grid>
                        </Form>
                    </Box>
                    <Box sx={{ mt: 2 }}>
                        <Stack direction="row" spacing={2}>
                            <Button variant="contained" onClick={() => setCreateDialog(true)}>新增物料</Button>
                            {/* {asnId !== '' && <ConfirmButton label={'删除'} variant="outlined" color="error" onConfirm={handleDelete} />} */}
                            <ConfirmButton label={'删除'} variant="outlined" color="error" onConfirm={handleDeleteItem} />
                        </Stack>
                    </Box>
                    <Box sx={{ mt: 2 }}>
                        <AsnOrderModalTable tabelData={tabelData} setTableData={setTableData} asnId={asnId} selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef}></AsnOrderModalTable>
                    </Box>
                </DialogContent>
                <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
                    <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}  >
                        <Button disabled={disabled} onClick={handleSubmit} variant="contained" startIcon={<SaveIcon />}>
                            {translate('toolbar.confirm')}
                        </Button>
                    </Toolbar>
                </DialogActions>
            </Dialog>
            <AsnWareModal
                open={createDialog}
                setOpen={setCreateDialog}
                data={tabelData}
                setData={setTableData}
            />
        </>
    )
}
export default AsnOrderModal;
const SelectInputSplrNameEditCell = (params) => {
    const [formData, setFormData] = useState([{}])
    useEffect(() => {
        getOptions();
    }, []);
    const getOptions = async () => {
        const parmas = {
            "type": "supplier"
        }
        const {
            data: { code, data, msg },
        } = await request.post("companys/page",parmas);
        if (code === 200) {
            setFormData(data.records)
            console.log(data.records)
        } else {
            notify(msg);
        }
    }
    return (
        <Select
        value={params.value}
        onChange={(e) =>{
            params.api.setEditCellValue({
                id: params.id,
                field: params.field,
                value: e.target.value,
              })
              // 找到选中的供应商记录
          const selectedSupplier = formData.find(supplier => supplier.name === e.target.value);
          // 如果找到对应的供应商记录,同时更新splrCode字段
          if (selectedSupplier) {
            params.api.setEditCellValue({
              id: params.id,
              field: 'splrCode',
              value: selectedSupplier.id,
            });
          }
        }
        }
        fullWidth
      >
          {formData.map(e => {
            return(
                <MenuItem value={e.name} children={e.name} key={e.id} />
            );
          })}
      </Select>
    );
  };
  const SelectInputSplrCodeEditCell = (params) => {
    const [formData, setFormData] = useState([{}])
    useEffect(() => {
        getOptions();
    }, []);
    const getOptions = async () => {
        const parmas = {
            "type": "supplier"
        }
        const {
            data: { code, data, msg },
        } = await request.post("companys/page",parmas);
        if (code === 200) {
            setFormData(data.records)
            console.log(data.records)
        } else {
            notify(msg);
        }
    }
    return (
        <Select
        value={params.value}
        onChange={(e) =>{
            params.api.setEditCellValue({
                id: params.id,
                field: params.field,
                value: e.target.value,
              })
              const selectedSupplier = formData.find(supplier => supplier.id === e.target.value);
              // 如果找到对应的供应商记录,同时更新splrCode字段
              if (selectedSupplier) {
                params.api.setEditCellValue({
                  id: params.id,
                  field: 'splrName',
                  value: selectedSupplier.name,
                });
              }
        }
        }
        fullWidth
      >
          {formData.map(e => {
            return(
                <MenuItem value={e.id} children={e.name} key={e.id} />
            );
          })}
      </Select>
    );
  };
const AsnOrderModalTable = ({ tabelData, setTableData, asnId, selectedRows, setSelectedRows, tableRef }) => {
    const translate = useTranslate();
    const notify = useNotify();
    const [columns, setColumns] = useState([
        {
            field: 'maktx',
            headerName: translate('table.field.asnOrderItem.maktx'),
            width: 250,
            editable: false,
        },
        {
            field: 'matnrCode',
            headerName: translate('table.field.asnOrderItem.matnrCode'),
            width: 130,
            editable: false,
        },
        {
            field: 'anfme',
            headerName: translate('table.field.asnOrderItem.anfme')+"*",
            type: 'number',
            minWidth: 100,
            flex: 1,
            editable: true,
            valueFormatter: (val) => val < 0 ? 0 : val,
            headerClassName: "custom",
        },
        {
            field: 'splrCode',
            headerName: translate('table.field.asnOrderItem.splrCode')+"*",
            minWidth: 100,
            flex: 1,
            editable: true,
            renderEditCell: (params) => (
                <SelectInputSplrCodeEditCell {...params} />
            ),
            headerClassName: "custom",
        },
        {
            field: 'splrName',
            headerName: translate('table.field.asnOrderItem.splrName')+"*",
            minWidth: 100,
            flex: 1,
            editable: true,
            renderEditCell: (params) => (
                <SelectInputSplrNameEditCell {...params} />
            ),
            headerClassName: "custom",
        },
        // {
        //     field: 'packName',
        //     headerName: translate('table.field.asnOrderItem.packName'),
        //     minWidth: 100,
        //     flex: 1,
        //     editable: true,
        // },
        // {
        //     field: 'poDetlId',
        //     headerName: translate('table.field.asnOrderItem.poDetlId'),
        //     minWidth: 100,
        //     flex: 1,
        // },
        {
            field: 'poCode',
            headerName: translate('table.field.asnOrderItem.poDetlCode')+"*",
            minWidth: 100,
            flex: 1,
            editable: true,
            headerClassName: "custom",
        },
        {
            field: 'stockUnit',
            headerName: translate('table.field.asnOrderItem.stockUnit'),
            minWidth: 100,
            flex: 1,
            editable: false,
        },
        // {
        //     field: 'purQty',
        //     headerName: translate('table.field.asnOrderItem.purQty'),
        //     minWidth: 100,
        //     flex: 1,
        //     editable: true,
        // },
        {
            field: 'purUnit',
            headerName: translate('table.field.asnOrderItem.purUnit'),
            minWidth: 100,
            flex: 1,
            editable: false,
        },
    ])
    const action = {
        field: 'action',
        headerName: '操作',
        width: 70,
        lockPosition: 'left',
        renderCell: (params) => (
            <Tooltip title="Delete">
                <IconButton onClick={() => handleDelete(params.row)}>
                    <Delete />
                </IconButton>
            </Tooltip>
        ),
    }
    let cdata = useRef([]);
    useEffect(() => {
        getDynamicFields();
    }, []);
    useEffect(() => {
        cdata.current = tabelData
    }, [tabelData]);
    const getDynamicFields = async () => {
        const {
            data: { code, data, msg },
        } = await request.get("/fields/enable/list");
        if (code === 200) {
            const cols = data.map(el => ({
                field: el.fields,
                headerName: el.fieldsAlise,
                minWidth: 100,
                flex: 1,
                editable: false
            }))
            setColumns([...columns, ...cols, action])
        } else {
            notify(msg);
        }
    }
    const handleDelete = (row) => {
        const newData = _.filter(cdata.current, (item) => item.matnrId !== row.matnrId);
        setTableData(newData);
    };
    const processRowUpdate = (newRow, oldRow) => {
        const rows = tabelData.map((r) =>
            r.matnrId === newRow.matnrId ? { ...newRow } : r
        )
        setTableData(rows)
        // setTableData((prevData) =>
        //     prevData.map((r) =>
        //         r.matnrId === newRow.matnrId ? { ...newRow } : r
        //     )
        // );
        return newRow;
    };
    const handleSelectionChange = (ids) => {
        setSelectedRows(ids)
    };
    tableRef.current = useGridApiRef();
    return (
        <div style={{ height: 400, width: '100%' }}>
            <DataGrid
                apiRef={tableRef}
                rows={tabelData}
                columns={columns}
                disableRowSelectionOnClick
                getRowId={(row) => row.matnrId}
                disableColumnFilter
                disableColumnSelector
                disableColumnSorting
                disableMultipleColumnsSorting
                processRowUpdate={processRowUpdate}
                initialState={{
                    pagination: {
                        paginationModel: {
                            pageSize: 25,
                        },
                    },
                }}
                pageSizeOptions={[10, 25, 50, 100]}
                editMode="row"
                checkboxSelection
                onRowSelectionModelChange={handleSelectionChange}
                selectionModel={selectedRows}
                sx={{
                    '& .MuiDataGrid-cell input': {
                        border: '1px solid #ccc'
                    },
                }}
            />
        </div>
    );
};
rsf-admin/src/page/orders/outStock/AsnWareModal.jsx
New file
@@ -0,0 +1,249 @@
import React, { useState, useEffect } from "react";
import {
    Dialog,
    DialogActions,
    DialogContent,
    DialogTitle,
    Stack,
    Grid,
    TextField,
    Box,
    Button,
    Paper,
    styled
} from '@mui/material';
import DialogCloseButton from "../../components/DialogCloseButton";
import { useTranslate, useNotify, useRefresh } from 'react-admin';
import request from '@/utils/request';
import { DataGrid } from '@mui/x-data-grid';
import SaveIcon from '@mui/icons-material/Save';
import TreeSelectInput from "@/page/components/TreeSelectInput";
const AsnWareModal = (props) => {
    const { open, setOpen, data, setData } = props;
    const translate = useTranslate();
    const notify = useNotify();
    const refresh = useRefresh();
    const handleClose = (event, reason) => {
        if (reason !== "backdropClick") {
            setOpen(false);
        }
    };
    const [formData, setFormData] = useState({});
    const [tableData, setTableData] = useState([]);
    const [dyFields, setDyFields] = useState([]);
    const [selectedRows, setSelectedRows] = useState([]);
    const handleChange = (e) => {
        const { name, value } = e.target;
        setFormData(() => ({
            [name]: value
        }));
    };
    const reset = () => {
        setFormData({
            name: '',
            code: '',
            groupId: 0
        })
    }
    const handleSubmit = () => {
        const hasarr = data.map(el => +el.matnrId)
        const selectedData = selectedRows.filter(item => !hasarr.includes(item)).map(id => (tableData.find(row => row.id === id)));
        const value = selectedData.map((el => {
            const dynamicFields = dyFields.reduce((acc, item) => {
                acc[item.fields] = el['extendFields']?.[item.fields] || '';
                return acc;
            }, {});
            return {
                matnrId: el.id,
                maktx: el.name,
                matnrCode: el.code,
                stockUnit: el.stockUnit || '',
                purUnit: el.purchaseUnit || '',
                ...dynamicFields
            }
        }))
        setData([...data, ...value]);
        setOpen(false);
        reset();
    };
    const getData = async () => {
        const res = await request.post(`/matnr/page`, {
            ...formData,
            current: 1,
            pageSize: 100,
            orderBy: "create_time desc"
        });
        if (res?.data?.code === 200) {
            setTableData(res.data.data.records);
        } else {
            notify(res.data.msg);
        }
    };
    useEffect(() => {
        getData();
    }, [open]);
    const handleSearch = () => {
        getData()
    };
    return (
        <Dialog
            open={open}
            onClose={handleClose}
            aria-labelledby="form-dialog-title"
            fullWidth
            disableRestoreFocus
            maxWidth="lg"
        >
            <DialogTitle id="form-dialog-title" sx={{
                position: 'sticky',
                top: 0,
                backgroundColor: 'background.paper',
                zIndex: 1000
            }}>
                选择物料
                <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
                    <DialogCloseButton onClose={handleClose} />
                </Box>
            </DialogTitle>
            <DialogContent sx={{ mt: 2 }}>
                <Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                    <Grid container spacing={2}>
                        <Grid item md={4}>
                            <TextField
                                label={translate('table.field.matnr.name')}
                                name="name"
                                value={formData.name}
                                onChange={handleChange}
                                size="small"
                            />
                        </Grid>
                        <Grid item md={4}>
                            <TextField
                                label={translate('table.field.matnr.code')}
                                name="code"
                                value={formData.code}
                                onChange={handleChange}
                                size="small"
                            />
                        </Grid>
                        <Grid item md={4}>
                            <TreeSelectInput
                                label="table.field.matnr.groupId"
                                value={formData.groupId}
                                resource={'matnrGroup'}
                                source="groupId"
                                name="groupId"
                                onChange={handleChange}
                            />
                        </Grid>
                    </Grid>
                </Box>
                <Box sx={{ mt: 2 }}>
                    <Stack direction="row" spacing={2}>
                        <Button variant="contained" onClick={handleSearch}>搜索</Button>
                    </Stack>
                </Box>
                <Box sx={{ mt: 2, height: 400, width: '100%' }}>
                    <AsnWareModalTable
                        tableData={tableData}
                        setTableData={setTableData}
                        dyFields={dyFields}
                        setDyFields={setDyFields}
                        selectedRows={selectedRows}
                        setSelectedRows={setSelectedRows}
                    />
                </Box>
            </DialogContent>
            <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
                <Box sx={{ width: '100%', display: 'flex', justifyContent: 'space-between' }}>
                    <Button onClick={handleSubmit} variant="contained" startIcon={<SaveIcon />}>
                        {translate('toolbar.confirm')}
                    </Button>
                </Box>
            </DialogActions>
        </Dialog>
    );
};
export default AsnWareModal;
const AsnWareModalTable = ({ tableData, setTableData, selectedRows, setSelectedRows, dyFields, setDyFields }) => {
    const translate = useTranslate();
    const notify = useNotify();
    const [columns, setColumns] = useState([
        // { field: 'id', headerName: 'ID', width: 100 },
        { field: 'name', headerName: translate('table.field.matnr.name'), width: 300 },
        { field: 'code', headerName: translate('table.field.matnr.code'), width: 200 },
        { field: 'groupId$', headerName: translate('table.field.matnr.groupId'), width: 100 },
        { field: 'spec', headerName: translate('table.field.matnr.spec'), width: 100 },
        { field: 'model', headerName: translate('table.field.matnr.model'), width: 100 },
        { field: 'weight', headerName: translate('table.field.matnr.weight'), width: 100 },
        { field: 'describle', headerName: translate('table.field.matnr.describle'), width: 100 },
        { field: 'nromNum', headerName: translate('table.field.matnr.nromNum'), width: 100 },
        { field: 'unit', headerName: translate('table.field.matnr.unit'), width: 100 },
        { field: 'purchaseUnit', headerName: translate('table.field.matnr.purUnit'), width: 100 },
        { field: 'stockUnit', headerName: translate('table.field.matnr.stockUnit'), width: 100 },
        { field: 'stockLeval$', headerName: translate('table.field.matnr.stockLevel'), width: 100, sortable: false },
    ])
    const handleSelectionChange = (ids) => {
        setSelectedRows(ids)
    };
    useEffect(() => {
        getDynamicFields();
    }, []);
    const getDynamicFields = async () => {
        const {
            data: { code, data, msg },
        } = await request.get("/fields/enable/list");
        if (code === 200) {
            const cols = data.map(el => ({
                field: el.fields,
                headerName: el.fieldsAlise,
                minWidth: 100,
                flex: 1,
                editable: el.unique,
                valueGetter: (value, row) => {
                    return row.extendFields?.[el.fields] || '';
                },
            }))
            setDyFields(data)
            setColumns([...columns, ...cols])
        } else {
            notify(msg);
        }
    }
    return (
        <div style={{ height: 400, width: '100%' }}>
            <DataGrid
                size="small"
                rows={tableData}
                columns={columns}
                checkboxSelection
                onRowSelectionModelChange={handleSelectionChange}
                selectionModel={selectedRows}
                disableColumnMenu={true}
                disableColumnSorting
                disableMultipleColumnsSorting
            />
        </div>
    );
};
rsf-admin/src/page/orders/outStock/OutOrderCreate.jsx
@@ -27,6 +27,8 @@
    useList,
    ListContextProvider,
    useListContext,
    Button,
    useRecordContext,
} from 'react-admin';
import {
    Dialog,
@@ -43,7 +45,8 @@
import StatusSelectInput from "../../components/StatusSelectInput";
import OutOrderItemList from "./OutOrderItemList";
import MemoInput from "../../components/MemoInput";
import AddIcon from '@mui/icons-material/Add';
import SelectMatnrModal from "./SelectMatnrModal";
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
    '& .css-1vooibu-MuiSvgIcon-root': {
@@ -67,6 +70,7 @@
    const translate = useTranslate();
    const notify = useNotify();
    const [drawerVal, setDrawerVal] = useState(false);
    const [matCreate, setMatCreate] = useState(false);
    const handleClose = (event, reason) => {
        if (reason !== "backdropClick") {
@@ -120,7 +124,7 @@
                        </DialogTitle>
                        <DialogContent sx={{ mt: 2 }}>
                            <>
                                <Grid container rowSpacing={2} columnSpacing={2}>
                                <Grid container>
                                    <Grid item xs={12} display="flex" gap={1}>
                                        <TextInput
                                            label="table.field.asnOrder.poCode"
@@ -154,12 +158,13 @@
                                            source="anfme"
                                            validate={required()}
                                        />
                                    </Grid>
                                    <Grid item xs={12} display="flex" gap={1}>
                                        <NumberInput
                                            label="table.field.asnOrder.qty"
                                            source="qty"
                                        />
                                    </Grid>
                                    <Grid item xs={12} display="flex" gap={1}>
                                        <TextInput
                                            label="table.field.asnOrder.logisNo"
                                            source="logisNo"
@@ -179,64 +184,15 @@
                                        />
                                    </Grid>
                                </Grid>
                                <SelectMatnrModal open={matCreate} setOpen={setMatCreate} />
                                <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
                                    <Toolbar sx={{ width: '100%', justifyContent: 'end' }}  >
                                        <AddOutOrderButton setMatCreate={setMatCreate} />
                                        <SaveButton />
                                    </Toolbar>
                                </DialogActions>
                                <Box>
                                    <ListContextProvider
                                        // resource="waveItemÏ"
                                        value={listContext}
                                        sx={{
                                            flexGrow: 1,
                                            transition: (theme) =>
                                                theme.transitions.create(['all'], {
                                                    duration: theme.transitions.duration.enteringScreen,
                                                }),
                                            marginRight: !!drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0,
                                        }}
                                        title={"menu.waveItem"}
                                        empty={false}
                                        sort={{ field: "create_time", order: "desc" }}
                                        filters={false}
                                        actions={(
                                            <TopToolbar>
                                                <SelectColumnsButton preferenceKey='waveItem' />
                                            </TopToolbar>
                                        )}
                                        perPage={DEFAULT_PAGE_SIZE}
                                    >
                                        <StyledDatagrid
                                            preferenceKey='waveItem'
                                            bulkActionButtons={false}
                                            rowClick={(id, resource, record) => false}
                                            expand={false}
                                            expandSingle={false}
                                            omit={['id', 'createTime', 'matnrId', 'waveId', 'batch', 'orderItemId', 'unit', 'batch', 'trackCode', 'fieldsIndex', 'createBy', 'memo']}
                                        >
                                            <NumberField source="id" />
                                            <NumberField source="waveId" label="table.field.waveItem.waveId" />
                                            <TextField source="waveCode" label="table.field.waveItem.waveCode" />
                                            <TextField source="orderCode" label="table.field.waveItem.orderCode" />
                                            <NumberField source="matnrId" label="table.field.waveItem.matnrId" />
                                            <TextField source="matnrCode" label="table.field.waveItem.matnrCode" />
                                            <TextField source="batch" label="table.field.waveItem.batch" />
                                            <TextField source="splrBatch" label="table.field.waveItem.splrBatch" />
                                            <NumberField source="orderItemId" label="table.field.waveItem.orderItemId" />
                                            <TextField source="unit" label="table.field.waveItem.unit" />
                                            <TextField source="trackCode" label="table.field.waveItem.trackCode" />
                                            <TextField source="fieldsIndex" label="table.field.waveItem.fieldsIndex" />
                                            <NumberField source="anfme" label="table.field.waveItem.anfme" />
                                            <NumberField source="workQty" label="table.field.waveItem.workQty" />
                                            <NumberField source="qty" label="table.field.waveItem.qty" />
                                            <NumberField source="stockQty" label="table.field.waveItem.stockQty" />
                                            <WrapperField cellClassName="opt" label="table.field.waveItem.stockLocs">
                                                {/* <TagsField /> */}
                                            </WrapperField>
                                        </StyledDatagrid>
                                    </ListContextProvider>
                                </Box>
                            </>
                        </DialogContent>
@@ -248,3 +204,18 @@
}
export default OutOrderCreate;
const AddOutOrderButton = (setMatCreate) => {
    const record = useRecordContext();
    const addMats = (event) => {
        event.stopPropagation();
        setMatCreate(true)
    }
    return (
        <Button label={"common.action.newAddMats"} onClick={addMats} variant="contained" sx={{ padding: '0.6em', marginRight: '1em' }}>
            <AddIcon />
        </Button>
    );
}
rsf-admin/src/page/orders/outStock/OutOrderList.jsx
@@ -56,6 +56,7 @@
import AddIcon from '@mui/icons-material/Add';
import OutOrderModal from "./OutOrderModal";
import PublicIcon from '@mui/icons-material/Public';
import SelectMatnrModal from "./SelectMatnrModal";
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
  '& .css-1vooibu-MuiSvgIcon-root': {
@@ -139,7 +140,7 @@
          <TopToolbar>
            <FilterButton />
            <CreateByOrderButton setCreateDialog={setCreateDialog} />
            <MyCreateButton onClick={() => { setManualDialog(true) }} />
            <MyCreateButton onClick={() => { setManualDialog(true); setmodalType(0) }} />
            <SelectColumnsButton preferenceKey='outStock' />
            <ImportButton value={'asnOrderItem'} />
            {/* <MyExportButton /> */}
@@ -173,12 +174,19 @@
          <BillStatusField cellClassName="status" source="exceStatus" label="table.field.outStock.exceStatus" />
          <TextField source="memo" label="common.field.memo" sortable={false} />
          <WrapperField cellClassName="opt" label="common.field.opt" >
            <MyButton setCreateDialog={setManualDialog} setmodalType={setmodalType} />
            <EditButton label="toolbar.detail" icon={(<DetailsIcon />)}></EditButton>
            <CancelButton />
          </WrapperField>
        </StyledDatagrid>
      </List>
      <OutOrderCreate
      {/* <OutOrderCreate
        open={manualDialog}
        setOpen={setManualDialog}
      /> */}
      <SelectMatnrModal
        asnId={modalType}
        billReload={billReload}
        open={manualDialog}
        setOpen={setManualDialog}
      />
@@ -190,8 +198,7 @@
        title='AsnOrder Detail'
        drawerVal={drawerVal}
        setDrawerVal={setDrawerVal}
      >
      </PageDrawer>
      />
    </Box >
  )
}
rsf-admin/src/page/orders/outStock/SelectMatnrModal.jsx
New file
@@ -0,0 +1,613 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
    CreateBase,
    useTranslate,
    TextInput,
    NumberInput,
    BooleanInput,
    DateInput,
    SaveButton,
    SelectInput,
    ReferenceInput,
    ReferenceArrayInput,
    AutocompleteInput,
    Toolbar,
    required,
    useDataProvider,
    useNotify,
    Form,
    useCreateController,
    useListContext,
    useRefresh,
} from 'react-admin';
import {
    Dialog,
    DialogActions,
    DialogContent,
    DialogTitle,
    Stack,
    Grid,
    TextField,
    Box,
    Button,
    Paper,
    TableContainer,
    Table,
    TableHead,
    TableBody,
    TableRow,
    TableCell,
    Tooltip,
    IconButton,
    styled,
    Select,
    MenuItem
} from '@mui/material';
import DialogCloseButton from "../../components/DialogCloseButton";
import StatusSelectInput from "../../components/StatusSelectInput";
import ConfirmButton from "../../components/ConfirmButton";
import AsnWareModal from "./AsnWareModal";
import { useForm, Controller, useWatch, FormProvider, useFormContext } from "react-hook-form";
import SaveIcon from '@mui/icons-material/Save';
import request from '@/utils/request';
import { Add, Edit, Delete } from '@mui/icons-material';
import _, { set } from 'lodash';
import { DataGrid, useGridApiRef } from '@mui/x-data-grid';
import DictionarySelect from "../../components/DictionarySelect";
import DictSelect from "../../components/DictSelect";
import "./asnOrder.css";
const SelectMatnrModal = (props) => {
    const { open, setOpen, asnId, billReload } = props;
    const translate = useTranslate();
    const notify = useNotify();
    const refresh = useRefresh();
    const [disabled, setDisabled] = useState(false)
    const [createDialog, setCreateDialog] = useState(false);
    const tableRef = useRef();
    useEffect(() => {
        if (open && asnId !== 0) {
            requestGetHead()
            requestGetBody()
        }
        setDisabled(false)
    }, [open])
    const handleClose = (event, reason) => {
        if (reason !== "backdropClick") {
            setOpen(false);
            refresh();
            setFormData({ type: '', wkType: '' })
            setTableData([])
        }
    };
    const [formData, setFormData] = useState({
        type: '',
        wkType: '',
        poCode: '',
        logisNo: '',
        arrTime: ''
    });
    const [tabelData, setTableData] = useState([]);
    const handleChange = (value, name) => {
        setFormData((prevData) => ({
            ...prevData,
            [name]: value
        }));
        console.log(formData);
    };
    const resetData = () => {
        setFormData({
            type: '',
            wkType: '',
            poCode: '',
            logisNo: '',
            arrTime: ''
        })
        setTableData([])
    }
    const setFinally = () => {
        const rows = tableRef.current.state.editRows;
        for (const key in rows) {
            const find = tabelData.find(item => item.matnrId === +key);
            find.anfme = rows[key].anfme.value;
        }
        setTableData([...tabelData]);
    }
    const handleSubmit = async () => {
        setFinally()
        setDisabled(true)
        if (asnId === 0) {
            const parmas = {
                "orders": formData,
                "items": tabelData,
            }
            const res = await request.post(`/asnOrder/items/save`, parmas);
            if (res?.data?.code === 200) {
                setOpen(false);
                refresh();
                billReload?.current()
                resetData()
            } else {
                notify(res.data.msg);
            }
        } else {
            const parmas = {
                "orders": formData,
                "items": tabelData,
            }
            const res = await request.post(`/asnOrder/items/update`, parmas);
            if (res?.data?.code === 200) {
                setOpen(false);
                refresh();
                billReload?.current()
                resetData()
            } else {
                notify(res.data.msg);
            }
        }
        setDisabled(false)
    };
    const handleDelete = async () => {
        const res = await request.post(`/asnOrder/remove/${asnId}`);
        if (res?.data?.code === 200) {
            setOpen(false);
            refresh();
        } else {
            notify(res.data.msg);
        }
    };
    const requestGetHead = async () => {
        const res = await request.get(`/asnOrder/${asnId}`);
        if (res?.data?.code === 200) {
            setFormData(res.data.data)
        } else {
            notify(res.data.msg);
        }
    }
    const requestGetBody = async () => {
        const res = await request.post(`/asnOrderItem/page`, { asnId });
        if (res?.data?.code === 200) {
            setTableData(res.data.data.records)
        } else {
            notify(res.data.msg);
        }
    }
    const [selectedRows, setSelectedRows] = useState([]);
    const handleDeleteItem = () => {
        const newTableData = _.filter(tabelData, (item) => !selectedRows.includes(item.matnrId));
        setTableData(newTableData);
    }
    return (
        <>
            <Dialog
                open={open}
                onClose={handleClose}
                aria-labelledby="form-dialog-title"
                aria-hidden
                fullWidth
                disableRestoreFocus
                maxWidth="lg"   // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
            >
                <DialogTitle id="form-dialog-title" sx={{
                    position: 'sticky',
                    top: 0,
                    backgroundColor: 'background.paper',
                    zIndex: 1000
                }}>
                    {translate('create.title')}
                    <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
                        <DialogCloseButton onClose={handleClose} />
                    </Box>
                </DialogTitle>
                <DialogContent sx={{ mt: 2 }}>
                    <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                        <Form defaultValues={formData}>
                            <Grid container spacing={2}>
                                {/* <Grid item md={2}>
                                    <DictSelect
                                        label={translate("table.field.asnOrder.type")}
                                        value={formData.type}
                                        onChange={(e) => handleChange(e.target.value, 'type')}
                                        dictTypeCode="sys_order_type"
                                        required
                                    />
                                </Grid> */}
                                <Grid item md={2}>
                                    <DictSelect
                                        label={translate("table.field.asnOrder.wkType")}
                                        value={formData.wkType}
                                        variant="filled"
                                        onChange={(e) => handleChange(e.target.value, 'wkType')}
                                        dictTypeCode="sys_business_type"
                                        required
                                    />
                                </Grid>
                                <Grid item md={2}>
                                    <TextField
                                        label={translate("table.field.asnOrder.poCode")}
                                        value={formData.poCode}
                                        variant="filled"
                                        size='small'
                                        onChange={(e) => handleChange(e.target.value, 'poCode')}
                                    />
                                </Grid>
                                <Grid item md={2}>
                                    <TextField
                                        label={translate("table.field.asnOrder.logisNo")}
                                        value={formData.logisNo}
                                        variant="filled"
                                        size='small'
                                        onChange={(e) => handleChange(e.target.value, 'logisNo')}
                                    />
                                </Grid>
                                <Grid item md={2}>
                                    <DateInput
                                        source="arrTime"
                                        label="table.field.asnOrder.arrTime"
                                        size='small'
                                        variant="filled"
                                        value={formData.arrTime}
                                        onChange={(e) => handleChange(e.target.value, 'arrTime')}
                                    />
                                </Grid>
                            </Grid>
                        </Form>
                    </Box>
                    <Box sx={{ mt: 2 }}>
                        <Stack direction="row" spacing={2}>
                            <Button variant="contained" onClick={() => setCreateDialog(true)}>新增物料</Button>
                            {/* {asnId !== '' && <ConfirmButton label={'删除'} variant="outlined" color="error" onConfirm={handleDelete} />} */}
                            <ConfirmButton label={'删除'} variant="outlined" color="error" onConfirm={handleDeleteItem} />
                        </Stack>
                    </Box>
                    <Box sx={{ mt: 2 }}>
                        <AsnOrderModalTable tabelData={tabelData} setTableData={setTableData} asnId={asnId} selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef}></AsnOrderModalTable>
                    </Box>
                </DialogContent>
                <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
                    <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}  >
                        <Button disabled={disabled} onClick={handleSubmit} variant="contained" startIcon={<SaveIcon />}>
                            {translate('toolbar.confirm')}
                        </Button>
                    </Toolbar>
                </DialogActions>
            </Dialog>
            <AsnWareModal
                open={createDialog}
                setOpen={setCreateDialog}
                data={tabelData}
                setData={setTableData}
            />
        </>
    )
}
export default SelectMatnrModal;
const SelectInputSplrNameEditCell = (params) => {
    const [formData, setFormData] = useState([{}])
    useEffect(() => {
        getOptions();
    }, []);
    const getOptions = async () => {
        const parmas = {
            "type": "supplier"
        }
        const {
            data: { code, data, msg },
        } = await request.post("companys/page", parmas);
        if (code === 200) {
            setFormData(data.records)
            console.log(data.records)
        } else {
            notify(msg);
        }
    }
    return (
        <Select
            value={params.value}
            onChange={(e) => {
                params.api.setEditCellValue({
                    id: params.id,
                    field: params.field,
                    value: e.target.value,
                })
                // 找到选中的供应商记录
                const selectedSupplier = formData.find(supplier => supplier.name === e.target.value);
                // 如果找到对应的供应商记录,同时更新splrCode字段
                if (selectedSupplier) {
                    params.api.setEditCellValue({
                        id: params.id,
                        field: 'splrCode',
                        value: selectedSupplier.id,
                    });
                }
            }}
            fullWidth
        >
            {formData.map(e => {
                return (
                    <MenuItem value={e.name} children={e.name} key={e.id} />
                );
            })}
        </Select>
    );
};
const SelectInputSplrCodeEditCell = (params) => {
    const [formData, setFormData] = useState([{}])
    useEffect(() => {
        getOptions();
    }, []);
    const getOptions = async () => {
        const parmas = {
            "type": "supplier"
        }
        const {
            data: { code, data, msg },
        } = await request.post("companys/page", parmas);
        if (code === 200) {
            setFormData(data.records)
            console.log(data.records)
        } else {
            notify(msg);
        }
    }
    return (
        <Select
            value={params.value}
            onChange={(e) => {
                params.api.setEditCellValue({
                    id: params.id,
                    field: params.field,
                    value: e.target.value,
                })
                const selectedSupplier = formData.find(supplier => supplier.id === e.target.value);
                // 如果找到对应的供应商记录,同时更新splrCode字段
                if (selectedSupplier) {
                    params.api.setEditCellValue({
                        id: params.id,
                        field: 'splrName',
                        value: selectedSupplier.name,
                    });
                }
            }}
            fullWidth
        >
            {formData.map(e => {
                return (
                    <MenuItem value={e.id} children={e.name} key={e.id} />
                );
            })}
        </Select>
    );
};
const AsnOrderModalTable = ({ tabelData, setTableData, asnId, selectedRows, setSelectedRows, tableRef }) => {
    const translate = useTranslate();
    const notify = useNotify();
    const [columns, setColumns] = useState([
        {
            field: 'maktx',
            headerName: translate('table.field.asnOrderItem.maktx'),
            width: 250,
            editable: false,
        },
        {
            field: 'matnrCode',
            headerName: translate('table.field.asnOrderItem.matnrCode'),
            width: 130,
            editable: false,
        },
        {
            field: 'anfme',
            headerName: translate('table.field.asnOrderItem.anfme') + "*",
            type: 'number',
            minWidth: 100,
            flex: 1,
            editable: true,
            valueFormatter: (val) => val < 0 ? 0 : val,
            headerClassName: "custom",
        },
        {
            field: 'splrCode',
            headerName: translate('table.field.asnOrderItem.splrCode') + "*",
            minWidth: 100,
            flex: 1,
            editable: true,
            renderEditCell: (params) => (
                <SelectInputSplrCodeEditCell {...params} />
            ),
            headerClassName: "custom",
        },
        {
            field: 'splrName',
            headerName: translate('table.field.asnOrderItem.splrName') + "*",
            minWidth: 100,
            flex: 1,
            editable: true,
            renderEditCell: (params) => (
                <SelectInputSplrNameEditCell {...params} />
            ),
            headerClassName: "custom",
        },
        // {
        //     field: 'packName',
        //     headerName: translate('table.field.asnOrderItem.packName'),
        //     minWidth: 100,
        //     flex: 1,
        //     editable: true,
        // },
        // {
        //     field: 'poDetlId',
        //     headerName: translate('table.field.asnOrderItem.poDetlId'),
        //     minWidth: 100,
        //     flex: 1,
        // },
        {
            field: 'poCode',
            headerName: translate('table.field.asnOrderItem.poDetlCode') + "*",
            minWidth: 100,
            flex: 1,
            editable: true,
            headerClassName: "custom",
        },
        {
            field: 'stockUnit',
            headerName: translate('table.field.asnOrderItem.stockUnit'),
            minWidth: 100,
            flex: 1,
            editable: false,
        },
        // {
        //     field: 'purQty',
        //     headerName: translate('table.field.asnOrderItem.purQty'),
        //     minWidth: 100,
        //     flex: 1,
        //     editable: true,
        // },
        {
            field: 'purUnit',
            headerName: translate('table.field.asnOrderItem.purUnit'),
            minWidth: 100,
            flex: 1,
            editable: false,
        },
    ])
    const action = {
        field: 'action',
        headerName: '操作',
        width: 70,
        lockPosition: 'left',
        renderCell: (params) => (
            <Tooltip title="Delete">
                <IconButton onClick={() => handleDelete(params.row)}>
                    <Delete />
                </IconButton>
            </Tooltip>
        ),
    }
    let cdata = useRef([]);
    useEffect(() => {
        getDynamicFields();
    }, []);
    useEffect(() => {
        cdata.current = tabelData
    }, [tabelData]);
    const getDynamicFields = async () => {
        const {
            data: { code, data, msg },
        } = await request.get("/fields/enable/list");
        if (code === 200) {
            const cols = data.map(el => ({
                field: el.fields,
                headerName: el.fieldsAlise,
                minWidth: 100,
                flex: 1,
                editable: false
            }))
            setColumns([...columns, ...cols, action])
        } else {
            notify(msg);
        }
    }
    const handleDelete = (row) => {
        const newData = _.filter(cdata.current, (item) => item.matnrId !== row.matnrId);
        setTableData(newData);
    };
    const processRowUpdate = (newRow, oldRow) => {
        const rows = tabelData.map((r) =>
            r.matnrId === newRow.matnrId ? { ...newRow } : r
        )
        setTableData(rows)
        return newRow;
    };
    const handleSelectionChange = (ids) => {
        setSelectedRows(ids)
    };
    tableRef.current = useGridApiRef();
    return (
        <div style={{ height: 400, width: '100%' }}>
            <DataGrid
                apiRef={tableRef}
                rows={tabelData}
                columns={columns}
                disableRowSelectionOnClick
                getRowId={(row) => row.matnrId}
                disableColumnFilter
                disableColumnSelector
                disableColumnSorting
                disableMultipleColumnsSorting
                processRowUpdate={processRowUpdate}
                initialState={{
                    pagination: {
                        paginationModel: {
                            pageSize: 25,
                        },
                    },
                }}
                pageSizeOptions={[10, 25, 50, 100]}
                editMode="row"
                checkboxSelection
                onRowSelectionModelChange={handleSelectionChange}
                selectionModel={selectedRows}
                sx={{
                    '& .MuiDataGrid-cell input': {
                        border: '1px solid #ccc'
                    },
                }}
            />
        </div>
    );
};
rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx
@@ -150,7 +150,7 @@
                </DialogContent>
                <DialogActions>
                    <Toolbar sx={{ width: '100%', justifyContent: 'end' }} >
                        <GenerateTaskButton record={[record?.id]} dataSource={data} />
                        <GenerateTaskButton record={record?.id} dataSource={data} />
                    </Toolbar>
                </DialogActions>
            </Dialog>
@@ -165,7 +165,10 @@
    const notify = useNotify();
    const redirect = useRedirect();
    const generateTask = async () => {
        const res = await request.post(`/wave/public/task`, { wave: record, waveItem: dataSource });
        const params = {wave: record, waveItem: dataSource}
        console.log('---------->');
        console.log(record);
        const res = await request.post(`/wave/public/task`, { wave: record?.record, waveItem: record?.dataSource });
        if (res?.data?.code === 200) {
            notify(res.data.msg);
            redirect("/task")
rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx
@@ -116,7 +116,6 @@
    const translate = useTranslate();
    const [createDialog, setCreateDialog] = useState(false);
    const [drawerVal, setDrawerVal] = useState(false);
    const dicts = JSON.parse(localStorage.getItem('sys_dicts'))?.filter(dict => (dict.dictTypeCode == 'sys_ware_areas_type')) || [];
    return (
        <Box display="flex">
rsf-server/src/main/java/com/vincent/rsf/server/common/interceptor/severlet/CustomParameterFilter.java
@@ -8,7 +8,7 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
//@Component
public class CustomParameterFilter extends OncePerRequestFilter {
    @Override
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java
@@ -13,6 +13,7 @@
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.ExcelUtil;
import com.vincent.rsf.server.manager.controller.params.AsnOrderAndItemsParams;
import com.vincent.rsf.server.manager.entity.AsnOrder;
import com.vincent.rsf.server.manager.entity.AsnOrderItem;
import com.vincent.rsf.server.manager.entity.excel.AsnOrderTemplate;
@@ -214,4 +215,28 @@
        List<Long> ids = (List<Long>) params.get("ids");
        return outStockService.generateWaves(ids);
    }
    @PostMapping("/outStock/items/save")
    @ApiOperation("保存主单及明细")
    @PreAuthorize("hasAuthority('manager:outStock:save')")
    public R orderAndItem(@RequestBody AsnOrderAndItemsParams params) throws Exception {
        if (Objects.isNull(params)) {
            return R.error("参数不能为空!!");
        }
        return outStockService.saveOrderAndItems(params, getLoginUserId());
    }
    @ApiOperation("单据信息修改")
    @PostMapping("/outStock/items/update")
    @PreAuthorize("hasAuthority('manager:outStock:update')")
    public R orderAndrItemUpdate(@RequestBody AsnOrderAndItemsParams params) throws Exception {
        if (Objects.isNull(params)) {
            return R.error("参数不能为空!!");
        }
        return outStockService.updateOrderItem(params, getLoginUserId());
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
@@ -126,4 +126,8 @@
        }
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/OutStockService.java
@@ -16,4 +16,8 @@
    R genOutStock(List<Long> ids);
    R generateWaves(List<Long> ids);
    R saveOrderAndItems(AsnOrderAndItemsParams params, Long loginUserId);
    R updateOrderItem(AsnOrderAndItemsParams params, Long loginUserId);
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -310,6 +310,55 @@
    }
    /**
     * @author Ryan
     * @description 保存出库主单及明细
     * @param
     * @return
     * @time 2025/4/29 13:47
     */
    @Override
    public R saveOrderAndItems(AsnOrderAndItemsParams params, Long loginUserId) {
        if (Objects.isNull(params.getOrders())) {
            throw new CoolException("主单信息不能为空");
        }
        AsnOrder orders = params.getOrders();
        if (Objects.isNull(orders)) {
            throw new CoolException("单据不能为空!!");
        }
        String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_OUT_STOCK_CODE, orders);
        if (Objects.isNull(ruleCode) || StringUtils.isBlank(ruleCode)) {
            throw new CoolException("编码规则错误:请检查「SYS_OUT_STOCK_CODE」是否设置正确!!");
        }
        orders.setCode(ruleCode)
                .setUpdateBy(loginUserId)
                .setCreateBy(loginUserId);
        if (!this.save(orders)) {
            throw new CoolException("主单保存失败!!");
        }
        if (params.getItems().isEmpty()) {
            throw new CoolException("收货通知单明细不能为寒食节!!");
        }
//        svaeOrUpdateOrderItem(params,loginUserId);
        return null;
    }
    /**
     * @author Ryan
     * @description 修改主单及明细
     * @param
     * @return
     * @time 2025/4/29 13:47
     */
    @Override
    public R updateOrderItem(AsnOrderAndItemsParams params, Long loginUserId) {
        return null;
    }
    /**
     * @param
     * @param wave
     * @return
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
@@ -1,6 +1,7 @@
package com.vincent.rsf.server.manager.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -17,6 +18,7 @@
import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil;
import com.vincent.rsf.server.system.constant.SerialRuleCode;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,6 +28,7 @@
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service("waveService")
public class WaveServiceImpl extends ServiceImpl<WaveMapper, Wave> implements WaveService {
@@ -59,39 +62,40 @@
        if (Objects.isNull(itemParams) || itemParams.isEmpty()) {
            throw new CoolException("参数不能为空!!");
        }
        Wave wave = (Wave) map.get("wave");
        Wave waves = this.getById(new LambdaQueryWrapper<Wave>().in(Wave::getId, wave.getId()));
        List<WaveItem> items = JSONArray.parseArray(JSONArray.toJSONString(itemParams), WaveItem.class);
        String waveId = map.get("wave").toString();
        Wave waves = this.getById(Long.parseLong(waveId));
        if (Objects.isNull(waves)) {
            throw new CoolException("波次数据不存在!!");
        }
        List<Long> list = itemParams.stream().map(WaveItem::getWaveId).collect(Collectors.toList());
        List<WaveItem> waveItems = waveItemService.list(new LambdaQueryWrapper<WaveItem>().in(WaveItem::getWaveId, list));
//        List<Long> list = itemParams.stream().map(WaveItem::getWaveId).collect(Collectors.toList());
        List<WaveItem> waveItems = waveItemService.list(new LambdaQueryWrapper<WaveItem>().eq(WaveItem::getWaveId, waves.getId()));
        if (waveItems.isEmpty()) {
            throw new CoolException("波次明细不存在!!");
        }
        /**生成出库任务*/
        try {
            generateOutTask(itemParams, loginUserId, wave);
            generateOutTask(items, loginUserId, waves);
        } catch (Exception e) {
            throw new CoolException("库位获取失败!!!");
            log.error(e.getMessage());
            throw new CoolException("出库任务生成失败!!!");
        }
        //TODO 1. 根据波次明细生成出库任务
        // 2. 根据物料SKU寻找符合物料库位  {1. 根据物料编码,批次,动态字段 查询符合的库位,再根据库位中物料的数量选择最适合的库位 2. 判断当前订单是全拖出库还是拣料入库}
        // 3. 修改主单、波次执行数量
        // 4. 判断全仓出库或拣料出库
        List<Long> orderIds = waveItems.stream().map(WaveItem::getOrderId).collect(Collectors.toList());
        List<AsnOrder> orders = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().in(AsnOrder::getId, orderIds));
//        List<AsnOrder> orders = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().in(AsnOrder::getId, orderIds));
        /**修改出库单状态*/
        if (!asnOrderService.update(new LambdaQueryWrapper<AsnOrder>()
                .eq(AsnOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val)
                .in(AsnOrder::getId, orders))) {
        if (!asnOrderService.update(new LambdaUpdateWrapper<AsnOrder>()
                .set(AsnOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val)
                .in(AsnOrder::getId, orderIds))) {
            throw new CoolException("出库单据状态修改失败!!");
        }
        /**修改波次单据执行状态*/
        if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).eq(Wave::getId, wave.getId()))) {
            throw new CoolException("波次状态修改失败!!");
        }
//        /**修改波次单据执行状态*/
//        if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).eq(Wave::getId, waves.getId()))) {
//            throw new CoolException("波次状态修改失败!!");
//        }
        return R.ok();
    }
@@ -113,7 +117,7 @@
            if (locItems.isEmpty()) {
                continue;
            }
            List<Long> list = locItems.stream().map(LocItem::getId).collect(Collectors.toList());
            List<Long> list = locItems.stream().map(LocItem::getLocId).collect(Collectors.toList());
            /**根据供应商批次,物料码, 动态字段查询指定的物料库存信息*/
            List<LocItem> items = locItemService.list(new LambdaQueryWrapper<LocItem>()
                    .eq(LocItem::getSplrBatch, param.getSplrBatch())
@@ -185,7 +189,7 @@
            /**修改波次执行数量*/
            taskItems.forEach(item -> {
                boolean update = waveItemService.update(new LambdaUpdateWrapper<WaveItem>()
                        .eq(WaveItem::getWaveId, item.getSource())
                        .eq(WaveItem::getId, item.getSource())
                        .set(WaveItem::getWorkQty, item.getAnfme()));
                if (!update) {
                    throw new CoolException("波次执行数量修改失败!!");
@@ -195,7 +199,7 @@
            List<WaveItem> waveItems = waveItemService.list(new LambdaQueryWrapper<WaveItem>().eq(WaveItem::getWaveId, wave.getId()));
            double sum = waveItems.stream().mapToDouble(WaveItem::getWorkQty).sum();
            /**波次主单信息修改*/
            if (!update(new LambdaUpdateWrapper<Wave>()
            if (!this.update(new LambdaUpdateWrapper<Wave>()
                    .eq(Wave::getId, wave.getId())
                    .set(Wave::getWorkQty, sum)
                    .set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK.val))) {