1个文件已添加
1个文件已删除
24个文件已修改
1020 ■■■■ 已修改文件
rsf-admin/src/i18n/en.js 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/zh.js 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/deviceBind/DeviceBindCreate.jsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/deviceBind/DeviceBindEdit.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/waitPakin/SelectSiteModel.jsx 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/Test/CombinationFinder.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/Test/MinimalCombinationSum.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/params/TaskInParam.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/TaskStsType.java 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/TaskType.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/AsnOrderController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/TaskController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/AsnOrder.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/TaskItem.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java 182 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/OptimalAlgorithmUtil.java 174 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/en.js
@@ -70,6 +70,7 @@
            loadMore: 'Load More Data',
            complete: 'Complete',
            deprecate: 'Deprecate',
            stockError: 'Empty',
            inputPlaceholder: 'Use commas to separate',
            resend: 'RESEND',
            selected: 'selected',
@@ -746,7 +747,9 @@
                anfme: "anfme",
                workQty: "workQty",
                qty: "Qty",
                stockLocs: "Stock Locs"
                stockLocs: "Stock Locs",
                stockQty: "Stock Qty",
            },
            task: {
                taskCode: "TaskCode",
rsf-admin/src/i18n/zh.js
@@ -71,6 +71,7 @@
            loadMore: '加载更多',
            complete: '完成',
            deprecate: '废弃',
            stockError: '没有库存',
            resend: '重发',
            selected: '项选中',
            batch: '批量编辑'
@@ -792,7 +793,8 @@
                anfme: "数量",
                workQty: "执行数",
                qty: "完成数量",
                stockLocs: "库存位置"
                stockLocs: "库存位置",
                stockQty: "库存数量",
            },
            task: {
                taskCode: "任务号",
rsf-admin/src/page/deviceBind/DeviceBindCreate.jsx
@@ -128,11 +128,10 @@
                                        parse={v => v}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.deviceBind.typeId"
                                        source="typeId"
                                    />
                                <Grid item xs={6} display="flex" gap={1}>
                                    <ReferenceInput source="typeId" label="table.field.deviceBind.typeId" reference="warehouseAreas" filter={{}}>
                                        <AutocompleteInput optionValue="id" optionText="name" label="table.field.deviceBind.typeId" />
                                    </ReferenceInput>
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <TextInput
rsf-admin/src/page/deviceBind/DeviceBindEdit.jsx
@@ -108,10 +108,9 @@
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.deviceBind.typeId"
                                source="typeId"
                            />
                            <ReferenceInput source="typeId" label="table.field.deviceBind.typeId" reference="warehouseAreas" filter={{}}>
                                <AutocompleteInput optionValue="id" optionText="name" label="table.field.deviceBind.typeId" />
                            </ReferenceInput>
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx
@@ -37,11 +37,10 @@
    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 { Box, Typography, Card, Stack, DialogContent, DialogTitle, DialogActions, Dialog, Chip, ListItem, Paper } from '@mui/material';
import { styled } from '@mui/material/styles';
import DialogCloseButton from "../../components/DialogCloseButton";
import request from '@/utils/request';
@@ -131,7 +130,6 @@
                            <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" />
@@ -143,22 +141,16 @@
                            <NumberField source="anfme" label="table.field.waveItem.anfme" />
                            <NumberField source="workQty" label="table.field.waveItem.workQty" />
                            <NumberField source="qty" label="table.field.waveItem.qty" />
                            <NumberField source="stockQty" label="table.field.waveItem.stockQty" />
                            <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} /> */}
                                <TagsField />
                            </WrapperField>
                        </StyledDatagrid>
                    </ListContextProvider>
                </DialogContent>
                <DialogActions>
                    <Toolbar sx={{ width: '100%', justifyContent: 'end' }} >
                        <GenerateTaskButton record={[record?.id]} />
                        <GenerateTaskButton record={[record?.id]} dataSource={data} />
                    </Toolbar>
                </DialogActions>
            </Dialog>
@@ -168,12 +160,12 @@
export default ItemToTaskModal;
const GenerateTaskButton = (record) => {
const GenerateTaskButton = (record, dataSource) => {
    const refresh = useRefresh();
    const notify = useNotify();
    const redirect = useRedirect();
    const generateTask = async () => {
        const res = await request.post(`/wave/public/task`, { ids: record?.record });
        const res = await request.post(`/wave/public/task`, { wave: record, waveItem: dataSource });
        if (res?.data?.code === 200) {
            notify(res.data.msg);
            redirect("/task")
@@ -183,4 +175,32 @@
        refresh();
    }
    return (<Button variant="contained" label={"ra.action.save"} onClick={generateTask}></Button>)
}
const TagsField = () => {
    const record = useRecordContext();
    const translate = useTranslate();
    const locs = JSON.parse(record.stockLocs);
    if (locs == undefined || locs.length < 1) {
        return (
            <>
                <ListItem>
                    <Chip color="error" label={translate("common.action.stockError")} variant="outlined" />
                </ListItem>
            </>
        )
    } else {
        return (
            <>
                {locs.map((data) => {
                    return (
                        <ListItem key={data?.id}>
                            <Chip label={data?.locCode} />
                        </ListItem>
                    )
                })}
            </>
        )
    }
}
rsf-admin/src/page/waitPakin/SelectSiteModel.jsx
@@ -19,6 +19,27 @@
    },
}));
const SelectSiteButton = (props) => {
    const { source, setOpen, refresh, notify } = props;
    const record = useRecordContext();
    const handleClick = async () => {
        setOpen(false);
        const id = record.id;
        const res = await request.post(`/waitPakin/merge`, {waitPakins: source, siteId: id});
        if (res?.data?.code === 200) {
            refresh();
            notify(res.data.msg);
        } else {
            notify(res.data.msg);
        }
    };
    return (
        <Button label="toolbar.selectSite" onClick={handleClick} />
    );
};
const SelectSiteModel = (props) => {
    const { open, setOpen, source } = props;
    const translate = useTranslate();
@@ -47,6 +68,7 @@
                <DialogContent>
                    <List
                        resource='deviceSite'
                        filter={{type : 1}}
                        sx={{
                            flexGrow: 1,
                            transition: (theme) =>
@@ -68,17 +90,7 @@
                        <StyledDatagrid
                            preferenceKey='deviceSite'
                            bulkActionButtons={false}
                            rowClick='toggleSelection'
                            onToggleItem={async (id)=>{
                                setOpen(false);
                                const res = await request.post(`/waitPakin/merge`, {waitPakins: source, siteId: id});
                                if (res?.data?.code === 200) {
                                    refresh();
                                    notify(res.data.msg);
                                } else {
                                    notify(res.data.msg);
                                }
                            }}
                            rowClick={false}
                            omit={['id','name', 'createTime','label', 'createBy', 'memo', 'updateBy$', 'createBy$', 'createTime', 'updateTime']}
                        >
                            <NumberField source="id" />
@@ -97,9 +109,7 @@
                            <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">
                                <Button label="toolbar.selectSite" onClick={(event)=>{
                                    setOpen(false)
                                 }}/>
                            <SelectSiteButton source={source} setOpen={setOpen} refresh={refresh} notify={notify} />
                            </WrapperField>
                        </StyledDatagrid>
                    </List>
rsf-server/src/main/Test/CombinationFinder.java
New file
@@ -0,0 +1,123 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CombinationFinder {
    public List<Integer> findCombination(double[] nums, double target) {
        // 处理等值情况
        List<Integer> equal = findEqual(nums, target);
        if (equal != null && !equal.isEmpty()) {
            return equal;
        }
        // 处理大于的情况
        List<Integer> greater = findGreater(nums, target);
        if (greater != null && !greater.isEmpty()) {
            return greater;
        }
        // 处理小于的情况
        List<Integer> less = findLess(nums, target);
        return less != null ? less : new ArrayList<>(); // 确保不返回null
    }
    private List<Integer> findEqual(double[] nums, double target) {
        int n = nums.length;
        double[] dp = new double[(int) (target * 100 + 1)]; // 放大100倍处理精度问题
        Arrays.fill(dp, Double.MAX_VALUE);
        dp[0] = 0;
        int[] prev = new int[(int) (target * 100 + 1)];
        Arrays.fill(prev, -1);
        for (int j = 0; j < n; j++) {
            int num = (int) (nums[j] * 100); // 放大100倍处理精度问题
            for (int i = (int) (target * 100); i >= num; i--) {
                if (dp[i - num] != Double.MAX_VALUE && dp[i - num] + 1 < dp[i]) {
                    dp[i] = dp[i - num] + 1;
                    prev[i] = j;
                }
            }
        }
        if (dp[(int) (target * 100)] == Double.MAX_VALUE) {
            return null;
        }
        List<Integer> indices = new ArrayList<>();
        int current = (int) (target * 100);
        while (current > 0) {
            int j = prev[current];
            if (j == -1) return null;
            indices.add(j);
            current -= (int) (nums[j] * 100);
        }
        return indices;
    }
    private List<Integer> findGreater(double[] nums, double target) {
        List<double[]> sorted = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            sorted.add(new double[]{nums[i], i});
        }
        sorted.sort((a, b) -> Double.compare(b[0], a[0]));
        double sum = 0;
        List<Integer> indices = new ArrayList<>();
        for (double[] pair : sorted) {
            sum += pair[0];
            indices.add((int) pair[1]);
            if (sum > target) {
                return indices;
            }
        }
        return null;
    }
    private List<Integer> findLess(double[] nums, double target) {
        int n = nums.length;
        List<Integer> bestIndices = new ArrayList<>();
        double[] maxSum = {-1.0};
        for (int k = 1; k <= n; k++) {
            List<Integer> currentIndices = new ArrayList<>();
            double[] currentMax = {-1.0};
            backtrack(nums, target, 0, k, 0.0, 0, currentIndices, currentMax, new ArrayList<>());
            if (currentMax[0] != -1.0) {
                if (currentMax[0] > maxSum[0] || (currentMax[0] == maxSum[0] && currentIndices.size() < bestIndices.size())) {
                    maxSum[0] = currentMax[0];
                    bestIndices = new ArrayList<>(currentIndices);
                }
            }
        }
        return bestIndices.isEmpty() ? null : bestIndices;
    }
    private void backtrack(double[] nums, double target, int start, int k, double currentSum, int count, List<Integer> currentIndices, double[] currentMax, List<Integer> path) {
        if (count == k) {
            if (currentSum <= target && currentSum > currentMax[0]) {
                currentMax[0] = currentSum;
                currentIndices.clear();
                currentIndices.addAll(path);
            }
            return;
        }
        for (int i = start; i < nums.length; i++) {
            if (currentSum + nums[i] > target) continue;
            path.add(i);
            backtrack(nums, target, i + 1, k, currentSum + nums[i], count + 1, currentIndices, currentMax, path);
            path.remove(path.size() - 1);
        }
    }
    public static void main(String[] args) {
        CombinationFinder finder = new CombinationFinder();
        double[] nums = {3.0, 1.0, 4.0, 2.0};
        double target = 0.1;
        List<Integer> result = finder.findCombination(nums, target);
        System.out.println("最优组合的索引: " + result); // 例如,等值组合可能为索引2(4.0)和3(1.0)
    }
}
rsf-server/src/main/Test/MinimalCombinationSum.java
File was deleted
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/params/TaskInParam.java
@@ -9,7 +9,6 @@
    private Integer sourceStaNo; //作业站点 or 来源站点
    private String barcode; //容器条码
    private Integer locType1; //库位类型
    private Integer area;
//    private Integer locType2; //库位类型
//    private Integer locType3; //库位类型
}
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/TaskStsType.java
@@ -3,13 +3,13 @@
public enum TaskStsType {
    //任务状态
    GENERATE_IN(1L, "创建入库任务"),
    WCS_EXECUTE_IN(2L, "RCS任务已下发"),
    WCS_CONTAINER_RECEIVE(3L, "RCS容器到达"),
    WCS_CONVEYOR_START(4L, "RCS容器流动任务已下发"),
    WCS_TOTE_LOAD(5L, "RCS取箱完成"),
    WCS_TOTE_UNLOAD(6L, "RCS放箱完成"),
    WCS_PUTAWAY_SUCESS(7L, "RCS任务完成"),
    GENERATE_IN("1", "创建入库任务"),
    WCS_EXECUTE_IN("2", "RCS任务已下发"),
    WCS_CONTAINER_RECEIVE("3", "RCS容器到达"),
    WCS_CONVEYOR_START("4", "RCS容器流动任务已下发"),
    WCS_TOTE_LOAD("5", "RCS取箱完成"),
    WCS_TOTE_UNLOAD("6", "RCS放箱完成"),
    WCS_PUTAWAY_SUCESS("7", "RCS任务完成"),
//    WCS_PUTAWAY_FAILED(11L, "任务失败"),
//
@@ -17,36 +17,36 @@
//
//    WCS_PUTAWAY_SUSPEND(13L, "入库任务挂起"),
    COMPLETE_IN(99L, "入库完成"),
    COMPLETE_IN("99", "入库完成"),
    UPDATED_IN(100L, "库存更新完成"),
    UPDATED_IN("100", "库存更新完成"),
    GENERATE_OUT(101L, "创建出库任务"),
    GENERATE_OUT("101", "创建出库任务"),
    WCS_EXECUTE_OUT(102L, "RCS出库任务已下发"),
    WCS_EXECUTE_OUT("102", "RCS出库任务已下发"),
    WCS_EXECUTE_OUT_TOTE_LOAD(103L, "RCS取箱完成"),
    WCS_EXECUTE_OUT_TOTE_LOAD("103", "RCS取箱完成"),
    WCS_EXECUTE_OUT_TOTE_UNLOAD(104L, "RCS放箱完成"),
    WCS_EXECUTE_OUT_TOTE_UNLOAD("104", "RCS放箱完成"),
    WCS_EXECUTE_OUT_TASK_DONE(105L, "RCS任务完成"),
    WCS_EXECUTE_OUT_TASK_DONE("105", "RCS任务完成"),
    WCS_EXECUTE_OUT_ARRIVED(106L, "RCS容器已到达"),
    WCS_EXECUTE_OUT_ARRIVED("106", "RCS容器已到达"),
    WCS_EXECUTE_OUT_CONVEYOR(107L, "RCS容器流动任务已下发"),
    WCS_EXECUTE_OUT_CONVEYOR("107", "RCS容器流动任务已下发"),
    GENERATE_WAVE_SEED(197L, "等待容器到达"),
    WAVE_SEED(198L, "播种中"),
    GENERATE_WAVE_SEED("197", "等待容器到达"),
    WAVE_SEED("198", "播种中"),
    COMPLETE_OUT(199L, "出库完成"),
    UPDATED_OUT(200L, "库存更新完成"),
    COMPLETE_OUT("199", "出库完成"),
    UPDATED_OUT("200", "库存更新完成"),
    ;
    public Long id;
    public Short id;
    public String desc;
    TaskStsType(Long id, String desc) {
        this.id = id;
    TaskStsType(String id, String desc) {
        this.id = Short.parseShort(id);
        this.desc = desc;
    }
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/enums/TaskType.java
@@ -9,23 +9,23 @@
 */
public enum TaskType {
    TASK_TYPE_IN(1, "入库"),
    TASK_TYPE_EMPITY_IN(10, "空板入库"),
    TASK_TYPE_LOC_MOVE(11, "库格移载"),
    TASK_TYPE_PICK_IN(53, "拣料再入库"),
    TASK_TYPE_MERGE_IN(54, "并板再入库"),
    TASK_TYPE_CHECK_IN(57, "盘点再入库"),
    TASK_TYPE_OUT(101, "出库"),
    TASK_TYPE_PICK_AGAIN_IN(103, "拣料入库"),
    TASK_TYPE_MERGE_OUT(104, "并板出库"),
    TASK_TYPE_CHECK_OUT(107, "盘点出库"),
    TASK_TYPE_EMPITY_OUT(110, "空板出库"),
    TASK_TYPE_IN("1", "入库"),
    TASK_TYPE_EMPITY_IN("10", "空板入库"),
    TASK_TYPE_LOC_MOVE("11", "库格移载"),
    TASK_TYPE_PICK_IN("53", "拣料再入库"),
    TASK_TYPE_MERGE_IN("54", "并板再入库"),
    TASK_TYPE_CHECK_IN("57", "盘点再入库"),
    TASK_TYPE_OUT("101", "出库"),
    TASK_TYPE_PICK_AGAIN_IN("103", "拣料入库"),
    TASK_TYPE_MERGE_OUT("104", "并板出库"),
    TASK_TYPE_CHECK_OUT("107", "盘点出库"),
    TASK_TYPE_EMPITY_OUT("110", "空板出库"),
    ;
    public Integer type;
    public Short type;
    public String desc;
    TaskType(Integer type, String desc) {
        this.type = type;
    TaskType(String type, String desc) {
        this.type = Short.parseShort(type);
        this.desc = desc;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -315,7 +315,11 @@
        if (Cools.isEmpty(deviceSites)) {
            throw new CoolException("未找到站点路径信息");
        }
        WarehouseAreas warehouseArea = warehouseAreasService.getById(param.getArea());
        DeviceBind deviceBind = deviceBindService.getById(LocUtils.getAreaType(param.getSourceStaNo()));
        if (Cools.isEmpty(deviceBind)) {
            throw new CoolException("数据异常,请联系管理员===>库位规则未知");
        }
        WarehouseAreas warehouseArea = warehouseAreasService.getById(deviceBind.getTypeId());
        if (Cools.isEmpty(warehouseArea)) {
            throw new CoolException("未找到所属库区信息");
        }
@@ -323,17 +327,18 @@
        InTaskMsgDto dto = null;
        switch (warehouseArea.getType()) {
            case "CRN": //堆垛机
                dto = getLocNoCrn(param.getArea(), param.getSourceStaNo(), matnr,batch, locTypeDto, 0, param.getIoType());
                dto = getLocNoCrn(deviceBind,warehouseArea.getId(), param.getSourceStaNo(), matnr,batch, locTypeDto, 0, param.getIoType());
                break;
            case "SXC": //四向库
                break;
            case "CTU": //四向库
            case "CTU": //ctu
                dto = getLocNoCtu(deviceBind,warehouseArea.getId(), param.getSourceStaNo(), matnr,batch, locTypeDto, 0, param.getIoType());
                break;
        }
        return dto;
    }
    private InTaskMsgDto getLocNoCrn(Integer area,Integer sourceStaNo, String matnr, String batch,LocTypeDto locTypeDto, int times,Integer ioType){
    private InTaskMsgDto getLocNoCrn(DeviceBind deviceBind,Long area,Integer sourceStaNo, String matnr, String batch,LocTypeDto locTypeDto, int times,Integer ioType){
        if (Cools.isEmpty(matnr)) {  //物料号
            matnr = "";
        }
@@ -348,10 +353,7 @@
        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();
@@ -384,7 +386,7 @@
                    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 (shallowLoc != null && shallowLoc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_O.type)) {
                        if (LocUtils.locMoveCheckLocTypeComplete(shallowLoc, locTypeDto)) {
                                loc = shallowLoc;
                                deviceNo = shallowLoc.getDeviceNo();
@@ -443,7 +445,7 @@
            List<Loc> locMasts = null;
            locMasts = locService.list(new LambdaQueryWrapper<Loc>()
                    .eq(Loc::getRow, nearRow)
                    .eq(Loc::getUseStatus, "O")
                    .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                    .eq(Loc::getType, locTypeDto.getLocType1())
                    .eq(Loc::getAreaId,area)
                    .orderByAsc(Loc::getLev)
@@ -458,7 +460,7 @@
                    //相似物料打开,判断深库位有没有货,没货就放深库位,有货就不操作
                    Loc locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>()
                            .eq(Loc::getRow, shallowLoc)
                            .eq(Loc::getUseStatus, "O")
                            .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                            .eq(Loc::getAreaId,area)
                    );
                    if (!Cools.isEmpty(locMast2)) {
@@ -469,7 +471,7 @@
                    //相似物料关闭,判断深库位有没有货,有货就放浅库位,无货就不操作
                    Loc locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>()
                            .eq(Loc::getCode, shallowLoc)
                            .in(Loc::getUseStatus, "D","F")
                            .in(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_D.type,LocStsType.LOC_STS_TYPE_F.type)
                            .eq(Loc::getAreaId,area)
                    );
                    if (!Cools.isEmpty(locMast2)) {
@@ -478,7 +480,7 @@
                    }else{
                        locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>()
                                .eq(Loc::getCode, shallowLoc)
                                .eq(Loc::getUseStatus, "O")
                                .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                                .eq(Loc::getAreaId,area)
                        );
                        if (!Cools.isEmpty(locMast2)) {
@@ -497,7 +499,7 @@
                        String shallowLoc = LocUtils.getDeepLoc(slaveProperties, locMast1.getCode());
                        Loc locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>()
                                .eq(Loc::getCode, shallowLoc)
                                .eq(Loc::getUseStatus, "O")
                                .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                                .eq(Loc::getAreaId,area)
                        );
                        if (!Cools.isEmpty(locMast2)) {
@@ -506,7 +508,7 @@
                        } else {
                            locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>()
                                    .eq(Loc::getCode, shallowLoc)
                                    .in(Loc::getUseStatus, "D","F")
                                    .in(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_D.type,LocStsType.LOC_STS_TYPE_D.type)
                                    .eq(Loc::getAreaId,area)
                            );
                            if (!Cools.isEmpty(locMast2)) {
@@ -525,7 +527,7 @@
        }
        //查询当前库位类型空库位 小于5个则locmast = null
        List<Loc> locTypeLocMasts = locService.list(new LambdaQueryWrapper<Loc>()
                .eq(Loc::getUseStatus, "O")
                .eq(Loc::getUseStatus,LocStsType.LOC_STS_TYPE_O.type)
                .eq(Loc::getDeviceNo, deviceNo)
                .eq(Loc::getType, locTypeDto.getLocType1())
                .eq(Loc::getAreaId,area)
@@ -534,18 +536,18 @@
            loc = null;
        }
        // 递归查询
        if (Cools.isEmpty(loc) || !loc.getUseStatus().equals("O")) {
        if (Cools.isEmpty(loc) || !loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_O.type)) {
            // 当前巷道无空库位时,递归调整至下一巷道,检索全部巷道无果后,跳出递归
            if (times < rowCount * 2) {
                times = times + 1;
                return getLocNoCrn(area,sourceStaNo,matnr,batch,locTypeDto,times, ioType);
                return getLocNoCrn(deviceBind,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);
                return getLocNoCrn(deviceBind,area,sourceStaNo,matnr,batch,locTypeDto,0, ioType);
            }
            throw new CoolException("没有空库位");
        }
@@ -558,4 +560,68 @@
        inTaskMsgDto.setLocNo(locNo);
        return inTaskMsgDto;
    }
    private InTaskMsgDto getLocNoCtu(DeviceBind deviceBind,Long 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;
        Loc loc = new Loc();
        InTaskMsgDto inTaskMsgDto = new InTaskMsgDto();
        List<Loc> loc1 = locService.list(new LambdaQueryWrapper<Loc>()
                .eq(Loc::getAreaId, area)
                .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                .eq(Loc::getType, locTypeDto.getLocType1())
                .orderByAsc(Loc::getRow)
                .orderByAsc(Loc::getCol)
                .orderByAsc(Loc::getLev)
        );
        for (Loc loc2 :loc1){
            if (!LocUtils.locMoveCheckLocTypeComplete(loc2, locTypeDto)) {
                continue;
            }
            loc = loc2;
            break;
        }
        //查找路径
        DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>()
                .eq(DeviceSite::getType, ioType)
                .eq(DeviceSite::getSite, sourceStaNo)
                .eq(DeviceSite::getDeviceCode, loc.getDeviceNo())
        );
        if (Cools.isEmpty(deviceSite)){
            deviceNo = 0;
            loc = null;
        }else {
            inTaskMsgDto.setStaNo(Integer.parseInt(deviceSite.getDeviceSite()));
        }
        // 递归查询
        if (Cools.isEmpty(loc) || !loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_O.type)) {
            // 当前巷道无空库位时,递归调整至下一巷道,检索全部巷道无果后,跳出递归
            if (times < 5) {
                times = times + 1;
                return getLocNoCtu(deviceBind,area,sourceStaNo,matnr,batch,locTypeDto,times, ioType);
            }
            // 2.库位当前所属尺寸无空库位时,调整尺寸参数,向上兼容检索库位
            if (locTypeDto.getLocType1() < 3) {
                int i = locTypeDto.getLocType1() + 1;
                locTypeDto.setLocType1(i);
                return getLocNoCtu(deviceBind,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;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/AsnOrderController.java
@@ -3,6 +3,7 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.R;
@@ -52,7 +53,10 @@
    public R page(@RequestBody Map<String, Object> map) {
        BaseParam baseParam = buildParam(map, BaseParam.class);
        PageParam<AsnOrder, BaseParam> pageParam = new PageParam<>(baseParam, AsnOrder.class);
        return R.ok().add(asnOrderService.page(pageParam, pageParam.buildWrapper(true)));
        QueryWrapper<AsnOrder> queryWrapper = pageParam.buildWrapper(true);
        List<String> asList = Arrays.asList(OrderType.ORDER_OUT.type);
        queryWrapper.notIn("type", asList);
        return R.ok().add(asnOrderService.page(pageParam, queryWrapper));
    }
    @PreAuthorize("hasAuthority('manager:asnOrder:list')")
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/TaskController.java
@@ -95,7 +95,7 @@
        if (Objects.isNull(ids) || ids.length < 1) {
            return R.error("参数不能为空!!");
        }
        List<Long> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Short> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>().in(Task::getId, ids).in(Task::getTaskStatus, longs));
        if (tasks.isEmpty()) {
            throw new CoolException("任务已处执行状态不可取消!!");
@@ -136,7 +136,7 @@
        if (Objects.isNull(id)) {
            throw new CoolException("参数不能为空!!");
        }
        List<Long> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Short> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>().eq(Task::getId, id).in(Task::getTaskStatus, longs));
//        if (tasks.isEmpty()) {
//            throw new CoolException("任务已处执行状态不可一键完成!!");
@@ -163,7 +163,7 @@
        if (Objects.isNull(id)) {
            throw new CoolException("参数不能为空!!");
        }
        List<Long> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Short> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>().eq(Task::getId, id).in(Task::getTaskStatus, longs));
        if (tasks.isEmpty()) {
            throw new CoolException("任务已处执行状态不可一键置顶!!");
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java
@@ -111,16 +111,14 @@
        ExcelUtil.build(ExcelUtil.create(waveService.list(), Wave.class), response);
    }
    @PreAuthorize("hasAuthority('manager:wave:update')")
    @ApiOperation("波次下发任务")
    @PostMapping("/wave/public/task")
    public R publicTask(@RequestBody Map<String, Object> map) {
        if (Cools.isEmpty(map)) {
        if (Cools.isEmpty(map) || Cools.isEmpty(map.get("wave"))) {
            throw new CoolException("参数不能为空!!");
        }
        return waveService.publicTask(map);
        return waveService.publicTask(map, getLoginUserId());
    }
    @PreAuthorize("hasAuthority('manager:wave:list')")
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/AsnOrder.java
@@ -9,11 +9,9 @@
import com.vincent.rsf.server.system.service.DictDataService;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import com.baomidou.mybatisplus.annotation.TableLogic;
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;
@@ -79,6 +77,9 @@
    @ApiModelProperty(value= "送货数量")
    private Double anfme;
    @ApiModelProperty("执行数量")
    private Double workQty;
    /**
     * 已收数量
     */
@@ -91,6 +92,9 @@
    @ApiModelProperty(value= "物流单号")
    private String logisNo;
    @ApiModelProperty("波次ID")
    private Long waveId;
    /**
     * 预计到达时间
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java
@@ -1,6 +1,7 @@
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;
@@ -9,10 +10,7 @@
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;
@@ -106,8 +104,10 @@
    @ApiModelProperty(value= "数量")
    private Double anfme;
    @ApiModelProperty("执行数量")
    private Double workQty;
    @ApiModelProperty("完成数量")
    private Double qty;
@@ -187,6 +187,17 @@
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    @TableField(exist = false)
    private Long waveId;
    @TableField(exist = false)
    private Long waveItemId;
    @TableField(exist = false)
    private String waveCode;
    /**
     * 备注
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/TaskItem.java
@@ -77,6 +77,9 @@
     */
    @ApiModelProperty("物料跟踪码")
    private String trackCode;
    @ApiModelProperty("供应商波次")
    private String splrBatch;
    /**
     * 物料编码
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java
@@ -70,6 +70,9 @@
    @ApiModelProperty(value= "已完成数量")
    private Double qty;
    @ApiModelProperty("目标位置")
    private String targSite;
    /**
     * 单据数量
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java
@@ -170,11 +170,27 @@
    @ApiModelProperty(value= "修改人员")
    private Long updateBy;
    /**
     * 目标库位
     */
    @ApiModelProperty("目标库位")
    @TableField(exist = false)
    private String stockLocs;
    /**
     * 库存数量
     */
    @ApiModelProperty("库存数量")
    @TableField(exist = false)
    private Double stockQty;
    /***
     * 是否全拖出库
     */
    @TableField(exist = false)
    private Short flagAll;
    /**
     * 备注
     */
    @ApiModelProperty(value= "备注")
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java
@@ -11,13 +11,14 @@
public interface WaveService extends IService<Wave> {
    /**
     * @param
     * @param loginUserId
     * @return
     * @author Ryan
     * @description 波次任务下发
     * @param
     * @return
     * @time 2025/4/25 16:24
     */
    R publicTask(Map<String, Object> map);
    R publicTask(Map<String, Object> map, Long loginUserId);
    /**
     * @author Ryan
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -290,7 +290,18 @@
            throw new CoolException("主单修改失败!!");
        }
        for (int i = 0; i < orderItems.size(); i++) {
            orderItems.get(i).setWorkQty(orderItems.get(i).getAnfme());
        }
        if (!asnOrderItemService.saveOrUpdateBatch(orderItems)) {
            throw new CoolException("出库单执行数量修改失败!!");
        }
        double sum2 = orderItems.stream().mapToDouble(AsnOrderItem::getWorkQty).sum();
        if (!this.update(new LambdaUpdateWrapper<AsnOrder>()
                .set(AsnOrder::getWaveId, wave.getId())
                        .set(AsnOrder::getWorkQty, sum2)
                .set(AsnOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WAVE.val)
                .in(AsnOrder::getId, ids))) {
            throw new CoolException("执行状态修改修改失败!!");
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -2,11 +2,13 @@
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.server.api.entity.enums.OrderType;
import com.vincent.rsf.server.api.entity.enums.TaskStsType;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.api.entity.enums.TaskType;
import com.vincent.rsf.server.api.utils.LocUtils;
import com.vincent.rsf.server.manager.controller.params.GenerateTaskParams;
import com.vincent.rsf.server.manager.entity.*;
import com.vincent.rsf.server.manager.enums.PakinIOStatus;
@@ -62,6 +64,11 @@
    @Autowired
    private LocItemService locItemService;
    @Autowired
    private DeviceBindService deviceBindService;
    @Autowired
    private WarehouseAreasService warehouseAreasService;
    /**
     * @param
@@ -82,6 +89,20 @@
        if (Objects.isNull(deviceSite)) {
            throw new CoolException("站点不存在!!");
        }
        DeviceBind deviceBind = deviceBindService.getById(LocUtils.getAreaType(Integer.valueOf(deviceSite.getSite())));
        if (Cools.isEmpty(deviceBind)) {
            throw new CoolException("库位规则未知");
        }
        WarehouseAreas warehouseArea = warehouseAreasService.getById(deviceBind.getTypeId());
        if (Cools.isEmpty(warehouseArea)) {
            throw new CoolException("未找到所属库区信息");
        }
        /**获取库位*/
        String targetLoc = LocManageUtil.getTargetLoc(warehouseArea.getId());
        if (Cools.isEmpty(targetLoc)) {
            throw new CoolException("该站点对应库区未找到库位");
        }
        /**获取组拖*/
        List<Long> ids = waitPakin.getWaitPakins().stream().map(WaitPakin::getId).collect(Collectors.toList());
        List<WaitPakin> waitPakins = waitPakinService.list(new LambdaQueryWrapper<WaitPakin>()
@@ -100,28 +121,28 @@
            task.setTaskCode(ruleCode)
                    .setTaskStatus(TaskStsType.GENERATE_IN.id.shortValue())
                    .setTaskType(TaskType.TASK_TYPE_IN.type.shortValue())
                    .setTargLoc(LocManageUtil.getTargetLoc())
                    .setTargLoc(targetLoc)
                    .setBarcode(pakin.getBarcode())
                    .setTargSite(deviceSite.getDeviceCode())
                    .setOrgSite(deviceSite.getSite())
                    .setTargSite(deviceSite.getDeviceSite())
                    .setCreateBy(loginUserId)
                    .setUpdateBy(loginUserId);
            if (!Objects.isNull(waitPakin.getSiteId()) && waitPakin.getSiteId() > 0) {
                DeviceSite site = deviceSiteService.getById(waitPakin.getSiteId());
                task.setTargSite(site.getSite() + "");
            } else {
                task.setTargSite(LocManageUtil.getTargetSite());
            }
            if (!Objects.isNull(waitPakin.getLocCode()) && StringUtils.isNotBlank(waitPakin.getLocCode())) {
                List<Loc> locs = locService.list(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, waitPakin.getLocCode()));
//                if (!locs.isEmpty()) {
//                    throw new CoolException("库位错误:相同库位应只一条")
//                }
                Loc loc = locs.stream().findFirst().get();
                task.setTargSite(loc.getCode());
            } else {
                task.setTargSite(LocManageUtil.getTargetSite());
            }
//            if (!Objects.isNull(waitPakin.getSiteId()) && waitPakin.getSiteId() > 0) {
//                DeviceSite site = deviceSiteService.getById(waitPakin.getSiteId());
//                task.setTargSite(site.getSite() + "");
//            } else {
//                task.setTargSite(LocManageUtil.getTargetSite());
//            }
//            if (!Objects.isNull(waitPakin.getLocCode()) && StringUtils.isNotBlank(waitPakin.getLocCode())) {
//                List<Loc> locs = locService.list(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, waitPakin.getLocCode()));
////                if (!locs.isEmpty()) {
////                    throw new CoolException("库位错误:相同库位应只一条")
////                }
//                Loc loc = locs.stream().findFirst().get();
//                task.setTargSite(loc.getCode());
//            } else {
//                task.setTargSite(LocManageUtil.getTargetSite());
//            }
            if (!this.save(task)) {
                throw new CoolException("任务保存失败!!");
@@ -218,7 +239,7 @@
        if (!locService.update(new LambdaUpdateWrapper<Loc>().set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_F.type).in(Loc::getCode, locCodes))) {
            throw new CoolException("库位状态修改失败!!");
        }
        if (!this.update(new LambdaUpdateWrapper<Task>().set(Task::getTaskStatus, TaskStsType.UPDATED_IN.id))) {
        if (!this.update(new LambdaUpdateWrapper<Task>().in(Task::getId, list).set(Task::getTaskStatus, TaskStsType.UPDATED_IN.id))) {
            throw new CoolException("任务状态修改失败!!");
        }
    }
@@ -231,7 +252,7 @@
     * @time 2025/4/15 15:28
     */
    @Transactional(rollbackFor = Exception.class)
    void saveLocItem(List<TaskItem> items, Long taskId) throws Exception {
    public void saveLocItem(List<TaskItem> items, Long taskId) throws Exception {
        Task task = this.getById(taskId);
        if (Objects.isNull(task)) {
            throw new CoolException("任务不存在!!");
@@ -255,7 +276,7 @@
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    void saveStockItems(List<TaskItem> items, WaitPakinItem order) throws Exception {
    public void saveStockItems(List<TaskItem> items, WaitPakinItem order) throws Exception {
        Stock stock = new Stock();
//        if (!Objects.isNull(order.getPoCode()) && StringUtils.isNotBlank(order.getPoCode())) {
//            Purchase purchase = purchaseService.getOne(new LambdaQueryWrapper<Purchase>().eq(Purchase::getCode, order.getPoCode()));
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
@@ -6,25 +6,24 @@
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
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.LocItem;
import com.vincent.rsf.server.manager.entity.WaveItem;
import com.vincent.rsf.server.api.entity.enums.TaskStsType;
import com.vincent.rsf.server.api.entity.enums.TaskType;
import com.vincent.rsf.server.manager.entity.*;
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.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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;
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.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
@Service("waveService")
@@ -42,9 +41,12 @@
    private TaskItemService taskItemService;
    @Autowired
    private LocItemService locItemService;
    @Autowired
    private LocService locService;
    /**
     * @param
     * @param loginUserId
     * @return
     * @author Ryan
     * @description 波次任务下发
@@ -52,25 +54,24 @@
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R publicTask(Map<String, Object> map) {
        List<Long> ids = (List<Long>) map.get("ids");
        if (Objects.isNull(ids) || ids.isEmpty()) {
    public R publicTask(Map<String, Object> map, Long loginUserId) {
        List<WaveItem> itemParams = (List<WaveItem>) map.get("waveItem");
        if (Objects.isNull(itemParams) || itemParams.isEmpty()) {
            throw new CoolException("参数不能为空!!");
        }
        List<Wave> waves = this.list(new LambdaQueryWrapper<Wave>().in(Wave::getId, ids));
        if (Objects.isNull(waves) || waves.isEmpty()) {
        Wave wave = (Wave) map.get("wave");
        Wave waves = this.getById(new LambdaQueryWrapper<Wave>().in(Wave::getId, wave.getId()));
        if (Objects.isNull(waves)) {
            throw new CoolException("波次数据不存在!!");
        }
        List<Long> list = waves.stream().map(Wave::getId).collect(Collectors.toList());
        List<Long> list = itemParams.stream().map(WaveItem::getWaveId).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());
        /**查询每条明细匹配的库位*/
        /**生成出库任务*/
        try {
            List<WaveItem> items = getLocs(waveItems);
            generateOutTask(itemParams, loginUserId, wave);
        } catch (Exception e) {
            throw new CoolException("库位获取失败!!!");
        }
@@ -78,17 +79,130 @@
        // 2. 根据物料SKU寻找符合物料库位  {1. 根据物料编码,批次,动态字段 查询符合的库位,再根据库位中物料的数量选择最适合的库位 2. 判断当前订单是全拖出库还是拣料入库}
        // 3. 修改主单、波次执行数量
        // 4. 判断全仓出库或拣料出库
        List<Long> orderIds = waveItems.stream().map(WaveItem::getOrderId).collect(Collectors.toList());
        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))) {
        /**修改波次单据执行状态*/
        if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).eq(Wave::getId, wave.getId()))) {
            throw new CoolException("波次状态修改失败!!");
        }
        return R.ok();
    }
    /**
     * @param
     * @param loginUserId
     * @param wave
     * @return
     * @author Ryan
     * @description 生成出库任务
     * @time 2025/4/28 14:01
     */
    @Transactional(rollbackFor = Exception.class)
    private synchronized void generateOutTask(List<WaveItem> itemParams, Long loginUserId, Wave wave) throws Exception {
        List<LocItem> locItemList = new ArrayList<>();
        for (WaveItem param : itemParams) {
            String locs = param.getStockLocs();
            List<LocItem> locItems = JSONArray.parseArray(locs, LocItem.class);
            if (locItems.isEmpty()) {
                continue;
            }
            List<Long> list = locItems.stream().map(LocItem::getId).collect(Collectors.toList());
            /**根据供应商批次,物料码, 动态字段查询指定的物料库存信息*/
            List<LocItem> items = locItemService.list(new LambdaQueryWrapper<LocItem>()
                    .eq(LocItem::getSplrBatch, param.getSplrBatch())
                    .in(LocItem::getLocId, list)
                    .eq(StringUtils.isNotBlank(param.getFieldsIndex()), LocItem::getFieldsIndex, param.getFieldsIndex())
                    .eq(LocItem::getMatnrCode, param.getMatnrCode()));
            if (items.isEmpty()) {
                throw new CoolException("库存信息有变,请取消当前波次,生新生成新的波次!!");
            }
            /***将有货有的明细信息存放到库位信息中*/
            for (int i = 0; i < items.size(); i++) {
                items.get(i)
                        .setWaveId(param.getWaveId())
                        .setWaveCode(param.getWaveCode())
                        .setWaveItemId(param.getId());
            }
            locItemList.addAll(items);
        }
        if (locItemList.isEmpty()) {
            throw new CoolException("没有合适库位!!");
        }
        /**拆分波次明细库位集,合并相同库位,分解任务明细*/
        Map<Long, List<LocItem>> listMap = locItemList.stream().collect(Collectors.groupingBy(LocItem::getLocId));
        /**根据库位汇总信息,生成任务明细**/
        listMap.keySet().forEach(key -> {
            List<LocItem> locItems = listMap.get(key);
            LocItem item1 = locItems.stream().findFirst().get();
            WaveItem waveItem = waveItemService.getById(item1.getWaveItemId());
            if (null == waveItem || Objects.isNull(waveItem)) {
                throw new CoolException("数据错误:波次明细不存在!!");
            }
            //TODO 当前任务完成后,通过定时事件判断是全盘出库,还是拣料再入库
            Loc loc = locService.getById(key);
            Task task = new Task();
            String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null);
            if (StringUtils.isBlank(ruleCode)) {
                throw new CoolException("编码规则错误:请检查「SYS_TASK_CODE」是否设置完成!!");
            }
            if (Objects.isNull(loc)) {
                throw new CoolException("库位不存在!!");
            }
            task.setTaskCode(ruleCode)
                    .setTaskType(TaskType.TASK_TYPE_OUT.type)
                    .setTaskStatus(TaskStsType.GENERATE_OUT.id)
                    .setBarcode(loc.getBarcode())
                    .setOrgLoc(loc.getCode())
                    .setCreateBy(loginUserId)
                    .setUpdateBy(loginUserId)
                    .setTargSite(wave.getTargSite());
            if (!taskService.save(task)) {
                throw new CoolException("任务生成失败!!");
            }
            List<TaskItem> taskItems = new ArrayList<>();
            /**生成任务明细信息*/
            for (LocItem item : locItems) {
                TaskItem taskItem = new TaskItem();
                BeanUtils.copyProperties(item, taskItem);
                taskItem.setTaskId(task.getId())
                        .setId(null)
                        .setSource(item.getWaveItemId());
                taskItems.add(taskItem);
            }
            if (!taskItemService.saveBatch(taskItems)) {
                throw new CoolException("任务明细保存失败!!");
            }
            /**修改波次执行数量*/
            taskItems.forEach(item -> {
                boolean update = waveItemService.update(new LambdaUpdateWrapper<WaveItem>()
                        .eq(WaveItem::getWaveId, item.getSource())
                        .set(WaveItem::getWorkQty, item.getAnfme()));
                if (!update) {
                    throw new CoolException("波次执行数量修改失败!!");
                }
            });
            List<WaveItem> waveItems = waveItemService.list(new LambdaQueryWrapper<WaveItem>().eq(WaveItem::getWaveId, wave.getId()));
            double sum = waveItems.stream().mapToDouble(WaveItem::getWorkQty).sum();
            /**波次主单信息修改*/
            if (!update(new LambdaUpdateWrapper<Wave>()
                    .eq(Wave::getId, wave.getId())
                    .set(Wave::getWorkQty, sum)
                    .set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK.val))) {
                throw new CoolException("波次主单信息修改失败!!");
            }
        });
    }
    /**
@@ -130,24 +244,34 @@
        //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")
                    .select("id", "loc_id", "loc_code", "order_id", "SUM(anfme) anfme", "SUM(qty) qty", "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));
            List<Double> doubles1 = locItems.stream().map(LocItem::getAnfme).collect(Collectors.toList());
            double[] doubles = doubles1.stream().mapToDouble(Double::doubleValue).toArray();
            Double[] doubles = locItems.stream().map(LocItem::getAnfme).toArray(Double[]::new);
            List<Double> result = OptimalAlgorithmUtil.findBestCombination(doubles, waveItem.getAnfme());
            /**使用回溯算法计算,获取符合出库量的最简组合*/
            List<Integer> result = OptimalAlgorithmUtil.findCombination(doubles, waveItem.getAnfme());
            String locs = JSONArray.toJSONString(new ArrayList<>());
            String locs = "[]";
            if (Objects.isNull(result) || result.isEmpty()) {
                waveItem.setStockLocs(locs).setStockQty(0.0);
            } else {
                /**过滤集合中最简短的组合*/
                List<LocItem> locsInfo = result.stream()
                        .filter(i -> i >= 0 && i < locItems.size())
                        .map(locItems::get).collect(Collectors.toList());
            if (!locItems.isEmpty()) {
                List<String> codes = locItems.stream().map(LocItem::getLocCode).collect(Collectors.toList());
                locs = JSONArray.toJSONString(codes);
                locs = JSONArray.toJSONString(locsInfo);
                Double sumQty = locsInfo.stream().mapToDouble(LocItem::getAnfme).sum();
                Double surQty = locsInfo.stream().mapToDouble(LocItem::getWorkQty).sum();
                Double qty = locsInfo.stream().mapToDouble(LocItem::getQty).sum();
                Double v = sumQty - surQty - qty;
                waveItem.setStockLocs(locs).setStockQty(v);
            }
            waveItem.setStockLocs(locs);
        });
        return waveItems;
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
@@ -21,11 +21,14 @@
     * @return
     * @time 2025/3/31 08:50
     */
    public static String getTargetLoc() {
    public static String getTargetLoc(Long areaId) {
        //TODO 库位策略后续排期
        LocService locService = SpringUtils.getBean(LocService.class);
        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type), false);
        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>()
                .eq(Loc::getAreaId, areaId)
                .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type), false
        );
        return !Objects.isNull(loc) ? loc.getCode() : null;
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/OptimalAlgorithmUtil.java
@@ -6,9 +6,9 @@
import java.util.stream.Collectors;
/**
 * @param
 * @author Ryan
 * @description 根据对象多个属性合并处理
 * @param
 * @return
 * @time 2025/4/25 10:55
 */
@@ -46,9 +46,9 @@
    }
    /**
     * @param
     * @author Ryan
     * @description 用于存储多个分组键的内部类
     * @param
     * @return
     * @time 2025/4/25 10:56
     */
@@ -80,52 +80,140 @@
    }
    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);
    /**
     * @param
     * @return
     * @author Ryan
     * @description 获取全拖或非全拖库位
     * @time 2025/4/28 09:41
     */
    public static List<Integer> findCombination(double[] nums, Double target) {
        // 处理等值情况
        List<Integer> equal = findEqual(nums, target);
        if (equal != null && !equal.isEmpty()) {
            return equal;
        }
        // 如果已经找到精确解,不需要继续搜索更长的组合
        if (minDifference < EPSILON) {
        // 处理大于的情况
        List<Integer> greater = findGreater(nums, target);
        if (greater != null && !greater.isEmpty()) {
            return greater;
        }
        // 处理小于的情况
        List<Integer> less = findLess(nums, target);
        return less != null ? less : new ArrayList<>(); // 确保不返回null
    }
    /**
     * @author Ryan
     * @description 使用动态规划寻找和等于目标值的最少元素组合。动态规划数组dp记录达到每个和所需的最少元素数目,prev数组记录路径以便回溯找到具体索引。
     * @param
     * @return
     * @time 2025/4/28 12:33
     */
    private static List<Integer> findEqual(double[] nums, double target) {
        int n = nums.length;
        double[] dp = new double[(int) (target * 100 + 1)]; // 放大100倍处理精度问题
        Arrays.fill(dp, Double.MAX_VALUE);
        dp[0] = 0;
        int[] prev = new int[(int) (target * 100 + 1)];
        Arrays.fill(prev, -1);
        for (int j = 0; j < n; j++) {
            int num = (int) (nums[j] * 100); // 放大100倍处理精度问题
            for (int i = (int) (target * 100); i >= num; i--) {
                if (dp[i - num] != Double.MAX_VALUE && dp[i - num] + 1 < dp[i]) {
                    dp[i] = dp[i - num] + 1;
                    prev[i] = j;
                }
            }
        }
        if (dp[(int) (target * 100)] == Double.MAX_VALUE) {
            return null;
        }
        List<Integer> indices = new ArrayList<>();
        int current = (int) (target * 100);
        while (current > 0) {
            int j = prev[current];
            if (j == -1) return null;
            indices.add(j);
            current -= (int) (nums[j] * 100);
        }
        return indices;
    }
    /**
     * @author Ryan
     * @description 将数组降序排序后,贪心地累加元素直到总和超过目标值,返回这些元素的索引
     * @param
     * @return
     * @time 2025/4/28 12:34
     */
    private static List<Integer> findGreater(double[] nums, double target) {
        List<double[]> sorted = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            sorted.add(new double[]{nums[i], i});
        }
        sorted.sort((a, b) -> Double.compare(b[0], a[0]));
        double sum = 0;
        List<Integer> indices = new ArrayList<>();
        for (double[] pair : sorted) {
            sum += pair[0];
            indices.add((int) pair[1]);
            if (sum > target) {
                return indices;
            }
        }
        return null;
    }
    /**
     * @author Ryan
     * @description 使用回溯法生成所有可能的组合,寻找总和最大但不超过目标值的组合,并记录元素数目最少的组合。
     * @param
     * @return
     * @time 2025/4/28 12:34
     */
    private static List<Integer> findLess(double[] nums, double target) {
        int n = nums.length;
        List<Integer> bestIndices = new ArrayList<>();
        double[] maxSum = {-1.0};
        for (int k = 1; k <= n; k++) {
            List<Integer> currentIndices = new ArrayList<>();
            double[] currentMax = {-1.0};
            backtrack(nums, target, 0, k, 0.0, 0, currentIndices, currentMax, new ArrayList<>());
            if (currentMax[0] != -1.0) {
                if (currentMax[0] > maxSum[0] || (currentMax[0] == maxSum[0] && currentIndices.size() < bestIndices.size())) {
                    maxSum[0] = currentMax[0];
                    bestIndices = new ArrayList<>(currentIndices);
                }
            }
        }
        return bestIndices.isEmpty() ? null : bestIndices;
    }
    private static void backtrack(double[] nums, double target, int start, int k, double currentSum, int count, List<Integer> currentIndices, double[] currentMax, List<Integer> path) {
        if (count == k) {
            if (currentSum <= target && currentSum > currentMax[0]) {
                currentMax[0] = currentSum;
                currentIndices.clear();
                currentIndices.addAll(path);
            }
            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);
        for (int i = start; i < nums.length; i++) {
            if (currentSum + nums[i] > target) continue;
            path.add(i);
            backtrack(nums, target, i + 1, k, currentSum + nums[i], count + 1, currentIndices, currentMax, path);
            path.remove(path.size() - 1);
        }
    }