69a3c374ca3afb770e3b9ffcbdda07ce362cbf58..b5b400a615743a74e9d127261bd3785554aa06aa
昨天 zhou zhou
#
b5b400 对比 | 目录
昨天 1
lsh#
e461c8 对比 | 目录
昨天 1
#
e9db3c 对比 | 目录
昨天 zhou zhou
#application-dev
24bee1 对比 | 目录
14个文件已添加
16个文件已修改
1475 ■■■■■ 已修改文件
rsf-admin/src/page/system/role/AssignPermissions_warehouse.jsx 380 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/system/role/RoleList.jsx 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseRoleMenu/WarehouseRoleMenuCreate.jsx 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseRoleMenu/WarehouseRoleMenuEdit.jsx 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseRoleMenu/WarehouseRoleMenuList.jsx 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseRoleMenu/WarehouseRoleMenuPanel.jsx 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseRoleMenu/index.jsx 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/params/WcsChangeLocParam.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/params/WcsTaskReportParam.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/WmsWcsServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/WcsMsgTypeEvent.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ChangeLocParam.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ReassignLocParam.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskReportParam.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/params/WaitPakinParam.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/TaskService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/WarehouseRoleMenuController.java 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/WarehouseRoleMenu.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/WarehouseRoleMenuMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/service/WarehouseRoleMenuService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/WarehouseRoleMenuServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/warehouseRoleMenu.sql 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-dev.yml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/mapper/system/WarehouseRoleMenuMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/system/role/AssignPermissions_warehouse.jsx
New file
@@ -0,0 +1,380 @@
import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
import {
    useTranslate,
    useNotify,
    TextInput
} from 'react-admin';
import { Box, Button, Card, Stack, CardContent, Skeleton, TextField } from '@mui/material';
import { SimpleTreeView, TreeItem, RichTreeView, useTreeViewApiRef } from '@mui/x-tree-view';
import SaveIcon from '@mui/icons-material/Save';
import request from '@/utils/request'
const DEFAULT_EXPAND_ALL = true;
const AssignPermissionsMatnr = (props) => {
    const { role, originMenuIds, setDrawerVal, closeCallback, authType } = props;
    const translate = useTranslate();
    const notify = useNotify();
    const [loading, setLoading] = useState(false);
    const [treeData, setTreeData] = useState([]);
    const [selectedItems, setSelectedItems] = useState([]);
    const [expandedItems, setExpandedItems] = useState([]);
    const [parmas, setParmas] = useState({ condition: '', authType: authType });
    const toggledItemRef = useRef({});
    const apiRef = useTreeViewApiRef();
    useEffect(() => {
        reload()
    }, [role, originMenuIds])
    const reload = () => {
        setSelectedItems(originMenuIds.map(item => item + ""));
        const transformTree = (treeData) => {
            return treeData.map(data => {
                return {
                    id: data.id + '',
                    label: data.type === 0 ? translate(data.name || data.code) : data.name || data.code,
                    type: data.type,
                    children: (data.children && data.children.length > 0 ? transformTree(data.children) : null)
                }
            })
        }
        const http = async () => {
            const res = await request.post('/menuWarehouse/tree', parmas);
            if (res?.data?.code === 200) {
                const transformData = transformTree(res.data.data);
                setTreeData(transformData);
                if (DEFAULT_EXPAND_ALL) {
                    setExpandedItems(getAllItemsWithChildrenItemIds(transformData));
                }
            } else {
                notify(res.data.msg, { type: 'error' });
            }
            setLoading(false);
        }
        setLoading(true);
        setTimeout(() => {
            http();
        }, 200);
    }
    const getAllItemItemIds = () => {
        const ids = [];
        const registerItemId = (item) => {
            ids.push(item.id);
            item.children?.forEach(registerItemId);
        };
        treeData.forEach(registerItemId);
        return ids;
    };
    const getAllItemsWithChildrenItemIds = (treeDataHandle) => {
        const itemIds = [];
        const registerItemId = (item) => {
            if (item.children?.length) {
                itemIds.push(item.id);
                item.children.forEach(registerItemId);
            }
        };
        if (treeDataHandle) {
            treeDataHandle.forEach(registerItemId);
        } else {
            treeData.forEach(registerItemId);
        }
        return itemIds;
    };
    const handleSelectedItemsChange = (event, newSelectedItems) => {
        const itemsToSelect = [];
        const itemsToUnSelect = {};
        Object.entries(toggledItemRef.current).forEach(([itemId, isSelected]) => {
            const item = apiRef.current.getItem(itemId);
            if (isSelected) {
                itemsToSelect.push(...getItemDescendantsIds(item));
                const parentIds = getParentIds(treeData, itemId);
                itemsToSelect.push(...parentIds);
            } else {
                // 取消子节点
                const treeNode = checkoutTreeNode(treeData, itemId);
                if (treeNode?.children && treeNode?.children.length > 0) {
                    const allChildren = getItemDescendantsIds(treeNode);
                    const childrenSet = new Set(allChildren);
                    newSelectedItems = newSelectedItems.filter(item => !childrenSet.has(item));
                }
                // 取消父节点
                const removeParentIfAllSiblingsDeselected = (itemId, newSelectedItems, treeData) => {
                    let updatedSelectedItems = [...newSelectedItems];
                    let currentId = itemId;
                    while (true) {
                        const parentId = getParentId(treeData, currentId);
                        if (!parentId) break;
                        const siblings = getChildrenIds(treeData, parentId);
                        const allSiblingsDeselected = siblings.every(siblingId => !updatedSelectedItems.includes(siblingId));
                        if (allSiblingsDeselected) {
                            updatedSelectedItems = updatedSelectedItems.filter(id => id !== parentId);
                            currentId = parentId;
                        } else {
                            break;
                        }
                    }
                    return updatedSelectedItems;
                };
                newSelectedItems = removeParentIfAllSiblingsDeselected(itemId, newSelectedItems, treeData);
            }
        });
        const newSelectedItemsWithChildren = Array.from(
            new Set(
                [...newSelectedItems, ...itemsToSelect].filter(
                    (itemId) => !itemsToUnSelect[itemId],
                ),
            ),
        );
        setSelectedItems(newSelectedItemsWithChildren);
        toggledItemRef.current = {};
    };
    const handleSave = (event) => {
        request.post('/roleWarehouse/scope/update', {
            id: role.id,
            menuIds: {
                checked: selectedItems,
                halfChecked: []
            }
        }).then(res => {
            if (res?.data.code === 200) {
                setDrawerVal(null);
                if (closeCallback) {
                    closeCallback();
                }
                notify(res.data.msg, { type: 'info', messageArgs: { _: res.data.msg } })
            } else {
                notify(res.data.msg, { type: 'error' })
            }
        })
    }
    const search = (e) => {
        const value = e.target.value;
        setParmas({
            ...parmas,
            condition: value
        })
        reload()
    }
    return (
        <>
            <Card sx={{
                ml: 1,
                mr: 1,
                height: 'calc(100vh - 140px)',
                overflowY: 'auto'
            }}>
                <CardContent sx={{
                    overflow: 'auto',
                    display: 'flex',
                    flexDirection: 'column',
                    justifyContent: 'space-between'
                }}>
                    <Box>
                        <Box mb={1} sx={{
                            display: 'flex',
                            justifyContent: 'space-between'
                        }}>
                            <Button onClick={() => {
                                setSelectedItems((oldSelected) =>
                                    oldSelected.length === 0 ? getAllItemItemIds() : [],
                                );
                            }}>
                                {selectedItems.length === 0 ? translate('ra.action.select_all') : translate('ra.action.unselect')}
                            </Button>
                            <Button onClick={() => {
                                setExpandedItems((oldExpanded) =>
                                    oldExpanded.length === 0 ? getAllItemsWithChildrenItemIds() : [],
                                );
                            }}>
                                {expandedItems.length === 0 ? translate('common.action.expandAll') : translate('common.action.collapseAll')}
                            </Button>
                        </Box>
                        <Box sx={{
                            display: 'flex',
                            justifyContent: 'space-between',
                            alignItems: 'center'
                        }}>
                            <TextField sx={{ width: '200px' }} label="搜索菜单" variant="outlined" value={parmas.condition} onChange={(e) => search(e)} />
                            <Button startIcon={<SaveIcon />} size="small" variant="contained" onClick={handleSave} sx={{ height: '40px' }}>
                                {translate('ra.action.save')}
                            </Button>
                        </Box>
                        <Box sx={{
                            minWidth: 290,
                            overflow: 'auto',
                            marginTop: '10px',
                            padding: 1,
                            borderBottom: '1px solid background.paper',
                            borderRadius: '4px',
                            boxShadow: '0 1px 2px rgba(0, 0, 0, 0.2)',
                            backgroundColor: 'background.paper',
                        }}>
                            {loading ? (
                                <SkeletonBox />
                            ) : (
                                <RichTreeView
                                    multiSelect
                                    checkboxSelection
                                    apiRef={apiRef}
                                    items={treeData}
                                    selectedItems={selectedItems}
                                    onSelectedItemsChange={handleSelectedItemsChange}
                                    onItemSelectionToggle={(event, itemId, isSelected) => {
                                        toggledItemRef.current[itemId] = isSelected;
                                    }}
                                    expandedItems={expandedItems}
                                    onExpandedItemsChange={(event, itemIds) => {
                                        setExpandedItems(itemIds);
                                    }}
                                />
                            )}
                        </Box>
                    </Box>
                </CardContent>
            </Card>
        </>
    )
}
const checkoutTreeNode = (treeData, targetId) => {
    let result = null;
    const checkout = (node) => {
        if (node.id === targetId) {
            result = node;
            return true;
        } else {
            if (node.children) {
                for (const child of node.children) {
                    if (checkout(child)) {
                        return true;
                    }
                }
            }
        }
        return false;
    };
    treeData.forEach(item => {
        if (checkout(item)) {
            return;
        }
    });
    return result;
};
const getItemDescendantsIds = (item) => {
    const ids = [];
    item.children?.forEach((child) => {
        ids.push(child.id);
        ids.push(...getItemDescendantsIds(child));
    });
    return ids;
}
const getParentIds = (tree, targetId) => {
    let parentIds = [];
    const searchTree = (node, path = []) => {
        if (node.id === targetId) {
            parentIds = [...path];
            return true;
        }
        if (node.children) {
            for (const child of node.children) {
                if (searchTree(child, [...path, node.id])) {
                    return true;
                }
            }
        }
        return false;
    };
    tree.forEach(item => {
        searchTree(item);
    })
    return parentIds;
};
const getParentId = (tree, targetId) => {
    let parentId = null;
    const searchTree = (node) => {
        if (node.children) {
            for (const child of node.children) {
                if (child.id === targetId) {
                    parentId = node.id;
                    return true;
                }
                if (searchTree(child)) {
                    return true;
                }
            }
        }
        return false;
    };
    tree.forEach(item => {
        if (searchTree(item)) {
            return parentId;
        }
    });
    return parentId;
};
const getChildrenIds = (tree, targetId) => {
    let childrenIds = [];
    const searchTree = (node) => {
        if (node.id === targetId && node.children) {
            childrenIds = node.children.map(child => child.id);
        } else if (node.children) {
            for (const child of node.children) {
                searchTree(child);
            }
        }
    };
    tree.forEach(item => {
        searchTree(item);
    });
    return childrenIds;
};
const SkeletonBox = () => {
    return (
        <Stack spacing={1}>
            <Skeleton variant="rounded" width={200} height={20} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
        </Stack>
    )
}
export default AssignPermissionsMatnr;
rsf-admin/src/page/system/role/RoleList.jsx
@@ -45,6 +45,7 @@
import AssignPermissions from "./AssignPermissions";
import AssignPermissionsPda from "./AssignPermissions_pda";
import AssignPermissionsMatnr from "./AssignPermissions_matnr";
import AssignPermissionsWarehouse from "./AssignPermissions_warehouse";
import request from '@/utils/request';
import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
import AdUnitsIcon from '@mui/icons-material/AdUnits';
@@ -88,6 +89,7 @@
    const [drawerVal, setDrawerVal] = useState(false);
    const [drawerValPda, setDrawerValPda] = useState(false);
    const [drawerValMatnr, setDrawerValMatnr] = useState(false);
    const [drawerValWarehouse, setDrawerValWarehouse] = useState(false);
    const [menuIds, setMenuIds] = useState([]);
@@ -95,6 +97,8 @@
    const assign = (record) => {
        setDrawerValPda(false);
        setDrawerValMatnr(false);
        setDrawerValWarehouse(false);
        request('/role/scope/list', {
            method: 'GET',
            params: {
@@ -113,6 +117,8 @@
    const assignPda = (record) => {
        setDrawerVal(false);
        setDrawerValMatnr(false);
        setDrawerValWarehouse(false);
        request('/rolePda/scope/list', {
            method: 'GET',
            params: {
@@ -131,6 +137,8 @@
    const assignMatnr = (record) => {
        setDrawerVal(false);
        setDrawerValPda(false);
        setDrawerValWarehouse(false);
        request('/roleMatnr/scope/list', {
            method: 'GET',
            params: {
@@ -147,6 +155,26 @@
        });
    }
    const assignWarehouse = (record) => {
        setDrawerVal(false);
        setDrawerValPda(false);
        setDrawerValMatnr(false);
        request('/roleWarehouse/scope/list', {
            method: 'GET',
            params: {
                roleId: record.id
            }
        }).then((res) => {
            if (res?.data?.code === 200) {
                const { data: menuIds } = res.data;
                setMenuIds(menuIds || []);
                setDrawerValWarehouse(!!drawerValWarehouse && drawerValWarehouse === record ? null : record);
            } else {
                notify(res.data.msg, { type: 'error' });
            }
        });
    }
    return (
        <Box display="flex">
            <List
@@ -156,7 +184,7 @@
                        theme.transitions.create(['all'], {
                            duration: theme.transitions.duration.enteringScreen,
                        }),
                    marginRight: (!!drawerVal || !!drawerValPda || !!drawerValMatnr) ? `${PAGE_DRAWER_WIDTH}px` : 0,
                    marginRight: (!!drawerVal || !!drawerValPda || !!drawerValMatnr || !!drawerValWarehouse) ? `${PAGE_DRAWER_WIDTH}px` : 0,
                }}
                title={"menu.role"}
                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
@@ -196,6 +224,7 @@
                            assign={assign}
                            assignPda={assignPda}
                            assignMatnr={assignMatnr}
                            assignWarehouse={assignWarehouse}
                            setAuthType={setAuthType}
                        />
                        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
@@ -261,11 +290,29 @@
                    authType={authType}
                />
            </PageDrawer>
            <PageDrawer
                drawerVal={drawerValWarehouse}
                setDrawerVal={setDrawerValWarehouse}
                title={!!drawerValWarehouse ? `Scope by ${drawerValWarehouse.code || drawerValWarehouse.name}` : 'Role Detail'}
                closeCallback={() => {
                    setMenuIds([]);
                }}
            >
                <AssignPermissionsWarehouse
                    role={drawerValWarehouse}
                    originMenuIds={menuIds}
                    setDrawerVal={setDrawerValWarehouse}
                    closeCallback={() => {
                        setMenuIds([]);
                    }}
                    authType={authType}
                />
            </PageDrawer>
        </Box>
    )
}
const PermissionMenuButton = ({ assign, assignPda, assignMatnr, setAuthType }) => {
const PermissionMenuButton = ({ assign, assignPda, assignMatnr, assignWarehouse, setAuthType }) => {
    const record = useRecordContext();
    const [anchorEl, setAnchorEl] = useState(null);
    const open = Boolean(anchorEl);
@@ -298,6 +345,13 @@
        event.stopPropagation();
        setAuthType(2);
        assignMatnr(record);
        handleClose();
    };
    const handleWarehousePermission = (event) => {
        event.stopPropagation();
        setAuthType(3);
        assignWarehouse(record);
        handleClose();
    };
@@ -343,6 +397,12 @@
                    </ListItemIcon>
                    <ListItemText>物料权限</ListItemText>
                </MenuItem>
                <MenuItem onClick={handleWarehousePermission}>
                    <ListItemIcon>
                        <ArticleIcon fontSize="small" />
                    </ListItemIcon>
                    <ListItemText>仓库权限</ListItemText>
                </MenuItem>
            </Menu>
        </>
    );
rsf-admin/src/page/warehouseRoleMenu/WarehouseRoleMenuCreate.jsx
New file
@@ -0,0 +1,125 @@
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,
} from 'react-admin';
import {
    Dialog,
    DialogActions,
    DialogContent,
    DialogTitle,
    Stack,
    Grid,
    Box,
} from '@mui/material';
import DialogCloseButton from "../components/DialogCloseButton";
import StatusSelectInput from "../components/StatusSelectInput";
import MemoInput from "../components/MemoInput";
const WarehouseRoleMenuCreate = (props) => {
    const { open, setOpen } = props;
    const translate = useTranslate();
    const notify = useNotify();
    const handleClose = (event, reason) => {
        if (reason !== "backdropClick") {
            setOpen(false);
        }
    };
    const handleSuccess = async (data) => {
        setOpen(false);
        notify('common.response.success');
    };
    const handleError = async (error) => {
        notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } });
    };
    return (
        <>
            <CreateBase
                record={{}}
                transform={(data) => {
                    return data;
                }}
                mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
            >
                <Dialog
                    open={open}
                    onClose={handleClose}
                    aria-labelledby="form-dialog-title"
                    fullWidth
                    disableRestoreFocus
                    maxWidth="md"   // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
                >
                    <Form>
                        <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 }}>
                            <Grid container rowSpacing={2} columnSpacing={2}>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.warehouseRoleMenu.roleId"
                                        source="roleId"
                                        autoFocus
                                        validate={required()}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.warehouseRoleMenu.menuId"
                                        source="menuId"
                                        validate={required()}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <StatusSelectInput />
                                </Grid>
                                <Grid item xs={12} display="flex" gap={1}>
                                    <Stack direction="column" spacing={1} width={'100%'}>
                                        <MemoInput />
                                    </Stack>
                                </Grid>
                            </Grid>
                        </DialogContent>
                        <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
                            <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}  >
                                <SaveButton />
                            </Toolbar>
                        </DialogActions>
                    </Form>
                </Dialog>
            </CreateBase>
        </>
    )
}
export default WarehouseRoleMenuCreate;
rsf-admin/src/page/warehouseRoleMenu/WarehouseRoleMenuEdit.jsx
New file
@@ -0,0 +1,97 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
    Edit,
    SimpleForm,
    FormDataConsumer,
    useTranslate,
    TextInput,
    NumberInput,
    BooleanInput,
    DateInput,
    SelectInput,
    ReferenceInput,
    ReferenceArrayInput,
    AutocompleteInput,
    SaveButton,
    Toolbar,
    Labeled,
    NumberField,
    required,
    useRecordContext,
    DeleteButton,
} from 'react-admin';
import { useWatch, useFormContext } from "react-hook-form";
import { Stack, Grid, Box, Typography } from '@mui/material';
import * as Common from '@/utils/common';
import { EDIT_MODE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting';
import EditBaseAside from "../components/EditBaseAside";
import CustomerTopToolBar from "../components/EditTopToolBar";
import MemoInput from "../components/MemoInput";
import StatusSelectInput from "../components/StatusSelectInput";
const FormToolbar = () => {
    const { getValues } = useFormContext();
    return (
        <Toolbar sx={{ justifyContent: 'space-between' }}>
            <SaveButton />
            <DeleteButton mutationMode="optimistic" />
        </Toolbar>
    )
}
const WarehouseRoleMenuEdit = () => {
    const translate = useTranslate();
    return (
        <Edit
            redirect="list"
            mutationMode={EDIT_MODE}
            actions={<CustomerTopToolBar />}
            aside={<EditBaseAside />}
        >
            <SimpleForm
                shouldUnregister
                warnWhenUnsavedChanges
                toolbar={<FormToolbar />}
                mode="onTouched"
                defaultValues={{}}
            // validate={(values) => { }}
            >
                <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
                    <Grid item xs={12} md={8}>
                        <Typography variant="h6" gutterBottom>
                            {translate('common.edit.title.main')}
                        </Typography>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.warehouseRoleMenu.roleId"
                                source="roleId"
                                autoFocus
                                validate={required()}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.warehouseRoleMenu.menuId"
                                source="menuId"
                                validate={required()}
                            />
                        </Stack>
                    </Grid>
                    <Grid item xs={12} md={4}>
                        <Typography variant="h6" gutterBottom>
                            {translate('common.edit.title.common')}
                        </Typography>
                        <StatusSelectInput />
                        <Box mt="2em" />
                        <MemoInput />
                    </Grid>
                </Grid>
            </SimpleForm>
        </Edit >
    )
}
export default WarehouseRoleMenuEdit;
rsf-admin/src/page/warehouseRoleMenu/WarehouseRoleMenuList.jsx
New file
@@ -0,0 +1,154 @@
import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
import { useNavigate } from 'react-router-dom';
import {
    List,
    DatagridConfigurable,
    SearchInput,
    TopToolbar,
    SelectColumnsButton,
    EditButton,
    FilterButton,
    CreateButton,
    ExportButton,
    BulkDeleteButton,
    WrapperField,
    useRecordContext,
    useTranslate,
    useNotify,
    useListContext,
    FunctionField,
    TextField,
    NumberField,
    DateField,
    BooleanField,
    ReferenceField,
    TextInput,
    DateTimeInput,
    DateInput,
    SelectInput,
    NumberInput,
    ReferenceInput,
    ReferenceArrayInput,
    AutocompleteInput,
    DeleteButton,
} from 'react-admin';
import { Box, Typography, Card, Stack } from '@mui/material';
import { styled } from '@mui/material/styles';
import WarehouseRoleMenuCreate from "./WarehouseRoleMenuCreate";
import WarehouseRoleMenuPanel from "./WarehouseRoleMenuPanel";
import EmptyData from "../components/EmptyData";
import MyCreateButton from "../components/MyCreateButton";
import MyExportButton from '../components/MyExportButton';
import PageDrawer from "../components/PageDrawer";
import MyField from "../components/MyField";
import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
import * as Common from '@/utils/common';
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
    '& .css-1vooibu-MuiSvgIcon-root': {
        height: '.9em'
    },
    '& .RaDatagrid-row': {
        cursor: 'auto'
    },
    '& .column-name': {
    },
    '& .opt': {
        width: 200
    },
}));
const filters = [
    <SearchInput source="condition" alwaysOn />,
    <DateInput label='common.time.after' source="timeStart" alwaysOn />,
    <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
    <NumberInput source="roleId" label="table.field.warehouseRoleMenu.roleId" />,
    <NumberInput source="menuId" label="table.field.warehouseRoleMenu.menuId" />,
    <TextInput label="common.field.memo" source="memo" />,
    <SelectInput
        label="common.field.status"
        source="status"
        choices={[
            { id: '1', name: 'common.enums.statusTrue' },
            { id: '0', name: 'common.enums.statusFalse' },
        ]}
        resettable
    />,
]
const WarehouseRoleMenuList = () => {
    const translate = useTranslate();
    const [createDialog, setCreateDialog] = useState(false);
    const [drawerVal, setDrawerVal] = useState(false);
    return (
        <Box display="flex">
            <List
                sx={{
                    flexGrow: 1,
                    transition: (theme) =>
                        theme.transitions.create(['all'], {
                            duration: theme.transitions.duration.enteringScreen,
                        }),
                    marginRight: !!drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0,
                }}
                title={"menu.warehouseRoleMenu"}
                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
                filters={filters}
                sort={{ field: "create_time", order: "desc" }}
                actions={(
                    <TopToolbar>
                        <FilterButton />
                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
                        <SelectColumnsButton preferenceKey='warehouseRoleMenu' />
                        <MyExportButton />
                    </TopToolbar>
                )}
                perPage={DEFAULT_PAGE_SIZE}
            >
                <StyledDatagrid
                    preferenceKey='warehouseRoleMenu'
                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
                    rowClick={(id, resource, record) => false}
                    expand={() => <WarehouseRoleMenuPanel />}
                    expandSingle={true}
                    omit={['id', 'createTime', 'createBy', 'memo']}
                >
                    <NumberField source="id" />
                    <NumberField source="roleId" label="table.field.warehouseRoleMenu.roleId" />
                    <NumberField source="menuId" label="table.field.warehouseRoleMenu.menuId" />
                    <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}>
                        <TextField source="nickname" />
                    </ReferenceField>
                    <DateField source="updateTime" label="common.field.updateTime" showTime />
                    <ReferenceField source="createBy" label="common.field.createBy" reference="user" link={false} sortable={false}>
                        <TextField source="nickname" />
                    </ReferenceField>
                    <DateField source="createTime" label="common.field.createTime" showTime />
                    <BooleanField source="statusBool" label="common.field.status" sortable={false} />
                    <TextField source="memo" label="common.field.memo" sortable={false} />
                    <WrapperField cellClassName="opt" label="common.field.opt">
                        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                        <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
                    </WrapperField>
                </StyledDatagrid>
            </List>
            <WarehouseRoleMenuCreate
                open={createDialog}
                setOpen={setCreateDialog}
            />
            <PageDrawer
                title='WarehouseRoleMenu Detail'
                drawerVal={drawerVal}
                setDrawerVal={setDrawerVal}
            >
            </PageDrawer>
        </Box>
    )
}
export default WarehouseRoleMenuList;
rsf-admin/src/page/warehouseRoleMenu/WarehouseRoleMenuPanel.jsx
New file
@@ -0,0 +1,63 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import { Box, Card, CardContent, Grid, Typography, Tooltip } from '@mui/material';
import {
    useTranslate,
    useRecordContext,
} from 'react-admin';
import PanelTypography from "../components/PanelTypography";
import * as Common from '@/utils/common'
const WarehouseRoleMenuPanel = () => {
    const record = useRecordContext();
    if (!record) return null;
    const translate = useTranslate();
    return (
        <>
            <Card sx={{ width: { xs: 300, sm: 500, md: 600, lg: 800 }, margin: 'auto' }}>
                <CardContent>
                    <Grid container spacing={2}>
                        <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'space-between' }}>
                            <Typography variant="h6" gutterBottom align="left" sx={{
                                maxWidth: { xs: '100px', sm: '180px', md: '260px', lg: '360px' },
                                whiteSpace: 'nowrap',
                                overflow: 'hidden',
                                textOverflow: 'ellipsis',
                            }}>
                                {Common.camelToPascalWithSpaces(translate('table.field.warehouseRoleMenu.id'))}: {record.id}
                            </Typography>
                            {/*  inherit, primary, secondary, textPrimary, textSecondary, error */}
                            <Typography variant="h6" gutterBottom align="right" >
                                ID: {record.id}
                            </Typography>
                        </Grid>
                    </Grid>
                    <Grid container spacing={2}>
                        <Grid item xs={12} container alignContent="flex-end">
                            <Typography variant="caption" color="textSecondary" sx={{ wordWrap: 'break-word', wordBreak: 'break-all' }}>
                                {Common.camelToPascalWithSpaces(translate('common.field.memo'))}:{record.memo}
                            </Typography>
                        </Grid>
                    </Grid>
                    <Box height={20}>&nbsp;</Box>
                    <Grid container spacing={2}>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.warehouseRoleMenu.roleId"
                                property={record.roleId}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.warehouseRoleMenu.menuId"
                                property={record.menuId}
                            />
                        </Grid>
                    </Grid>
                </CardContent>
            </Card >
        </>
    );
};
export default WarehouseRoleMenuPanel;
rsf-admin/src/page/warehouseRoleMenu/index.jsx
New file
@@ -0,0 +1,18 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
    ListGuesser,
    EditGuesser,
    ShowGuesser,
} from "react-admin";
import WarehouseRoleMenuList from "./WarehouseRoleMenuList";
import WarehouseRoleMenuEdit from "./WarehouseRoleMenuEdit";
export default {
    list: WarehouseRoleMenuList,
    edit: WarehouseRoleMenuEdit,
    show: ShowGuesser,
    recordRepresentation: (record) => {
        return `${record.id}`
    }
};
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/params/WcsChangeLocParam.java
@@ -13,16 +13,28 @@
@ApiModel(value = "WcsTaskReportParam", description = "WCS任务上报通知")
public class WcsChangeLocParam implements Serializable {
    @ApiModelProperty("托盘码")
    private String barcode;
    @ApiModelProperty("通知流水号")
    private Long id;
    @ApiModelProperty("源站")
    private Integer sourceStaNo;
    @ApiModelProperty("通知类型:task")
    private String notifyType;
    @ApiModelProperty("可用排")
    private List<Integer> rowList;
    @ApiModelProperty("设备号")
    private String device;
    @ApiModelProperty("库位类型")
    private Integer locType1;
    @ApiModelProperty("WCS工作号")
    private String taskNo;
    @ApiModelProperty("上级系统工作号")
    private String superTaskNo;
    @ApiModelProperty("消息类型:task_complete、task_cancel")
    private String msgType;
    @ApiModelProperty("消息描述")
    private String msgDesc;
    @ApiModelProperty("消息数据")
    private String data;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/params/WcsTaskReportParam.java
@@ -13,16 +13,28 @@
@ApiModel(value = "WcsTaskReportParam", description = "WCS任务上报通知")
public class WcsTaskReportParam implements Serializable {
    @ApiModelProperty("托盘码")
    private String barcode;
    @ApiModelProperty("通知流水号")
    private Long id;
    @ApiModelProperty("源站")
    private Integer sourceStaNo;
    @ApiModelProperty("通知类型:task")
    private String notifyType;
    @ApiModelProperty("可用排")
    private List<Integer> rowList;
    @ApiModelProperty("设备号")
    private String device;
    @ApiModelProperty("库位类型")
    private Integer locType1;
    @ApiModelProperty("WCS工作号")
    private String taskNo;
    @ApiModelProperty("上级系统工作号")
    private String superTaskNo;
    @ApiModelProperty("消息类型:task_complete、task_cancel")
    private String msgType;
    @ApiModelProperty("消息描述")
    private String msgDesc;
    @ApiModelProperty("消息数据")
    private String data;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/WmsWcsServiceImpl.java
@@ -106,7 +106,7 @@
            try {
                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                if (result.getCode() == 200) {
                    return R.ok().add(result.getData());
                    return R.ok(result.getMsg()).add(result.getData());
                } else {
                    return R.error(result.getMsg());
//                    throw new CoolException("任务执行状态上报失败!!");
@@ -145,7 +145,7 @@
            try {
                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                if (result.getCode() == 200) {
                    return R.ok().add(result.getData());
                    return R.ok(result.getMsg()).add(result.getData());
                } else {
                    return R.error(result.getMsg());
//                    throw new CoolException("任务执行状态上报失败!!");
@@ -184,7 +184,7 @@
            try {
                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                if (result.getCode() == 200) {
                    return R.ok().add(result.getData());
                    return R.ok(result.getMsg()).add(result.getData());
                } else {
                    return R.error(result.getMsg());
//                    throw new CoolException("任务执行状态上报失败!!");
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/WcsMsgTypeEvent.java
New file
@@ -0,0 +1,23 @@
package com.vincent.rsf.server.api.entity.enums;
/**
 * @author Munch D. Luffy
 * @date 2026/01/10
 * @description: WCS消息类型
 * @version 1.0
 */
public enum WcsMsgTypeEvent {
    /**消息类型*/
    TASK_COMPLETE("task_complete", "任务完成"),
    TASK_CANCEL("task_cancel", "任务取消"),
    ;
    WcsMsgTypeEvent(String event, String desc) {
        this.event = event;
        this.desc = desc;
    }
    public String event;
    public String desc;
}
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ChangeLocParam.java
@@ -10,7 +10,7 @@
@Data
@Accessors(chain = true)
@ApiModel(value = "ChangeLocParam", description = "WCS任务上报通知")
@ApiModel(value = "ChangeLocParam", description = "WCS申请在库库位更换库位")
public class ChangeLocParam implements Serializable {
    @ApiModelProperty("托盘码")
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ReassignLocParam.java
@@ -10,7 +10,7 @@
@Data
@Accessors(chain = true)
@ApiModel(value = "ReassignLocParam", description = "WCS任务上报通知")
@ApiModel(value = "ReassignLocParam", description = "WCS申请任务重新分配入库")
public class ReassignLocParam implements Serializable {
    @ApiModelProperty("托盘码")
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/TaskReportParam.java
@@ -13,16 +13,28 @@
@ApiModel(value = "TaskReportParam", description = "WCS任务上报通知")
public class TaskReportParam implements Serializable {
    @ApiModelProperty("托盘码")
    private String barcode;
    @ApiModelProperty("通知流水号")
    private Long id;
    @ApiModelProperty("源站")
    private Integer sourceStaNo;
    @ApiModelProperty("通知类型:task")
    private String notifyType;
    @ApiModelProperty("可用排")
    private List<Integer> rowList;
    @ApiModelProperty("设备号")
    private String device;
    @ApiModelProperty("库位类型")
    private Integer locType1;
    @ApiModelProperty("WCS工作号")
    private String taskNo;
    @ApiModelProperty("上级系统工作号")
    private String superTaskNo;
    @ApiModelProperty("消息类型:task_complete、task_cancel")
    private String msgType;
    @ApiModelProperty("消息描述")
    private String msgDesc;
    @ApiModelProperty("消息数据")
    private String data;
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -19,6 +19,7 @@
import com.vincent.rsf.server.api.controller.erp.params.TaskInParam;
import com.vincent.rsf.server.api.entity.dto.SyncLocsDto;
import com.vincent.rsf.server.api.entity.enums.CallBackEvent;
import com.vincent.rsf.server.api.entity.enums.WcsMsgTypeEvent;
import com.vincent.rsf.server.api.entity.params.*;
import com.vincent.rsf.server.manager.controller.params.GenerateTaskParams;
import com.vincent.rsf.server.manager.enums.*;
@@ -938,6 +939,14 @@
        if (waitPakins.isEmpty()) {
            throw new CoolException("单据不存在 !!");
        }
        DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>().eq(DeviceSite::getSite,params.getSourceStaNo()).orderByDesc(DeviceSite::getId),false);
        if (Objects.isNull(deviceSite)) {
            throw new CoolException(params.getSourceStaNo()+"站点不存在!!");
        }
        if (deviceSite.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type.toString())) {
            throw new CoolException(params.getSourceStaNo()+"站点非光电站点!!请使用PDA绑定入库");
        }
        Task one = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, params.getBarcode()));
        if (!Cools.isEmpty(one)) {
            InTaskWcsReportParam inTaskWcsReportParam = new InTaskWcsReportParam();
@@ -946,15 +955,10 @@
            inTaskWcsReportParam.setTaskPri(one.getSort());
            return R.ok("任务已存在直接下发!").add(inTaskWcsReportParam);
        }
        DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>().eq(DeviceSite::getSite,params.getSourceStaNo()).orderByDesc(DeviceSite::getId),false);
        if (Objects.isNull(deviceSite)) {
            throw new CoolException(params.getSourceStaNo()+"站点不存在!!");
        }
        GenerateTaskParams taskParams = new GenerateTaskParams();
        taskParams.setWaitPakins(waitPakins)
                .setSiteId(deviceSite.getId());
        R r = taskService.generateTasks(taskParams, 111L);
        R r = taskService.generateTasksWcs(taskParams, 111L,params.getRowList());//lsh待修改  WCS用户信息
        if (r.get("msg").equals("任务生成完毕!")) {
            one = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, params.getBarcode()));
            InTaskWcsReportParam inTaskWcsReportParam = new InTaskWcsReportParam();
@@ -970,7 +974,7 @@
    }
    /**
     * WCS入库任务申请
     * WCS任务上报通知
     *
     * @return
     */
@@ -979,21 +983,28 @@
        if (Objects.isNull(params)) {
            return R.error("参数不能为空!!");
        }
        List<WaitPakin> waitPakins = waitPakinService.list(new LambdaQueryWrapper<WaitPakin>().eq(WaitPakin::getBarcode, params.getBarcode()));
        if (waitPakins.isEmpty()) {
            throw new CoolException("单据不存在 !!");
        Task one = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, params.getSuperTaskNo()));
        if (params.getMsgType().equals(WcsMsgTypeEvent.TASK_COMPLETE.event)){
            if (!Cools.isEmpty(one)) {
                one.setTaskStatus(one.getTaskType() < 100 ? TaskStsType.COMPLETE_IN.id : TaskStsType.AWAIT.id);
                if (!taskService.updateById(one)) {
//                    throw new CoolException("完成任务失败");
                    return R.error("完成任务失败").add(one);
                }
                return R.ok("任务完成成功").add(one);
            }
        } else if (params.getMsgType().equals(WcsMsgTypeEvent.TASK_CANCEL.event)){
//            if (!Cools.isEmpty(one)) {
//                one.setTaskStatus(one.getTaskType() < 100 ? TaskStsType.COMPLETE_IN.id : TaskStsType.AWAIT.id);
//                return R.ok("!").add(one);
//            }
            return R.error("暂不允许取消");
        }
        GenerateTaskParams taskParams = new GenerateTaskParams();
        taskParams.setWaitPakins(waitPakins)
                .setSiteId(params.getSourceStaNo().longValue());
        return R.ok(taskService.generateTasks(taskParams, 111L));
//        return R.ok("任务生成完毕!");
//        log.info(JSONObject.toJSONString(params));
//        return R.ok(JSONObject.toJSONString(params));
        return R.error("数据异常");
    }
    /**
     * WCS入库任务申请
     * WCS申请任务重新分配入库
     *
     * @return
     */
@@ -1016,7 +1027,7 @@
    }
    /**
     * WCS入库任务申请
     * WCS申请在库库位更换库位
     *
     * @return
     */
rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
@@ -15,14 +15,14 @@
        generator.frontendPrefixPath = "rsf-admin/";
        generator.sqlOsType = SqlOsType.MYSQL;
        generator.url = "192.168.4.15:3306/rsf_20250106";
        generator.url = "192.168.4.36:3306/rsf";
        generator.username = "root";
        generator.password = "1234";
        generator.password = "root";
//        generator.url="47.97.1.152:51433;databasename=jkasrs";
//        generator.username="sa";
//        generator.password="Zoneyung@zy56$";
        generator.table = "sys_matnr_role_menu";
        generator.table = "sys_warehouse_role_menu";
        generator.tableDesc = "物料权限";
        generator.packagePath = "com.vincent.rsf.server.system";
rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
@@ -55,6 +55,7 @@
                        "sys_pda_role_menu",
                        "sys_menu_pda",
                        "sys_matnr_role_menu",
                        "sys_warehouse_role_menu",
                        "man_loc_type_rela",
                        "man_qly_inspect_result",
                        "view_stock_manage",
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/params/WaitPakinParam.java
@@ -25,5 +25,8 @@
    @ApiModelProperty("组拖类型{null: 组拖, defective: 不良品}")
    private String type;
    @ApiModelProperty("AGV站点")
    private String staNo;
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/TaskService.java
@@ -10,6 +10,7 @@
public interface TaskService extends IService<Task> {
    R generateTasks(GenerateTaskParams waitPakin, Long loginUserId);
    R generateTasksWcs(GenerateTaskParams waitPakin, Long loginUserId,List<Integer> rowList);
    R generateFlatWarehouseTasks(WaitPakin waitPakins, String locCode, Long loginUserId);
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -446,6 +446,123 @@
        return R.ok("任务生成完毕!");
    }
    /**
     * @param loginUserId
     * @author Munch D. Luffy
     * @date 2026/01/10
     * @description: WCS入库任务申请
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized R generateTasksWcs(GenerateTaskParams waitPakin, Long loginUserId,List<Integer> rowList) {
        if (Objects.isNull(waitPakin) || waitPakin.getWaitPakins().isEmpty()) {
            throw new CoolException("参数不能为空!!");
        }
        DeviceSite deviceSite = deviceSiteService.getById(waitPakin.getSiteId());
        if (Objects.isNull(deviceSite)) {
            throw new CoolException("站点不存在!!");
        }
        DeviceBind deviceBind = deviceBindService.getById(LocUtils.getAreaType(deviceSite.getSite()));
        if (Cools.isEmpty(deviceBind)) {
            throw new CoolException("库位规则未知");
        }
        WarehouseAreas warehouseArea = warehouseAreasService.getById(deviceBind.getTypeId());
        if (Cools.isEmpty(warehouseArea)) {
            throw new CoolException("未找到所属库区信息");
        }
        /**获取组拖*/
        List<Long> ids = waitPakin.getWaitPakins().stream().map(WaitPakin::getId).collect(Collectors.toList());
        List<WaitPakin> waitPakins = waitPakinService.list(new LambdaQueryWrapper<WaitPakin>()
                .in(WaitPakin::getId, ids)
                .eq(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val));
        if (waitPakins.isEmpty()) {
            throw new CoolException("请检查组拖状态是否完成!!");
        }
        waitPakins.forEach(pakin -> {
            BasContainer container = basContainerService.getOne(new LambdaUpdateWrapper<BasContainer>()
                    .eq(BasContainer::getCode, pakin.getBarcode()));
            if (Objects.isNull(container)) {
                throw new CoolException("容器未维护入库,请维护后再操作!!");
            }
            /**获取库位*/
            String targetLoc = LocManageUtil.getTargetLoc(warehouseArea.getId(), container.getContainerType(),rowList);
            if (Cools.isEmpty(targetLoc)) {
                throw new CoolException("该站点对应库区未找到库位");
            }
            List<TaskItem> taskItems = new ArrayList<>();
            String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null);
            if (StringUtils.isBlank(ruleCode)) {
                throw new CoolException("编码错误:请确认编码「SYS_TASK_CODE」是否已生成!!");
            }
            Task task = new Task();
            task.setTaskCode(ruleCode)
                    .setTaskStatus(TaskStsType.WCS_EXECUTE_IN.id)
                    .setTaskType(TaskType.TASK_TYPE_IN.type)
                    .setResource(TaskResouceType.TASK_RESOUCE_PAKIN_TYPE.val)
                    .setTargLoc(targetLoc)
                    .setBarcode(pakin.getBarcode())
                    .setOrgSite(deviceSite.getSite())
                    .setTargSite(deviceSite.getDeviceSite())
                    .setCreateBy(loginUserId)
                    .setUpdateBy(loginUserId);
            if (!this.save(task)) {
                throw new CoolException("任务保存失败!!");
            }
            if (!locService.update(new LambdaUpdateWrapper<Loc>().eq(Loc::getCode, task.getTargLoc())
                    .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type).set(Loc::getBarcode, pakin.getBarcode()))) {
                throw new CoolException("库位预约失败!!");
            }
            /**获取组拖明细**/
            List<WaitPakinItem> waitPakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, pakin.getId()));
            if (waitPakinItems.isEmpty()) {
                throw new CoolException("数据错误:组拖明细不存在");
            }
            waitPakinItems.forEach(item -> {
                TaskItem taskItem = new TaskItem();
                BeanUtils.copyProperties(item, taskItem);
                taskItem.setTaskId(task.getId())
                        .setOrderType(OrderType.ORDER_IN.type)
                        .setSource(item.getId())
                        .setTrackCode(item.getTrackCode())
                        .setPlatItemId(item.getPlatItemId())
                        .setPlatOrderCode(item.getPlatOrderCode())
                        .setPlatWorkCode(item.getPlatWorkCode())
                        .setProjectCode(item.getProjectCode())
                        .setCreateBy(loginUserId)
                        .setUpdateBy(loginUserId)
                        .setExtendFields(item.getExtendFields())
                        .setOrderId(item.getAsnId())
                        .setOrderItemId(item.getAsnItemId());
                taskItems.add(taskItem);
            });
            if (!taskItemService.saveBatch(taskItems)) {
                throw new CoolException("任务明细保存失败!!");
            }
            waitPakinItems.forEach(item -> {
                if (!waitPakinItemService.update(new LambdaUpdateWrapper<WaitPakinItem>()
                        .set(WaitPakinItem::getWorkQty, item.getAnfme())
                        .eq(WaitPakinItem::getId, item.getId()))) {
                    throw new CoolException("组托明细修执行数量修改失败!!");
                }
            });
        });
        if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                .in(WaitPakin::getId, ids)
                .set(WaitPakin::getUpdateBy, loginUserId)
                .set(WaitPakin::getCreateBy, loginUserId)
                .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val))) {
            throw new CoolException("组拖状态修改失败!!");
        }
        return R.ok("任务生成完毕!");
    }
    /**
     * 入库任务
     *
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
@@ -33,6 +33,25 @@
        return getTargetLoc(areaId, null);
    }
    public static String getTargetLoc(Long areaId, Long containerType,List<Integer> rowList) {
        Long locType = containerType;
        //TODO 库位策略后续排期
        LocService locService = SpringUtils.getBean(LocService.class);
        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>()
                .eq(!Objects.isNull(locType), Loc::getType, locType)
                .eq(Loc::getAreaId, areaId)
                .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                .in(Objects.nonNull(rowList) && !rowList.isEmpty(), Loc::getRow, rowList)
                .orderByAsc(Loc::getLev)
                .orderByAsc(Loc::getCol)
                .orderByAsc(Loc::getRow)
                .last("LIMIT 1")
        );
        return !Objects.isNull(loc) ? loc.getCode() : null;
    }
    public static String getTargetLoc(Long areaId, Long containerType) {
        Long locType = null;
//        if (!Objects.isNull(containerType)) {
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/WarehouseRoleMenuController.java
New file
@@ -0,0 +1,113 @@
package com.vincent.rsf.server.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.common.utils.Utils;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.common.utils.ExcelUtil;
import com.vincent.rsf.server.common.annotation.OperationLog;
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.manager.entity.MatnrGroup;
import com.vincent.rsf.server.manager.entity.Warehouse;
import com.vincent.rsf.server.manager.entity.WarehouseAreas;
import com.vincent.rsf.server.manager.service.WarehouseAreasService;
import com.vincent.rsf.server.manager.service.WarehouseService;
import com.vincent.rsf.server.system.controller.param.RoleScopeParam;
import com.vincent.rsf.server.system.entity.PdaRoleMenu;
import com.vincent.rsf.server.system.entity.WarehouseRoleMenu;
import com.vincent.rsf.server.system.service.WarehouseRoleMenuService;
import com.vincent.rsf.server.system.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
@RestController
public class WarehouseRoleMenuController extends BaseController {
    @Autowired
    private WarehouseRoleMenuService warehouseRoleMenuService;
    @Autowired
    private WarehouseAreasService warehouseAreasService;
    @Autowired
    private WarehouseService warehouseService;
    @GetMapping("/roleWarehouse/scope/list")
    public R scopeList(@RequestParam Long roleId) {
        return R.ok().add(warehouseRoleMenuService.listStrictlyMenuByRoleId(roleId));
    }
    @PreAuthorize("hasAuthority('system:role:update')")
    @OperationLog("Assign Permissions")
    @PostMapping("/roleWarehouse/scope/update")
    @Transactional
    public R scopeUpdate(@RequestBody RoleScopeParam param) {
        Long roleId = param.getId();
        List<Long> menuIds = new ArrayList<>(param.getMenuIds().getChecked());
        menuIds.addAll(param.getMenuIds().getHalfChecked());
        warehouseRoleMenuService
                .remove(new LambdaQueryWrapper<WarehouseRoleMenu>().eq(WarehouseRoleMenu::getRoleId, roleId));
        for (Long menuId : menuIds) {
            if (!warehouseRoleMenuService.save(new WarehouseRoleMenu(roleId, menuId))) {
                throw new CoolException("Internal Server Error!");
            }
        }
        return R.ok("Assign Success");
    }
    @PostMapping("/menuWarehouse/tree")
    public R tree(@RequestBody Map<String, Object> map) {
        // 查询所有仓库
        List<Warehouse> warehouseList = warehouseService
                .list(new LambdaQueryWrapper<Warehouse>().orderByAsc(Warehouse::getId));
        // 查询所有库区
        List<WarehouseAreas> areasList = warehouseAreasService
                .list(new LambdaQueryWrapper<WarehouseAreas>().orderByAsc(WarehouseAreas::getId));
        // 按仓库ID分组库区
        Map<Long, List<WarehouseAreas>> areasMap = areasList.stream()
                .collect(java.util.stream.Collectors.groupingBy(WarehouseAreas::getWarehouseId));
        // 构建树形结构:将库区设置为仓库的children
        for (Warehouse warehouse : warehouseList) {
            List<WarehouseAreas> children = areasMap.getOrDefault(warehouse.getId(), new ArrayList<>());
            // 仓库使用100000+id,避免与库区id重复
            warehouse.setId(100000L + warehouse.getId());
            warehouse.setFlagWare(1); // 1表示仓库
            // 库区保持原id不变
            for (WarehouseAreas area : children) {
                area.setFlagWare(0); // 0表示库区
            }
            warehouse.setChildren(children);
        }
        // 条件过滤
        if (!Cools.isEmpty(map.get("condition"))) {
            String condition = String.valueOf(map.get("condition"));
            // 过滤仓库名称和库区名称
            warehouseList.removeIf(warehouse -> {
                // 先过滤库区
                if (warehouse.getChildren() != null) {
                    warehouse.getChildren()
                            .removeIf(area -> area.getName() != null && !area.getName().contains(condition));
                }
                // 如果仓库名称不匹配且没有匹配的库区,则移除该仓库
                boolean warehouseMatch = warehouse.getName() != null && warehouse.getName().contains(condition);
                boolean hasMatchingChildren = warehouse.getChildren() != null && !warehouse.getChildren().isEmpty();
                return !warehouseMatch && !hasMatchingChildren;
            });
        }
        return R.ok().add(warehouseList);
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/WarehouseRoleMenu.java
New file
@@ -0,0 +1,50 @@
package com.vincent.rsf.server.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.SpringUtils;
import com.vincent.rsf.server.system.service.UserService;
import com.vincent.rsf.server.system.entity.User;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("sys_warehouse_role_menu")
public class WarehouseRoleMenu implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value= "")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value= "")
    private Long roleId;
    @ApiModelProperty(value= "")
    private Long menuId;
    public WarehouseRoleMenu() {}
    public WarehouseRoleMenu(Long roleId,Long menuId) {
        this.roleId = roleId;
        this.menuId = menuId;
    }
//    WarehouseRoleMenu warehouseRoleMenu = new WarehouseRoleMenu(
//            null,    // [非空]
//            null    // [非空]
//    );
}
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/WarehouseRoleMenuMapper.java
New file
@@ -0,0 +1,16 @@
package com.vincent.rsf.server.system.mapper;
import com.vincent.rsf.server.system.entity.WarehouseRoleMenu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface WarehouseRoleMenuMapper extends BaseMapper<WarehouseRoleMenu> {
    List<Long> listStrictlyMenuByRoleId(@Param("roleId") Long roleId);
}
rsf-server/src/main/java/com/vincent/rsf/server/system/service/WarehouseRoleMenuService.java
New file
@@ -0,0 +1,11 @@
package com.vincent.rsf.server.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.server.system.entity.WarehouseRoleMenu;
import java.util.List;
public interface WarehouseRoleMenuService extends IService<WarehouseRoleMenu> {
    List<Long> listStrictlyMenuByRoleId(Long roleId);
}
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/WarehouseRoleMenuServiceImpl.java
New file
@@ -0,0 +1,18 @@
package com.vincent.rsf.server.system.service.impl;
import com.vincent.rsf.server.system.mapper.WarehouseRoleMenuMapper;
import com.vincent.rsf.server.system.entity.WarehouseRoleMenu;
import com.vincent.rsf.server.system.service.WarehouseRoleMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("warehouseRoleMenuService")
public class WarehouseRoleMenuServiceImpl extends ServiceImpl<WarehouseRoleMenuMapper, WarehouseRoleMenu> implements WarehouseRoleMenuService {
    @Override
    public List<Long> listStrictlyMenuByRoleId(Long roleId) {
        return this.baseMapper.listStrictlyMenuByRoleId(roleId);
    }
}
rsf-server/src/main/java/warehouseRoleMenu.sql
New file
@@ -0,0 +1,23 @@
-- save warehouseRoleMenu record
-- mysql
insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.warehouseRoleMenu', '0', '/system/warehouseRoleMenu', 'warehouseRoleMenu', '0' , '0', '1' , '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query 物料权限', '', '1', 'system:warehouseRoleMenu:list', '0', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create 物料权限', '', '1', 'system:warehouseRoleMenu:save', '1', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update 物料权限', '', '1', 'system:warehouseRoleMenu:update', '2', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete 物料权限', '', '1', 'system:warehouseRoleMenu:remove', '3', '1', '1');
-- locale menu name
warehouseRoleMenu: 'WarehouseRoleMenu',
-- locale field
warehouseRoleMenu: {
    roleId: "roleId",
    menuId: "menuId",
},
-- ResourceContent
import warehouseRoleMenu from './warehouseRoleMenu';
case 'warehouseRoleMenu':
    return warehouseRoleMenu;
rsf-server/src/main/resources/application-dev.yml
@@ -16,8 +16,8 @@
    username: root
#    url: jdbc:mysql://10.10.10.200:3306/rsf?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
#    password: xltys1995
    url: jdbc:mysql://192.168.4.15:3306/rsf_20250106?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    password: 1234
    url: jdbc:mysql://192.168.4.36:3306/rsf?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5
@@ -34,7 +34,7 @@
      #pool-prepared-statements: false
      #max-pool-prepared-statement-per-connection-size: 20
      filters: stat, wall
      validation-query: SELECT 'x'
      validation-query: SELECT 1
      aop-patterns: com.zy.*.*.service.*
      stat-view-servlet:
        url-pattern: /druid/*
rsf-server/src/main/resources/application.yml
@@ -33,7 +33,7 @@
    :banner: false
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-delete-value: 0
      logic-not-delete-value: 0
super:
rsf-server/src/main/resources/mapper/system/WarehouseRoleMenuMapper.xml
New file
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vincent.rsf.server.system.mapper.WarehouseRoleMenuMapper">
    <select id="listStrictlyMenuByRoleId" resultType="java.lang.Long">
        select sm.id
        from man_warehouse_areas sm
        left join sys_warehouse_role_menu srm on sm.id = srm.menu_id
        where 1=1
        and sm.deleted = 0
        and srm.role_id = #{roleId}
        <!--
        and sm.id not in (
            select sm.parent_id
            from sys_menu sm
            inner join sys_role_menu srm on sm.id = srm.menu_id
            and srm.role_id = #{roleId}
        )
        -->
        order by sm.id
    </select>
</mapper>