| New file |
| | |
| | | import React, { createContext, useCallback, useRef, useContext, useEffect } from 'react'; |
| | | |
| | | const STORAGE_KEY = 'rsf_tab_dialog_state'; |
| | | |
| | | // 按路径存储弹窗状态 |
| | | const TabDialogStateContext = createContext(null); |
| | | |
| | | // 规范化路径为「资源列表」路径 |
| | | export const getDialogStatePath = (pathnameOrLocation) => { |
| | | const pathname = typeof pathnameOrLocation === 'object' && pathnameOrLocation?.pathname != null |
| | | ? pathnameOrLocation.pathname |
| | | : String(pathnameOrLocation || ''); |
| | | if (!pathname || pathname === '/') return pathname || '/'; |
| | | const segments = pathname.replace(/^\//, '').split('/').filter(Boolean); |
| | | const first = segments[0]; |
| | | return first ? `/${first}` : pathname; |
| | | }; |
| | | |
| | | const readFromStorage = () => { |
| | | try { |
| | | const raw = typeof window !== 'undefined' ? window.localStorage.getItem(STORAGE_KEY) : null; |
| | | if (raw) { |
| | | const parsed = JSON.parse(raw); |
| | | return typeof parsed === 'object' && parsed !== null ? parsed : {}; |
| | | } |
| | | } catch (_) {} |
| | | return {}; |
| | | }; |
| | | |
| | | const writeToStorage = (data) => { |
| | | try { |
| | | if (typeof window !== 'undefined') { |
| | | window.localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); |
| | | } |
| | | } catch (_) {} |
| | | }; |
| | | |
| | | export const TabDialogStateProvider = ({ children }) => { |
| | | const storeRef = useRef({}); |
| | | |
| | | useEffect(() => { |
| | | storeRef.current = readFromStorage(); |
| | | }, []); |
| | | |
| | | const getDialogState = useCallback((path) => { |
| | | const fromRef = storeRef.current[path]; |
| | | if (fromRef !== undefined) return fromRef; |
| | | const fromStorage = readFromStorage()[path]; |
| | | if (fromStorage !== undefined) { |
| | | storeRef.current[path] = fromStorage; |
| | | return fromStorage; |
| | | } |
| | | return undefined; |
| | | }, []); |
| | | |
| | | const setDialogState = useCallback((path, state) => { |
| | | storeRef.current[path] = state; |
| | | const all = readFromStorage(); |
| | | all[path] = state; |
| | | writeToStorage(all); |
| | | }, []); |
| | | |
| | | const value = { getDialogState, setDialogState }; |
| | | return ( |
| | | <TabDialogStateContext.Provider value={value}> |
| | | {children} |
| | | </TabDialogStateContext.Provider> |
| | | ); |
| | | }; |
| | | |
| | | export const useTabDialogState = () => { |
| | | const ctx = useContext(TabDialogStateContext); |
| | | if (!ctx) return null; |
| | | return ctx; |
| | | }; |
| | |
| | | purUnit: "purchaseUnit", |
| | | stockUnit: "stockUnit", |
| | | stockLevel: "stockLeval", |
| | | stockQty: "Stock Qty", |
| | | isLabelMange: "isLabelMange", |
| | | safeQty: "safetyQty", |
| | | minQty: "minQty", |
| | |
| | | flagLabelMange: "FlagLabelMange", |
| | | locAttrs: "LocAttrs", |
| | | useStatus: 'useStatus', |
| | | locAreaId: 'locAreaId' |
| | | locAreaId: 'locAreaId', |
| | | locCode: 'Loc Code' |
| | | }, |
| | | locType: { |
| | | uuid: "uuid", |
| | |
| | | purUnit: "采购单位", |
| | | stockUnit: "库存单位", |
| | | stockLevel: "ABC分类", |
| | | stockQty: "库存数量", |
| | | isLabelMange: "标签管理", |
| | | safeQty: "安全值", |
| | | minQty: "最小值", |
| | |
| | | startLev: "起始层", |
| | | startRow: "起始排", |
| | | useStatus: '库位状态', |
| | | locAreaId: '逻辑分区' |
| | | locAreaId: '逻辑分区', |
| | | locCode: '库位' |
| | | }, |
| | | stockStatistic: { |
| | | id: "id", |
| | |
| | | closeRight: '关闭右侧标签', |
| | | closeOthers: '关闭其他标签', |
| | | closeAll: '关闭所有标签', |
| | | sort: '排序', |
| | | bulk_actions: '%{smart_count} 条被选中 |||| %{smart_count} 条被选中', |
| | | }, |
| | | page: { |
| | | empty_with_filters: '使用当前过滤条件未找到结果。', |
| | |
| | | const handleContextMenu = (event, tab) => { |
| | | event.preventDefault(); |
| | | event.stopPropagation(); |
| | | setContextMenu( |
| | | contextMenu === null |
| | | ? { |
| | | if (!tab) return; |
| | | const tabObj = tabs.find(t => t.path === tab.path || isSameResource(t.path, tab.path)); |
| | | if (!tabObj) return; |
| | | const hasItems = tabObj.closable || |
| | | canCloseLeftForTab(tab.path) || |
| | | canCloseRightForTab(tab.path) || |
| | | canCloseOthersForTab(tab.path); |
| | | if (!hasItems) return; |
| | | setContextMenu({ |
| | | mouseX: event.clientX + 2, |
| | | mouseY: event.clientY - 6, |
| | | } |
| | | : null |
| | | ); |
| | | }); |
| | | setContextMenuTab(tab); |
| | | }; |
| | | |
| | |
| | | ? { top: contextMenu.mouseY, left: contextMenu.mouseX } |
| | | : undefined |
| | | } |
| | | disableScrollLock |
| | | ModalProps={{ disablePortal: true }} |
| | | PaperProps={{ |
| | | sx: { |
| | | minWidth: 120, |
| | |
| | | sx: { py: 0 }, |
| | | }} |
| | | > |
| | | {/* 关闭当前标签 |
| | | {contextMenuTab && contextMenuTab.closable && ( |
| | | <MenuItem |
| | | onClick={handleCloseCurrentTab} |
| | |
| | | {t('ra.action.close', '关闭当前标签')} |
| | | </MenuItem> |
| | | )} |
| | | */} |
| | | {contextMenuTab && canCloseLeftForTab(contextMenuTab.path) && ( |
| | | <MenuItem |
| | | onClick={handleCloseLeftTabs} |
| | |
| | | import { MyMenu } from './MyMenu'; |
| | | import TabsBar from './TabsBar'; |
| | | import { Box } from '@mui/material'; |
| | | import { TabDialogStateProvider } from '@/context/TabDialogStateContext'; |
| | | |
| | | const LayoutContent = ({ children }) => { |
| | | const [sidebarIsOpen] = useSidebarState(); |
| | |
| | | top: 48, |
| | | left: sidebarWidth + 5, |
| | | right: 0, |
| | | zIndex: 1400, // 高于 Dialog/Modal(1300),通过 Portal 挂到 body 才能盖住弹窗 |
| | | zIndex: 1200, // 低于 Dialog/Modal 与 Select/Menu(1300),避免标签页遮盖下发窗口内的下拉(如出库策略:效率优先/先进先出) |
| | | transition: (theme) => |
| | | theme.transitions.create('left', { |
| | | easing: theme.transitions.easing.sharp, |
| | |
| | | }} |
| | | > |
| | | {createPortal(tabsBarEl, document.body)} |
| | | <TabDialogStateProvider> |
| | | {children} |
| | | </TabDialogStateProvider> |
| | | <CheckForApplicationUpdate /> |
| | | </RALayout> |
| | | ); |
| | |
| | | </Box> |
| | | </Card> |
| | | <Card sx={{mt: '1em'}}> |
| | | <Box> |
| | | <Box sx={{ pt: 2 }}> |
| | | {children} |
| | | </Box> |
| | | </Card> |
| | |
| | | <NumberField source="id" />, |
| | | <NumberField source="locId" label="table.field.locItem.locId" />, |
| | | <TextField source="locCode" label="table.field.locItem.locCode" />, |
| | | <TextField source="locUseStatus$" label="table.field.loc.useStatus" />, |
| | | <NumberField source="matnrId" label="table.field.locItem.matnrId" />, |
| | | <TextField source="maktx" label="table.field.locItem.maktx" />, |
| | | <TextField source="matnrCode" label="table.field.locItem.matnrCode" />, |
| | |
| | | Box, |
| | | Button, |
| | | Paper, |
| | | styled |
| | | styled, |
| | | Select, |
| | | MenuItem, |
| | | FormControl, |
| | | InputLabel |
| | | } from '@mui/material'; |
| | | import DialogCloseButton from "../../components/DialogCloseButton"; |
| | | import { EDIT_MODE, DEFAULT_START_PAGE, DEFAULT_PAGE_SIZE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting'; |
| | |
| | | } |
| | | }; |
| | | |
| | | const [formData, setFormData] = useState({}); |
| | | const [formData, setFormData] = useState({ locUseStatus: 'F' }); |
| | | const [tableData, setTableData] = useState([]); |
| | | const [dyFields, setDyFields] = useState([]); |
| | | const [selectedRows, setSelectedRows] = useState([]); |
| | |
| | | const [isLoading, setIsLoading] = useState(false); |
| | | const handleChange = (e) => { |
| | | const { name, value } = e.target; |
| | | setFormData(() => ({ |
| | | setFormData((prev) => ({ |
| | | ...prev, |
| | | [name]: value |
| | | })); |
| | | }; |
| | |
| | | setFormData({ |
| | | name: null, |
| | | code: null, |
| | | groupId: null |
| | | groupId: null, |
| | | locUseStatus: 'F' |
| | | }) |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | const hasarr = data.map(el => +el.matnrId) |
| | | const selectedData = selectedRows.filter(item => !hasarr.includes(item)).map(id => (tableData.find(row => row.id === id))); |
| | | const value = selectedData.map((el => { |
| | | const hasarr = data.map(el => +el.matnrId); |
| | | const selectedData = selectedRows |
| | | .filter((item) => !hasarr.includes(item)) |
| | | .map((id) => tableData.find((row) => row.id === id)) |
| | | .filter(Boolean); |
| | | const deduped = [...new Map(selectedData.map((s) => [s.id, s])).values()]; |
| | | const value = deduped.map((el, i) => { |
| | | const dynamicFields = dyFields.reduce((acc, item) => { |
| | | acc[item.fields] = el['extendFields']?.[item.fields] || ''; |
| | | return acc; |
| | | }, {}); |
| | | return { |
| | | _rowKey: `new_${Date.now()}_${i}`, |
| | | matnrId: el.id, |
| | | maktx: el.name, |
| | | matnrCode: el.code, |
| | | stockUnit: el.stockUnit || '', |
| | | purUnit: el.purchaseUnit || '', |
| | | ...dynamicFields |
| | | } |
| | | })) |
| | | }; |
| | | }); |
| | | setData([...data, ...value]); |
| | | setOpen(false); |
| | | reset(); |
| | | }; |
| | | |
| | | const getData = async () => { |
| | | setIsLoading(true) |
| | | console.log(page); |
| | | const res = await request.post(`/matnr/page`, { |
| | | setIsLoading(true); |
| | | const res = await request.post(`/outStock/matnr/page`, { |
| | | ...formData, |
| | | current: page?.page, |
| | | pageSize: page?.pageSize, |
| | | orderBy: "create_time desc" |
| | | }); |
| | | if (res?.data?.code === 200) { |
| | | setTableData(res.data.data.records); |
| | | setRowCount(res.data?.data?.total); |
| | | |
| | | setTableData(res.data.data.records || []); |
| | | setRowCount(res.data?.data?.total ?? 0); |
| | | } else { |
| | | notify(res.data.msg); |
| | | notify(res.data?.msg || '查询失败'); |
| | | } |
| | | setIsLoading(false) |
| | | |
| | | setIsLoading(false); |
| | | }; |
| | | |
| | | useEffect(() => { |
| | |
| | | size="small" |
| | | /> |
| | | </Grid> |
| | | <Grid item md={4}> |
| | | <Grid item md={3}> |
| | | <TreeSelectInput |
| | | label="table.field.matnr.groupId" |
| | | value={formData.groupId} |
| | |
| | | name="groupId" |
| | | onChange={handleChange} |
| | | /> |
| | | </Grid> |
| | | <Grid item md={3}> |
| | | <FormControl size="small" fullWidth variant="filled"> |
| | | <InputLabel>{translate('table.field.loc.useStatus')}</InputLabel> |
| | | <Select |
| | | name="locUseStatus" |
| | | value={formData.locUseStatus ?? ''} |
| | | onChange={handleChange} |
| | | label={translate('table.field.loc.useStatus')} |
| | | > |
| | | <MenuItem value="">全部</MenuItem> |
| | | {(JSON.parse(localStorage.getItem('sys_dicts')) || []) |
| | | .filter((d) => d.dictTypeCode === 'sys_loc_use_stas') |
| | | .map((d) => ( |
| | | <MenuItem key={d.value} value={d.value}>{d.label}</MenuItem> |
| | | ))} |
| | | </Select> |
| | | </FormControl> |
| | | </Grid> |
| | | </Grid> |
| | | </Box> |
| | |
| | | const notify = useNotify(); |
| | | |
| | | const [columns, setColumns] = useState([ |
| | | // { field: 'id', headerName: 'ID', width: 100 }, |
| | | { field: 'name', headerName: translate('table.field.matnr.name'), width: 300 }, |
| | | { field: 'code', headerName: translate('table.field.matnr.code'), width: 200 }, |
| | | { field: 'groupId$', headerName: translate('table.field.matnr.groupId'), width: 100 }, |
| | |
| | | { field: 'unit', headerName: translate('table.field.matnr.unit'), width: 100 }, |
| | | { field: 'purchaseUnit', headerName: translate('table.field.matnr.purUnit'), width: 100 }, |
| | | { field: 'stockUnit', headerName: translate('table.field.matnr.stockUnit'), width: 100 }, |
| | | { field: 'stockQty', headerName: translate('table.field.matnr.stockQty') || '库存数量', width: 110, type: 'number', valueFormatter: (v) => (v != null ? Number(v) : 0) }, |
| | | { field: 'locUseStatus$', headerName: translate('table.field.loc.useStatus'), width: 120 }, |
| | | { field: 'locCodes$', headerName: translate('table.field.loc.locCode'), width: 180, flex: 1 }, |
| | | { field: 'stockLeval$', headerName: translate('table.field.matnr.stockLevel'), width: 100, sortable: false }, |
| | | ]) |
| | | |
| | |
| | | import React, { useState, useRef, useEffect, useMemo, useCallback } from "react"; |
| | | import React, { useState, useRef, useEffect, useLayoutEffect, useMemo, useCallback } from "react"; |
| | | import { useLocation, useNavigate } from 'react-router-dom'; |
| | | import { useTabDialogState, getDialogStatePath } from '@/context/TabDialogStateContext'; |
| | | import { |
| | | List, |
| | | DatagridConfigurable, |
| | |
| | | })); |
| | | |
| | | const OutOrderList = (props) => { |
| | | const location = useLocation(); |
| | | const tabDialogState = useTabDialogState(); |
| | | |
| | | const dicts = JSON.parse(localStorage.getItem('sys_dicts'))?.filter(dict => (dict.dictTypeCode == 'sys_business_type')) || []; |
| | | const [createDialog, setCreateDialog] = useState(false); |
| | | const [createDialog, setCreateDialogInner] = useState(false); |
| | | const [manualDialog, setManualDialog] = useState(false); |
| | | |
| | | const pathKey = getDialogStatePath( |
| | | location.pathname === '/' && typeof window !== 'undefined' && window.location?.hash |
| | | ? (window.location.hash.replace(/^#/, '') || '/') |
| | | : location.pathname |
| | | ); |
| | | |
| | | const setCreateDialog = useCallback((open) => { |
| | | setCreateDialogInner(open); |
| | | if (tabDialogState) { |
| | | const current = tabDialogState.getDialogState(pathKey) || {}; |
| | | tabDialogState.setDialogState(pathKey, { ...current, createDialogOpen: open }); |
| | | } |
| | | }, [pathKey, tabDialogState]); |
| | | |
| | | const saveCreateDialogForm = useCallback((formData) => { |
| | | if (tabDialogState) { |
| | | const current = tabDialogState.getDialogState(pathKey) || {}; |
| | | tabDialogState.setDialogState(pathKey, { ...current, createDialogOpen: true, createDialogForm: formData }); |
| | | } |
| | | }, [pathKey, tabDialogState]); |
| | | |
| | | const savedCreateFormData = tabDialogState?.getDialogState(pathKey)?.createDialogForm; |
| | | |
| | | useLayoutEffect(() => { |
| | | if (tabDialogState) { |
| | | const saved = tabDialogState.getDialogState(pathKey); |
| | | if (saved?.createDialogOpen) { |
| | | setCreateDialogInner(true); |
| | | } |
| | | } |
| | | }, [pathKey, tabDialogState]); |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | const [waveRule, setWaveRule] = useState(false); |
| | | const [selectIds, setSelectIds] = useState([]); |
| | |
| | | <BillStatusField cellClassName="status" source="exceStatus" label="table.field.outStock.exceStatus" /> |
| | | <TextField source="memo" label="common.field.memo" sortable={false} /> |
| | | <WrapperField cellClassName="opt" label="common.field.opt" > |
| | | <MyButton setCreateDialog={setManualDialog} setmodalType={setmodalType} /> |
| | | <EditButton label="toolbar.detail" icon={(<DetailsIcon />)}></EditButton> |
| | | <OutOrderRowActions |
| | | setCreateDialog={setManualDialog} |
| | | setmodalType={setmodalType} |
| | | setDrawerVal={setDrawerVal} |
| | | drawerVal={drawerVal} |
| | | setSelect={setSelect} |
| | | /> |
| | | <CancelButton /> |
| | | <CompleteButton /> |
| | | <PublicButton setDrawerVal={setDrawerVal} drawerVal={drawerVal} setSelect={setSelect} /> |
| | | </WrapperField> |
| | | </StyledDatagrid> |
| | | </List> |
| | |
| | | setOpen={setCreateDialog} |
| | | preview={preview} |
| | | setPreview={setPreview} |
| | | initialFormData={savedCreateFormData} |
| | | saveFormData={saveCreateDialogForm} |
| | | /> |
| | | <OutStockWaveDialog open={waveRule} setOpen={setWaveRule} onClose={closeDialog} /> |
| | | <OutOrderPreview open={preview} setOpen={setPreview} /> |
| | |
| | | ) |
| | | } |
| | | |
| | | /** 出库单执行状态:10=初始化(仅此状态显示编辑/详情) */ |
| | | const OUT_STOCK_EXCE_STATUS_INIT = 10; |
| | | |
| | | const OutOrderRowActions = ({ setCreateDialog, setmodalType, setDrawerVal, drawerVal, setSelect }) => { |
| | | const record = useRecordContext(); |
| | | const isInit = record?.exceStatus === OUT_STOCK_EXCE_STATUS_INIT || record?.exceStatus === '10'; |
| | | return ( |
| | | <> |
| | | {isInit && ( |
| | | <> |
| | | <MyButton setCreateDialog={setCreateDialog} setmodalType={setmodalType} /> |
| | | <EditButton label="toolbar.detail" icon={(<DetailsIcon />)} /> |
| | | </> |
| | | )} |
| | | <PublicButton setDrawerVal={setDrawerVal} drawerVal={drawerVal} setSelect={setSelect} /> |
| | | </> |
| | | ); |
| | | }; |
| | | |
| | | const MyButton = ({ setCreateDialog, setmodalType }) => { |
| | | const record = useRecordContext(); |
| | | const handleEditClick = (btn) => { |
| | |
| | | import { Dialog, DialogActions, DialogContent, DialogTitle, Box, LinearProgress } from "@mui/material"; |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import React, { useState, useRef, useEffect, useMemo, useCallback } from "react"; |
| | | import { |
| | | List, |
| | | DatagridConfigurable, |
| | |
| | | ] |
| | | |
| | | const OutOrderModal = (props) => { |
| | | const { open, setOpen, preview, setPreview, record } = props; |
| | | const { open, setOpen, preview, setPreview, record, initialFormData, saveFormData } = props; |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | const [params, setParams] = useState({}); |
| | | const [params, setParams] = useState(() => initialFormData?.params ?? {}); |
| | | const [select, setSelect] = useState([]); |
| | | const translate = useTranslate(); |
| | | const refresh = useRefresh(); |
| | | |
| | | useEffect(() => { |
| | | if (open && initialFormData?.params) { |
| | | setParams(initialFormData.params); |
| | | } |
| | | }, [open, initialFormData?.params]); |
| | | |
| | | const handleClose = (event, reason) => { |
| | | if (reason !== "backdropClick") { |
| | | setOpen(false); |
| | | } |
| | | }; |
| | | |
| | | useEffect(() => { |
| | | if (open && params && Object.keys(params).length > 0 && saveFormData) { |
| | | saveFormData({ params, formValues: params }); |
| | | } |
| | | }, [open, params, saveFormData]); |
| | | |
| | | const handleFormValuesChange = useCallback((formValues) => { |
| | | if (saveFormData) { |
| | | saveFormData({ params: formValues, formValues }); |
| | | } |
| | | }, [saveFormData]); |
| | | |
| | | const CustomFilter = () => { |
| | | const { filterValues, setFilters, refetch } = useListContext(); |
| | | const [formValues, setFormValues] = useState(filterValues); |
| | | const { filterValues } = useListContext(); |
| | | const initialFormValues = initialFormData?.formValues ?? initialFormData?.params ?? params ?? filterValues; |
| | | const [formValues, setFormValues] = useState(initialFormValues || {}); |
| | | const initializedRef = useRef(false); |
| | | useEffect(() => { |
| | | if (open && (initialFormData?.formValues || initialFormData?.params) && !initializedRef.current) { |
| | | const init = initialFormData?.formValues ?? initialFormData?.params ?? {}; |
| | | setFormValues(init); |
| | | initializedRef.current = true; |
| | | } |
| | | if (!open) initializedRef.current = false; |
| | | }, [open, initialFormData?.formValues, initialFormData?.params]); |
| | | |
| | | const handleChange = (event) => { |
| | | if (event.target == undefined || event.target == null) { return } |
| | | setFormValues(formValues => ({ |
| | | const next = { |
| | | ...formValues, |
| | | [event.target.name]: event.target.value, |
| | | })); |
| | | }; |
| | | setFormValues(next); |
| | | handleFormValuesChange(next); |
| | | }; |
| | | |
| | | const handleSubmit = (event) => { |
| | | setParams(formValues) |
| | | setParams(formValues); |
| | | if (saveFormData) saveFormData({ params: formValues, formValues }); |
| | | }; |
| | | |
| | | return ( |
| | |
| | | source="condition" |
| | | label="common.action.search" |
| | | resettable |
| | | defaultValue={params?.condition} |
| | | value={formValues?.condition ?? ''} |
| | | onChange={handleChange} /> |
| | | </Stack> |
| | | <Stack> |
| | | <TextInput |
| | | source="deliveryCode" |
| | | label="table.field.deliveryItem.deliveryCode" |
| | | defaultValue={params?.deliveryCode} |
| | | value={formValues?.deliveryCode ?? ''} |
| | | onChange={handleChange} |
| | | resettable |
| | | /> |
| | |
| | | <TextInput |
| | | source="maktx" |
| | | label="table.field.deliveryItem.matnrName" |
| | | defaultValue={params?.maktx} |
| | | value={formValues?.maktx ?? ''} |
| | | onChange={handleChange} |
| | | resettable |
| | | /> |
| | |
| | | <TextInput |
| | | source="matnrCode" |
| | | label="table.field.deliveryItem.matnrCode" |
| | | defaultValue={params?.matnrCode} |
| | | value={formValues?.matnrCode ?? ''} |
| | | resettable |
| | | onChange={handleChange} /> |
| | | </Stack> |
| | |
| | | <TextInput |
| | | source="splrName" |
| | | label="table.field.deliveryItem.splrName" |
| | | defaultValue={params?.splrName} |
| | | value={formValues?.splrName ?? ''} |
| | | resettable |
| | | onChange={handleChange} /> |
| | | </Stack> |
| | |
| | | |
| | | const OutStockAnfme = React.memo(function OutStockAnfme(props) { |
| | | const { value } = props; |
| | | const num = Number(value); |
| | | const hasStock = typeof num === 'number' && !Number.isNaN(num) && num > 1e-6; |
| | | return ( |
| | | value > 0 ? |
| | | hasStock ? ( |
| | | <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
| | | <span>{value}</span> |
| | | </Box> |
| | | : |
| | | ) : ( |
| | | <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
| | | <span style={{ color: 'red' }}>{translate('common.edit.title.insuffInventory')}</span> |
| | | </Box> |
| | | ) |
| | | ); |
| | | }); |
| | | |
| | |
| | | } |
| | | |
| | | const handleSubmit = async () => { |
| | | setFinally() |
| | | setDisabled(true) |
| | | |
| | | setFinally(); |
| | | setDisabled(true); |
| | | try { |
| | | if (asnId === 0) { |
| | | const parmas = { |
| | | "orders": formData, |
| | | "items": tabelData, |
| | | } |
| | | const parmas = { "orders": formData, "items": tabelData }; |
| | | const res = await request.post(`/outStock/items/save`, parmas); |
| | | if (res?.data?.code === 200) { |
| | | setOpen(false); |
| | | refresh(); |
| | | resetData() |
| | | resetData(); |
| | | } else { |
| | | notify(res.data.msg); |
| | | notify(res?.data?.msg || '保存失败', { type: 'error' }); |
| | | } |
| | | } else { |
| | | const parmas = { |
| | | "orders": formData, |
| | | "items": tabelData, |
| | | } |
| | | const parmas = { "orders": formData, "items": tabelData }; |
| | | const res = await request.post(`/outStock/items/update`, parmas); |
| | | if (res?.data?.code === 200) { |
| | | setOpen(false); |
| | | refresh(); |
| | | resetData() |
| | | resetData(); |
| | | } else { |
| | | notify(res.data.msg); |
| | | notify(res?.data?.msg || '保存失败', { type: 'error' }); |
| | | } |
| | | } |
| | | setDisabled(false) |
| | | |
| | | } catch (error) { |
| | | const msg = error?.response?.data?.msg || error?.message || '保存失败,请重试'; |
| | | notify(msg, { type: 'error' }); |
| | | } finally { |
| | | setDisabled(false); |
| | | } |
| | | }; |
| | | |
| | | |
| | |
| | | |
| | | const [selectedRows, setSelectedRows] = useState([]); |
| | | |
| | | const getRowId = (row) => (row.id != null ? row.id : row._rowKey) ?? row.matnrId; |
| | | |
| | | const handleDeleteItem = () => { |
| | | const newTableData = _.filter(tabelData, (item) => !selectedRows.includes(item.matnrId)); |
| | | const newTableData = tabelData.filter((item) => !selectedRows.includes(getRowId(item))); |
| | | setTableData(newTableData); |
| | | setSelectedRows([]); |
| | | } |
| | | |
| | | return ( |
| | |
| | | </Stack> |
| | | </Box> |
| | | <Box sx={{ mt: 2 }}> |
| | | <AsnOrderModalTable tabelData={tabelData} setTableData={setTableData} asnId={asnId} selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef}></AsnOrderModalTable> |
| | | <AsnOrderModalTable tabelData={tabelData} setTableData={setTableData} asnId={asnId} selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef} getRowId={getRowId}></AsnOrderModalTable> |
| | | </Box> |
| | | </DialogContent> |
| | | <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}> |
| | |
| | | |
| | | |
| | | |
| | | const AsnOrderModalTable = ({ tabelData, setTableData, asnId, selectedRows, setSelectedRows, tableRef }) => { |
| | | const AsnOrderModalTable = ({ tabelData, setTableData, asnId, selectedRows, setSelectedRows, tableRef, getRowId: getRowIdProp }) => { |
| | | const translate = useTranslate(); |
| | | const notify = useNotify(); |
| | | |
| | |
| | | } |
| | | |
| | | |
| | | const getRowId = getRowIdProp || ((row) => (row.id != null ? row.id : row._rowKey) ?? row.matnrId); |
| | | |
| | | const handleDelete = (row) => { |
| | | const newData = _.filter(cdata.current, (item) => item.matnrId !== row.matnrId); |
| | | const rowId = getRowId(row); |
| | | const newData = cdata.current.filter((item) => getRowId(item) !== rowId); |
| | | setTableData(newData); |
| | | }; |
| | | |
| | | |
| | | const processRowUpdate = (newRow, oldRow) => { |
| | | const rows = tabelData.map((r) => |
| | | r.matnrId === newRow.matnrId ? { ...newRow } : r |
| | | ) |
| | | setTableData(rows) |
| | | getRowId(r) === getRowId(oldRow) ? { ...newRow } : r |
| | | ); |
| | | setTableData(rows); |
| | | return newRow; |
| | | }; |
| | | |
| | |
| | | rows={tabelData} |
| | | columns={columns} |
| | | disableRowSelectionOnClick |
| | | getRowId={(row) => row.matnrId ? row.matnrId : row.id} |
| | | getRowId={(row) => getRowId(row)} |
| | | disableColumnFilter |
| | | disableColumnSelector |
| | | disableColumnSorting |
| | |
| | | Card, |
| | | } from '@mui/material'; |
| | | import { EDIT_MODE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting'; |
| | | import _ from 'lodash'; |
| | | import ConfirmButton from "../../components/ConfirmButton"; |
| | | import TreeSelectInput from "@/page/components/TreeSelectInput"; |
| | | import { DataGrid, useGridApiRef } from '@mui/x-data-grid'; |
| | |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.common.utils.FieldsUtils; |
| | | import com.vincent.rsf.server.manager.controller.params.LocToTaskParams; |
| | | import com.vincent.rsf.server.manager.entity.Loc; |
| | | import com.vincent.rsf.server.manager.entity.LocItem; |
| | | import com.vincent.rsf.server.manager.entity.ViewStockManage; |
| | | import com.vincent.rsf.server.manager.enums.TaskResouceType; |
| | |
| | | Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex()); |
| | | record.setExtendFields(fields); |
| | | } |
| | | // 填充库位状态,便于列表展示 |
| | | if (record.getLocId() != null) { |
| | | Loc loc = locService.getById(record.getLocId()); |
| | | if (loc != null) { |
| | | record.setLocUseStatus(loc.getUseStatus()); |
| | | record.setLocUseStatus$(loc.getUseStatus$()); |
| | | } |
| | | } |
| | | } |
| | | page.setRecords(records); |
| | | |
| | |
| | | Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex()); |
| | | record.setExtendFields(fields); |
| | | } |
| | | if (record.getLocId() != null) { |
| | | Loc loc = locService.getById(record.getLocId()); |
| | | if (loc != null) { |
| | | record.setLocUseStatus(loc.getUseStatus()); |
| | | record.setLocUseStatus$(loc.getUseStatus$()); |
| | | } |
| | | } |
| | | } |
| | | page.setRecords(records); |
| | | |
| | |
| | | import com.vincent.rsf.server.manager.controller.params.OrderOutTaskParam; |
| | | import com.vincent.rsf.server.manager.controller.params.OutStockToTaskParams; |
| | | import com.vincent.rsf.server.manager.entity.DeliveryItem; |
| | | import com.vincent.rsf.server.manager.entity.Matnr; |
| | | import com.vincent.rsf.server.manager.entity.WkOrder; |
| | | import com.vincent.rsf.server.manager.entity.WkOrderItem; |
| | | import com.vincent.rsf.server.manager.enums.OrderType; |
| | |
| | | return R.ok().add(outStockService.getById(id)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:outStock:list')") |
| | | @PostMapping("/outStock/matnr/page") |
| | | @ApiOperation("出库单选物料分页(支持库位状态筛选、库存数量与库位状态展示)") |
| | | public R pageMatnr(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | PageParam<Matnr, BaseParam> pageParam = new PageParam<>(baseParam, Matnr.class); |
| | | return R.ok().add(outStockService.pageMatnrForOutStock(pageParam, map)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:outStock:save')") |
| | | @OperationLog("Create 出库单据") |
| | | @PostMapping("/outStock/save") |
| | |
| | | private Integer channel; |
| | | |
| | | /** |
| | | * 库位状态(来自关联库位,非表字段) |
| | | */ |
| | | @ApiModelProperty("库位状态") |
| | | @TableField(exist = false) |
| | | private String locUseStatus; |
| | | |
| | | /** |
| | | * 库位状态显示值(来自关联库位,非表字段) |
| | | */ |
| | | @ApiModelProperty("库位状态显示") |
| | | @TableField(exist = false) |
| | | private String locUseStatus$; |
| | | |
| | | /** |
| | | * 物料名称 |
| | | */ |
| | | @ApiModelProperty(value= "物料名称") |
| | |
| | | @TableField(exist = false) |
| | | private Map<String, String> extendFields; |
| | | |
| | | /** 库存数量(出库选物料时由库位汇总,非表字段) */ |
| | | @ApiModelProperty("库存数量") |
| | | @TableField(exist = false) |
| | | private Double stockQty; |
| | | |
| | | /** 库位状态展示(出库选物料时,非表字段) */ |
| | | @ApiModelProperty("库位状态") |
| | | @TableField(exist = false) |
| | | private String locUseStatus$; |
| | | |
| | | /** 库位展示(出库选物料时,有库存的库位编码,逗号分隔,非表字段) */ |
| | | @ApiModelProperty("库位") |
| | | @TableField(exist = false) |
| | | private String locCodes$; |
| | | |
| | | /** |
| | | * 租户 |
| | | */ |
| | |
| | | return null;
|
| | | }
|
| | |
|
| | | /** 根据状态码(type)取描述,如 F -> 在库 */
|
| | | public static String getDescByType(String type) {
|
| | | if (type == null || type.isEmpty()) return type;
|
| | | for (LocStsType value : LocStsType.values()) {
|
| | | if (type.trim().equals(value.type)) return value.desc;
|
| | | }
|
| | | return type;
|
| | | }
|
| | |
|
| | | /**
|
| | | * @author Ryan
|
| | | * @date 2025/8/28
|
| | |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Mapper |
| | | @Repository |
| | | public interface LocItemMapper extends BaseMapper<LocItem> { |
| | | |
| | | List<LocItem> listByMatnr(@Param("type") String type, @Param("channel") String channel, @Param(Constants.WRAPPER) LambdaQueryWrapper<LocItem> matnr); |
| | | |
| | | /** |
| | | * 按物料ID汇总库位库存数量;可选按库位状态过滤。 |
| | | * @param matnrIds 物料ID列表 |
| | | * @param locUseStatus 库位状态,为空则不过滤 |
| | | * @return 每行: matnrId, stockQty, locStatuses(逗号分隔的库位状态,仅当 locUseStatus 为空时返回) |
| | | */ |
| | | List<Map<String, Object>> listStockByMatnrIds(@Param("matnrIds") List<Long> matnrIds, @Param("locUseStatus") String locUseStatus); |
| | | } |
| | |
| | | /** |
| | | * 非光电站点任务下发 |
| | | */ |
| | | @Scheduled(cron = "0/55 * * * * ? ") |
| | | @Scheduled(cron = "0/35 * * * * ? ") |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void pubTaskToWcs() { |
| | | log.info("定时任务开始执行:任务下发到RCS"); |
| | |
| | | package com.vincent.rsf.server.manager.service; |
| | | |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.manager.entity.Matnr; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.manager.controller.params.AsnOrderAndItemsParams; |
| | |
| | | import com.vincent.rsf.server.manager.entity.DeliveryItem; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public interface OutStockService extends IService<WkOrder> { |
| | | |
| | | /** |
| | | * 出库单选物料分页:支持按库位状态筛选,并返回库存数量、库位状态展示 |
| | | */ |
| | | PageParam<Matnr, BaseParam> pageMatnrForOutStock(PageParam<Matnr, BaseParam> pageParam, Map<String, Object> params); |
| | | |
| | | |
| | | R cancelOutOrder(String id); |
| | | |
| | | R genOutStock(List<DeliveryItem> ids, Long loginUserId); |
| | |
| | | import com.vincent.rsf.server.system.constant.SerialRuleCode; |
| | | import com.vincent.rsf.server.system.service.FieldsService; |
| | | import com.vincent.rsf.server.system.utils.SerialRuleUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | } |
| | | |
| | | } |
| | | // locUseStatus 仅用于下方 EXISTS 子查询,不能作为 man_matnr 表字段参与 buildWrapper |
| | | Object locUseStatus = params.get("locUseStatus"); |
| | | if (pageParam.getWhere() != null && pageParam.getWhere().getMap() != null) { |
| | | pageParam.getWhere().getMap().remove("locUseStatus"); |
| | | } |
| | | QueryWrapper<Matnr> queryWrapper = pageParam.buildWrapper(true); |
| | | queryWrapper.in(!longs.isEmpty(),"group_id", longs); |
| | | // 出库选物料:按库位状态筛选(仅展示在该库位状态下有库存的物料) |
| | | if (locUseStatus != null && StringUtils.isNotBlank(locUseStatus.toString())) { |
| | | String useStatus = locUseStatus.toString().replace("'", "''"); |
| | | queryWrapper.apply("EXISTS (SELECT 1 FROM man_loc_item li INNER JOIN man_loc l ON li.loc_id = l.id WHERE li.matnr_id = man_matnr.id AND l.use_status = '" + useStatus + "')"); |
| | | } |
| | | |
| | | FieldsUtils.setFieldsFilters(queryWrapper,pageParam,Matnr.class); |
| | | /**拼接扩展字段*/ |
| | |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.api.utils.LocUtils; |
| | | import com.vincent.rsf.server.common.constant.Constants; |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.manager.controller.dto.ExistDto; |
| | | import com.vincent.rsf.server.manager.controller.dto.OrderOutItemDto; |
| | | import com.vincent.rsf.server.manager.controller.params.*; |
| | | import com.vincent.rsf.server.manager.enums.*; |
| | | import com.vincent.rsf.server.manager.entity.Matnr; |
| | | import com.vincent.rsf.server.manager.entity.*; |
| | | import com.vincent.rsf.server.manager.mapper.AsnOrderMapper; |
| | | import com.vincent.rsf.server.manager.mapper.LocItemMapper; |
| | | import com.vincent.rsf.server.manager.service.*; |
| | | import com.vincent.rsf.server.manager.utils.LocManageUtil; |
| | | import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil; |
| | |
| | | private WaveOrderRelaServiceImpl waveOrderRelaService; |
| | | @Autowired |
| | | private TaskItemService taskItemService; |
| | | @Autowired |
| | | private LocItemMapper locItemMapper; |
| | | |
| | | @Override |
| | | public PageParam<Matnr, BaseParam> pageMatnrForOutStock(PageParam<Matnr, BaseParam> pageParam, Map<String, Object> params) { |
| | | PageParam<Matnr, BaseParam> page = matnrService.getMatnrPage(pageParam, params); |
| | | List<Matnr> records = page.getRecords(); |
| | | if (records == null || records.isEmpty()) { |
| | | return page; |
| | | } |
| | | List<Long> matnrIds = records.stream().map(Matnr::getId).collect(Collectors.toList()); |
| | | String locUseStatus = params.get("locUseStatus") != null ? params.get("locUseStatus").toString() : null; |
| | | List<Map<String, Object>> stockList = locItemMapper.listStockByMatnrIds(matnrIds, locUseStatus); |
| | | Map<Long, Double> stockQtyMap = new HashMap<>(); |
| | | Map<Long, String> locStatusDescMap = new HashMap<>(); |
| | | Map<Long, String> locCodesMap = new HashMap<>(); |
| | | for (Map<String, Object> row : stockList) { |
| | | Long matnrId = getLong(row, "matnrId", "matnrid"); |
| | | if (matnrId == null) continue; |
| | | Object qty = getAny(row, "stockQty", "stockqty"); |
| | | double v = qty instanceof Number ? ((Number) qty).doubleValue() : 0d; |
| | | stockQtyMap.put(matnrId, v); |
| | | String locCodes = getStr(row, "locCodes", "loccodes"); |
| | | if (locCodes != null && !locCodes.isEmpty()) { |
| | | locCodesMap.put(matnrId, locCodes); |
| | | } |
| | | String locStatuses = getStr(row, "locStatuses", "locstatuses"); |
| | | if (locStatuses != null && !locStatuses.isEmpty()) { |
| | | String desc = Arrays.stream(locStatuses.split(",")) |
| | | .map(String::trim) |
| | | .map(LocStsType::getDescByType) |
| | | .collect(Collectors.joining(",")); |
| | | locStatusDescMap.put(matnrId, desc); |
| | | } else if (locUseStatus != null && !locUseStatus.isEmpty()) { |
| | | locStatusDescMap.put(matnrId, LocStsType.getDescByType(locUseStatus)); |
| | | } |
| | | } |
| | | for (Matnr record : records) { |
| | | record.setStockQty(stockQtyMap.getOrDefault(record.getId(), 0d)); |
| | | record.setLocUseStatus$(locStatusDescMap.get(record.getId())); |
| | | record.setLocCodes$(locCodesMap.get(record.getId())); |
| | | } |
| | | return page; |
| | | } |
| | | |
| | | private static Long getLong(Map<String, Object> map, String... keys) { |
| | | Object v = getAny(map, keys); |
| | | if (v == null) return null; |
| | | if (v instanceof Long) return (Long) v; |
| | | if (v instanceof Number) return ((Number) v).longValue(); |
| | | try { return Long.parseLong(v.toString()); } catch (NumberFormatException e) { return null; } |
| | | } |
| | | |
| | | private static String getStr(Map<String, Object> map, String... keys) { |
| | | Object v = getAny(map, keys); |
| | | return v != null ? v.toString() : null; |
| | | } |
| | | |
| | | private static Object getAny(Map<String, Object> map, String... keys) { |
| | | for (String key : keys) { |
| | | Object v = map.get(key); |
| | | if (v != null) return v; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * @param |
| | |
| | | public static List<LocItem> getEfficiencyFirstItemList(String matnrCode, String splrBatch, Double anfme) { |
| | | LambdaQueryWrapper<LocItem> locItemQueryWrapper = new LambdaQueryWrapper<>(); |
| | | locItemQueryWrapper.eq(LocItem::getMatnrCode, matnrCode); |
| | | locItemQueryWrapper.eq(StringUtils.isNotBlank(splrBatch), LocItem::getBatch, splrBatch); |
| | | // 有批次时:匹配库位批次=订单批次 或 库位批次为空(无批次库存可参与分配,避免误判库存不足) |
| | | if (StringUtils.isNotBlank(splrBatch)) { |
| | | locItemQueryWrapper.and(w -> w.eq(LocItem::getBatch, splrBatch).or().isNull(LocItem::getBatch)); |
| | | } |
| | | String applySql = String.format( |
| | | "EXISTS (SELECT 1 FROM man_loc ml " + |
| | | "WHERE ml.use_status = '%s'" + |
| | |
| | | public static List<LocItem> getFirstInFirstOutItemList(String matnrCode, String splrBatch, Double anfme) { |
| | | LambdaQueryWrapper<LocItem> locItemQueryWrapper = new LambdaQueryWrapper<>(); |
| | | locItemQueryWrapper.eq(LocItem::getMatnrCode, matnrCode); |
| | | locItemQueryWrapper.eq(StringUtils.isNotEmpty(splrBatch), LocItem::getBatch, splrBatch); |
| | | // 有批次时:匹配库位批次=订单批次 或 库位批次为空(无批次库存可参与分配,避免误判库存不足) |
| | | if (StringUtils.isNotBlank(splrBatch)) { |
| | | locItemQueryWrapper.and(w -> w.eq(LocItem::getBatch, splrBatch).or().isNull(LocItem::getBatch)); |
| | | } |
| | | //如果批次不为空,按批次先后出库 |
| | | if (StringUtils.isNotBlank(splrBatch)) { |
| | | locItemQueryWrapper.orderByAsc(LocItem::getBatch); |
| | |
| | | )t |
| | | ${ew.customSqlSegment} |
| | | </select> |
| | | |
| | | <select id="listStockByMatnrIds" resultType="java.util.HashMap"> |
| | | SELECT |
| | | li.matnr_id AS matnrId, |
| | | COALESCE(SUM(li.anfme), 0) AS stockQty, |
| | | GROUP_CONCAT(DISTINCT li.loc_code ORDER BY li.loc_code) AS locCodes |
| | | <if test="locUseStatus == null or locUseStatus == ''"> |
| | | , GROUP_CONCAT(DISTINCT l.use_status ORDER BY l.use_status) AS locStatuses |
| | | </if> |
| | | FROM man_loc_item li |
| | | INNER JOIN man_loc l ON l.id = li.loc_id |
| | | WHERE li.matnr_id IN |
| | | <foreach collection="matnrIds" item="id" open="(" separator="," close=")"> |
| | | #{id} |
| | | </foreach> |
| | | <if test="locUseStatus != null and locUseStatus != ''"> |
| | | AND l.use_status = #{locUseStatus} |
| | | </if> |
| | | GROUP BY li.matnr_id |
| | | </select> |
| | | </mapper> |