From 37467bf7d119ef9b599f1c19b869d046d730b7cb Mon Sep 17 00:00:00 2001
From: skyouc
Date: 星期日, 27 四月 2025 18:27:56 +0800
Subject: [PATCH] 修改优化   1. 波次生成修改优化    2. 出库库位查找 优化

---
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java |    4 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java                  |   10 
 rsf-admin/src/page/orders/wave/WaveList.jsx                                                   |   44 +-
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java     |  120 +++++++-
 rsf-admin/src/page/waveItem/WaveItemList.jsx                                                  |    8 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java                   |    6 
 rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java       |    7 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/OptimalAlgorithmUtil.java       |  132 +++++++++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java     |    2 
 /dev/null                                                                                     |  154 -----------
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java              |   11 
 rsf-admin/src/page/task/TaskList.jsx                                                          |    1 
 rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx                                            |  186 +++++++++++++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/AsnExceStatus.java              |    2 
 rsf-admin/src/i18n/zh.js                                                                      |    2 
 rsf-server/src/main/Test/MinimalCombinationSum.java                                           |   90 ++++++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java                      |    3 
 rsf-admin/src/i18n/en.js                                                                      |    2 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java        |   19 +
 19 files changed, 589 insertions(+), 214 deletions(-)

diff --git a/rsf-admin/src/i18n/en.js b/rsf-admin/src/i18n/en.js
index bdd97fd..3a583e9 100644
--- a/rsf-admin/src/i18n/en.js
+++ b/rsf-admin/src/i18n/en.js
@@ -745,6 +745,8 @@
                 fieldsIndex: "fieldsIndex",
                 anfme: "anfme",
                 workQty: "workQty",
+                qty: "Qty",
+                stockLocs: "Stock Locs"
             },
             task: {
                 taskCode: "TaskCode",
diff --git a/rsf-admin/src/i18n/zh.js b/rsf-admin/src/i18n/zh.js
index 32dc27a..af8f418 100644
--- a/rsf-admin/src/i18n/zh.js
+++ b/rsf-admin/src/i18n/zh.js
@@ -791,6 +791,8 @@
                 fieldsIndex: "鍔ㄦ�佹墿灞�",
                 anfme: "鏁伴噺",
                 workQty: "鎵ц鏁�",
+                qty: "瀹屾垚鏁伴噺",
+                stockLocs: "搴撳瓨浣嶇疆"
             },
             task: {
                 taskCode: "浠诲姟鍙�",
diff --git a/rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx b/rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx
new file mode 100644
index 0000000..6b6c39e
--- /dev/null
+++ b/rsf-admin/src/page/orders/wave/ItemToTaskModal.jsx
@@ -0,0 +1,186 @@
+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>)
+}
\ No newline at end of file
diff --git a/rsf-admin/src/page/orders/wave/WaveList.jsx b/rsf-admin/src/page/orders/wave/WaveList.jsx
index 73596e2..c1af1e3 100644
--- a/rsf-admin/src/page/orders/wave/WaveList.jsx
+++ b/rsf-admin/src/page/orders/wave/WaveList.jsx
@@ -48,6 +48,8 @@
 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 }) => ({
@@ -60,7 +62,7 @@
     '& .column-name': {
     },
     '& .opt': {
-        width: 200
+        width: 260
     },
 }));
 
@@ -68,7 +70,6 @@
     <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={[
@@ -87,7 +88,6 @@
     <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"
@@ -103,6 +103,8 @@
 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 (
@@ -130,9 +132,7 @@
             >
                 <StyledDatagrid
                     preferenceKey='wave'
-                    bulkActionButtons={
-                        <PublicTaskButton />
-                    }
+                    bulkActionButtons={false}
                     rowClick={(id, resource, record) => false}
                     expand={false}
                     expandSingle={false}
@@ -152,11 +152,17 @@
                     <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}
@@ -173,32 +179,16 @@
 
 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} />
+    );
 }
\ No newline at end of file
diff --git a/rsf-admin/src/page/task/TaskList.jsx b/rsf-admin/src/page/task/TaskList.jsx
index 087ce49..8f310bf 100644
--- a/rsf-admin/src/page/task/TaskList.jsx
+++ b/rsf-admin/src/page/task/TaskList.jsx
@@ -23,7 +23,6 @@
     DateInput,
     SelectInput,
     NumberInput,
-
     Button,
 } from 'react-admin';
 import { Box, Typography, Card, Stack, Drawer } from '@mui/material';
diff --git a/rsf-admin/src/page/waveItem/WaveItemList.jsx b/rsf-admin/src/page/waveItem/WaveItemList.jsx
index 07d87f0..b4b4338 100644
--- a/rsf-admin/src/page/waveItem/WaveItemList.jsx
+++ b/rsf-admin/src/page/waveItem/WaveItemList.jsx
@@ -60,9 +60,8 @@
 
 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" />,
@@ -77,7 +76,6 @@
     <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"
@@ -92,7 +90,6 @@
 
 const WaveItemList = () => {
     const translate = useTranslate();
-
     const [createDialog, setCreateDialog] = useState(false);
     const [drawerVal, setDrawerVal] = useState(false);
 
@@ -144,7 +141,6 @@
                     <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>
diff --git a/rsf-server/src/main/Test/MinimalCombinationSum.java b/rsf-server/src/main/Test/MinimalCombinationSum.java
new file mode 100644
index 0000000..624a4f1
--- /dev/null
+++ b/rsf-server/src/main/Test/MinimalCombinationSum.java
@@ -0,0 +1,90 @@
+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);
+
+        // 濡傛灉娌℃湁绮剧‘瑙o紝杩斿洖鏈�鎺ヨ繎鐨勮繎浼艰В
+        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);
+
+        // 濡傛灉鎵惧埌鏇翠紭瑙o紙宸�兼洿灏忔垨宸�肩浉鍚屼絾缁勫悎鏇寸煭锛�
+        if (difference < minDifference - EPSILON ||
+                (Math.abs(difference - minDifference) < EPSILON &&
+                        (bestSolution == null || current.size() < bestSolution.size()))) {
+            minDifference = difference;
+            bestSolution = new ArrayList<>(current);
+        }
+
+        // 濡傛灉宸茬粡鎵惧埌绮剧‘瑙o紝涓嶉渶瑕佺户缁悳绱㈡洿闀跨殑缁勫悎
+        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);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
index a22179e..33a6fb9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
@@ -654,7 +654,9 @@
         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()))
@@ -924,7 +926,8 @@
             }
             //鑾峰彇褰撳墠搴撳瓨淇℃伅
             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 褰撳墠搴撳瓨鏁伴噺
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java
index 2334f1c..cd19c30 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java
@@ -1,19 +1,23 @@
 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.*;
@@ -108,6 +112,7 @@
     }
 
 
+
     @PreAuthorize("hasAuthority('manager:wave:update')")
     @ApiOperation("娉㈡涓嬪彂浠诲姟")
     @PostMapping("/wave/public/task")
@@ -118,4 +123,18 @@
         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);
+    }
+
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java
index 3a38ea6..6382e57 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java
@@ -43,6 +43,9 @@
     @ApiModelProperty(value= "涓诲崟ID")
     private Long locId;
 
+    @ApiModelProperty("搴撲綅缂栫爜")
+    private String locCode;
+
     /**
      * 鍗曟嵁ID
      */
@@ -106,6 +109,9 @@
     @ApiModelProperty("鎵ц鏁伴噺")
     private Double workQty;
 
+    @ApiModelProperty("瀹屾垚鏁伴噺")
+    private Double qty;
+
     /**
      * 搴撳瓨鎵规
      */
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java
index c9dadf0..1490cc7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java
@@ -43,6 +43,9 @@
     @ApiModelProperty(value= "娉㈡鍙�")
     private String code;
 
+    @ApiModelProperty("鎵ц鏁伴噺")
+    private Double workQty;
+
     /**
      * 娉㈡绫诲瀷 0: 鎵嬪姩  1: 鑷姩  
      */
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java
index 392b2ac..ee1a48c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.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;
@@ -172,6 +170,10 @@
     @ApiModelProperty(value= "淇敼浜哄憳")
     private Long updateBy;
 
+    @ApiModelProperty("鐩爣搴撲綅")
+    @TableField(exist = false)
+    private String stockLocs;
+
     /**
      * 澶囨敞
      */
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/AsnExceStatus.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/AsnExceStatus.java
index c58518d..ca2141f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/AsnExceStatus.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/AsnExceStatus.java
@@ -17,9 +17,7 @@
     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) {
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java
index b38ff1e..e6648c9 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java
@@ -3,7 +3,9 @@
 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> {
@@ -16,4 +18,13 @@
      * @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);
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
index 17ea75b..b08055a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -12,7 +12,7 @@
 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;
@@ -321,7 +321,7 @@
                     .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(),
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
index 39a5d8a..ced10eb 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -241,7 +241,7 @@
             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)) {
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
index d42bfd6..49c98e8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
@@ -1,41 +1,53 @@
 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
@@ -49,16 +61,94 @@
         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;
+    }
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/GroupMergeUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/GroupMergeUtil.java
deleted file mode 100644
index 5ccb3d3..0000000
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/GroupMergeUtil.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package com.vincent.rsf.server.manager.utils;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.google.gson.JsonObject;
-import lombok.Data;
-
-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 GroupMergeUtil {
-    /**
-     * 鏍规嵁澶氫釜灞炴�у垎缁勫悎骞跺璞″垪琛�
-     *
-     * @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 void main(String[] args) {
-//        // 绀轰緥鏁版嵁绫�
-//        @Data
-//        class Product {
-//         private String category;
-//            private String subCategory;
-//            private String abc;
-//
-//            private String dbc;
-//
-//            private String brand;
-//            private int quantity;
-//            private double price;
-//
-//            public Product(String category, String abc, String dbc, String subCategory, String brand, int quantity, double price) {
-//                this.category = category;
-//                this.subCategory = subCategory;
-//                this.abc = abc;
-//                this.dbc = dbc;
-//                this.brand = brand;
-//                this.quantity = quantity;
-//                this.price = price;
-//            }
-//
-//            // getters...
-////            public String getCategory() { return category; }
-////            public String getSubCategory() { return subCategory; }
-////            public String getBrand() { return brand; }
-////            public int getQuantity() { return quantity; }
-////            public double getPrice() { return price; }
-//
-//            @Override
-//            public String toString() {
-//                return String.format("[%s, %s, %s] - Qty: %d, Price: %.2f",
-//                        category, subCategory, brand, quantity, price);
-//            }
-//        }
-//
-//        // 鍒涘缓娴嬭瘯鏁版嵁
-//        List<Product> products = Arrays.asList(
-//                new Product("Electronics", "Phone", "Apple", 10, 999.99),
-//                new Product("Electronics", "Phone", "Samsung", 15, 899.99),
-//                new Product("Electronics", "Phone", "Apple", 5, 999.99),
-//                new Product("Electronics", "Tablet", "Apple", 8, 799.99),
-//                new Product("Clothing", "Shirt", "Nike", 20, 29.99),
-//                new Product("Clothing", "Shirt", "Nike", 10, 29.99),
-//                new Product("Clothing", "Pants", "Adidas", 12, 49.99)
-//        );
-//
-//        // 鎸� category, subCategory, brand 鍒嗙粍锛屽悎骞� quantity 鐩稿姞锛宲rice 鍙栧钩鍧囧��
-//        List<Product> mergedProducts = groupAndMerge(
-//                products,
-//                (p1, p2) -> new Product(
-//                        p1.category,
-//                        p1.subCategory,
-//                        p1.brand,
-//                        p1.quantity + p2.quantity,
-//                        (p1.price + p2.price) / 2  // 杩欓噷绠�鍗曞钩鍧囷紝瀹為檯涓氬姟鍙兘涓嶅悓
-//                ),
-//                Product::getCategory,
-//                Product::getSubCategory,
-//                Product::getBrand
-//        );
-//
-////        // 鎵撳嵃缁撴灉
-////        System.out.println("鍚堝苟鍚庣殑浜у搧鍒楄〃:");
-////        System.out.println(JSONArray.toJSONString(mergedProducts));
-////        mergedProducts.forEach(item -> {
-////            System.out.println(JSONObject.toJSONString(item));
-////        });
-////        mergedProducts.forEach(System.out::println);
-//    }
-}
\ No newline at end of file
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/OptimalAlgorithmUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/OptimalAlgorithmUtil.java
new file mode 100644
index 0000000..7ada63a
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/OptimalAlgorithmUtil.java
@@ -0,0 +1,132 @@
+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);
+
+        // 濡傛灉娌℃湁绮剧‘瑙o紝杩斿洖鏈�鎺ヨ繎鐨勮繎浼艰В
+        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);
+
+        // 濡傛灉鎵惧埌鏇翠紭瑙o紙宸�兼洿灏忔垨宸�肩浉鍚屼絾缁勫悎鏇寸煭锛�
+        if (difference < minDifference - EPSILON ||
+                (Math.abs(difference - minDifference) < EPSILON &&
+                        (bestSolution == null || current.size() < bestSolution.size()))) {
+            minDifference = difference;
+            bestSolution = new ArrayList<>(current);
+        }
+
+        // 濡傛灉宸茬粡鎵惧埌绮剧‘瑙o紝涓嶉渶瑕佺户缁悳绱㈡洿闀跨殑缁勫悎
+        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);
+        }
+    }
+
+}
\ No newline at end of file

--
Gitblit v1.9.1