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