From b69b63a09089566b0b2505fbc39a673c3e18785f Mon Sep 17 00:00:00 2001
From: zjj <3272660260@qq.com>
Date: 星期一, 28 四月 2025 11:13:40 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/devlop' into devlop
---
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 | 117 +++++++-
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 | 4
/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, 587 insertions(+), 215 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..3943f40 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
@@ -218,7 +218,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("浠诲姟鐘舵�佷慨鏀瑰け璐ワ紒锛�");
}
}
@@ -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..111ac2f 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,91 @@
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