30个文件已添加
1 文件已重命名
7个文件已删除
21个文件已修改
| | |
| | | VITE_BASE_IP=192.168.4.56 |
| | | VITE_BASE_IP=127.0.0.1 |
| | | # VITE_BASE_IP=47.76.147.249 |
| | | VITE_BASE_PORT=8080 |
| | |
| | | fieldsIndex: "fieldsIndex", |
| | | anfme: "anfme", |
| | | workQty: "workQty", |
| | | qty: "Qty", |
| | | stockLocs: "Stock Locs" |
| | | }, |
| | | task: { |
| | | taskCode: "TaskCode", |
| | |
| | | fieldsIndex: "动态扩展", |
| | | anfme: "数量", |
| | | workQty: "执行数", |
| | | qty: "完成数量", |
| | | stockLocs: "库存位置" |
| | | }, |
| | | task: { |
| | | taskCode: "任务号", |
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 BasDeviceCreate = (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.basDevice.deviceNo" |
| | | source="deviceNo" |
| | | autoFocus |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <TextInput |
| | | label="table.field.basDevice.inEnable" |
| | | source="inEnable" |
| | | parse={v => v} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <TextInput |
| | | label="table.field.basDevice.outEnable" |
| | | source="outEnable" |
| | | parse={v => v} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <TextInput |
| | | label="table.field.basDevice.origin" |
| | | source="origin" |
| | | parse={v => v} |
| | | /> |
| | | </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 BasDeviceCreate; |
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 BasDeviceEdit = () => { |
| | | 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.basDevice.deviceNo" |
| | | source="deviceNo" |
| | | autoFocus |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <TextInput |
| | | label="table.field.basDevice.inEnable" |
| | | source="inEnable" |
| | | parse={v => v} |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <TextInput |
| | | label="table.field.basDevice.outEnable" |
| | | source="outEnable" |
| | | parse={v => v} |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <TextInput |
| | | label="table.field.basDevice.origin" |
| | | source="origin" |
| | | parse={v => v} |
| | | /> |
| | | </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 BasDeviceEdit; |
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 BasDeviceCreate from "./BasDeviceCreate"; |
| | | import BasDevicePanel from "./BasDevicePanel"; |
| | | 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="deviceNo" label="table.field.basDevice.deviceNo" />, |
| | | <TextInput source="inEnable" label="table.field.basDevice.inEnable" />, |
| | | <TextInput source="outEnable" label="table.field.basDevice.outEnable" />, |
| | | <TextInput source="origin" label="table.field.basDevice.origin" />, |
| | | |
| | | <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 BasDeviceList = () => { |
| | | 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.basDevice"} |
| | | empty={<EmptyData onClick={() => { setCreateDialog(true) }} />} |
| | | filters={filters} |
| | | sort={{ field: "create_time", order: "desc" }} |
| | | actions={( |
| | | <TopToolbar> |
| | | <FilterButton /> |
| | | <MyCreateButton onClick={() => { setCreateDialog(true) }} /> |
| | | <SelectColumnsButton preferenceKey='basDevice' /> |
| | | <MyExportButton /> |
| | | </TopToolbar> |
| | | )} |
| | | perPage={DEFAULT_PAGE_SIZE} |
| | | > |
| | | <StyledDatagrid |
| | | preferenceKey='basDevice' |
| | | bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />} |
| | | rowClick={(id, resource, record) => false} |
| | | expand={() => <BasDevicePanel />} |
| | | expandSingle={true} |
| | | omit={['id', 'createTime', 'createBy', 'memo']} |
| | | > |
| | | <NumberField source="id" /> |
| | | <NumberField source="deviceNo" label="table.field.basDevice.deviceNo" /> |
| | | <TextField source="inEnable" label="table.field.basDevice.inEnable" /> |
| | | <TextField source="outEnable" label="table.field.basDevice.outEnable" /> |
| | | <TextField source="origin" label="table.field.basDevice.origin" /> |
| | | |
| | | <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> |
| | | <BasDeviceCreate |
| | | open={createDialog} |
| | | setOpen={setCreateDialog} |
| | | /> |
| | | <PageDrawer |
| | | title='BasDevice Detail' |
| | | drawerVal={drawerVal} |
| | | setDrawerVal={setDrawerVal} |
| | | > |
| | | </PageDrawer> |
| | | </Box> |
| | | ) |
| | | } |
| | | |
| | | export default BasDeviceList; |
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 BasDevicePanel = () => { |
| | | 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.basDevice.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.basDevice.deviceNo" |
| | | property={record.deviceNo} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6}> |
| | | <PanelTypography |
| | | title="table.field.basDevice.inEnable" |
| | | property={record.inEnable} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6}> |
| | | <PanelTypography |
| | | title="table.field.basDevice.outEnable" |
| | | property={record.outEnable} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6}> |
| | | <PanelTypography |
| | | title="table.field.basDevice.origin" |
| | | property={record.origin} |
| | | /> |
| | | </Grid> |
| | | |
| | | </Grid> |
| | | </CardContent> |
| | | </Card > |
| | | </> |
| | | ); |
| | | }; |
| | | |
| | | export default BasDevicePanel; |
New file |
| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import { |
| | | ListGuesser, |
| | | EditGuesser, |
| | | ShowGuesser, |
| | | } from "react-admin"; |
| | | |
| | | import BasDeviceList from "./BasDeviceList"; |
| | | import BasDeviceEdit from "./BasDeviceEdit"; |
| | | |
| | | export default { |
| | | list: BasDeviceList, |
| | | edit: BasDeviceEdit, |
| | | show: ShowGuesser, |
| | | recordRepresentation: (record) => { |
| | | return `${record.id}` |
| | | } |
| | | }; |
New file |
| | |
| | | import React, { useState, useRef, useEffect, useMemo, useCallback } from "react"; |
| | | 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, |
| | | useRefresh, |
| | | useRedirect, |
| | | Button, |
| | | useGetList, |
| | | ListContextProvider, |
| | | useList, |
| | | Toolbar, |
| | | SingleFieldList, |
| | | } from 'react-admin'; |
| | | |
| | | import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting'; |
| | | import { Box, Typography, Card, Stack, DialogContent, DialogTitle, DialogActions, Dialog } from '@mui/material'; |
| | | import { styled } from '@mui/material/styles'; |
| | | import DialogCloseButton from "../../components/DialogCloseButton"; |
| | | import request from '@/utils/request'; |
| | | import { filter } from "lodash"; |
| | | import { height } from "@mui/system"; |
| | | |
| | | const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ |
| | | '& .css-1vooibu-MuiSvgIcon-root': { |
| | | height: '.9em' |
| | | }, |
| | | '& .RaDatagrid-row': { |
| | | cursor: 'auto' |
| | | }, |
| | | '& .column-name': { |
| | | }, |
| | | '& .opt': { |
| | | width: 200 |
| | | }, |
| | | })); |
| | | |
| | | const ItemToTaskModal = (props) => { |
| | | const { open, setOpen, record } = props; |
| | | const translate = useTranslate(); |
| | | const [createDialog, setCreateDialog] = useState(false); |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | const handleClose = (event, reason) => { |
| | | if (reason !== "backdropClick") { |
| | | setOpen(false); |
| | | } |
| | | }; |
| | | |
| | | const { data, total, isPending, error, refetch, meta } = useGetList('/wave/locs/preview', { filter: { waveId: record?.id } }); |
| | | const listContext = useList({ data, isPending }); |
| | | |
| | | return ( |
| | | <Box display="flex"> |
| | | <Dialog |
| | | open={open} |
| | | onClose={handleClose} |
| | | aria-labelledby="form-dialog-title" |
| | | aria-hidden |
| | | fullWidth |
| | | disableRestoreFocus |
| | | maxWidth="lg" |
| | | > |
| | | <DialogTitle id="form-dialog-title" sx={{ |
| | | position: 'sticky', |
| | | top: 0, |
| | | backgroundColor: 'background.paper', |
| | | zIndex: 1000 |
| | | }}> |
| | | {translate('toolbar.createTask')} |
| | | <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}> |
| | | <DialogCloseButton onClose={handleClose} /> |
| | | </Box> |
| | | </DialogTitle> |
| | | <DialogContent sx={{ height: 400 }}> |
| | | <ListContextProvider |
| | | value={listContext} |
| | | sx={{ |
| | | flexGrow: 1, |
| | | transition: (theme) => |
| | | theme.transitions.create(['all'], { |
| | | duration: theme.transitions.duration.enteringScreen, |
| | | }), |
| | | marginRight: !!drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0, |
| | | }} |
| | | title={"menu.waveItem"} |
| | | empty={false} |
| | | sort={{ field: "create_time", order: "desc" }} |
| | | actions={( |
| | | <TopToolbar> |
| | | <SelectColumnsButton preferenceKey='waveItem' /> |
| | | </TopToolbar> |
| | | )} |
| | | perPage={DEFAULT_PAGE_SIZE} |
| | | > |
| | | <StyledDatagrid |
| | | preferenceKey='waveItem' |
| | | bulkActionButtons={false} |
| | | rowClick={(id, resource, record) => false} |
| | | expand={false} |
| | | expandSingle={false} |
| | | omit={['id', 'createTime', 'matnrId', 'waveId', 'batch', 'orderItemId', 'unit', 'batch', 'trackCode', 'fieldsIndex', 'createBy', 'memo']} |
| | | > |
| | | <NumberField source="id" /> |
| | | <NumberField source="waveId" label="table.field.waveItem.waveId" /> |
| | | <TextField source="waveCode" label="table.field.waveItem.waveCode" /> |
| | | <TextField source="orderCode" label="table.field.waveItem.orderCode" /> |
| | | {/* <TextField source="maktx" label="table.field.waveItem.matnrName" /> */} |
| | | <NumberField source="matnrId" label="table.field.waveItem.matnrId" /> |
| | | <TextField source="matnrCode" label="table.field.waveItem.matnrCode" /> |
| | | <TextField source="batch" label="table.field.waveItem.batch" /> |
| | | <TextField source="splrBatch" label="table.field.waveItem.splrBatch" /> |
| | | <NumberField source="orderItemId" label="table.field.waveItem.orderItemId" /> |
| | | <TextField source="unit" label="table.field.waveItem.unit" /> |
| | | <TextField source="trackCode" label="table.field.waveItem.trackCode" /> |
| | | <TextField source="fieldsIndex" label="table.field.waveItem.fieldsIndex" /> |
| | | <NumberField source="anfme" label="table.field.waveItem.anfme" /> |
| | | <NumberField source="workQty" label="table.field.waveItem.workQty" /> |
| | | <NumberField source="qty" label="table.field.waveItem.qty" /> |
| | | <WrapperField cellClassName="opt" label="table.field.waveItem.stockLocs"> |
| | | <ArrayField source="stockLocs" stockLocs="table.field.waveItem.stockLocs"> |
| | | <SingleFieldList linkType={false}> |
| | | <ChilpField source="" size="small"></ChilpField> |
| | | </SingleFieldList> |
| | | </ArrayField> |
| | | {/* <NumberField source="anfme" label="table.field.waveItem.stockLocs" /> */} |
| | | {/* <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} /> |
| | | <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} /> */} |
| | | </WrapperField> |
| | | </StyledDatagrid> |
| | | </ListContextProvider> |
| | | </DialogContent> |
| | | <DialogActions> |
| | | <Toolbar sx={{ width: '100%', justifyContent: 'end' }} > |
| | | <GenerateTaskButton record={[record?.id]} /> |
| | | </Toolbar> |
| | | </DialogActions> |
| | | </Dialog> |
| | | </Box> |
| | | ) |
| | | } |
| | | |
| | | export default ItemToTaskModal; |
| | | |
| | | const GenerateTaskButton = (record) => { |
| | | const refresh = useRefresh(); |
| | | const notify = useNotify(); |
| | | const redirect = useRedirect(); |
| | | const generateTask = async () => { |
| | | const res = await request.post(`/wave/public/task`, { ids: record?.record }); |
| | | if (res?.data?.code === 200) { |
| | | notify(res.data.msg); |
| | | redirect("/task") |
| | | } else { |
| | | notify(res.data.msg); |
| | | } |
| | | refresh(); |
| | | } |
| | | return (<Button variant="contained" label={"ra.action.save"} onClick={generateTask}></Button>) |
| | | } |
| | |
| | | import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting'; |
| | | import * as Common from '@/utils/common'; |
| | | import PublicIcon from '@mui/icons-material/Public'; |
| | | import ItemToTaskModal from "./ItemToTaskModal"; |
| | | import ConfirmButton from "../../components/ConfirmButton"; |
| | | |
| | | |
| | | const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ |
| | |
| | | '& .column-name': { |
| | | }, |
| | | '& .opt': { |
| | | width: 200 |
| | | width: 260 |
| | | }, |
| | | })); |
| | | |
| | |
| | | <SearchInput source="condition" alwaysOn />, |
| | | <DateInput label='common.time.after' source="timeStart" alwaysOn />, |
| | | <DateInput label='common.time.before' source="timeEnd" alwaysOn />, |
| | | |
| | | <TextInput source="code" label="table.field.wave.code" />, |
| | | <SelectInput source="type" label="table.field.wave.type" |
| | | choices={[ |
| | |
| | | <NumberInput source="anfme" label="table.field.wave.anfme" />, |
| | | <NumberInput source="qty" label="table.field.wave.qty" />, |
| | | <NumberInput source="orderNum" label="table.field.wave.orderNum" />, |
| | | |
| | | <TextInput label="common.field.memo" source="memo" />, |
| | | <SelectInput |
| | | label="common.field.status" |
| | |
| | | const WaveList = () => { |
| | | const translate = useTranslate(); |
| | | const [createDialog, setCreateDialog] = useState(false); |
| | | const [detailDialog, setDetailDialog] = useState(false); |
| | | const [select, setSelectIds] = useState({}); |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | |
| | | return ( |
| | |
| | | > |
| | | <StyledDatagrid |
| | | preferenceKey='wave' |
| | | bulkActionButtons={ |
| | | <PublicTaskButton /> |
| | | } |
| | | bulkActionButtons={false} |
| | | rowClick={(id, resource, record) => false} |
| | | expand={false} |
| | | expandSingle={false} |
| | |
| | | <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"> |
| | | <PublicTaskButton setSelectIds={setSelectIds} setDetailDialog={setDetailDialog} /> |
| | | <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} /> |
| | | <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} /> |
| | | </WrapperField> |
| | | </StyledDatagrid> |
| | | </List> |
| | | <ItemToTaskModal |
| | | open={detailDialog} |
| | | record={select} |
| | | setOpen={setDetailDialog} |
| | | /> |
| | | <WaveCreate |
| | | open={createDialog} |
| | | setOpen={setCreateDialog} |
| | |
| | | |
| | | export default WaveList; |
| | | |
| | | const PublicTaskButton = () => { |
| | | const PublicTaskButton = ({ setSelectIds, setDetailDialog }) => { |
| | | const record = useRecordContext(); |
| | | const { selectedIds, onUnselectItems } = useListContext(); |
| | | const notify = useNotify(); |
| | | const refresh = useRefresh(); |
| | | const redirect = useRedirect(); |
| | | |
| | | const pubClick = async (event) => { |
| | | event.stopPropagation(); |
| | | console.log('=========>'); |
| | | |
| | | onUnselectItems(); |
| | | const res = await request.post(`/wave/public/task`, { ids: selectedIds }); |
| | | if (res?.data?.code === 200) { |
| | | notify(res.data.msg); |
| | | redirect("/task") |
| | | } else { |
| | | notify(res.data.msg); |
| | | } |
| | | refresh(); |
| | | setSelectIds(record); |
| | | setDetailDialog(true); |
| | | } |
| | | |
| | | return ( |
| | | <Button |
| | | onClick={pubClick} |
| | | label={"toolbar.createTask"} |
| | | startIcon={<PublicIcon />} |
| | | />); |
| | | <ConfirmButton label={"toolbar.createTask"} startIcon={<PublicIcon />} onConfirm={pubClick} /> |
| | | ); |
| | | } |
| | |
| | | DateInput, |
| | | SelectInput, |
| | | NumberInput, |
| | | |
| | | Button, |
| | | } from 'react-admin'; |
| | | import { Box, Typography, Card, Stack, Drawer } from '@mui/material'; |
| | |
| | | |
| | | const filters = [ |
| | | <SearchInput source="condition" alwaysOn />, |
| | | <DateInput label='common.time.after' source="timeStart" alwaysOn />, |
| | | <DateInput label='common.time.before' source="timeEnd" alwaysOn />, |
| | | |
| | | <DateInput label='common.time.after' source="timeStart" />, |
| | | <DateInput label='common.time.before' source="timeEnd" />, |
| | | <NumberInput source="waveId" label="table.field.waveItem.waveId" />, |
| | | <TextInput source="waveCode" label="table.field.waveItem.waveCode" />, |
| | | <NumberInput source="matnrId" label="table.field.waveItem.matnrId" />, |
| | |
| | | <TextInput source="fieldsIndex" label="table.field.waveItem.fieldsIndex" />, |
| | | <NumberInput source="anfme" label="table.field.waveItem.anfme" />, |
| | | <NumberInput source="workQty" label="table.field.waveItem.workQty" />, |
| | | |
| | | <TextInput label="common.field.memo" source="memo" />, |
| | | <SelectInput |
| | | label="common.field.status" |
| | |
| | | |
| | | const WaveItemList = () => { |
| | | const translate = useTranslate(); |
| | | |
| | | const [createDialog, setCreateDialog] = useState(false); |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | |
| | |
| | | <TextField source="fieldsIndex" label="table.field.waveItem.fieldsIndex" /> |
| | | <NumberField source="anfme" label="table.field.waveItem.anfme" /> |
| | | <NumberField source="workQty" label="table.field.waveItem.workQty" /> |
| | | |
| | | <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}> |
| | | <TextField source="nickname" /> |
| | | </ReferenceField> |
New file |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | |
| | | public class MinimalCombinationSum { |
| | | |
| | | private static List<Double> bestSolution; |
| | | private static double target; |
| | | private static double minDifference; |
| | | private static final double EPSILON = 1e-10; |
| | | |
| | | public static List<Double> findBestCombination(Double[] candidates, double targetSum) { |
| | | bestSolution = null; |
| | | target = targetSum; |
| | | minDifference = Double.MAX_VALUE; |
| | | Arrays.sort(candidates); |
| | | backtrack(candidates, 0, new ArrayList<>(), 0.0); |
| | | |
| | | // 如果没有精确解,返回最接近的近似解 |
| | | return bestSolution != null ? bestSolution : new ArrayList<>(); |
| | | } |
| | | |
| | | private static void backtrack(Double[] candidates, int start, |
| | | List<Double> current, double currentSum) { |
| | | // 计算当前和与目标的差值 |
| | | double difference = Math.abs(currentSum - target); |
| | | |
| | | // 如果找到更优解(差值更小或差值相同但组合更短) |
| | | if (difference < minDifference - EPSILON || |
| | | (Math.abs(difference - minDifference) < EPSILON && |
| | | (bestSolution == null || current.size() < bestSolution.size()))) { |
| | | minDifference = difference; |
| | | bestSolution = new ArrayList<>(current); |
| | | } |
| | | |
| | | // 如果已经找到精确解,不需要继续搜索更长的组合 |
| | | if (minDifference < EPSILON) { |
| | | return; |
| | | } |
| | | |
| | | // 遍历候选数字 |
| | | for (int i = start; i < candidates.length; i++) { |
| | | double num = candidates[i]; |
| | | |
| | | // 剪枝:如果当前和已经远大于目标值,跳过 |
| | | if (currentSum + num > target + minDifference + EPSILON) { |
| | | continue; |
| | | } |
| | | |
| | | // 跳过重复数字 |
| | | if (i > start && Math.abs(candidates[i] - candidates[i - 1]) < EPSILON) { |
| | | continue; |
| | | } |
| | | |
| | | current.add(num); |
| | | backtrack(candidates, i + 1, current, currentSum + num); |
| | | current.remove(current.size() - 1); |
| | | } |
| | | } |
| | | |
| | | public static void main(String[] args) { |
| | | Double[] candidates1 = {1.5, 2.3, 3.1, 4.7}; |
| | | double target1 = 3.8; |
| | | System.out.println("候选数字: " + Arrays.toString(candidates1)); |
| | | System.out.println("目标值: " + target1); |
| | | List<Double> result1 = findBestCombination(candidates1, target1); |
| | | printResult(result1, target1); |
| | | |
| | | // Double[] candidates2 = {0.8, 1.2, 1.7, 2.5}; |
| | | // double target2 = 3.9; |
| | | // System.out.println("\n候选数字: " + Arrays.toString(candidates2)); |
| | | // System.out.println("目标值: " + target2); |
| | | // List<Double> result2 = findBestCombination(candidates2, target2); |
| | | // printResult(result2, target2); |
| | | } |
| | | |
| | | private static void printResult(List<Double> result, double target) { |
| | | if (result.isEmpty()) { |
| | | System.out.println("无解"); |
| | | } else { |
| | | double sum = result.stream().mapToDouble(Double::doubleValue).sum(); |
| | | if (Math.abs(sum - target) < EPSILON) { |
| | | System.out.printf("精确解: %s (和=%.2f)\n", result, sum); |
| | | } else { |
| | | System.out.printf("近似解: %s (和=%.2f, 与目标相差: %.2f)\n", |
| | | result, sum, sum - target); |
| | | } |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | -- save basDevice record |
| | | -- mysql |
| | | insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.basDevice', '0', '/manager/basDevice', 'basDevice', '0' , '0', '1' , '1'); |
| | | |
| | | insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query 基础设备表', '', '1', 'manager:basDevice:list', '0', '1', '1'); |
| | | insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create 基础设备表', '', '1', 'manager:basDevice:save', '1', '1', '1'); |
| | | insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update 基础设备表', '', '1', 'manager:basDevice:update', '2', '1', '1'); |
| | | insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete 基础设备表', '', '1', 'manager:basDevice:remove', '3', '1', '1'); |
| | | |
| | | -- locale menu name |
| | | basDevice: 'BasDevice', |
| | | |
| | | -- locale field |
| | | basDevice: { |
| | | deviceNo: "deviceNo", |
| | | inEnable: "inEnable", |
| | | outEnable: "outEnable", |
| | | origin: "origin", |
| | | }, |
| | | |
| | | -- ResourceContent |
| | | import basDevice from './basDevice'; |
| | | |
| | | case 'basDevice': |
| | | return basDevice; |
New file |
| | |
| | | package com.vincent.rsf.server.api.controller; |
| | | |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.api.entity.dto.InTaskMsgDto; |
| | | import com.vincent.rsf.server.api.controller.params.TaskInParam; |
| | | import com.vincent.rsf.server.api.entity.enums.TaskType; |
| | | import com.vincent.rsf.server.api.service.WcsService; |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import io.swagger.annotations.Api; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | @RestController |
| | | @RequestMapping("/wcs") |
| | | @Api(tags = "wcs接口对接") |
| | | public class WcsController extends BaseController { |
| | | |
| | | @Autowired |
| | | private WcsService wcsService; |
| | | |
| | | // @ApiOperation(value = "wcs生成入库任务接口") |
| | | @PostMapping("/create/in/task") |
| | | public R createInTask(@RequestBody TaskInParam param) { |
| | | if (Cools.isEmpty(param.getIoType())) { |
| | | return R.error("入出库类型不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getSourceStaNo())) { |
| | | return R.error("源站编号不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getBarcode()) && param.getIoType().equals(TaskType.TASK_TYPE_IN.type)) { |
| | | return R.error("条码不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getLocType1())){ |
| | | return R.error("高低检测信号不能为空"); |
| | | } |
| | | InTaskMsgDto msgDto = wcsService.createInTask(param,getLoginUserId()); |
| | | return R.ok(msgDto); |
| | | |
| | | |
| | | } |
| | | |
| | | |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.api.controller.params; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class TaskInParam { |
| | | |
| | | private Integer ioType; //作业类型 |
| | | private Integer sourceStaNo; //作业站点 or 来源站点 |
| | | private String barcode; //容器条码 |
| | | private Integer locType1; //库位类型 |
| | | private Integer area; |
| | | // private Integer locType2; //库位类型 |
| | | // private Integer locType3; //库位类型 |
| | | } |
File was renamed from rsf-server/src/main/java/com/vincent/rsf/server/api/controller/MobileController.java |
| | |
| | | package com.vincent.rsf.server.api.controller; |
| | | package com.vincent.rsf.server.api.controller.pda; |
| | | |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
New file |
| | |
| | | package com.vincent.rsf.server.api.controller.pda; |
| | | |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.api.service.PdaOutStockService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Map; |
| | | |
| | | @Api(tags = "PDA出库操作接口") |
| | | @RequestMapping("/pda") |
| | | @RestController |
| | | public class pdaOutStockController { |
| | | |
| | | @Autowired |
| | | private PdaOutStockService pdaOutStockService; |
| | | |
| | | @PreAuthorize("hasAuthority('manager:task:list')") |
| | | @GetMapping("/outStockTaskItem/{barcode}") |
| | | @ApiOperation("快速拣货查询") |
| | | public R getOutStockTaskItem(@PathVariable String barcode) { |
| | | |
| | | return pdaOutStockService.getOutStockTaskItem(barcode); |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.api.entity.dto; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class InTaskMsgDto { |
| | | private Integer sourceStaNo; |
| | | |
| | | private Integer staNo; |
| | | |
| | | private Integer deviceNo; |
| | | |
| | | private String locNo; |
| | | |
| | | private String workNo; |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.api.entity.dto; |
| | | |
| | | import com.vincent.rsf.server.api.controller.params.TaskInParam; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class LocTypeDto { |
| | | // 高低类型{0:未知,1:低库位,2:高库位} |
| | | private Integer locType1; |
| | | |
| | | // 宽窄类型{0:未知,1:窄库位,2:宽库位} |
| | | private Integer locType2; |
| | | |
| | | // 轻重类型{0:未知,1:轻库位,2:重库位} |
| | | private Integer locType3; |
| | | |
| | | public LocTypeDto(TaskInParam param) { |
| | | this.locType1 = param.getLocType1(); // 高库位 |
| | | |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.api.service; |
| | | |
| | | import com.vincent.rsf.framework.common.R; |
| | | |
| | | import java.util.Map; |
| | | |
| | | public interface PdaOutStockService { |
| | | R getOutStockTaskItem(String barcode); |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.api.service; |
| | | |
| | | import com.vincent.rsf.server.api.entity.dto.InTaskMsgDto; |
| | | import com.vincent.rsf.server.api.controller.params.TaskInParam; |
| | | |
| | | public interface WcsService { |
| | | InTaskMsgDto createInTask(TaskInParam param, Long loginUserId); |
| | | } |
| | |
| | | itemList.forEach(asnOrderItem -> { |
| | | LocItem item = new LocItem(); |
| | | BeanUtils.copyProperties(asnOrderItem, item); |
| | | item.setId(loc.getId()) |
| | | item.setLocId(loc.getId()) |
| | | .setId(null) |
| | | .setLocCode(loc.getCode()) |
| | | .setOrderId(order.getId()) |
| | | .setOrderItemId(asnOrderItem.getId()) |
| | | .setWkType(Short.parseShort(order.getWkType())) |
| | |
| | | } |
| | | //获取当前库存信息 |
| | | LocItem stockItem = locItemService.getOne(new LambdaQueryWrapper<LocItem>() |
| | | .eq(LocItem::getOrderItemId, asnOrderItem.getId()) |
| | | // .eq(LocItem::getOrderItemId, asnOrderItem.getId()) |
| | | .eq(LocItem::getFieldsIndex, asnOrderItem.getFieldsIndex()) |
| | | .eq(LocItem::getBatch, asnOrderItem.getBatch()) |
| | | .eq(LocItem::getMatnrId, asnOrderItem.getMatnrId())); |
| | | //SET 当前库存数量 |
New file |
| | |
| | | package com.vincent.rsf.server.api.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.api.service.PdaOutStockService; |
| | | import com.vincent.rsf.server.manager.entity.AsnOrder; |
| | | import com.vincent.rsf.server.manager.entity.Task; |
| | | import com.vincent.rsf.server.manager.entity.TaskItem; |
| | | import com.vincent.rsf.server.manager.service.AsnOrderService; |
| | | import com.vincent.rsf.server.manager.service.OutStockService; |
| | | import com.vincent.rsf.server.manager.service.TaskItemService; |
| | | import com.vincent.rsf.server.manager.service.TaskService; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | @Service |
| | | public class PdaOutStockServiceImpl implements PdaOutStockService { |
| | | |
| | | @Resource |
| | | private TaskService taskService; |
| | | @Resource |
| | | private TaskItemService taskItemService; |
| | | |
| | | @Override |
| | | public R getOutStockTaskItem(String barcode) { |
| | | LambdaQueryWrapper<Task> lambdaQueryWrapper = new LambdaQueryWrapper<>(); |
| | | lambdaQueryWrapper.eq(Task::getBarcode, barcode); |
| | | Task task = taskService.getOne(lambdaQueryWrapper); |
| | | if (null == task){ |
| | | return R.error("未查询到相关任务"); |
| | | } |
| | | List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId())); |
| | | if (null == taskItems || taskItems.size() <= 0){ |
| | | return R.error("任务出错,未查询到相关任务明细"); |
| | | } |
| | | |
| | | |
| | | return R.ok(taskItems); |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.api.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.api.entity.dto.InTaskMsgDto; |
| | | import com.vincent.rsf.server.api.entity.dto.LocTypeDto; |
| | | import com.vincent.rsf.server.api.controller.params.TaskInParam; |
| | | import com.vincent.rsf.server.api.entity.enums.OrderType; |
| | | import com.vincent.rsf.server.api.entity.enums.TaskStsType; |
| | | import com.vincent.rsf.server.api.entity.enums.TaskType; |
| | | import com.vincent.rsf.server.api.service.WcsService; |
| | | import com.vincent.rsf.server.api.utils.LocUtils; |
| | | import com.vincent.rsf.server.api.utils.SlaveProperties; |
| | | import com.vincent.rsf.server.manager.entity.*; |
| | | import com.vincent.rsf.server.manager.enums.PakinIOStatus; |
| | | import com.vincent.rsf.server.manager.service.*; |
| | | import com.vincent.rsf.server.manager.service.impl.LocServiceImpl; |
| | | import com.vincent.rsf.server.manager.utils.LocManageUtil; |
| | | import com.vincent.rsf.server.system.constant.SerialRuleCode; |
| | | import com.vincent.rsf.server.system.enums.LocStsType; |
| | | import com.vincent.rsf.server.system.utils.SerialRuleUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Service |
| | | public class WcsServiceImpl implements WcsService { |
| | | @Autowired |
| | | private DeviceSiteService deviceSiteService; |
| | | @Autowired |
| | | private WaitPakinService waitPakinService; |
| | | @Autowired |
| | | private DeviceBindService deviceBindService; |
| | | @Autowired |
| | | private LocServiceImpl locService; |
| | | @Autowired |
| | | private LocItemService locItemService; |
| | | @Autowired |
| | | private SlaveProperties slaveProperties; |
| | | @Autowired |
| | | private WarehouseAreasService warehouseAreasService; |
| | | @Autowired |
| | | private TaskService taskService; |
| | | @Autowired |
| | | private TaskItemService taskItemService; |
| | | @Autowired |
| | | private WaitPakinItemService waitPakinItemService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public InTaskMsgDto createInTask(TaskInParam param, Long loginUserId) { |
| | | // 获取库位号 |
| | | InTaskMsgDto locNo = getLocNo(param); |
| | | |
| | | // 验证设备站点 |
| | | DeviceSite deviceSite = validateDeviceSite(param); |
| | | |
| | | // 验证组拖状态 |
| | | WaitPakin waitPakin = validateWaitPakin(param.getBarcode()); |
| | | |
| | | // 生成任务编码 |
| | | String ruleCode = generateTaskCode(); |
| | | |
| | | // 创建并保存任务 |
| | | Task task = createTask(ruleCode, locNo.getLocNo(), waitPakin.getBarcode(), |
| | | deviceSite.getDeviceSite(), param.getSourceStaNo().toString(), loginUserId); |
| | | |
| | | // 更新库位状态 |
| | | updateLocStatus(task.getTargLoc(), waitPakin.getBarcode()); |
| | | |
| | | // 获取并验证组拖明细 |
| | | List<WaitPakinItem> waitPakinItems = getWaitPakinItems(waitPakin.getId()); |
| | | |
| | | // 创建并保存任务明细 |
| | | saveTaskItems(task.getId(), waitPakinItems, loginUserId); |
| | | |
| | | // 更新组托状态 |
| | | updateWaitPakinStatus(param.getBarcode(), loginUserId); |
| | | |
| | | // 设置工作单号并返回 |
| | | locNo.setWorkNo(ruleCode); |
| | | return locNo; |
| | | } |
| | | |
| | | /** |
| | | * 验证设备站点 |
| | | */ |
| | | private DeviceSite validateDeviceSite(TaskInParam param) { |
| | | DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>() |
| | | .eq(DeviceSite::getSite, param.getSourceStaNo()) |
| | | .eq(DeviceSite::getType, param.getIoType())); |
| | | |
| | | if (Objects.isNull(deviceSite)) { |
| | | throw new CoolException("站点不存在!!"); |
| | | } |
| | | return deviceSite; |
| | | } |
| | | |
| | | /** |
| | | * 验证组拖状态 |
| | | */ |
| | | private WaitPakin validateWaitPakin(String barcode) { |
| | | WaitPakin waitPakin = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>() |
| | | .eq(WaitPakin::getBarcode, barcode) |
| | | .eq(WaitPakin::getIoStatus, Short.parseShort(PakinIOStatus.PAKIN_IO_STATUS_DONE.val))); |
| | | |
| | | if (Cools.isEmpty(waitPakin)) { |
| | | throw new CoolException("请检查组拖状态是否完成!!"); |
| | | } |
| | | return waitPakin; |
| | | } |
| | | |
| | | /** |
| | | * 生成任务编码 |
| | | */ |
| | | private String generateTaskCode() { |
| | | String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null); |
| | | if (StringUtils.isBlank(ruleCode)) { |
| | | throw new CoolException("编码错误:请确认编码「SYS_TASK_CODE」是否已生成!!"); |
| | | } |
| | | return ruleCode; |
| | | } |
| | | |
| | | /** |
| | | * 创建并保存任务 |
| | | */ |
| | | private Task createTask(String ruleCode, String targetLoc, String barcode, |
| | | String targetSite, String sourceSiteNo, Long loginUserId) { |
| | | Task task = new Task(); |
| | | task.setTaskCode(ruleCode) |
| | | .setTaskStatus(TaskStsType.GENERATE_IN.id.shortValue()) |
| | | .setTaskType(TaskType.TASK_TYPE_IN.type.shortValue()) |
| | | .setTargLoc(targetLoc) |
| | | .setBarcode(barcode) |
| | | .setTargSite(targetSite) |
| | | .setCreateBy(loginUserId) |
| | | .setUpdateBy(loginUserId) |
| | | .setOrgSite(sourceSiteNo); |
| | | |
| | | if (!taskService.save(task)) { |
| | | throw new CoolException("任务保存失败!!"); |
| | | } |
| | | return task; |
| | | } |
| | | |
| | | /** |
| | | * 更新库位状态 |
| | | */ |
| | | private void updateLocStatus(String locCode, String barcode) { |
| | | boolean updated = locService.update(new LambdaUpdateWrapper<Loc>() |
| | | .eq(Loc::getCode, locCode) |
| | | .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type) |
| | | .set(Loc::getBarcode, barcode)); |
| | | |
| | | if (!updated) { |
| | | throw new CoolException("库位预约失败!!"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取并验证组拖明细 |
| | | */ |
| | | private List<WaitPakinItem> getWaitPakinItems(Long pakinId) { |
| | | List<WaitPakinItem> waitPakinItems = waitPakinItemService.list( |
| | | new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, pakinId)); |
| | | |
| | | if (waitPakinItems.isEmpty()) { |
| | | throw new CoolException("数据错误:组拖明细不存在"); |
| | | } |
| | | return waitPakinItems; |
| | | } |
| | | |
| | | /** |
| | | * 创建并保存任务明细 |
| | | */ |
| | | private void saveTaskItems(Long taskId, List<WaitPakinItem> waitPakinItems, Long loginUserId) { |
| | | List<TaskItem> taskItems = waitPakinItems.stream().map(item -> { |
| | | TaskItem taskItem = new TaskItem(); |
| | | BeanUtils.copyProperties(item, taskItem); |
| | | |
| | | return taskItem.setTaskId(taskId) |
| | | .setOrderType(OrderType.ORDER_RECEIPT.type) |
| | | .setSource(item.getId()) |
| | | .setTrackCode(item.getTrackCode()) |
| | | .setCreateBy(loginUserId) |
| | | .setUpdateBy(loginUserId) |
| | | .setOrderId(item.getAsnId()) |
| | | .setOrderItemId(item.getAsnItemId()); |
| | | }).collect(Collectors.toList()); |
| | | |
| | | if (!taskItemService.saveBatch(taskItems)) { |
| | | throw new CoolException("任务明细保存失败!!"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新组托状态 |
| | | */ |
| | | private void updateWaitPakinStatus(String barcode, Long loginUserId) { |
| | | boolean updated = waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>() |
| | | .eq(WaitPakin::getBarcode, barcode) |
| | | .set(WaitPakin::getUpdateBy, loginUserId) |
| | | .set(WaitPakin::getCreateBy, loginUserId) |
| | | .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val)); |
| | | |
| | | if (!updated) { |
| | | throw new CoolException("组托状态修改失败!!"); |
| | | } |
| | | } |
| | | |
| | | // @Override |
| | | // public InTaskMsgDto createInTask(TaskInParam param, Long loginUserId) { |
| | | // InTaskMsgDto locNo = getLocNo(param); |
| | | // DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>() |
| | | // .eq(DeviceSite::getSite, param.getSourceStaNo()) |
| | | // .eq(DeviceSite::getType,param.getIoType()) |
| | | // ); |
| | | // if (Objects.isNull(deviceSite)) { |
| | | // throw new CoolException("站点不存在!!"); |
| | | // } |
| | | // |
| | | // WaitPakin waitPakin = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>() |
| | | // .eq(WaitPakin::getBarcode, param.getBarcode()) |
| | | // .eq(WaitPakin::getIoStatus, Short.parseShort(PakinIOStatus.PAKIN_IO_STATUS_DONE.val))); |
| | | // if (Cools.isEmpty(waitPakin)) { |
| | | // 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.GENERATE_IN.id.shortValue()) |
| | | // .setTaskType(TaskType.TASK_TYPE_IN.type.shortValue()) |
| | | // .setTargLoc(locNo.getLocNo()) |
| | | // .setBarcode(waitPakin.getBarcode()) |
| | | // .setTargSite(deviceSite.getDeviceSite()) |
| | | // .setCreateBy(loginUserId) |
| | | // .setUpdateBy(loginUserId) |
| | | // .setOrgSite(param.getSourceStaNo().toString()); |
| | | // |
| | | // |
| | | // |
| | | // |
| | | // if (!taskService.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, waitPakin.getBarcode()))) { |
| | | // throw new CoolException("库位预约失败!!"); |
| | | // } |
| | | // /**获取组拖明细**/ |
| | | // List<WaitPakinItem> waitPakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, waitPakin.getId())); |
| | | // if (waitPakinItems.isEmpty()) { |
| | | // throw new CoolException("数据错误:组拖明细不存在"); |
| | | // } |
| | | // waitPakinItems.forEach(item -> { |
| | | // TaskItem taskItem = new TaskItem(); |
| | | // BeanUtils.copyProperties(item, taskItem); |
| | | //// AsnOrder order = asnOrderService.getOne(new LambdaQueryWrapper<AsnOrder>().eq(AsnOrder::getId, item.getAsnId())); |
| | | //// if (Objects.isNull(order)) { |
| | | //// throw new CoolException("数据错误: 单据不存在!!"); |
| | | //// } |
| | | // taskItem.setTaskId(task.getId()) |
| | | // .setOrderType(OrderType.ORDER_RECEIPT.type) |
| | | // .setSource(item.getId()) |
| | | // .setTrackCode(item.getTrackCode()) |
| | | // .setCreateBy(loginUserId) |
| | | // .setUpdateBy(loginUserId) |
| | | // .setOrderId(item.getAsnId()) |
| | | // .setOrderItemId(item.getAsnItemId()); |
| | | // taskItems.add(taskItem); |
| | | // }); |
| | | // if (!taskItemService.saveBatch(taskItems)) { |
| | | // throw new CoolException("任务明细保存失败!!"); |
| | | // } |
| | | // |
| | | // |
| | | // if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>() |
| | | // .eq(WaitPakin::getBarcode, param.getBarcode()) |
| | | // .set(WaitPakin::getUpdateBy, loginUserId) |
| | | // .set(WaitPakin::getCreateBy, loginUserId) |
| | | // .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val))) { |
| | | // throw new CoolException("组托状态修改失败!!"); |
| | | // } |
| | | // locNo.setWorkNo(ruleCode); |
| | | // return locNo; |
| | | // } |
| | | |
| | | public InTaskMsgDto getLocNo(TaskInParam param) { |
| | | String matnr = null; String batch = null; |
| | | List<WaitPakin> waitPakins = waitPakinService.list(new LambdaQueryWrapper<WaitPakin>().eq(WaitPakin::getBarcode, param.getBarcode())); |
| | | if (Cools.isEmpty(waitPakins) && param.getIoType().equals(TaskType.TASK_TYPE_IN.type)) { |
| | | throw new CoolException("未找到组托信息,请组托"); |
| | | }else if (!Cools.isEmpty(waitPakins)) { |
| | | matnr = waitPakins.get(0).getCode(); |
| | | batch = waitPakins.get(0).getCode(); |
| | | } |
| | | List<DeviceSite> deviceSites = deviceSiteService.list(new LambdaQueryWrapper<DeviceSite>() |
| | | .eq(DeviceSite::getSite, param.getSourceStaNo()) |
| | | .eq(DeviceSite::getType,param.getIoType()) |
| | | ); |
| | | if (Cools.isEmpty(deviceSites)) { |
| | | throw new CoolException("未找到站点路径信息"); |
| | | } |
| | | WarehouseAreas warehouseArea = warehouseAreasService.getById(param.getArea()); |
| | | if (Cools.isEmpty(warehouseArea)) { |
| | | throw new CoolException("未找到所属库区信息"); |
| | | } |
| | | LocTypeDto locTypeDto = new LocTypeDto(param); |
| | | InTaskMsgDto dto = null; |
| | | switch (warehouseArea.getType()) { |
| | | case "CRN": //堆垛机 |
| | | dto = getLocNoCrn(param.getArea(), param.getSourceStaNo(), matnr,batch, locTypeDto, 0, param.getIoType()); |
| | | break; |
| | | case "SXC": //四向库 |
| | | break; |
| | | case "CTU": //四向库 |
| | | break; |
| | | } |
| | | return dto; |
| | | } |
| | | |
| | | private InTaskMsgDto getLocNoCrn(Integer area,Integer sourceStaNo, String matnr, String batch,LocTypeDto locTypeDto, int times,Integer ioType){ |
| | | if (Cools.isEmpty(matnr)) { //物料号 |
| | | matnr = ""; |
| | | } |
| | | if (Cools.isEmpty(batch)) { //批次 |
| | | batch = ""; |
| | | } |
| | | // 初始化参数 |
| | | int deviceNo = 0; //堆垛机号 |
| | | int nearRow = 0; //最浅库位排 |
| | | int curRow = 0; //最深库位排 |
| | | int rowCount = 0; //轮询轮次 |
| | | Loc loc = null; // 目标库位 |
| | | |
| | | InTaskMsgDto inTaskMsgDto = new InTaskMsgDto(); |
| | | DeviceBind deviceBind = deviceBindService.getById(LocUtils.getAreaType(sourceStaNo)); |
| | | if (Cools.isEmpty(deviceBind)) { |
| | | throw new CoolException("数据异常,请联系管理员===>库位规则未知"); |
| | | } |
| | | int sRow = deviceBind.getStartRow(); |
| | | int eRow = deviceBind.getEndRow(); |
| | | int deviceQty = deviceBind.getDeviceQty(); |
| | | |
| | | |
| | | // ===============>>>> 开始执行 |
| | | curRow = deviceBind.getCurrentRow(); |
| | | |
| | | //此程序用于优化堆垛机异常时的运行时间 |
| | | for (int i = times; i <= deviceQty * 2; i++) { |
| | | int[] locNecessaryParameters = LocUtils.LocNecessaryParameters(deviceBind, curRow, deviceQty); |
| | | curRow = locNecessaryParameters[1]; |
| | | deviceNo = locNecessaryParameters[2]; |
| | | rowCount = locNecessaryParameters[0]; |
| | | nearRow = locNecessaryParameters[3]; |
| | | break; |
| | | } |
| | | if (nearRow == 0) { |
| | | throw new CoolException("无可用堆垛机"); |
| | | } |
| | | //入库靠近摆放 |
| | | if (ioType== 1 && deviceBind.getBeSimilar().equals("1") && !Cools.isEmpty(matnr)) { |
| | | if (nearRow != curRow) { |
| | | List<LocItem> locItems = locItemService.list(new LambdaQueryWrapper<LocItem>().eq(LocItem::getMatnrCode, matnr)); |
| | | for (LocItem locItem : locItems) { |
| | | Loc loc1 = locService.getById(locItem.getLocId()); |
| | | if (LocUtils.isShallowLoc(slaveProperties, loc1.getCode())) { |
| | | continue; |
| | | } |
| | | String shallowLocNo = LocUtils.getShallowLoc(slaveProperties, loc1.getCode()); |
| | | // 检测目标库位是否为空库位 |
| | | Loc shallowLoc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode,shallowLocNo)); |
| | | if (shallowLoc != null && shallowLoc.getUseStatus().equals("O")) { |
| | | if (LocUtils.locMoveCheckLocTypeComplete(shallowLoc, locTypeDto)) { |
| | | loc = shallowLoc; |
| | | deviceNo = shallowLoc.getDeviceNo(); |
| | | break; |
| | | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // // 靠近摆放规则 --- 空托 //互通版 |
| | | // if (ioType == 10 && deviceBind.getEmptySimilar().equals("1")) { |
| | | // List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>() |
| | | // .eq("loc_sts", "D").ge("row1", sRow).le("row1", eRow).eq("whs_type", rowLastnoType.getType().longValue())); |
| | | // if (!locMasts.isEmpty()) { |
| | | // for (LocMast loc : locMasts) { |
| | | // if (Utils.isShallowLoc(slaveProperties, loc.getLocNo())) { |
| | | // continue; |
| | | // } |
| | | // String shallowLocNo = Utils.getShallowLoc(slaveProperties, loc.getLocNo()); |
| | | // // 检测目标库位是否为空库位 |
| | | // LocMast shallowLoc = locMastService.selectById(shallowLocNo); |
| | | // if (shallowLoc != null && shallowLoc.getLocSts().equals("O")) { |
| | | // if (VersionUtils.locMoveCheckLocTypeComplete(shallowLoc, locTypeDto)) { |
| | | // if (basCrnpService.checkSiteError(shallowLoc.getCrnNo(), true)) { |
| | | // locMast = shallowLoc; |
| | | // crnNo = locMast.getCrnNo(); |
| | | // break; |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | //查找路径 |
| | | DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>() |
| | | .eq(DeviceSite::getType, ioType) |
| | | .eq(DeviceSite::getSite, sourceStaNo) |
| | | .eq(DeviceSite::getDeviceCode, deviceNo) |
| | | ); |
| | | if (Cools.isEmpty(deviceSite)){ |
| | | deviceNo = 0; |
| | | }else { |
| | | inTaskMsgDto.setStaNo(Integer.parseInt(deviceSite.getDeviceSite())); |
| | | } |
| | | |
| | | //更新当前排 |
| | | deviceBind.setCurrentRow(curRow); |
| | | deviceBindService.updateById(deviceBind); |
| | | |
| | | // 开始查找库位 ==============================>> |
| | | |
| | | // 1.按规则查找库位 |
| | | if (Cools.isEmpty(loc) && deviceNo != 0) { |
| | | List<Loc> locMasts = null; |
| | | locMasts = locService.list(new LambdaQueryWrapper<Loc>() |
| | | .eq(Loc::getRow, nearRow) |
| | | .eq(Loc::getUseStatus, "O") |
| | | .eq(Loc::getType, locTypeDto.getLocType1()) |
| | | .eq(Loc::getAreaId,area) |
| | | .orderByAsc(Loc::getLev) |
| | | .orderByAsc(Loc::getCol) |
| | | ); |
| | | for (Loc locMast1 : locMasts) { |
| | | if (!LocUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) { |
| | | continue; |
| | | } |
| | | String shallowLoc = LocUtils.getDeepLoc(slaveProperties, locMast1.getCode()); |
| | | if ((ioType== 1 && deviceBind.getBeSimilar().equals("1"))) { |
| | | //相似物料打开,判断深库位有没有货,没货就放深库位,有货就不操作 |
| | | Loc locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>() |
| | | .eq(Loc::getRow, shallowLoc) |
| | | .eq(Loc::getUseStatus, "O") |
| | | .eq(Loc::getAreaId,area) |
| | | ); |
| | | if (!Cools.isEmpty(locMast2)) { |
| | | loc = locMast2; |
| | | break; |
| | | } |
| | | } else { |
| | | //相似物料关闭,判断深库位有没有货,有货就放浅库位,无货就不操作 |
| | | Loc locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>() |
| | | .eq(Loc::getCode, shallowLoc) |
| | | .in(Loc::getUseStatus, "D","F") |
| | | .eq(Loc::getAreaId,area) |
| | | ); |
| | | if (!Cools.isEmpty(locMast2)) { |
| | | loc = locMast1; |
| | | break; |
| | | }else{ |
| | | locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>() |
| | | .eq(Loc::getCode, shallowLoc) |
| | | .eq(Loc::getUseStatus, "O") |
| | | .eq(Loc::getAreaId,area) |
| | | ); |
| | | if (!Cools.isEmpty(locMast2)) { |
| | | loc = locMast2; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (Cools.isEmpty(loc) && deviceBind.getBeSimilar().equals("1")) { |
| | | for (Loc locMast1 : locMasts) { |
| | | if (!LocUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) { |
| | | continue; |
| | | } |
| | | if (deviceBind.getBeSimilar().equals("1")) { |
| | | String shallowLoc = LocUtils.getDeepLoc(slaveProperties, locMast1.getCode()); |
| | | Loc locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>() |
| | | .eq(Loc::getCode, shallowLoc) |
| | | .eq(Loc::getUseStatus, "O") |
| | | .eq(Loc::getAreaId,area) |
| | | ); |
| | | if (!Cools.isEmpty(locMast2)) { |
| | | loc = locMast2; |
| | | break; |
| | | } else { |
| | | locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>() |
| | | .eq(Loc::getCode, shallowLoc) |
| | | .in(Loc::getUseStatus, "D","F") |
| | | .eq(Loc::getAreaId,area) |
| | | ); |
| | | if (!Cools.isEmpty(locMast2)) { |
| | | loc = locMast1; |
| | | break; |
| | | } |
| | | } |
| | | } else { |
| | | if (!Cools.isEmpty(locMast1)) { |
| | | loc = locMast1; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | //查询当前库位类型空库位 小于5个则locmast = null |
| | | List<Loc> locTypeLocMasts = locService.list(new LambdaQueryWrapper<Loc>() |
| | | .eq(Loc::getUseStatus, "O") |
| | | .eq(Loc::getDeviceNo, deviceNo) |
| | | .eq(Loc::getType, locTypeDto.getLocType1()) |
| | | .eq(Loc::getAreaId,area) |
| | | ); |
| | | if (null !=locTypeLocMasts && locTypeLocMasts.size()<=5){ |
| | | loc = null; |
| | | } |
| | | // 递归查询 |
| | | if (Cools.isEmpty(loc) || !loc.getUseStatus().equals("O")) { |
| | | // 当前巷道无空库位时,递归调整至下一巷道,检索全部巷道无果后,跳出递归 |
| | | if (times < rowCount * 2) { |
| | | times = times + 1; |
| | | return getLocNoCrn(area,sourceStaNo,matnr,batch,locTypeDto,times, ioType); |
| | | |
| | | } |
| | | // 2.库位当前所属尺寸无空库位时,调整尺寸参数,向上兼容检索库位 |
| | | if (locTypeDto.getLocType1() < 3) { |
| | | int i = locTypeDto.getLocType1() + 1; |
| | | locTypeDto.setLocType1(i); |
| | | return getLocNoCrn(area,sourceStaNo,matnr,batch,locTypeDto,0, ioType); |
| | | } |
| | | throw new CoolException("没有空库位"); |
| | | } |
| | | String locNo = loc.getCode(); |
| | | |
| | | // 返回dto |
| | | inTaskMsgDto.setDeviceNo(deviceNo); |
| | | inTaskMsgDto.setSourceStaNo(sourceStaNo); |
| | | // inTaskMsgDto.setStaNo(); |
| | | inTaskMsgDto.setLocNo(locNo); |
| | | return inTaskMsgDto; |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.api.utils; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.common.Arith; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.framework.common.SpringUtils; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.api.entity.dto.LocTypeDto; |
| | | import com.vincent.rsf.server.manager.entity.DeviceBind; |
| | | import com.vincent.rsf.server.manager.entity.Loc; |
| | | import com.vincent.rsf.server.manager.service.DeviceBindService; |
| | | |
| | | |
| | | import java.util.List; |
| | | |
| | | public class LocUtils { |
| | | |
| | | /** |
| | | * 获取 浅库位对应的深库位号 |
| | | */ |
| | | public static String getDeepLoc(SlaveProperties slaveProperties, String shallowLoc) { |
| | | int row = getRow(shallowLoc); |
| | | int remainder = (int) Arith.remainder(row, slaveProperties.getGroupCount()); |
| | | int targetRow; |
| | | if (remainder == 2) { |
| | | targetRow = row - 1; |
| | | } else if (remainder == 3) { |
| | | targetRow = row + 1; |
| | | } else { |
| | | throw new CoolException(shallowLoc + "不是浅库位,系统繁忙"); |
| | | } |
| | | return zerofill(String.valueOf(targetRow), 2) + shallowLoc.substring(2); |
| | | } |
| | | |
| | | /** |
| | | * 获取 深库位对应的浅库位号 |
| | | */ |
| | | public static String getShallowLoc(SlaveProperties slaveProperties, String deepLoc) { |
| | | int row = getRow(deepLoc); |
| | | int remainder = (int) Arith.remainder(row, slaveProperties.getGroupCount()); |
| | | int shallowRow = remainder == 1 ? (row + 1) : (row - 1); |
| | | return zerofill(String.valueOf(shallowRow), 2) + deepLoc.substring(2); |
| | | } |
| | | |
| | | /** |
| | | * 判断是否为浅库位 |
| | | */ |
| | | public static boolean isShallowLoc(SlaveProperties slaveProperties, String locNo) { |
| | | if (slaveProperties.isDoubleDeep()) { |
| | | int row = getRow(locNo); |
| | | return !slaveProperties.getDoubleLocs().contains(row); |
| | | } else { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | //获取站点对应的库类型 |
| | | public static Long getAreaType(Integer sourceStaNo) { |
| | | DeviceBindService rowLastnoService = SpringUtils.getBean(DeviceBindService.class); |
| | | List<DeviceBind> deviceBinds = rowLastnoService.list(new LambdaQueryWrapper<DeviceBind>()); |
| | | for (DeviceBind deviceBind : deviceBinds) { |
| | | String[] staNoList = deviceBind.getStaList().split(";"); |
| | | for (String staNo : staNoList) { |
| | | if (staNo.equals(sourceStaNo.toString())) { |
| | | return deviceBind.getId(); |
| | | } |
| | | } |
| | | } |
| | | return 0L; |
| | | } |
| | | |
| | | //库位排号分配 |
| | | public static int[] LocNecessaryParameters(DeviceBind deviceBind, Integer curRow, Integer crnNumber) { |
| | | |
| | | return LocNecessaryParametersDoubleExtension(curRow, crnNumber); //已完善 |
| | | |
| | | |
| | | } |
| | | |
| | | //经典双伸库位 |
| | | public static int[] LocNecessaryParametersDoubleExtension( Integer curRow, Integer crnNumber) { |
| | | int[] necessaryParameters = new int[]{0, 0, 0, 0}; |
| | | |
| | | necessaryParameters[0] = crnNumber; // 轮询次数 |
| | | //满板正常入库 |
| | | if (curRow.equals(crnNumber * 4)) { |
| | | necessaryParameters[1] = 1; //curRow 最深库位排 |
| | | necessaryParameters[2] = 1; //crnNo 堆垛机号 |
| | | necessaryParameters[3] = 2; //nearRow 最浅库位排 |
| | | } else if (curRow.equals(crnNumber * 4 - 3)) { |
| | | necessaryParameters[1] = 4; //curRow 最深库位排 |
| | | necessaryParameters[2] = 1; //crnNo 堆垛机号 |
| | | necessaryParameters[3] = 3; //nearRow 最浅库位排 |
| | | } else { |
| | | curRow = curRow + 4; |
| | | if (curRow < 1 || curRow > (crnNumber * 4)) { |
| | | throw new CoolException("库位排号异常:排号:" + curRow); |
| | | } |
| | | if ((curRow - 1) % 4 == 0) { |
| | | necessaryParameters[1] = curRow; //curRow 最深库位排 |
| | | necessaryParameters[2] = (curRow + 3) / 4; //crnNo 堆垛机号 |
| | | necessaryParameters[3] = curRow + 1; //nearRow 最浅库位排 |
| | | } else if (curRow % 4 == 0) { |
| | | necessaryParameters[1] = curRow; //curRow 最深库位排 |
| | | necessaryParameters[2] = curRow / 4; //crnNo 堆垛机号 |
| | | necessaryParameters[3] = curRow - 1; //nearRow 最浅库位排 |
| | | } else { |
| | | throw new CoolException("库位排号异常:排号:" + curRow); |
| | | } |
| | | } |
| | | |
| | | return necessaryParameters; |
| | | } |
| | | |
| | | /** |
| | | * 通过库位号获取 排 |
| | | */ |
| | | public static int getRow(String locNo) { |
| | | if (!Cools.isEmpty(locNo)) { |
| | | return Integer.parseInt(locNo.substring(0, 2)); |
| | | } |
| | | throw new RuntimeException("库位解析异常"); |
| | | } |
| | | |
| | | /** |
| | | * 通过库位号获取 列 |
| | | */ |
| | | public static int getBay(String locNo) { |
| | | if (!Cools.isEmpty(locNo)) { |
| | | return Integer.parseInt(locNo.substring(2, 5)); |
| | | } |
| | | throw new RuntimeException("库位解析异常"); |
| | | } |
| | | |
| | | /** |
| | | * 通过库位号获取 层 |
| | | */ |
| | | public static int getLev(String locNo) { |
| | | if (!Cools.isEmpty(locNo)) { |
| | | return Integer.parseInt(locNo.substring(5, 7)); |
| | | } |
| | | throw new RuntimeException("库位解析异常"); |
| | | } |
| | | |
| | | /** |
| | | * 类型检测 |
| | | * 完全检测 |
| | | **/ |
| | | public static boolean locMoveCheckLocTypeComplete(Loc loc, LocTypeDto dto) { |
| | | // 如果源库位是高库位,目标库位是低库位 |
| | | return dto.getLocType1().equals(Integer.parseInt(loc.getType())); |
| | | } |
| | | public static String zerofill(String msg, Integer count) { |
| | | if (msg.length() == count) { |
| | | return msg; |
| | | } else if (msg.length() > count) { |
| | | return msg.substring(0, 16); |
| | | } else { |
| | | StringBuilder msgBuilder = new StringBuilder(msg); |
| | | for (int i = 0; i < count - msg.length(); i++) { |
| | | msgBuilder.insert(0, "0"); |
| | | } |
| | | return msgBuilder.toString(); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.api.utils; |
| | | |
| | | import lombok.Data; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Created by vincent on 2020/8/4 |
| | | */ |
| | | @Data |
| | | @Configuration |
| | | @ConfigurationProperties(prefix = "wcs-slave") |
| | | public class SlaveProperties { |
| | | |
| | | private boolean doubleDeep; |
| | | // 双深库位排号 |
| | | private List<Integer> doubleLocs = new ArrayList<>(); |
| | | // 左深库位排号 |
| | | private List<Integer> doubleLocsLeft = new ArrayList<>(); |
| | | // 右深库位排号 |
| | | private List<Integer> doubleLocsRight = new ArrayList<>(); |
| | | |
| | | private int groupCount; |
| | | |
| | | } |
| | |
| | | package com.vincent.rsf.server.common.config; |
| | | |
| | | import com.vincent.rsf.server.common.constant.Constants; |
| | | import com.vincent.rsf.server.common.interceptor.DynamicFieldsInterceptor; |
| | | import com.vincent.rsf.server.common.interceptor.severlet.DynamicFieldsInterceptor; |
| | | import com.vincent.rsf.server.common.utils.Http; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
New file |
| | |
| | | package com.vincent.rsf.server.common.interceptor.severlet; |
| | | |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.web.filter.OncePerRequestFilter; |
| | | import javax.servlet.FilterChain; |
| | | import javax.servlet.ServletException; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.IOException; |
| | | |
| | | @Component |
| | | public class CustomParameterFilter extends OncePerRequestFilter { |
| | | |
| | | @Override |
| | | protected void doFilterInternal(HttpServletRequest request, |
| | | HttpServletResponse response, |
| | | FilterChain filterChain) |
| | | throws ServletException, IOException { |
| | | |
| | | ParameterAddableRequest wrappedRequest = new ParameterAddableRequest(request); |
| | | // 添加系统级参数 |
| | | wrappedRequest.addParameter("systemVersion", "1.0.0"); |
| | | wrappedRequest.addParameter("environment", System.getProperty("spring.profiles.active", "default")); |
| | | // 添加请求追踪信息 |
| | | addTraceInfo(wrappedRequest); |
| | | |
| | | filterChain.doFilter(wrappedRequest, response); |
| | | } |
| | | |
| | | private void addTraceInfo(ParameterAddableRequest request) { |
| | | String traceId = request.getHeader("X-Trace-Id"); |
| | | // if (traceId == null || traceId.isEmpty()) { |
| | | // traceId = UUID.randomUUID().toString(); |
| | | // } |
| | | request.addParameter("traceId", "traceId"); |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.common.interceptor.severlet; |
| | | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.web.servlet.HandlerInterceptor; |
| | | import org.springframework.web.servlet.ModelAndView; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | |
| | | /** |
| | | * @author Ryan |
| | | * @version 1.0 |
| | | * @title DynamicFieldsInterceptor |
| | | * @description |
| | | * @create 2025/4/16 16:10 |
| | | */ |
| | | @Slf4j |
| | | public class DynamicFieldsInterceptor implements HandlerInterceptor { |
| | | |
| | | @Override |
| | | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
| | | |
| | | |
| | | // if (request instanceof ParameterAddableRequest) { |
| | | // ParameterAddableRequest wrappedRequest = (ParameterAddableRequest) request; |
| | | // |
| | | // // 添加业务参数 |
| | | // wrappedRequest.addParameter("processedBy", "CustomParameterInterceptor"); |
| | | // |
| | | // // 可以从Session或其他地方获取数据 |
| | | // Object userId = request.getSession().getAttribute("userId"); |
| | | // if (userId != null) { |
| | | // wrappedRequest.addParameter("currentUserId", userId.toString()); |
| | | // } |
| | | // } |
| | | // |
| | | // return true; |
| | | |
| | | |
| | | // List<Fields> fields = FieldsUtils.getFieldsSta(); |
| | | // List<String> fieldsKey = fields.stream().filter(item -> item.getFlagEnable() == 1).map(Fields::getFields).collect(Collectors.toList()); |
| | | // fieldsKey.forEach(key -> { |
| | | // if (request.getParameterMap().keySet().contains(key)) { |
| | | // request.setAttribute(key, request.getParameterMap().get(key)); |
| | | // } |
| | | // }) |
| | | // if (request instanceof ParameterAddableRequest) { |
| | | // ParameterAddableRequest wrappedRequest = (ParameterAddableRequest) request; |
| | | // wrappedRequest.addParameter("DynamicFields", "springValue"); |
| | | // } |
| | | |
| | | // return true; |
| | | return HandlerInterceptor.super.preHandle(request, response, handler); |
| | | } |
| | | |
| | | @Override |
| | | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { |
| | | HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); |
| | | } |
| | | |
| | | @Override |
| | | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { |
| | | System.out.println(response.getOutputStream().toString()); |
| | | HandlerInterceptor.super.afterCompletion(request, response, handler, ex); |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.common.interceptor.severlet; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletRequestWrapper; |
| | | import java.util.*; |
| | | |
| | | public class ParameterAddableRequest extends HttpServletRequestWrapper { |
| | | private final Map<String, String[]> additionalParams = new HashMap<>(); |
| | | |
| | | public ParameterAddableRequest(HttpServletRequest request) { |
| | | super(request); |
| | | } |
| | | |
| | | /** |
| | | * 添加新参数 |
| | | * @param name 参数名 |
| | | * @param value 参数值 |
| | | */ |
| | | public void addParameter(String name, String value) { |
| | | additionalParams.put(name, new String[]{value}); |
| | | } |
| | | |
| | | /** |
| | | * 添加新参数(多值) |
| | | * @param name 参数名 |
| | | * @param values 参数值数组 |
| | | */ |
| | | public void addParameter(String name, String[] values) { |
| | | additionalParams.put(name, values); |
| | | } |
| | | |
| | | @Override |
| | | public String getParameter(String name) { |
| | | if (additionalParams.containsKey(name)) { |
| | | return additionalParams.get(name)[0]; |
| | | } |
| | | return super.getParameter(name); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, String[]> getParameterMap() { |
| | | Map<String, String[]> combinedMap = new HashMap<>(super.getParameterMap()); |
| | | combinedMap.putAll(additionalParams); |
| | | return Collections.unmodifiableMap(combinedMap); |
| | | } |
| | | |
| | | @Override |
| | | public Enumeration<String> getParameterNames() { |
| | | Set<String> names = new HashSet<>(Collections.list(super.getParameterNames())); |
| | | names.addAll(additionalParams.keySet()); |
| | | return Collections.enumeration(names); |
| | | } |
| | | |
| | | @Override |
| | | public String[] getParameterValues(String name) { |
| | | if (additionalParams.containsKey(name)) { |
| | | return additionalParams.get(name); |
| | | } |
| | | return super.getParameterValues(name); |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.common.interceptor.severlet; |
| | | |
| | | import javax.servlet.*; |
| | | import javax.servlet.annotation.WebFilter; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.io.IOException; |
| | | |
| | | @WebFilter("/*") // 拦截所有请求 |
| | | public class ParameterAddingFilter implements Filter { |
| | | |
| | | @Override |
| | | public void init(FilterConfig filterConfig) throws ServletException { |
| | | // 初始化逻辑 |
| | | } |
| | | |
| | | @Override |
| | | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
| | | throws IOException, ServletException { |
| | | |
| | | if (request instanceof HttpServletRequest) { |
| | | // 创建可修改参数的请求包装器 |
| | | ParameterAddableRequest wrappedRequest = |
| | | new ParameterAddableRequest((HttpServletRequest) request); |
| | | |
| | | // 添加自定义参数 |
| | | addCustomParameters(wrappedRequest); |
| | | |
| | | // 继续过滤器链 |
| | | chain.doFilter(wrappedRequest, response); |
| | | } else { |
| | | chain.doFilter(request, response); |
| | | } |
| | | } |
| | | |
| | | private void addCustomParameters(ParameterAddableRequest request) { |
| | | // 添加请求ID和时间戳 |
| | | // request.addParameter("requestId", UUID.randomUUID().toString()); |
| | | request.addParameter("timestamp", String.valueOf(System.currentTimeMillis())); |
| | | // 根据业务需求添加其他参数 |
| | | String userAgent = request.getHeader("User-Agent"); |
| | | if (userAgent != null && userAgent.contains("Mobile")) { |
| | | request.addParameter("deviceType", "mobile"); |
| | | } else { |
| | | request.addParameter("deviceType", "desktop"); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void destroy() { |
| | | // 清理逻辑 |
| | | } |
| | | } |
| | |
| | | "/v2/api-docs/**", |
| | | "/v3/api-docs/**", |
| | | "/swagger-ui/**", |
| | | "/ws/**" |
| | | "/ws/**", |
| | | "/wcs/**" |
| | | }; |
| | | |
| | | @Resource |
New file |
| | |
| | | package com.vincent.rsf.server.manager.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.framework.common.R; |
| | | 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.BasDevice; |
| | | import com.vincent.rsf.server.manager.service.BasDeviceService; |
| | | 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.web.bind.annotation.*; |
| | | |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.util.*; |
| | | |
| | | @RestController |
| | | public class BasDeviceController extends BaseController { |
| | | |
| | | @Autowired |
| | | private BasDeviceService basDeviceService; |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:list')") |
| | | @PostMapping("/basDevice/page") |
| | | public R page(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | PageParam<BasDevice, BaseParam> pageParam = new PageParam<>(baseParam, BasDevice.class); |
| | | return R.ok().add(basDeviceService.page(pageParam, pageParam.buildWrapper(true))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:list')") |
| | | @PostMapping("/basDevice/list") |
| | | public R list(@RequestBody Map<String, Object> map) { |
| | | return R.ok().add(basDeviceService.list()); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:list')") |
| | | @PostMapping({"/basDevice/many/{ids}", "/basDevices/many/{ids}"}) |
| | | public R many(@PathVariable Long[] ids) { |
| | | return R.ok().add(basDeviceService.listByIds(Arrays.asList(ids))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:list')") |
| | | @GetMapping("/basDevice/{id}") |
| | | public R get(@PathVariable("id") Long id) { |
| | | return R.ok().add(basDeviceService.getById(id)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:save')") |
| | | @OperationLog("Create 基础设备表") |
| | | @PostMapping("/basDevice/save") |
| | | public R save(@RequestBody BasDevice basDevice) { |
| | | basDevice.setCreateBy(getLoginUserId()); |
| | | basDevice.setCreateTime(new Date()); |
| | | basDevice.setUpdateBy(getLoginUserId()); |
| | | basDevice.setUpdateTime(new Date()); |
| | | if (!basDeviceService.save(basDevice)) { |
| | | return R.error("Save Fail"); |
| | | } |
| | | return R.ok("Save Success").add(basDevice); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:update')") |
| | | @OperationLog("Update 基础设备表") |
| | | @PostMapping("/basDevice/update") |
| | | public R update(@RequestBody BasDevice basDevice) { |
| | | basDevice.setUpdateBy(getLoginUserId()); |
| | | basDevice.setUpdateTime(new Date()); |
| | | if (!basDeviceService.updateById(basDevice)) { |
| | | return R.error("Update Fail"); |
| | | } |
| | | return R.ok("Update Success").add(basDevice); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:remove')") |
| | | @OperationLog("Delete 基础设备表") |
| | | @PostMapping("/basDevice/remove/{ids}") |
| | | public R remove(@PathVariable Long[] ids) { |
| | | if (!basDeviceService.removeByIds(Arrays.asList(ids))) { |
| | | return R.error("Delete Fail"); |
| | | } |
| | | return R.ok("Delete Success").add(ids); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:list')") |
| | | @PostMapping("/basDevice/query") |
| | | public R query(@RequestParam(required = false) String condition) { |
| | | List<KeyValVo> vos = new ArrayList<>(); |
| | | LambdaQueryWrapper<BasDevice> wrapper = new LambdaQueryWrapper<>(); |
| | | if (!Cools.isEmpty(condition)) { |
| | | wrapper.like(BasDevice::getId, condition); |
| | | } |
| | | basDeviceService.page(new Page<>(1, 30), wrapper).getRecords().forEach( |
| | | item -> vos.add(new KeyValVo(item.getId(), item.getId())) |
| | | ); |
| | | return R.ok().add(vos); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basDevice:list')") |
| | | @PostMapping("/basDevice/export") |
| | | public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | ExcelUtil.build(ExcelUtil.create(basDeviceService.list(), BasDevice.class), response); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.vincent.rsf.server.manager.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | 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.domain.PageResult; |
| | | 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.Wave; |
| | | import com.vincent.rsf.server.manager.entity.WaveItem; |
| | | import com.vincent.rsf.server.manager.service.WaveService; |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | @PreAuthorize("hasAuthority('manager:wave:update')") |
| | | @ApiOperation("波次下发任务") |
| | | @PostMapping("/wave/public/task") |
| | |
| | | return waveService.publicTask(map); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:wave:list')") |
| | | @ApiOperation("波次出库任务预览") |
| | | @PostMapping("/wave/locs/preview/page") |
| | | public R mergeWavePreview(@RequestBody Map<String, Object> map) { |
| | | if (Cools.isEmpty(map.get("waveId")) || StringUtils.isBlank(map.get("waveId").toString())) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | Long waveId = Long.parseLong(map.get("waveId").toString()); |
| | | List<WaveItem> waveItems = waveService.mergeWavePreview(waveId); |
| | | PageResult<WaveItem> pageResult = new PageResult<>(); |
| | | pageResult.setRecords(waveItems).setTotal(Long.parseLong(waveItems.size() + "")); |
| | | return R.ok().add(pageResult); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.manager.entity; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | |
| | | 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("man_bas_device") |
| | | public class BasDevice implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** |
| | | * id |
| | | */ |
| | | @ApiModelProperty(value= "id") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | /** |
| | | * 设备号 |
| | | */ |
| | | @ApiModelProperty(value= "设备号") |
| | | private Long deviceNo; |
| | | |
| | | /** |
| | | * 可入 |
| | | */ |
| | | @ApiModelProperty(value= "可入") |
| | | private String inEnable; |
| | | |
| | | /** |
| | | * 可出 |
| | | */ |
| | | @ApiModelProperty(value= "可出") |
| | | private String outEnable; |
| | | |
| | | /** |
| | | * 状态 |
| | | */ |
| | | @ApiModelProperty(value= "状态") |
| | | private String status; |
| | | |
| | | /** |
| | | * 源数据 |
| | | */ |
| | | @ApiModelProperty(value= "源数据") |
| | | private String origin; |
| | | |
| | | /** |
| | | * 创建人 |
| | | */ |
| | | @ApiModelProperty(value= "创建人") |
| | | private Long createBy; |
| | | |
| | | /** |
| | | * 创建时间 |
| | | */ |
| | | @ApiModelProperty(value= "创建时间") |
| | | @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") |
| | | private Date createTime; |
| | | |
| | | /** |
| | | * 更新人 |
| | | */ |
| | | @ApiModelProperty(value= "更新人") |
| | | private Long updateBy; |
| | | |
| | | /** |
| | | * 更新时间 |
| | | */ |
| | | @ApiModelProperty(value= "更新时间") |
| | | @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") |
| | | private Date updateTime; |
| | | |
| | | /** |
| | | * 备注 |
| | | */ |
| | | @ApiModelProperty(value= "备注") |
| | | private String memo; |
| | | |
| | | public BasDevice() {} |
| | | |
| | | public BasDevice(Long deviceNo,String inEnable,String outEnable,String status,String origin,Long createBy,Date createTime,Long updateBy,Date updateTime,String memo) { |
| | | this.deviceNo = deviceNo; |
| | | this.inEnable = inEnable; |
| | | this.outEnable = outEnable; |
| | | this.status = status; |
| | | this.origin = origin; |
| | | this.createBy = createBy; |
| | | this.createTime = createTime; |
| | | this.updateBy = updateBy; |
| | | this.updateTime = updateTime; |
| | | this.memo = memo; |
| | | } |
| | | |
| | | // BasDevice basDevice = new BasDevice( |
| | | // null, // 设备号 |
| | | // null, // 可入 |
| | | // null, // 可出 |
| | | // null, // 状态 |
| | | // null, // 源数据 |
| | | // null, // 创建人 |
| | | // null, // 创建时间 |
| | | // null, // 更新人 |
| | | // null, // 更新时间 |
| | | // null // 备注 |
| | | // ); |
| | | |
| | | public String getCreateTime$(){ |
| | | if (Cools.isEmpty(this.createTime)){ |
| | | return ""; |
| | | } |
| | | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime); |
| | | } |
| | | |
| | | public String getUpdateTime$(){ |
| | | if (Cools.isEmpty(this.updateTime)){ |
| | | return ""; |
| | | } |
| | | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime); |
| | | } |
| | | |
| | | |
| | | |
| | | // public Boolean getStatusBool(){ |
| | | // if (null == this.status){ return null; } |
| | | // switch (this.status){ |
| | | // case "1": |
| | | // return true; |
| | | // case "0": |
| | | // return false; |
| | | // default: |
| | | // return null; |
| | | // } |
| | | // } |
| | | |
| | | } |
| | |
| | | */ |
| | | @ApiModelProperty(value= "宽") |
| | | private Double width; |
| | | |
| | | /** |
| | | * 排 |
| | | */ |
| | |
| | | private Integer row; |
| | | |
| | | /** |
| | | * 设备号 |
| | | */ |
| | | @ApiModelProperty(value= "设备号") |
| | | private Integer deviceNo; |
| | | |
| | | /** |
| | | * 列 |
| | | */ |
| | | @ApiModelProperty(value= "列") |
| | |
| | | @ApiModelProperty(value= "主单ID") |
| | | private Long locId; |
| | | |
| | | @ApiModelProperty("库位编码") |
| | | private String locCode; |
| | | |
| | | /** |
| | | * 单据ID |
| | | */ |
| | |
| | | @ApiModelProperty("执行数量") |
| | | private Double workQty; |
| | | |
| | | @ApiModelProperty("完成数量") |
| | | private Double qty; |
| | | |
| | | /** |
| | | * 库存批次 |
| | | */ |
| | |
| | | @ApiModelProperty(value= "波次号") |
| | | private String code; |
| | | |
| | | @ApiModelProperty("执行数量") |
| | | private Double workQty; |
| | | |
| | | /** |
| | | * 波次类型 0: 手动 1: 自动 |
| | | */ |
| | |
| | | package com.vincent.rsf.server.manager.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableLogic; |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | |
| | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | |
| | | 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; |
| | |
| | | @ApiModelProperty(value= "修改人员") |
| | | private Long updateBy; |
| | | |
| | | @ApiModelProperty("目标库位") |
| | | @TableField(exist = false) |
| | | private String stockLocs; |
| | | |
| | | /** |
| | | * 备注 |
| | | */ |
| | |
| | | ASN_EXCE_STATUS_TASK_CLOSE("4", "已关闭"), |
| | | OUT_STOCK_STATUS_TASK_INIT("5", "初始化"), |
| | | OUT_STOCK_STATUS_TASK_EXCE("6", "待处理"), |
| | | |
| | | OUT_STOCK_STATUS_TASK_WAVE("7", "生成波次"), |
| | | |
| | | OUT_STOCK_STATUS_TASK_WORKING("8", "作业中") |
| | | ; |
| | | AsnExceStatus(String val, String desc) { |
New file |
| | |
| | | package com.vincent.rsf.server.manager.mapper; |
| | | |
| | | import com.vincent.rsf.server.manager.entity.BasDevice; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | @Mapper |
| | | @Repository |
| | | public interface BasDeviceMapper extends BaseMapper<BasDevice> { |
| | | |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.manager.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.server.manager.entity.BasDevice; |
| | | |
| | | public interface BasDeviceService extends IService<BasDevice> { |
| | | |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.manager.entity.Wave; |
| | | import com.vincent.rsf.server.manager.entity.WaveItem; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public interface WaveService extends IService<Wave> { |
| | |
| | | * @time 2025/4/25 16:24 |
| | | */ |
| | | R publicTask(Map<String, Object> map); |
| | | |
| | | /** |
| | | * @author Ryan |
| | | * @description 预览波次下发任务 |
| | | * @param |
| | | * @return |
| | | * @time 2025/4/27 11:08 |
| | | */ |
| | | List<WaveItem> mergeWavePreview(Long waveId); |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.manager.service.impl; |
| | | |
| | | import com.vincent.rsf.server.manager.mapper.BasDeviceMapper; |
| | | import com.vincent.rsf.server.manager.entity.BasDevice; |
| | | import com.vincent.rsf.server.manager.service.BasDeviceService; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | @Service("basDeviceService") |
| | | public class BasDeviceServiceImpl extends ServiceImpl<BasDeviceMapper, BasDevice> implements BasDeviceService { |
| | | |
| | | } |
| | |
| | | import com.vincent.rsf.server.manager.enums.WaveExceStatus; |
| | | import com.vincent.rsf.server.manager.mapper.AsnOrderMapper; |
| | | import com.vincent.rsf.server.manager.service.*; |
| | | import com.vincent.rsf.server.manager.utils.GroupMergeUtil; |
| | | import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil; |
| | | import com.vincent.rsf.server.system.constant.SerialRuleCode; |
| | | import com.vincent.rsf.server.system.utils.SerialRuleUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | |
| | | .setWaveCode(wave.getCode()); |
| | | items.add(item); |
| | | }); |
| | | List<WaveItem> waveItems = GroupMergeUtil.groupAndMerge(items, |
| | | List<WaveItem> waveItems = OptimalAlgorithmUtil.groupAndMerge(items, |
| | | (p1, p2) -> new WaveItem( |
| | | p1.getWaveId(), |
| | | p1.getWaveCode(), |
| | |
| | | Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, task.getTargLoc()), false); |
| | | LocItem item = new LocItem(); |
| | | BeanUtils.copyProperties(taskItem, item); |
| | | item.setLocId(loc.getId()).setType(taskItem.getOrderType()); |
| | | item.setLocCode(loc.getCode()).setId(null).setLocId(loc.getId()).setType(taskItem.getOrderType()); |
| | | locItems.add(item); |
| | | }); |
| | | if (!locItemService.saveBatch(locItems)) { |
| | |
| | | package com.vincent.rsf.server.manager.service.impl; |
| | | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | 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.manager.entity.AsnOrder; |
| | | import com.vincent.rsf.server.manager.entity.AsnOrderItem; |
| | | import com.vincent.rsf.server.manager.entity.LocItem; |
| | | import com.vincent.rsf.server.manager.entity.WaveItem; |
| | | import com.vincent.rsf.server.manager.enums.AsnExceStatus; |
| | | import com.vincent.rsf.server.manager.enums.WaveExceStatus; |
| | | import com.vincent.rsf.server.manager.mapper.WaveMapper; |
| | | import com.vincent.rsf.server.manager.entity.Wave; |
| | | import com.vincent.rsf.server.manager.service.AsnOrderItemService; |
| | | import com.vincent.rsf.server.manager.service.AsnOrderService; |
| | | import com.vincent.rsf.server.manager.service.WaveService; |
| | | import com.vincent.rsf.server.manager.service.*; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Service("waveService") |
| | | public class WaveServiceImpl extends ServiceImpl<WaveMapper, Wave> implements WaveService { |
| | | |
| | | @Autowired |
| | | private AsnOrderItemService asnOrderItemService; |
| | | |
| | | @Autowired |
| | | private AsnOrderService asnOrderService; |
| | | @Autowired |
| | | private WaveItemService waveItemService; |
| | | @Autowired |
| | | private TaskService taskService; |
| | | @Autowired |
| | | private TaskItemService taskItemService; |
| | | @Autowired |
| | | private LocItemService locItemService; |
| | | |
| | | /** |
| | | * @author Ryan |
| | | * @description 波次任务下发 |
| | | * @param |
| | | * @return |
| | | * @author Ryan |
| | | * @description 波次任务下发 |
| | | * @time 2025/4/25 16:24 |
| | | */ |
| | | @Override |
| | |
| | | if (Objects.isNull(waves) || waves.isEmpty()) { |
| | | throw new CoolException("波次数据不存在!!"); |
| | | } |
| | | List<Long> list = waves.stream().map(Wave::getId).collect(Collectors.toList()); |
| | | List<WaveItem> waveItems = waveItemService.list(new LambdaQueryWrapper<WaveItem>().in(WaveItem::getWaveId, list)); |
| | | if (waveItems.isEmpty()) { |
| | | throw new CoolException("波次明细不存在!!"); |
| | | } |
| | | List<Long> orderIds = waveItems.stream().map(WaveItem::getOrderId).collect(Collectors.toList()); |
| | | |
| | | List<AsnOrder> orders = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().eq(AsnOrder::getId, ids)); |
| | | |
| | | |
| | | asnOrderItemService.list(new LambdaQueryWrapper<AsnOrderItem>().eq(AsnOrderItem::getAsnId, ids)); |
| | | |
| | | if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).in(Wave::getId,ids))) { |
| | | /**查询每条明细匹配的库位*/ |
| | | try { |
| | | List<WaveItem> items = getLocs(waveItems); |
| | | } catch (Exception e) { |
| | | throw new CoolException("库位获取失败!!!"); |
| | | } |
| | | //TODO 1. 根据波次明细生成出库任务 |
| | | // 2. 根据物料SKU寻找符合物料库位 {1. 根据物料编码,批次,动态字段 查询符合的库位,再根据库位中物料的数量选择最适合的库位 2. 判断当前订单是全拖出库还是拣料入库} |
| | | // 3. 修改主单、波次执行数量 |
| | | // 4. 判断全仓出库或拣料出库 |
| | | List<AsnOrder> orders = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().in(AsnOrder::getId, orderIds)); |
| | | /**修改原出库单状态*/ |
| | | if (!asnOrderService.update(new LambdaQueryWrapper<AsnOrder>() |
| | | .eq(AsnOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val) |
| | | .in(AsnOrder::getId, orders))) { |
| | | throw new CoolException("出库单据状态修改失败!!"); |
| | | } |
| | | if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).in(Wave::getId, ids))) { |
| | | throw new CoolException("波次状态修改失败!!"); |
| | | } |
| | | |
| | | return R.ok(); |
| | | } |
| | | |
| | | /** |
| | | * @param |
| | | * @return |
| | | * @author Ryan |
| | | * @description 预览波次下发任务 |
| | | * @time 2025/4/27 11:09 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public List<WaveItem> mergeWavePreview(Long waveId) { |
| | | Wave wave = this.getById(waveId); |
| | | if (Objects.isNull(wave)) { |
| | | throw new CoolException("波次不能存在!!"); |
| | | } |
| | | List<WaveItem> waveItems = waveItemService.list(new LambdaQueryWrapper<WaveItem>().eq(WaveItem::getWaveId, waveId)); |
| | | if (waveItems.isEmpty()) { |
| | | throw new CoolException("波次明细不存在!!"); |
| | | } |
| | | List<WaveItem> itemPreview = null; |
| | | try { |
| | | itemPreview = getLocs(waveItems); |
| | | } catch (Exception e) { |
| | | throw new CoolException("库位获取失败!!!!"); |
| | | } |
| | | return itemPreview; |
| | | } |
| | | |
| | | /** |
| | | * @param |
| | | * @param waveItems |
| | | * @return |
| | | * @author Ryan |
| | | * @description 根据物料编码,批次,动态字段 查询符合的库位,再根据库位中物料的数量选择最适合的库位 |
| | | * @time 2025/4/27 09:26 |
| | | */ |
| | | private synchronized List<WaveItem> getLocs(List<WaveItem> waveItems) throws Exception { |
| | | //TODO 根据物料编码,批次,动态字段 查询符合的库位,再根据库位中物料的数量选择最适合的库位 |
| | | waveItems.forEach(waveItem -> { |
| | | List<LocItem> locItems = locItemService.list(new QueryWrapper<LocItem>() |
| | | .select("id", "loc_id", "loc_code", "order_id", "SUM(anfme) anfme", "SUM(work_qty) work_qty", "splr_batch", "fields_index", "matnr_code") |
| | | .lambda() |
| | | .eq(LocItem::getMatnrCode, waveItem.getMatnrCode()) |
| | | .eq(LocItem::getSplrBatch, waveItem.getSplrBatch()) |
| | | .eq(StringUtils.isNotBlank(waveItem.getFieldsIndex()), LocItem::getFieldsIndex, waveItem.getFieldsIndex()) |
| | | .groupBy(LocItem::getMatnrCode, LocItem::getSplrBatch, LocItem::getFieldsIndex, LocItem::getId)); |
| | | |
| | | Double[] doubles = locItems.stream().map(LocItem::getAnfme).toArray(Double[]::new); |
| | | List<Double> result = OptimalAlgorithmUtil.findBestCombination(doubles, waveItem.getAnfme()); |
| | | |
| | | |
| | | String locs = JSONArray.toJSONString(new ArrayList<>()); |
| | | |
| | | if (!locItems.isEmpty()) { |
| | | List<String> codes = locItems.stream().map(LocItem::getLocCode).collect(Collectors.toList()); |
| | | locs = JSONArray.toJSONString(codes); |
| | | } |
| | | waveItem.setStockLocs(locs); |
| | | }); |
| | | return waveItems; |
| | | } |
| | | } |
New file |
| | |
| | | package com.vincent.rsf.server.manager.utils; |
| | | |
| | | import java.util.*; |
| | | import java.util.function.BinaryOperator; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * @author Ryan |
| | | * @description 根据对象多个属性合并处理 |
| | | * @param |
| | | * @return |
| | | * @time 2025/4/25 10:55 |
| | | */ |
| | | public class OptimalAlgorithmUtil { |
| | | |
| | | private static List<Double> bestSolution; |
| | | private static double target; |
| | | private static double minDifference; |
| | | private static final double EPSILON = 1e-10; |
| | | |
| | | /** |
| | | * 根据多个属性分组合并对象列表 |
| | | * |
| | | * @param list 待分组的对象列表 |
| | | * @param mergers 合并函数,定义如何合并同一组中的对象 |
| | | * @param keyGetters 分组属性的getter方法数组 |
| | | * @param <T> 对象类型 |
| | | * @param <K> 分组键类型 |
| | | * @return 分组合并后的对象列表 |
| | | */ |
| | | @SafeVarargs |
| | | public static <T, K> List<T> groupAndMerge( |
| | | List<T> list, |
| | | BinaryOperator<T> mergers, |
| | | Function<T, K>... keyGetters) { |
| | | |
| | | return new ArrayList<>(list.stream() |
| | | .collect(Collectors.toMap( |
| | | item -> new GroupingKeys<>(Arrays.stream(keyGetters) |
| | | .map(getter -> getter.apply(item)) |
| | | .toArray()), |
| | | Function.identity(), |
| | | mergers)) |
| | | .values()); |
| | | } |
| | | |
| | | /** |
| | | * @author Ryan |
| | | * @description 用于存储多个分组键的内部类 |
| | | * @param |
| | | * @return |
| | | * @time 2025/4/25 10:56 |
| | | */ |
| | | private static class GroupingKeys<K> { |
| | | private final Object[] keys; |
| | | private final int hashCode; |
| | | |
| | | public GroupingKeys(Object[] keys) { |
| | | this.keys = keys; |
| | | this.hashCode = Arrays.deepHashCode(keys); |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object o) { |
| | | if (this == o) { |
| | | return true; |
| | | } |
| | | if (o == null || getClass() != o.getClass()) { |
| | | return false; |
| | | } |
| | | GroupingKeys<?> that = (GroupingKeys<?>) o; |
| | | return Arrays.deepEquals(keys, that.keys); |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode() { |
| | | return hashCode; |
| | | } |
| | | } |
| | | |
| | | |
| | | public static List<Double> findBestCombination(Double[] candidates, double targetSum) { |
| | | bestSolution = null; |
| | | target = targetSum; |
| | | minDifference = Double.MAX_VALUE; |
| | | Arrays.sort(candidates); |
| | | backtrack(candidates, 0, new ArrayList<>(), 0.0); |
| | | |
| | | // 如果没有精确解,返回最接近的近似解 |
| | | return bestSolution != null ? bestSolution : new ArrayList<>(); |
| | | } |
| | | |
| | | private static void backtrack(Double[] candidates, int start, |
| | | List<Double> current, double currentSum) { |
| | | // 计算当前和与目标的差值 |
| | | double difference = Math.abs(currentSum - target); |
| | | |
| | | // 如果找到更优解(差值更小或差值相同但组合更短) |
| | | if (difference < minDifference - EPSILON || |
| | | (Math.abs(difference - minDifference) < EPSILON && |
| | | (bestSolution == null || current.size() < bestSolution.size()))) { |
| | | minDifference = difference; |
| | | bestSolution = new ArrayList<>(current); |
| | | } |
| | | |
| | | // 如果已经找到精确解,不需要继续搜索更长的组合 |
| | | if (minDifference < EPSILON) { |
| | | return; |
| | | } |
| | | |
| | | // 遍历候选数字 |
| | | for (int i = start; i < candidates.length; i++) { |
| | | double num = candidates[i]; |
| | | |
| | | // 剪枝:如果当前和已经远大于目标值,跳过 |
| | | if (currentSum + num > target + minDifference + EPSILON) { |
| | | continue; |
| | | } |
| | | |
| | | // 跳过重复数字 |
| | | if (i > start && Math.abs(candidates[i] - candidates[i - 1]) < EPSILON) { |
| | | continue; |
| | | } |
| | | |
| | | current.add(num); |
| | | backtrack(candidates, i + 1, current, currentSum + num); |
| | | current.remove(current.size() - 1); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | logging:
|
| | | file:
|
| | | path: logs/@pom.artifactId@
|
| | |
|
| | | # 下位机配置
|
| | | wcs-slave:
|
| | | # 双深
|
| | | doubleDeep: true
|
| | | # 双深库位排号
|
| | | doubleLocs: 1,4,5,8
|
| | | # 一个堆垛机负责的货架排数
|
| | | groupCount: 4
|
| | | # 左深库位排号
|
| | | doubleLocsLeft: 1,5,9,13
|
| | | # 右深库位排号
|
| | | doubleLocsRight: 4,8,12,16
|
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.manager.mapper.BasDeviceMapper"> |
| | | |
| | | </mapper> |