skyouc
3 天以前 456f4f168f615b1d25fcc88f35efe1d7bf933302
库存出库生成任务 优化
9个文件已修改
348 ■■■■ 已修改文件
rsf-admin/src/i18n/en.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/zh.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/outWork/outBound/OutBoundList.jsx 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/outWork/outBound/locItemInfoModal.jsx 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocItemController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/LocItemService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/en.js
@@ -1155,6 +1155,8 @@
        error: {
            stock: "Insufficient inventory to deliver !!",
            select_error_order: "Please Select Asn Orders",
            out_stock_qty: "The outbound quantity cannot be greater than the inventory quantity",
        }
    }
rsf-admin/src/i18n/zh.js
@@ -1152,6 +1152,8 @@
        error: {
            stock: "库存不足,无法提交!!", 
            select_error_order: "请选择通知单",
            out_stock_qty: "出库数量不能大于库存数量",
        }
    }
rsf-admin/src/page/outWork/outBound/OutBoundList.jsx
@@ -20,7 +20,8 @@
    useCreateController,
    useListContext,
    useRefresh,
    Edit,
    Edit,
    useRedirect,
} from 'react-admin';
import {
    Dialog,
@@ -59,13 +60,15 @@
import { Delete } from '@mui/icons-material';
import _, { set } from 'lodash';
import StaSelect from "./StaSelect";
import { redirect } from "react-router";
import { number } from "prop-types";
const OutBoundList = () => {
const OutBoundList = () => {
    const [createDialog, setCreateDialog] = useState(false);
    const [tabelData, setTableData] = useState([]);
    const [selectedRows, setSelectedRows] = useState([]);
    const [sta,setSta] = useState("");
    const [sta, setSta] = useState("");
    const notify = useNotify();
    const tableRef = useRef();
    tableRef.current = useGridApiRef();
@@ -75,7 +78,7 @@
        const newTableData = _.filter(tabelData, (item) => !selectedRows.includes(item.matnrId));
        setTableData(newTableData);
    }
    // 添加一个处理新数据的函数,设置outQty默认值
    const handleSetData = (newData) => {
        // 为新添加的数据设置outQty默认值为anfme的值
@@ -86,109 +89,126 @@
        setTableData([...tabelData, ...dataWithDefaultQty]);
    };
    return (
        <>
        <Card sx={{ p: 2, mb: 2, mt:2}}>
            <Grid container spacing={2}>
                <Grid item xs={12}>
                    <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1 }}>
                        <Typography variant="h6">
                            {translate('table.field.outBound.stockWithdrawal')}
                        </Typography>
                        <Stack direction='row' spacing={2}>
                            <Button
                                variant="contained"
                                color="primary"
                                startIcon={<AddIcon />}
                                onClick={() => setCreateDialog(true)}
                            >
                                {translate('table.field.outBound.withdrawal')}
                            </Button>
                        </Stack>
                    </Box>
            <Card sx={{ p: 2, mb: 2, mt: 2 }}>
                <Grid container spacing={2}>
                    <Grid item xs={12}>
                        <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1 }}>
                            <Typography variant="h6">
                                {translate('table.field.outBound.stockWithdrawal')}
                            </Typography>
                            <Stack direction='row' spacing={2}>
                                <Button
                                    variant="contained"
                                    color="primary"
                                    startIcon={<AddIcon />}
                                    onClick={() => setCreateDialog(true)}
                                >
                                    {translate('table.field.outBound.withdrawal')}
                                </Button>
                            </Stack>
                        </Box>
                    </Grid>
                </Grid>
            </Grid>
        </Card>
        <Card sx={{ p: 2, mb: 2}}>
            <Form>
            <Grid container spacing={2}>
                <Grid item xs={12}>
                    <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1 }}>
                        <Typography  variant="h6" >
                            {translate('table.field.outBound.outSta')}
                         </Typography>
                        <Stack direction='row' spacing={2} minWidth={200}>
                            <StaSelect
                                source="sta"
                                label={translate("table.field.outBound.outSta")}
                                onChange={(e) => {
                                    setSta(e.target.value);
                                    console.log("站点已选择:", e.target.value);
                                }}
                                size="small"
                                type="1"
                            />
                        </Stack>
                        <Stack direction='row' spacing={2} minWidth={200}>
                            <SubmitButton
                                sta={sta}
                                data={tabelData}
                            />
                        </Stack>
                    </Box>
                </Grid>
            </Grid>
            </Form>
        </Card>
        <Card sx={{ mb: 2}}>
            <Box sx={{  }}>
                <ModalTable tabelData={tabelData} setTableData={setTableData}  selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef}></ModalTable>
            </Box>
        </Card>
        <LocItemInfoModal
            </Card>
            <Card sx={{ p: 2, mb: 2 }}>
                <Form>
                    <Grid container spacing={2}>
                        <Grid item xs={12}>
                            <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1 }}>
                                <Typography variant="h6" >
                                    {translate('table.field.outBound.outSta')}
                                </Typography>
                                <Stack direction='row' spacing={2} minWidth={200}>
                                    <StaSelect
                                        source="sta"
                                        label={translate("table.field.outBound.outSta")}
                                        onChange={(e) => {
                                            setSta(e.target.value);
                                            console.log("站点已选择:", e.target.value);
                                        }}
                                        size="small"
                                        type="1"
                                    />
                                </Stack>
                                <Stack direction='row' spacing={2} minWidth={200}>
                                    <SubmitButton
                                        sta={sta}
                                        data={tabelData}
                                        setTableData={setTableData}
                                    />
                                </Stack>
                            </Box>
                        </Grid>
                    </Grid>
                </Form>
            </Card>
            <Card sx={{ mb: 2 }}>
                <Box sx={{}}>
                    <ModalTable tabelData={tabelData} setTableData={setTableData} selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef}></ModalTable>
                </Box>
            </Card>
            <LocItemInfoModal
                open={createDialog}
                setOpen={setCreateDialog}
                data={tabelData}
                setData={handleSetData}
            />
        </>
    )
}
export default OutBoundList;
const SubmitButton = (props) =>{
const SubmitButton = (props) => {
    const translate = useTranslate();
    const notify = useNotify();
    const { sta, data } = props;
    const check = ()=>{
        if(sta === "" || sta === undefined || sta === null){
    const redirect = useRedirect();
    const refresh = useRefresh();
    const { sta, data, setTableData } = props;
    const check = () => {
        if (sta === "" || sta === undefined || sta === null) {
            notify("请选择站点");
            return;
        }
        if(data.length === 0){
        if (data.length === 0) {
            notify("请选择物料");
            return;
        }
        http(sta,data);
        http(sta, data);
    }
    const http = async (sta, items) => {
        console.log(items);
    }
    const http = async (sta,data) => {
        console.log("提交数据",sta,data);
        const filter = items.filter(item => (item.outQty + item.workQty) > item.anfme);
        if (filter.length > 0) {
            notify(translate('toolbar.request.error.out_stock_qty'))
            return
        }
        const { data: { code, data, msg } } = await request.post(`/locItem/generate/task`, { siteNo: sta, items: items });
        if (code === 200) {
            notify(msg);
            refresh()
            setTableData([])
            redirect("/task")
        } else {
            notify(msg);
        }
    }
    return (
        <Button
            variant="contained"
            color="primary"
            onClick={check}
        <ConfirmButton
            variant="contained"
            color="primary"
            onConfirm={check}
            label={"table.field.outBound.createTask"}
        >
          {translate('table.field.outBound.createTask')}
        </Button>
        </ConfirmButton>
    )
}
const ModalTable = ({ tabelData, setTableData, selectedRows, setSelectedRows, tableRef }) => {
@@ -198,23 +218,32 @@
    const [columns, setColumns] = useState([
        {
            field: 'outQty',
            headerName: translate('table.field.outBound.outQty')+"*",
            headerName: translate('table.field.outBound.outQty') + "*",
            width: 100,
            editable: true,
            headerClassName: "custom",
            type: 'number',
            editable: true,
            headerClassName: "custom",
        },
        {
            field: 'anfme',
            headerName: translate('table.field.locItem.anfme'),
            type: 'number',
            width: 100,
            editable: false,
        },
        },
        {
            field: 'workQty',
            headerName: translate('table.field.locItem.workQty'),
            width: 100,
            type: 'number',
            editable: false,
        },
        {
            field: 'matnrCode',
            headerName: translate('table.field.locItem.matnrCode'),
            width: 130,
            editable: false,
        },
        },
        {
            field: 'maktx',
            headerName: translate('table.field.locItem.maktx'),
rsf-admin/src/page/outWork/outBound/locItemInfoModal.jsx
@@ -44,7 +44,7 @@
    };
    const reset = () => {
        setFormData({
        setFormData({
        })
    }
@@ -129,7 +129,7 @@
                                onChange={handleChange}
                                size="small"
                            />
                        </Grid>
                        </Grid>
                    </Grid>
                </Box>
                <Box sx={{ mt: 2 }}>
@@ -172,9 +172,9 @@
        { field: 'maktx', headerName: translate('table.field.locItem.maktx'), width: 300 },
        { field: 'batch', headerName: translate('table.field.locItem.batch'), width: 100 },
        { field: 'anfme', headerName: translate('table.field.locItem.anfme'), width: 100 },
        { field: 'workQty', headerName: translate('table.field.locItem.workQty'), width: 100 },
        { field: 'unit', headerName: translate('table.field.locItem.unit'), width: 100 },
    ])
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java
@@ -81,8 +81,4 @@
        return R.ok();
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocItemController.java
@@ -78,6 +78,15 @@
    }
    @PreAuthorize("hasAuthority('manager:locItem:list')")
    @PostMapping("/locItem/generate/task")
    public R generateTask(@RequestBody Map<String, Object> map) {
        if (Objects.isNull(map)) {
            return R.error("参数不能为空!!");
        }
        return locItemService.generateTask(map);
    }
    @PreAuthorize("hasAuthority('manager:locItem:list')")
    @PostMapping("/locItem/list")
    public R list(@RequestBody Map<String, Object> map) {
        return R.ok().add(locItemService.list());
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java
@@ -94,6 +94,10 @@
    @ApiModelProperty("项目号")
    private String projectCode;
    @ApiModelProperty("出库数量")
    @TableField(exist = false)
    private Double outQty;
    /**
     * 物料名称
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/LocItemService.java
@@ -1,8 +1,12 @@
package com.vincent.rsf.server.manager.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.server.manager.entity.LocItem;
import java.util.Map;
public interface LocItemService extends IService<LocItem> {
    R generateTask(Map<String, Object> map);
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java
@@ -1,12 +1,128 @@
package com.vincent.rsf.server.manager.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.manager.entity.Loc;
import com.vincent.rsf.server.manager.entity.Task;
import com.vincent.rsf.server.manager.entity.TaskItem;
import com.vincent.rsf.server.manager.enums.*;
import com.vincent.rsf.server.manager.mapper.LocItemMapper;
import com.vincent.rsf.server.manager.entity.LocItem;
import com.vincent.rsf.server.manager.service.LocItemService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vincent.rsf.server.manager.service.LocService;
import com.vincent.rsf.server.manager.service.TaskItemService;
import com.vincent.rsf.server.manager.service.TaskService;
import com.vincent.rsf.server.system.constant.SerialRuleCode;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Service("locItemService")
public class LocItemServiceImpl extends ServiceImpl<LocItemMapper, LocItem> implements LocItemService {
    @Autowired
    private LocService locService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private TaskItemService taskItemService;
    @Autowired
    private LocItemService locItemService;
    /**
     * 库存出库生成出库任务
     *
     * @param map
     * @return
     */
    @Override
    public R generateTask(Map<String, Object> map) {
        if (Objects.isNull(map.get("siteNo"))) {
            throw new CoolException("站点不能为空!");
        }
        if (Objects.isNull(map.get("items"))) {
            throw new CoolException("明细不能为空!");
        }
        String siteNo = map.get("siteNo").toString();
        List<LocItem> items = JSONArray.parseArray(JSONArray.toJSONString(map.get("items")), LocItem.class);
        Map<Long, List<LocItem>> listMap = items.stream().collect(Collectors.groupingBy(LocItem::getLocId));
        listMap.keySet().forEach(key -> {
            Task task = new Task();
            Loc loc = locService.getById(key);
            if (Objects.isNull(loc)) {
                throw new CoolException("数据错误:所选库存信息不存在!!");
            }
            String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null);
            task.setOrgLoc(loc.getCode())
                    .setTaskCode(ruleCode)
                    .setTargSite(siteNo)
                    .setTaskStatus(TaskStsType.GENERATE_OUT.id)
                    .setBarcode(loc.getBarcode());
            List<LocItem> locItems = this.list(new LambdaQueryWrapper<LocItem>().eq(LocItem::getLocId, key));
            if (locItems.isEmpty()) {
                throw new CoolException("数据错误:所选库存明细不存在!!");
            }
            Double orgQty = locItems.stream().mapToDouble(LocItem::getAnfme).sum();
            List<LocItem> locItemList = listMap.get(key);
            Double outQty = locItemList.stream().mapToDouble(LocItem::getOutQty).sum();
            if (orgQty.compareTo(outQty) > 0) {
                //拣料出库
                task.setTaskType(TaskType.TASK_TYPE_PICK_AGAIN_IN.type);
            } else {
                //全板出库
                task.setTaskType(TaskType.TASK_TYPE_OUT.type);
            }
            if (!taskService.save(task)) {
                throw new CoolException("任务创建失败!!");
            }
            List<TaskItem> taskItems = new ArrayList<>();
            listMap.get(key).forEach(item -> {
                TaskItem taskItem = new TaskItem();
                BeanUtils.copyProperties(item, taskItem);
                taskItem.setTaskId(task.getId())
                        .setAnfme(item.getOutQty())
                        .setBatch(item.getBatch())
                        .setOrderType(OrderType.ORDER_OUT.type)
                        .setWkType(Short.parseShort(OrderWorkType.ORDER_WORK_TYPE_STOCK_OUT.type));
                taskItems.add(taskItem);
                Double qty = Math.round((item.getWorkQty() + item.getOutQty()) * 10000) / 10000.0;
                LocItem locItem = locItemService.getById(item.getId());
                if (Objects.isNull(locItem)) {
                    throw new CoolException("库存信息不存在!");
                }
                if (locItem.getAnfme().compareTo(qty) < 0) {
                    Double minusQty = Math.round((locItem.getAnfme() - locItem.getWorkQty()) * 10000) / 10000.0;
                    item.setWorkQty(minusQty);
                } else {
                    item.setWorkQty(qty);
                }
                if (! locItemService.updateById(item)) {
                    throw new CoolException("库存信息修改失败!!");
                }
            });
            if (!taskItemService.saveBatch(taskItems)) {
                throw new CoolException("任务明细生成失败!!");
            }
        });
        return R.ok("任务生成完成!!");
    }
}