From f6881a3c81210316d56751f612346439c3ab814c Mon Sep 17 00:00:00 2001
From: zjj <3272660260@qq.com>
Date: 星期三, 21 五月 2025 11:06:57 +0800
Subject: [PATCH] #出库作业

---
 rsf-admin/src/page/outWork/outBound/locItemInfoModal.jsx |  233 +++++++++++++++++++++++
 rsf-admin/src/page/outWork/outBound/OutBoundList.jsx     |  277 +++++++++++++++++++++++++++
 rsf-admin/src/i18n/zh.js                                 |   10 +
 rsf-admin/src/i18n/en.js                                 |    4 
 rsf-admin/src/page/ResourceContent.js                    |    3 
 rsf-admin/src/page/outWork/outBound/index.jsx            |   18 +
 6 files changed, 545 insertions(+), 0 deletions(-)

diff --git a/rsf-admin/src/i18n/en.js b/rsf-admin/src/i18n/en.js
index 858bcf1..53ea47c 100644
--- a/rsf-admin/src/i18n/en.js
+++ b/rsf-admin/src/i18n/en.js
@@ -207,6 +207,10 @@
     },
     table: {
         field: {
+            outBound: {
+                stockWithdrawal: 'StockWithdrawal',
+                withdrawal:'Withdrawal'
+            },
             basContainer: {
                 containerType:'containerType',
                 codeType: 'codeType',
diff --git a/rsf-admin/src/i18n/zh.js b/rsf-admin/src/i18n/zh.js
index 9b0169f..6da2272 100644
--- a/rsf-admin/src/i18n/zh.js
+++ b/rsf-admin/src/i18n/zh.js
@@ -1,4 +1,5 @@
 import basContainer from "../page/basicInfo/basContainer";
+import outBound from "../page/outWork/outBound";
 import chineseMessages from "./core/chineseMessages";
 
 const customChineseMessages = {
@@ -207,9 +208,18 @@
         wave: '娉㈡绠$悊',
         basStation: '绔欑偣淇℃伅',
         basContainer: '瀹瑰櫒绠$悊',
+        outBound: '鍑哄簱浣滀笟',
     },
     table: {        
         field: {
+            outBound: {
+                stockWithdrawal: '鎻愬彇搴撳瓨',
+                withdrawal:'鎻愬彇',
+                outSta: '鍑哄簱绔�',
+                outQty: '鍑哄簱鏁伴噺',
+                anfme: '鏁伴噺',
+                
+            },
             basContainer: {
                 containerType:'瀹瑰櫒绫诲瀷',
                 codeType: '鏉$爜绫诲瀷',
diff --git a/rsf-admin/src/page/ResourceContent.js b/rsf-admin/src/page/ResourceContent.js
index 3f3cbb4..9636298 100644
--- a/rsf-admin/src/page/ResourceContent.js
+++ b/rsf-admin/src/page/ResourceContent.js
@@ -49,6 +49,7 @@
 import basStation from './basicInfo/basStation';
 import warehouseStock from './statistics/stockManage';
 import basContainer from './basicInfo/basContainer';
+import outBound from "./outWork/outBound";
 
 const ResourceContent = (node) => {
     switch (node.component) {
@@ -142,6 +143,8 @@
             return basStation;
         case 'basContainer':
             return basContainer;
+        case 'outBound':
+            return outBound;
         default:
             return {
                 list: ListGuesser,
diff --git a/rsf-admin/src/page/outWork/outBound/OutBoundList.jsx b/rsf-admin/src/page/outWork/outBound/OutBoundList.jsx
new file mode 100644
index 0000000..6afcfcd
--- /dev/null
+++ b/rsf-admin/src/page/outWork/outBound/OutBoundList.jsx
@@ -0,0 +1,277 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import { useWatch, useFormContext } from "react-hook-form";
+import {
+    CreateBase,
+    useTranslate,
+    TextInput,
+    NumberInput,
+    BooleanInput,
+    DateInput,
+    SaveButton,
+    SelectInput,
+    ReferenceInput,
+    ReferenceArrayInput,
+    AutocompleteInput,
+    Toolbar,
+    required,
+    useDataProvider,
+    useNotify,
+    Form,
+    useCreateController,
+    useListContext,
+    useRefresh,
+    Edit,    
+} 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,
+    Typography,
+    Card,
+} from '@mui/material';
+import { EDIT_MODE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting';
+import ConfirmButton from "../../components/ConfirmButton";
+import TreeSelectInput from "@/page/components/TreeSelectInput";
+import { DataGrid, useGridApiRef } from '@mui/x-data-grid';
+import DictSelect from "../../components/DictSelect";
+import AddIcon from '@mui/icons-material/Add';
+import DeleteIcon from '@mui/icons-material/Delete';
+import request from '@/utils/request';
+import LocItemInfoModal from "./locItemInfoModal";
+import { Delete } from '@mui/icons-material';
+
+const OutBoundList = () => {    
+
+    const [createDialog, setCreateDialog] = useState(false);
+    const [tabelData, setTableData] = useState([]);
+    const [selectedRows, setSelectedRows] = useState([]);
+    const tableRef = useRef();
+    tableRef.current = useGridApiRef();
+    const translate = useTranslate();
+
+    const handleDeleteItem = () => {
+        const newTableData = _.filter(tabelData, (item) => !selectedRows.includes(item.matnrId));
+        setTableData(newTableData);
+    }
+    return (
+        <>
+        <Card sx={{ p: 2, mb: 2, mt:2}}>
+            <Grid container spacing={2}>                
+                <Grid item xs={12}>
+                    <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1 }}>
+                        <Typography variant="h6">
+                            {translate('table.field.outBound.stockWithdrawal')}
+                        </Typography>
+                        <Stack direction='row' spacing={2}>
+                            <Button 
+                                variant="contained" 
+                                color="primary"
+                                startIcon={<AddIcon />}
+                                onClick={() => setCreateDialog(true)}
+                            >
+                                {translate('table.field.outBound.withdrawal')}
+                            </Button>
+                            <ConfirmButton 
+                                label={'鍒犻櫎'} 
+                                variant="outlined" 
+                                color="error" 
+                                onConfirm={handleDeleteItem} 
+                            />
+                        </Stack>
+                    </Box>
+                </Grid>
+            </Grid>
+        </Card>
+        <Card sx={{ p: 2, mb: 2, mt:2}}>
+            <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>                        
+                <Grid container spacing={2}> 
+                    <Grid item md={2}>  
+                        <Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 1 }}>
+                            <Typography width={100} vvariant="h6" gutterBottom>
+                            {translate('table.field.outBound.outSta')}
+                                </Typography>                                 
+                            <DictSelect
+                                label={translate("table.field.outStock.wkType")}                                        
+                                variant="filled"
+                                group='2'                                        
+                                dictTypeCode="sys_business_type"
+                                required
+                            />
+                        </Box>
+                    </Grid> 
+                </Grid>        
+            </Box>
+        </Card>
+        <Card sx={{ mb: 2}}>
+            <Box sx={{  }}>
+                <ModalTable tabelData={tabelData} setTableData={setTableData}  selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef}></ModalTable>
+            </Box>
+        </Card>
+        <LocItemInfoModal
+                open={createDialog}
+                setOpen={setCreateDialog}
+                data={tabelData}
+                setData={setTableData}
+            />
+            
+        </>
+    )
+}
+
+export default OutBoundList;
+
+
+const ModalTable = ({ tabelData, setTableData, selectedRows, setSelectedRows, tableRef }) => {
+    const translate = useTranslate();
+    const notify = useNotify();
+
+    const [columns, setColumns] = useState([
+        {
+            field: 'outQty',
+            headerName: translate('table.field.outBound.outQty'),
+            width: 100,
+            editable: true,
+        },
+        {
+            field: 'anfme',
+            headerName: translate('table.field.locItem.anfme'),
+            width: 100,
+            editable: false,
+        }, 
+        {
+            field: 'matnrCode',
+            headerName: translate('table.field.locItem.matnrCode'),
+            width: 130,
+            editable: false,
+        }, 
+        {
+            field: 'maktx',
+            headerName: translate('table.field.locItem.maktx'),
+            width: 250,
+            editable: false,
+        },
+        {
+            field: 'batch',
+            headerName: translate('table.field.locItem.batch'),
+            width: 250,
+            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: 500, width: '100%' }}>
+            <DataGrid
+                apiRef={tableRef}
+                rows={tabelData}
+                columns={columns}
+                disableRowSelectionOnClick
+                getRowId={(row) => row.matnrId ? row.matnrId : row.id}
+                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>
+    );
+};
diff --git a/rsf-admin/src/page/outWork/outBound/index.jsx b/rsf-admin/src/page/outWork/outBound/index.jsx
new file mode 100644
index 0000000..b3fa3be
--- /dev/null
+++ b/rsf-admin/src/page/outWork/outBound/index.jsx
@@ -0,0 +1,18 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    ListGuesser,
+    EditGuesser,
+    ShowGuesser,
+} from "react-admin";
+
+import OutBoundList from "./OutBoundList";
+
+
+export default {
+    list: OutBoundList,
+    edit: EditGuesser,
+    show: ShowGuesser,
+    recordRepresentation: (record) => {
+        return `${record.id}`
+    }
+};
diff --git a/rsf-admin/src/page/outWork/outBound/locItemInfoModal.jsx b/rsf-admin/src/page/outWork/outBound/locItemInfoModal.jsx
new file mode 100644
index 0000000..6e3d3bc
--- /dev/null
+++ b/rsf-admin/src/page/outWork/outBound/locItemInfoModal.jsx
@@ -0,0 +1,233 @@
+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 LocItemInfoModal = (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(`/locItem/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
+            }}>
+                {translate("common.action.newAddMats")}
+                <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>
+                </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 LocItemInfoModal;
+
+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: 'locCode', headerName: translate('table.field.locItem.locCode'), width: 100 },
+        { field: 'matnrCode', headerName: translate('table.field.locItem.matnrCode'), width: 200 },
+        { field: 'maktx', headerName: translate('table.field.locItem.maktx'), width: 300 },
+        { field: 'batch', headerName: translate('table.field.locItem.batch'), width: 100 },
+        { field: 'anfme', headerName: translate('table.field.locItem.anfme'), width: 100 },
+        
+        { field: 'unit', headerName: translate('table.field.locItem.unit'), width: 100 },
+        
+    ])
+
+
+    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>
+    );
+};
\ No newline at end of file

--
Gitblit v1.9.1