| New file |
| | |
| | | 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; |
| | |
| | | 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'; |
| | |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | const [drawerValPda, setDrawerValPda] = useState(false); |
| | | const [drawerValMatnr, setDrawerValMatnr] = useState(false); |
| | | const [drawerValWarehouse, setDrawerValWarehouse] = useState(false); |
| | | |
| | | const [menuIds, setMenuIds] = useState([]); |
| | | |
| | |
| | | |
| | | const assign = (record) => { |
| | | setDrawerValPda(false); |
| | | setDrawerValMatnr(false); |
| | | setDrawerValWarehouse(false); |
| | | request('/role/scope/list', { |
| | | method: 'GET', |
| | | params: { |
| | |
| | | |
| | | const assignPda = (record) => { |
| | | setDrawerVal(false); |
| | | setDrawerValMatnr(false); |
| | | setDrawerValWarehouse(false); |
| | | request('/rolePda/scope/list', { |
| | | method: 'GET', |
| | | params: { |
| | |
| | | |
| | | const assignMatnr = (record) => { |
| | | setDrawerVal(false); |
| | | setDrawerValPda(false); |
| | | setDrawerValWarehouse(false); |
| | | request('/roleMatnr/scope/list', { |
| | | method: 'GET', |
| | | params: { |
| | |
| | | }); |
| | | } |
| | | |
| | | 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 |
| | |
| | | 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) }} />} |
| | |
| | | assign={assign} |
| | | assignPda={assignPda} |
| | | assignMatnr={assignMatnr} |
| | | assignWarehouse={assignWarehouse} |
| | | setAuthType={setAuthType} |
| | | /> |
| | | <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} /> |
| | |
| | | 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); |
| | |
| | | event.stopPropagation(); |
| | | setAuthType(2); |
| | | assignMatnr(record); |
| | | handleClose(); |
| | | }; |
| | | |
| | | const handleWarehousePermission = (event) => { |
| | | event.stopPropagation(); |
| | | setAuthType(3); |
| | | assignWarehouse(record); |
| | | handleClose(); |
| | | }; |
| | | |
| | |
| | | </ListItemIcon> |
| | | <ListItemText>物料权限</ListItemText> |
| | | </MenuItem> |
| | | <MenuItem onClick={handleWarehousePermission}> |
| | | <ListItemIcon> |
| | | <ArticleIcon fontSize="small" /> |
| | | </ListItemIcon> |
| | | <ListItemText>仓库权限</ListItemText> |
| | | </MenuItem> |
| | | </Menu> |
| | | </> |
| | | ); |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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}> </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; |
| New file |
| | |
| | | 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}` |
| | | } |
| | | }; |
| | |
| | | @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; |
| | | |
| | | } |
| | |
| | | @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; |
| | | |
| | | } |
| | |
| | | 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("任务执行状态上报失败!!"); |
| | |
| | | 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("任务执行状态上报失败!!"); |
| | |
| | | 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("任务执行状态上报失败!!"); |
| New file |
| | |
| | | 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; |
| | | } |
| | |
| | | |
| | | @Data |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "ChangeLocParam", description = "WCS任务上报通知") |
| | | @ApiModel(value = "ChangeLocParam", description = "WCS申请在库库位更换库位") |
| | | public class ChangeLocParam implements Serializable { |
| | | |
| | | @ApiModelProperty("托盘码") |
| | |
| | | |
| | | @Data |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "ReassignLocParam", description = "WCS任务上报通知") |
| | | @ApiModel(value = "ReassignLocParam", description = "WCS申请任务重新分配入库") |
| | | public class ReassignLocParam implements Serializable { |
| | | |
| | | @ApiModelProperty("托盘码") |
| | |
| | | @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; |
| | | |
| | | } |
| | |
| | | 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.*; |
| | |
| | | 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(); |
| | |
| | | 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(); |
| | |
| | | } |
| | | |
| | | /** |
| | | * WCS入库任务申请 |
| | | * WCS任务上报通知 |
| | | * |
| | | * @return |
| | | */ |
| | |
| | | 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 |
| | | */ |
| | |
| | | } |
| | | |
| | | /** |
| | | * WCS入库任务申请 |
| | | * WCS申请在库库位更换库位 |
| | | * |
| | | * @return |
| | | */ |
| | |
| | | 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"; |
| | | |
| | |
| | | "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", |
| | |
| | | @ApiModelProperty("组拖类型{null: 组拖, defective: 不良品}") |
| | | private String type; |
| | | |
| | | @ApiModelProperty("AGV站点") |
| | | private String staNo; |
| | | |
| | | } |
| | | |
| | |
| | | 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); |
| | | |
| | |
| | | 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("任务生成完毕!"); |
| | | } |
| | | |
| | | /** |
| | | * 入库任务 |
| | | * |
| | |
| | | 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)) { |
| New file |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | 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 // [非空] |
| | | // ); |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| New file |
| | |
| | | -- 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; |
| | |
| | | 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 |
| | |
| | | #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/* |
| | |
| | | :banner: false
|
| | | db-config:
|
| | | id-type: auto
|
| | | logic-delete-value: 1
|
| | | logic-delete-value: 0
|
| | | logic-not-delete-value: 0
|
| | |
|
| | | super:
|
| New file |
| | |
| | | <?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> |