20个文件已添加
65个文件已修改
3330 ■■■■■ 已修改文件
.gitignore 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/config/MyDataProvider.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/en.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/zh.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/basContainer/BasContainerCreate.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/basContainer/BasContainerEdit.jsx 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/basContainer/BasContainerPanel.jsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/basStation/BasStationCreate.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/basStation/BasStationEdit.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/basStation/CrossZoneAreaField.jsx 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/deviceSite/InitModal.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/loc/BatchModal.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/loc/InitModal.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/loc/LocCreate.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/loc/LocEdit.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/loc/LocList.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/locArea/LocAreaCreate.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/locArea/LocAreaEdit.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/locAreaMat/BindLocModal.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/locAreaMat/BindMatnrModal.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/locAreaMat/LocAreaMatCreate.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/locAreaMat/LocAreaMatEdit.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/locAreaMatRela/LocAreaMatRelaCreate.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/locAreaMatRela/LocAreaMatRelaEdit.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/matnr/BindModal.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/components/AreasSortInput.jsx 286 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/components/DictionarySelect.jsx 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/components/SortableAreasInput.jsx 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/deviceBind/DeviceBindCreate.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/deviceBind/DeviceBindEdit.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/locPreview/LocPreviewEdit.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/locPreview/LocPreviewList.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/stockManage/locRevise/LocReviseCreate.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeCreate.jsx 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeEdit.jsx 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeList.jsx 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergePanel.jsx 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplateMerge/index.jsx 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseAreas/WarehouseAreasCreate.jsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseAreas/WarehouseAreasEdit.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/warehouseAreas/WarehouseAreasPanel.jsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/MissionTransferStationController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOtherController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOtherService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOtherServiceImpl.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/utils/LocUtils.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasContainer.java 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasStation.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WarehouseAreas.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/MissionStepType.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/StationTypeEnum.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/TaskStsType.java 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/TaskType.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/CheckOrderSchedules.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskMissionSchedules.java 182 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java 421 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/CheckOrderServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasDeserializer.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasSerializer.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasTypeHandler.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateMergeController.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowInstance.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepInstance.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepTemplate.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstance.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstanceNode.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateMerge.java 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMergeMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateMergeService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateMergeServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/taskPathTemplateMerge.sql 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/mapper/system/TaskPathTemplateMergeMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
version/db/man_warehouse_areas_add_sort.sql 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -37,4 +37,5 @@
.DS_Store
**/.DS_Store
logs
logs/**
logs/**
/log.path_IS_UNDEFINED/*.log
rsf-admin/src/config/MyDataProvider.js
@@ -38,7 +38,15 @@
  // get a list of records based on an array of ids
  getMany: async (resource, params) => {
    // console.log("getMany", resource, params);
    const res = await request.post(resource + "/many/" + params.ids);
    // Old format: [1, 2, 3] (array of integers)
    // New format: [{id: 1, sort: 1}, {id: 2, sort: 2}] (array of objects)
    const ids = params.ids.map(id => {
      if (typeof id === 'object' && id !== null && 'id' in id) {
        return id.id;
      }
      return id;
    });
    const res = await request.post(resource + "/many/" + ids.join(','));
    const { code, msg, data } = res.data;
    if (code === 200) {
      return Promise.resolve({
@@ -90,9 +98,18 @@
  // update a list of records based on an array of ids and a common patch
  updateMany: async (resource, params) => {
    console.log("updateMany", resource, params);
    // Extract IDs from params.ids - handle both formats:
    // Old format: [1, 2, 3] (array of integers)
    // New format: [{id: 1, sort: 1}, {id: 2, sort: 2}] (array of objects)
    const ids = params.ids.map(id => {
      if (typeof id === 'object' && id !== null && 'id' in id) {
        return id.id;
      }
      return id; // Already a number
    });
    const res = await request.post(
      resource + "/update/many",
      params.ids.map((id) => ({ id, ...params.data })),
      ids.map((id) => ({ id, ...params.data })),
    );
    const { code, msg, data } = res.data;
    if (code === 200) {
@@ -121,7 +138,16 @@
  // delete a list of records based on an array of ids
  deleteMany: async (resource, params) => {
    console.log("deleteMany", resource, params);
    const res = await request.post(resource + "/remove/" + params?.ids);
    // Extract IDs from params.ids - handle both formats:
    // Old format: [1, 2, 3] (array of integers)
    // New format: [{id: 1, sort: 1}, {id: 2, sort: 2}] (array of objects)
    const ids = params?.ids ? params.ids.map(id => {
      if (typeof id === 'object' && id !== null && 'id' in id) {
        return id.id;
      }
      return id; // Already a number
    }) : [];
    const res = await request.post(resource + "/remove/" + ids.join(','));
    const { code, msg, data } = res.data;
    if (code === 200) {
      return Promise.resolve({
rsf-admin/src/i18n/en.js
@@ -428,6 +428,7 @@
                name: "name",
                wareId: "ware",
                code: "code",
                sort: "Sort",
                shipperId: "shipperId",
                supplierId: "supplierId",
                flagMinus: "flagMinus",
rsf-admin/src/i18n/zh.js
@@ -458,6 +458,7 @@
                type: "库区类型",
                wareId: "所属仓库",
                code: "库区编码",
                sort: "序号",
                shipperId: "货主",
                supplierId: "供应商",
                flagMinus: "允许负库存",
rsf-admin/src/page/basicInfo/basContainer/BasContainerCreate.jsx
@@ -103,7 +103,7 @@
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <ReferenceArrayInput source="areas" reference="warehouseAreas">
                                    <ReferenceArrayInput source="areas" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }}>
                                        <SelectArrayInput
                                            label="table.field.basStation.crossZoneArea"
                                            optionText="name"
rsf-admin/src/page/basicInfo/basContainer/BasContainerEdit.jsx
@@ -30,6 +30,7 @@
import MemoInput from "../../components/MemoInput";
import StatusSelectInput from "../../components/StatusSelectInput";
import DictionarySelect from "../../components/DictionarySelect";
import AreasSortInput from "../../components/AreasSortInput";
const FormToolbar = () => {
    const { getValues } = useFormContext();
@@ -51,6 +52,51 @@
            mutationMode={EDIT_MODE}
            actions={<CustomerTopToolBar />}
            aside={<EditBaseAside />}
            transform={(data) => {
                // 保存前转换:将 areas 从纯ID数组转换为 [{id, sort}] 格式
                // 从隐藏字段 areasSort 获取排序信息
                const areas = data.areas || [];
                const areasSort = data.areasSort || [];
                if (areas.length > 0) {
                    if (typeof areas[0] === 'number') {
                        // 如果是纯ID数组,使用 areasSort 中的排序信息
                        if (areasSort.length > 0 && typeof areasSort[0] === 'object') {
                            // 使用 areasSort 中的排序信息,但只保留 areas 中存在的ID
                            const areaIds = new Set(areas);
                            const sortedAreas = areasSort
                                .filter(item => areaIds.has(item.id))
                                .sort((a, b) => (a.sort || 0) - (b.sort || 0));
                            // 如果 areasSort 中有所有ID的排序信息,使用它
                            if (sortedAreas.length === areas.length) {
                                data.areas = sortedAreas;
                            } else {
                                // 否则,为缺失的ID添加默认排序
                                const existingIds = new Set(sortedAreas.map(item => item.id));
                                const missingIds = areas.filter(id => !existingIds.has(id));
                                const maxSort = sortedAreas.length > 0
                                    ? Math.max(...sortedAreas.map(item => item.sort || 1))
                                    : 0;
                                const newItems = missingIds.map((id, index) => ({
                                    id: id,
                                    sort: maxSort + index + 1,
                                }));
                                data.areas = [...sortedAreas, ...newItems];
                            }
                        } else {
                            // 如果没有排序信息,使用默认排序
                            data.areas = areas.map((id, index) => ({
                                id: id,
                                sort: index + 1,
                            }));
                        }
                    }
                    // 删除临时字段
                    delete data.areasSort;
                }
                return data;
            }}
        >
            <SimpleForm
                shouldUnregister
@@ -82,7 +128,26 @@
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <ReferenceArrayInput source="areas" reference="warehouseAreas">
                            <ReferenceArrayInput
                                source="areas"
                                reference="warehouseAreas"
                                sort={{ field: 'sort', order: 'ASC' }}
                                format={(value) => {
                                    // 从后端接收时:将 [{id, sort}] 转换为 [id, id, ...]
                                    if (!value || !Array.isArray(value)) return [];
                                    if (value.length === 0) return [];
                                    // 如果是对象数组,提取id
                                    if (typeof value[0] === 'object' && value[0] !== null && value[0].id !== undefined) {
                                        return value.map(item => item.id);
                                    }
                                    // 如果已经是纯ID数组,直接返回
                                    return value;
                                }}
                                parse={(value) => {
                                    // 保存时:保持原值,由 AreasSortInput 处理转换
                                    return value;
                                }}
                            >
                                <SelectArrayInput
                                    label="table.field.basContainer.areas"
                                    optionText="name"
@@ -92,6 +157,10 @@
                                />
                            </ReferenceArrayInput>
                        </Stack>
                        {/* 下方显示已选库区和排序编辑 */}
                        <AreasSortInput source="areas" />
                        {/* 隐藏字段:存储排序信息 */}
                        <TextInput source="areasSort" style={{ display: 'none' }} />
                    </Grid>
                    <Grid item xs={12} md={4}>
rsf-admin/src/page/basicInfo/basContainer/BasContainerPanel.jsx
@@ -55,7 +55,16 @@
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.basContainer.areas" 
                                property={record.areas}
                                property={
                                    record.areas && Array.isArray(record.areas)
                                        ? record.areas.map(area => {
                                            if (typeof area === 'object' && area !== null && 'id' in area) {
                                                return area.id;
                                            }
                                            return area;
                                        }).join(', ')
                                        : record.areas
                                }
                            />
                        </Grid>
rsf-admin/src/page/basicInfo/basStation/BasStationCreate.jsx
@@ -148,7 +148,7 @@
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <ReferenceInput source="area" reference="warehouseAreas">
                                    <ReferenceInput source="area" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }}>
                                        <SelectInput
                                            label="table.field.basStation.area"
                                            optionText="name"
@@ -170,7 +170,7 @@
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <ReferenceArrayInput source="areaIds" reference="warehouseAreas">
                                    <ReferenceArrayInput source="areaIds" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }}>
                                        <SelectArrayInput
                                            label="table.field.basStation.crossZoneArea"
                                            optionText="name"
rsf-admin/src/page/basicInfo/basStation/BasStationEdit.jsx
@@ -122,7 +122,7 @@
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>                            
                            <ReferenceInput source="area" reference="warehouseAreas">
                            <ReferenceInput source="area" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }}>
                                <SelectInput 
                                    label="table.field.basStation.area"
                                    optionText="name"
@@ -144,7 +144,7 @@
                            />
                        </Stack>
                        <Stack direction='row' gap={2}> 
                            <ReferenceArrayInput source="areaIds" reference="warehouseAreas">
                            <ReferenceArrayInput source="areaIds" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }}>
                                <SelectArrayInput 
                                    label="table.field.basStation.crossZoneArea"
                                    optionText="name"
rsf-admin/src/page/basicInfo/basStation/CrossZoneAreaField.jsx
@@ -24,9 +24,43 @@
        setLoading(true);
        try {
            const res = await request.post(`/warehouseAreas/many/${record.areas.join(',')}`);
            // 提取排序信息和ID
            // Old format: [1, 2, 3] (array of integers)
            // New format: [{id: 1, sort: 1}, {id: 2, sort: 2}] (array of objects)
            const isObjectArray = record.areas.length > 0 &&
                typeof record.areas[0] === 'object' &&
                record.areas[0] !== null &&
                'id' in record.areas[0];
            let areaIds = [];
            let sortMap = new Map(); // 存储 id -> sort 的映射
            if (isObjectArray) {
                // 对象数组格式,提取ID和排序信息
                areaIds = record.areas.map(area => {
                    const id = area.id;
                    sortMap.set(id, area.sort || 0);
                    return id;
                });
            } else {
                // 纯ID数组格式
                areaIds = record.areas.map(id => Number(id));
            }
            const res = await request.post(`/warehouseAreas/many/${areaIds.join(',')}`);
            if (res?.data?.code === 200) {
                setAreaNames(res.data.data || []);
                let areas = res.data.data || [];
                // 如果有排序信息,按排序值排序
                if (sortMap.size > 0) {
                    areas = areas.sort((a, b) => {
                        const sortA = sortMap.get(a.id) || 0;
                        const sortB = sortMap.get(b.id) || 0;
                        return sortA - sortB;
                    });
                }
                setAreaNames(areas);
            }
        } catch (error) {
            console.error('获取区域名称失败:', error);
rsf-admin/src/page/basicInfo/deviceSite/InitModal.jsx
@@ -160,12 +160,12 @@
                                />
                            </Grid>
                            <Grid item xs={6} display="flex" gap={1}>
                                <ReferenceInput source="areaIdStart" label="table.field.deviceBind.typeId" reference="warehouseAreas" filter={{}}>
                                <ReferenceInput source="areaIdStart" label="table.field.deviceBind.typeId" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }} filter={{}}>
                                    <AutocompleteInput optionValue="id" optionText="name" label={translate('table.field.deviceSite.areaIdStart')} />
                                </ReferenceInput>
                            </Grid>
                            <Grid item xs={6} display="flex" gap={1}>
                                <ReferenceInput source="areaIdEnd" label="table.field.deviceBind.typeId" reference="warehouseAreas" filter={{}}>
                                <ReferenceInput source="areaIdEnd" label="table.field.deviceBind.typeId" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }} filter={{}}>
                                    <AutocompleteInput optionValue="id" optionText="name" label={translate('table.field.deviceSite.areaIdEnd')} />
                                </ReferenceInput>
                            </Grid>
rsf-admin/src/page/basicInfo/loc/BatchModal.jsx
@@ -155,6 +155,7 @@
                                    <ReferenceInput
                                        source="areaId"
                                        reference="warehouseAreas"
                                        sort={{ field: 'sort', order: 'ASC' }}
                                    >
                                        <AutocompleteInput
                                            label="table.field.loc.areaId"
rsf-admin/src/page/basicInfo/loc/InitModal.jsx
@@ -138,6 +138,7 @@
                                <ReferenceInput
                                    source="areaId"
                                    reference="warehouseAreas"
                                    sort={{ field: 'sort', order: 'ASC' }}
                                    filter={{ warehouseId: formData.warehouseId }}
                                >
                                    <AutocompleteInput
rsf-admin/src/page/basicInfo/loc/LocCreate.jsx
@@ -112,6 +112,7 @@
                                    <ReferenceInput
                                        source="areaId"
                                        reference="warehouseAreas"
                                        sort={{ field: 'sort', order: 'ASC' }}
                                        filter={{ warehouseId }}
                                    >
                                        <AutocompleteInput
rsf-admin/src/page/basicInfo/loc/LocEdit.jsx
@@ -104,6 +104,7 @@
                                    <ReferenceInput
                                        source="areaId"
                                        reference="warehouseAreas"
                                        sort={{ field: 'sort', order: 'ASC' }}
                                        filter={{ warehouseId }}
                                    >
                                        <AutocompleteInput
rsf-admin/src/page/basicInfo/loc/LocList.jsx
@@ -109,6 +109,7 @@
            source="areaId"
            label="table.field.loc.areaId"
            reference="warehouseAreas"
            sort={{ field: 'sort', order: 'ASC' }}
        >
            <AutocompleteInput
                label="table.field.loc.areaId"
rsf-admin/src/page/basicInfo/locArea/LocAreaCreate.jsx
@@ -104,6 +104,7 @@
                                    <ReferenceInput
                                        source="areaId"
                                        reference="warehouseAreas"
                                        sort={{ field: 'sort', order: 'ASC' }}
                                    >
                                        <AutocompleteInput
                                            label="table.field.locArea.areaId"
rsf-admin/src/page/basicInfo/locArea/LocAreaEdit.jsx
@@ -84,6 +84,7 @@
                            <ReferenceInput
                                source="areaId"
                                reference="warehouseAreas"
                                sort={{ field: 'sort', order: 'ASC' }}
                            >
                                <AutocompleteInput
                                    label="table.field.locArea.areaId"
rsf-admin/src/page/basicInfo/locAreaMat/BindLocModal.jsx
@@ -159,6 +159,7 @@
                                <ReferenceInput
                                    source="areaId"
                                    reference="warehouseAreas"
                                    sort={{ field: 'sort', order: 'ASC' }}
                                >
                                    <AutocompleteInput
                                        label="table.field.loc.areaId"
rsf-admin/src/page/basicInfo/locAreaMat/BindMatnrModal.jsx
@@ -158,6 +158,7 @@
                                <ReferenceInput
                                    source="areaId"
                                    reference="warehouseAreas"
                                    sort={{ field: 'sort', order: 'ASC' }}
                                >
                                    <AutocompleteInput
                                        label="table.field.loc.areaId"
rsf-admin/src/page/basicInfo/locAreaMat/LocAreaMatCreate.jsx
@@ -124,6 +124,7 @@
                                    <ReferenceInput
                                        source="areaId"
                                        reference="warehouseAreas"
                                        sort={{ field: 'sort', order: 'ASC' }}
                                        filter={{ warehouseId }}
                                    >
                                        <AutocompleteInput
rsf-admin/src/page/basicInfo/locAreaMat/LocAreaMatEdit.jsx
@@ -102,6 +102,7 @@
                            <ReferenceInput
                                source="areaId"
                                reference="warehouseAreas"
                                sort={{ field: 'sort', order: 'ASC' }}
                                filter={{ warehouseId }}
                            >
                                <AutocompleteInput
rsf-admin/src/page/basicInfo/locAreaMatRela/LocAreaMatRelaCreate.jsx
@@ -90,6 +90,7 @@
                                    <ReferenceInput
                                        source="areaId"
                                        reference="warehouseAreas"
                                        sort={{ field: 'sort', order: 'ASC' }}
                                    >
                                        <AutocompleteInput
                                            label="table.field.locAreaMatRela.areaId"
rsf-admin/src/page/basicInfo/locAreaMatRela/LocAreaMatRelaEdit.jsx
@@ -70,6 +70,7 @@
                            <ReferenceInput
                                source="areaId"
                                reference="warehouseAreas"
                                sort={{ field: 'sort', order: 'ASC' }}
                            >
                                <AutocompleteInput
                                    label="table.field.locAreaMatRela.areaId"
rsf-admin/src/page/basicInfo/matnr/BindModal.jsx
@@ -159,6 +159,7 @@
                                <ReferenceInput
                                    source="areaId"
                                    reference="warehouseAreas"
                                    sort={{ field: 'sort', order: 'ASC' }}
                                >
                                    <AutocompleteInput
                                        label="table.field.loc.areaId"
rsf-admin/src/page/components/AreasSortInput.jsx
New file
@@ -0,0 +1,286 @@
/**
 *
 * @author chen.lin
 * @time 2026-02-02
 * 库区排序编辑组件
 * 功能:显示已选中的库区,允许修改排序值
 * 数据格式:[{"id": 1, "sort": 1}, {"id": 2, "sort": 2}]
 *
 * 使用隐藏字段存储排序信息,保存时合并到 areas 字段
 */
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import {
    Box,
    TextField,
    Chip,
    IconButton,
    Paper,
    Typography,
    Stack,
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import request from '@/utils/request';
const AreasSortInput = ({ source }) => {
    const { setValue, watch } = useFormContext();
    const sortSource = `${source}Sort`; // 隐藏字段名:areasSort
    const [areas, setAreas] = useState([]);
    const [selectedAreas, setSelectedAreas] = useState([]);
    const [loading, setLoading] = useState(false);
    const currentValue = watch(source) || [];
    const prevSelectedAreasRef = useRef([]);
    // 加载所有库区选项(用于获取名称)
    useEffect(() => {
        const loadAreas = async () => {
            setLoading(true);
            try {
                const res = await request.post('/warehouseAreas/list', {});
                if (res?.data?.code === 200) {
                    setAreas(res.data.data || []);
                } else {
                    console.error('加载库区失败:', res?.data?.msg);
                }
            } catch (error) {
                console.error('加载库区失败:', error);
            } finally {
                setLoading(false);
            }
        };
        loadAreas();
    }, []);
    // 初始化选中项:将后端数据格式转换为前端格式
    useEffect(() => {
        // currentValue 现在应该是纯ID数组 [1, 2, 3],因为 ReferenceArrayInput 的 format 已经处理了
        if (currentValue && currentValue.length > 0) {
            // 检查是否是纯ID数组
            const isIdArray = currentValue.every(item => typeof item === 'number' || typeof item === 'string');
            if (isIdArray) {
                // 纯ID数组格式 [1, 2, 3],需要与现有数据合并
                const currentIds = new Set(currentValue.map(id => Number(id)));
                // 保留已有的排序信息(如果存在)
                const existingAreas = prevSelectedAreasRef.current.filter(item =>
                    currentIds.has(Number(item.id))
                );
                const existingIds = new Set(existingAreas.map(item => Number(item.id)));
                // 找出新增的ID
                const newIds = currentValue
                    .map(id => Number(id))
                    .filter(id => !existingIds.has(id));
                // 为新增的ID创建排序项(默认排序从库区的 sort 字段获取,如果没有则使用已有最大排序值+1)
                const maxSort = existingAreas.length > 0
                    ? Math.max(...existingAreas.map(item => item.sort || 1), 0)
                    : 0;
                const newItems = newIds.map((id, index) => {
                    // 从 areas 数组中查找对应库区的 sort 字段
                    const area = areas.find(a => a.id === id);
                    const defaultSort = area && area.sort !== undefined && area.sort !== null
                        ? area.sort
                        : (maxSort + index + 1);
                    return {
                        id: id,
                        sort: defaultSort,
                    };
                });
                // 合并已有项和新项
                const converted = [...existingAreas, ...newItems];
                setSelectedAreas(converted);
                prevSelectedAreasRef.current = converted;
                // 保存排序信息到隐藏字段
                setValue(sortSource, converted, { shouldValidate: false });
            } else {
                // 如果已经是对象数组格式 [{id, sort}](从后端直接加载的情况)
                const sorted = [...currentValue].sort((a, b) => {
                    const sortA = a.sort || 0;
                    const sortB = b.sort || 0;
                    return sortA - sortB;
                });
                setSelectedAreas(sorted);
                prevSelectedAreasRef.current = sorted;
                // 保存排序信息到隐藏字段
                setValue(sortSource, sorted, { shouldValidate: false });
                // 同时更新 ReferenceArrayInput 的值为纯ID数组
                const ids = sorted.map(item => item.id);
                setValue(source, ids, { shouldValidate: false });
            }
        } else {
            setSelectedAreas([]);
            prevSelectedAreasRef.current = [];
            // 清空排序信息
            setValue(sortSource, [], { shouldValidate: false });
        }
    }, [currentValue, source, setValue]);
    // 处理删除库区
    const handleDeleteArea = (id) => {
        const filtered = selectedAreas.filter(item => item.id !== id);
        setSelectedAreas(filtered);
        prevSelectedAreasRef.current = filtered;
        // 更新表单值为纯ID数组(ReferenceArrayInput 需要)
        const ids = filtered.map(item => item.id);
        setValue(source, ids, { shouldValidate: true, shouldDirty: true, shouldTouch: true });
        // 更新排序信息
        setValue(sortSource, filtered, { shouldValidate: false, shouldDirty: true, shouldTouch: true });
    };
    // 处理修改排序值
    const handleSortChange = (id, newSort) => {
        const sortValue = parseInt(newSort) || 1;
        const updated = selectedAreas.map(item => {
            if (item.id === id) {
                return { ...item, sort: sortValue };
            }
            return item;
        });
        // 按排序值排序
        const sorted = [...updated].sort((a, b) => {
            const sortA = a.sort || 0;
            const sortB = b.sort || 0;
            return sortA - sortB;
        });
        setSelectedAreas(sorted);
        prevSelectedAreasRef.current = sorted;
        // 更新排序信息到隐藏字段,设置 shouldDirty: true 以触发表单的 dirty 状态
        setValue(sortSource, sorted, { shouldValidate: false, shouldDirty: true, shouldTouch: true });
    };
    // 获取库区名称
    const getAreaName = (id) => {
        if (!id) return '未知';
        const area = areas.find(a => a.id === id);
        return area ? area.name : `ID: ${id}`;
    };
    // 确保列表始终按排序值排序
    const sortedAreas = useMemo(() => {
        if (!selectedAreas || selectedAreas.length === 0) {
            return [];
        }
        return [...selectedAreas].sort((a, b) => {
            const sortA = a.sort || 0;
            const sortB = b.sort || 0;
            return sortA - sortB;
        });
    }, [selectedAreas]);
    // 如果没有选中的库区,不显示
    if (!sortedAreas || sortedAreas.length === 0) {
        return null;
    }
    return (
        <Box sx={{ mt: 2 }}>
            <Typography
                variant="body2"
                sx={{
                    mb: 1,
                    color: 'text.secondary',
                    width: '100%',
                }}
            >
                已选库区(可修改排序):
            </Typography>
            {/* 表头 */}
            <Paper
                elevation={0}
                sx={{
                    p: 1,
                    display: 'flex',
                    alignItems: 'center',
                    gap: 2,
                    width: '100%',
                    bgcolor: 'grey.100',
                    mb: 0.5,
                }}
            >
                <Box sx={{ flex: 1, minWidth: 0 }}>
                    <Typography variant="body2" sx={{ fontWeight: 'medium' }}>
                        库区
                    </Typography>
                </Box>
                <Box sx={{ width: 120, textAlign: 'center' }}>
                    <Typography variant="body2" sx={{ fontWeight: 'medium' }}>
                        排序
                    </Typography>
                </Box>
                <Box sx={{ width: 48 }}></Box> {/* 删除按钮占位 */}
            </Paper>
            <Stack spacing={1}>
                {sortedAreas.map((item) => {
                    // 确保 item.id 存在且有效
                    if (!item || item.id === undefined || item.id === null) {
                        return null;
                    }
                    return (
                        <Paper
                            key={item.id}
                            elevation={1}
                            sx={{
                                p: 1.5,
                                display: 'flex',
                                alignItems: 'center',
                                gap: 2,
                                width: '100%',
                                height: '30px',
                                minHeight: 'auto',
                                maxWidth: '100%',
                                boxSizing: 'border-box',
                            }}
                        >
                            {/* 左边:库区名称 */}
                            <Box sx={{ flex: 1, minWidth: 0 }}>
                                <Chip
                                    label={getAreaName(item.id)}
                                    size="small"
                                    sx={{ maxWidth: '100%' }}
                                />
                            </Box>
                            {/* 右边:排序输入框(无 label) */}
                            <TextField
                                type="number"
                                value={item.sort || ''}
                                onChange={(e) => handleSortChange(item.id, e.target.value)}
                                size="small"
                                placeholder="排序"
                                sx={{
                                    width: 120,
                                    '& .MuiInputBase-root': {
                                        height: '30px',
                                    },
                                    '& .MuiInputBase-input': {
                                        height: '30px',
                                        padding: '8.5px 14px',
                                    }
                                }}
                                inputProps={{
                                    min: 1,
                                    step: 1
                                }}
                            />
                            {/* 删除按钮 */}
                            <IconButton
                                size="small"
                                onClick={() => handleDeleteArea(item.id)}
                                color="error"
                            >
                                <DeleteIcon fontSize="small" />
                            </IconButton>
                        </Paper>
                    );
                })}
            </Stack>
        </Box>
    );
};
export default AreasSortInput;
rsf-admin/src/page/components/DictionarySelect.jsx
@@ -1,10 +1,11 @@
import EditIcon from '@mui/icons-material/Edit';
import { useState, useEffect } from 'react';
import { useState, useEffect, useMemo } from 'react';
import {
    Button, useListContext, SelectInput,
    required, SelectArrayInput,
    useTranslate, useNotify
} from 'react-admin';
import { useFormContext } from 'react-hook-form';
import request from '@/utils/request';
const DictionarySelect = (props) => {
@@ -19,8 +20,12 @@
    } = props;
    const translate = useTranslate();
    const notify = useNotify();
    const { watch } = useFormContext();
    const [list, setList] = useState([]);
    const [loading, setLoading] = useState(false);
    // 获取当前表单值
    const currentValue = watch(name);
    useEffect(() => {
        http();
@@ -54,12 +59,33 @@
        }
    };
    // 确保当前值在选项中,如果不在且正在加载,添加占位选项
    const choices = useMemo(() => {
        if (!list || list.length === 0) {
            // 如果列表为空但当前有值,添加占位选项以避免警告
            if (currentValue !== undefined && currentValue !== null && currentValue !== '') {
                return [{ id: currentValue, name: String(currentValue) }];
            }
            return [];
        }
        // 检查当前值是否在选项中
        const valueExists = list.some(item => String(item.id) === String(currentValue));
        // 如果当前值不在选项中,添加它(可能是加载延迟导致的)
        if (currentValue !== undefined && currentValue !== null && currentValue !== '' && !valueExists) {
            return [...list, { id: currentValue, name: String(currentValue) }];
        }
        return list;
    }, [list, currentValue]);
    const InputComponent = multiple ? SelectArrayInput : SelectInput;
    return (
        <InputComponent
            source={name}
            choices={list}
            choices={choices}
            isLoading={loading}
            {...parmas}
        />
rsf-admin/src/page/components/SortableAreasInput.jsx
New file
@@ -0,0 +1,208 @@
import React, { useState, useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import {
    Box,
    TextField,
    Autocomplete,
    Chip,
    IconButton,
    Paper,
    Typography,
    Stack,
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import request from '@/utils/request';
/**
 * @author chen.lin
 * @time 2026-02-02
 * 可排序的库区选择组件
 * 数据格式:[{"id": 1, "sort": 1}, {"id": 2, "sort": 2}]
 *
 * 功能:
 * 1. 上方:多选框选择库区
 * 2. 下方:显示已选中的库区,每行显示库区名称和排序输入框
 * 3. 默认排序为1,可以修改排序值
 */
const SortableAreasInput = ({ source, label, validate, ...props }) => {
    const { setValue, watch } = useFormContext();
    const [areas, setAreas] = useState([]);
    const [selectedAreas, setSelectedAreas] = useState([]);
    const [loading, setLoading] = useState(false);
    const currentValue = watch(source) || [];
    // 加载所有库区选项
    useEffect(() => {
        const loadAreas = async () => {
            setLoading(true);
            try {
                const res = await request.post('/warehouseAreas/list', {});
                if (res?.data?.code === 200) {
                    setAreas(res.data.data || []);
                } else {
                    console.error('加载库区失败:', res?.data?.msg);
                }
            } catch (error) {
                console.error('加载库区失败:', error);
            } finally {
                setLoading(false);
            }
        };
        loadAreas();
    }, []);
    // 初始化选中项:将后端数据格式转换为前端格式
    useEffect(() => {
        if (currentValue && currentValue.length > 0) {
            // 如果已经是对象数组格式 [{id, sort}]
            if (typeof currentValue[0] === 'object' && currentValue[0].id !== undefined) {
                const sorted = [...currentValue].sort((a, b) => {
                    const sortA = a.sort || 0;
                    const sortB = b.sort || 0;
                    return sortA - sortB;
                });
                setSelectedAreas(sorted);
            } else {
                // 如果是旧格式 [1, 2, 3],转换为新格式,默认排序为1
                const converted = currentValue.map((id, index) => ({
                    id: id,
                    sort: index + 1,
                }));
                setSelectedAreas(converted);
                // 同步更新表单值
                setValue(source, converted, { shouldValidate: false });
            }
        } else {
            setSelectedAreas([]);
        }
    }, [currentValue, source, setValue]);
    // 处理添加库区
    const handleAddArea = (event, newValue) => {
        if (newValue && !selectedAreas.find(item => item.id === newValue.id)) {
            // 默认排序为1,如果已有数据,使用最大排序值+1
            const maxSort = selectedAreas.length > 0
                ? Math.max(...selectedAreas.map(item => item.sort || 1))
                : 0;
            const newItem = {
                id: newValue.id,
                sort: maxSort + 1,
            };
            const updated = [...selectedAreas, newItem];
            setSelectedAreas(updated);
            setValue(source, updated, { shouldValidate: true });
        }
    };
    // 处理删除库区
    const handleDeleteArea = (id) => {
        const filtered = selectedAreas.filter(item => item.id !== id);
        setSelectedAreas(filtered);
        setValue(source, filtered, { shouldValidate: true });
    };
    // 处理修改排序值
    const handleSortChange = (id, newSort) => {
        const sortValue = parseInt(newSort) || 1;
        const updated = selectedAreas.map(item => {
            if (item.id === id) {
                return { ...item, sort: sortValue };
            }
            return item;
        });
        setSelectedAreas(updated);
        setValue(source, updated, { shouldValidate: true });
    };
    // 获取库区名称
    const getAreaName = (id) => {
        const area = areas.find(a => a.id === id);
        return area ? area.name : `ID: ${id}`;
    };
    // 过滤已选中的选项
    const availableOptions = areas.filter(
        area => !selectedAreas.find(item => item.id === area.id)
    );
    return (
        <Box>
            {/* 多选框 */}
            <Autocomplete
                multiple
                options={availableOptions}
                getOptionLabel={(option) => option.name || ''}
                onChange={handleAddArea}
                loading={loading}
                value={[]} // 始终为空,因为已选中的在下方面板显示
                renderInput={(params) => (
                    <TextField
                        {...params}
                        label={label}
                        placeholder="选择库区..."
                        variant="outlined"
                        size="small"
                    />
                )}
                sx={{ mb: 2 }}
            />
            {/* 已选中的库区列表,显示键值对(库区名称:排序值) */}
            {selectedAreas.length > 0 && (
                <Box sx={{ mt: 2 }}>
                    <Typography variant="body2" sx={{ mb: 1, color: 'text.secondary' }}>
                        已选库区(可修改排序):
                    </Typography>
                    <Stack spacing={1}>
                        {selectedAreas.map((item) => (
                            <Paper
                                key={item.id}
                                elevation={1}
                                sx={{
                                    p: 1.5,
                                    display: 'flex',
                                    alignItems: 'center',
                                    gap: 2,
                                }}
                            >
                                {/* 左边:库区名称 */}
                                <Box sx={{ flex: 1, minWidth: 0 }}>
                                    <Chip
                                        label={getAreaName(item.id)}
                                        size="small"
                                        sx={{ maxWidth: '100%' }}
                                    />
                                </Box>
                                {/* 右边:排序输入框 */}
                                <TextField
                                    type="number"
                                    label="排序"
                                    value={item.sort || ''}
                                    onChange={(e) => handleSortChange(item.id, e.target.value)}
                                    size="small"
                                    sx={{ width: 120 }}
                                    inputProps={{
                                        min: 1,
                                        step: 1
                                    }}
                                />
                                {/* 删除按钮 */}
                                <IconButton
                                    size="small"
                                    onClick={() => handleDeleteArea(item.id)}
                                    color="error"
                                >
                                    <DeleteIcon fontSize="small" />
                                </IconButton>
                            </Paper>
                        ))}
                    </Stack>
                </Box>
            )}
        </Box>
    );
};
export default SortableAreasInput;
rsf-admin/src/page/deviceBind/DeviceBindCreate.jsx
@@ -129,7 +129,7 @@
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>                                    
                                    <ReferenceInput source="typeId" label="table.field.deviceBind.typeId" reference="warehouseAreas" filter={{}}>
                                    <ReferenceInput source="typeId" label="table.field.deviceBind.typeId" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }} filter={{}}>
                                        <AutocompleteInput optionValue="id" optionText="name" label="table.field.deviceBind.typeId" />
                                    </ReferenceInput>
                                </Grid>
rsf-admin/src/page/deviceBind/DeviceBindEdit.jsx
@@ -108,7 +108,7 @@
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <ReferenceInput source="typeId" label="table.field.deviceBind.typeId" reference="warehouseAreas" filter={{}}>
                            <ReferenceInput source="typeId" label="table.field.deviceBind.typeId" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }} filter={{}}>
                                <AutocompleteInput optionValue="id" optionText="name" label="table.field.deviceBind.typeId" />
                            </ReferenceInput>
                        </Stack>
rsf-admin/src/page/locPreview/LocPreviewEdit.jsx
@@ -95,6 +95,7 @@
                                    <ReferenceInput
                                        source="areaId"
                                        reference="warehouseAreas"
                                        sort={{ field: 'sort', order: 'ASC' }}
                                        filter={{ warehouseId }}
                                    >
                                        <AutocompleteInput
rsf-admin/src/page/locPreview/LocPreviewList.jsx
@@ -95,6 +95,7 @@
            source="areaId"
            label="table.field.loc.areaId"
            reference="warehouseAreas"
            sort={{ field: 'sort', order: 'ASC' }}
        >
            <AutocompleteInput
                label="table.field.loc.areaId"
rsf-admin/src/page/stockManage/locRevise/LocReviseCreate.jsx
@@ -180,7 +180,7 @@
                                />
                            </Grid>
                            <Grid item md={2}>
                                <ReferenceInput source="areaId" reference="warehouseAreas">
                                <ReferenceInput source="areaId" reference="warehouseAreas" sort={{ field: 'sort', order: 'ASC' }}>
                                    <AutocompleteInput
                                        optionText='name'
                                        optionValue="id"
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeCreate.jsx
New file
@@ -0,0 +1,219 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
    CreateBase,
    useTranslate,
    TextInput,
    NumberInput,
    BooleanInput,
    DateInput,
    SaveButton,
    SelectInput,
    ReferenceInput,
    ReferenceArrayInput,
    AutocompleteInput,
    Toolbar,
    required,
    useDataProvider,
    useNotify,
    Form,
    useCreateController,
} from 'react-admin';
import {
    Dialog,
    DialogActions,
    DialogContent,
    DialogTitle,
    Stack,
    Grid,
    Box,
} from '@mui/material';
import DialogCloseButton from "../components/DialogCloseButton";
import StatusSelectInput from "../components/StatusSelectInput";
import MemoInput from "../components/MemoInput";
const TaskPathTemplateMergeCreate = (props) => {
    const { open, setOpen } = props;
    const translate = useTranslate();
    const notify = useNotify();
    const handleClose = (event, reason) => {
        if (reason !== "backdropClick") {
            setOpen(false);
        }
    };
    const handleSuccess = async (data) => {
        setOpen(false);
        notify('common.response.success');
    };
    const handleError = async (error) => {
        notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } });
    };
    return (
        <>
            <CreateBase
                record={{}}
                transform={(data) => {
                    return data;
                }}
                mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
            >
                <Dialog
                    open={open}
                    onClose={handleClose}
                    aria-labelledby="form-dialog-title"
                    fullWidth
                    disableRestoreFocus
                    maxWidth="md"   // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
                >
                    <Form>
                        <DialogTitle id="form-dialog-title" sx={{
                            position: 'sticky',
                            top: 0,
                            backgroundColor: 'background.paper',
                            zIndex: 1000
                        }}
                        >
                            {translate('create.title')}
                            <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
                                <DialogCloseButton onClose={handleClose} />
                            </Box>
                        </DialogTitle>
                        <DialogContent sx={{ mt: 2 }}>
                            <Grid container rowSpacing={2} columnSpacing={2}>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <TextInput
                                        label="table.field.taskPathTemplateMerge.templateCode"
                                        source="templateCode"
                                        parse={v => v}
                                        autoFocus
                                        validate={required()}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <TextInput
                                        label="table.field.taskPathTemplateMerge.templateName"
                                        source="templateName"
                                        parse={v => v}
                                        validate={required()}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <TextInput
                                        label="table.field.taskPathTemplateMerge.sourceType"
                                        source="sourceType"
                                        parse={v => v}
                                        validate={required()}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <TextInput
                                        label="table.field.taskPathTemplateMerge.targetType"
                                        source="targetType"
                                        parse={v => v}
                                        validate={required()}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <TextInput
                                        label="table.field.taskPathTemplateMerge.conditionExpression"
                                        source="conditionExpression"
                                        parse={v => v}
                                        validate={required()}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <TextInput
                                        label="table.field.taskPathTemplateMerge.conditionDesc"
                                        source="conditionDesc"
                                        parse={v => v}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.taskPathTemplateMerge.version"
                                        source="version"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.taskPathTemplateMerge.isCurrent"
                                        source="isCurrent"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <DateInput
                                        label="table.field.taskPathTemplateMerge.effectiveTime"
                                        source="effectiveTime"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <DateInput
                                        label="table.field.taskPathTemplateMerge.expireTime"
                                        source="expireTime"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.taskPathTemplateMerge.priority"
                                        source="priority"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.taskPathTemplateMerge.timeoutMinutes"
                                        source="timeoutMinutes"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.taskPathTemplateMerge.maxRetryTimes"
                                        source="maxRetryTimes"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.taskPathTemplateMerge.retryIntervalSeconds"
                                        source="retryIntervalSeconds"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <TextInput
                                        label="table.field.taskPathTemplateMerge.remark"
                                        source="remark"
                                        parse={v => v}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.taskPathTemplateMerge.stepSize"
                                        source="stepSize"
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <StatusSelectInput />
                                </Grid>
                                <Grid item xs={12} display="flex" gap={1}>
                                    <Stack direction="column" spacing={1} width={'100%'}>
                                        <MemoInput />
                                    </Stack>
                                </Grid>
                            </Grid>
                        </DialogContent>
                        <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
                            <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}  >
                                <SaveButton />
                            </Toolbar>
                        </DialogActions>
                    </Form>
                </Dialog>
            </CreateBase>
        </>
    )
}
export default TaskPathTemplateMergeCreate;
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeEdit.jsx
New file
@@ -0,0 +1,191 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
    Edit,
    SimpleForm,
    FormDataConsumer,
    useTranslate,
    TextInput,
    NumberInput,
    BooleanInput,
    DateInput,
    SelectInput,
    ReferenceInput,
    ReferenceArrayInput,
    AutocompleteInput,
    SaveButton,
    Toolbar,
    Labeled,
    NumberField,
    required,
    useRecordContext,
    DeleteButton,
} from 'react-admin';
import { useWatch, useFormContext } from "react-hook-form";
import { Stack, Grid, Box, Typography } from '@mui/material';
import * as Common from '@/utils/common';
import { EDIT_MODE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting';
import EditBaseAside from "../components/EditBaseAside";
import CustomerTopToolBar from "../components/EditTopToolBar";
import MemoInput from "../components/MemoInput";
import StatusSelectInput from "../components/StatusSelectInput";
const FormToolbar = () => {
    const { getValues } = useFormContext();
    return (
        <Toolbar sx={{ justifyContent: 'space-between' }}>
            <SaveButton />
            <DeleteButton mutationMode="optimistic" />
        </Toolbar>
    )
}
const TaskPathTemplateMergeEdit = () => {
    const translate = useTranslate();
    return (
        <Edit
            redirect="list"
            mutationMode={EDIT_MODE}
            actions={<CustomerTopToolBar />}
            aside={<EditBaseAside />}
        >
            <SimpleForm
                shouldUnregister
                warnWhenUnsavedChanges
                toolbar={<FormToolbar />}
                mode="onTouched"
                defaultValues={{}}
            // validate={(values) => { }}
            >
                <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
                    <Grid item xs={12} md={8}>
                        <Typography variant="h6" gutterBottom>
                            {translate('common.edit.title.main')}
                        </Typography>
                        <Stack direction='row' gap={2}>
                            <TextInput
                                label="table.field.taskPathTemplateMerge.templateCode"
                                source="templateCode"
                                parse={v => v}
                                autoFocus
                                validate={required()}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
                                label="table.field.taskPathTemplateMerge.templateName"
                                source="templateName"
                                parse={v => v}
                                validate={required()}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
                                label="table.field.taskPathTemplateMerge.sourceType"
                                source="sourceType"
                                parse={v => v}
                                validate={required()}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
                                label="table.field.taskPathTemplateMerge.targetType"
                                source="targetType"
                                parse={v => v}
                                validate={required()}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
                                label="table.field.taskPathTemplateMerge.conditionExpression"
                                source="conditionExpression"
                                parse={v => v}
                                validate={required()}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
                                label="table.field.taskPathTemplateMerge.conditionDesc"
                                source="conditionDesc"
                                parse={v => v}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.taskPathTemplateMerge.version"
                                source="version"
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.taskPathTemplateMerge.isCurrent"
                                source="isCurrent"
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <DateInput
                                label="table.field.taskPathTemplateMerge.effectiveTime"
                                source="effectiveTime"
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <DateInput
                                label="table.field.taskPathTemplateMerge.expireTime"
                                source="expireTime"
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.taskPathTemplateMerge.priority"
                                source="priority"
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.taskPathTemplateMerge.timeoutMinutes"
                                source="timeoutMinutes"
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.taskPathTemplateMerge.maxRetryTimes"
                                source="maxRetryTimes"
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.taskPathTemplateMerge.retryIntervalSeconds"
                                source="retryIntervalSeconds"
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
                                label="table.field.taskPathTemplateMerge.remark"
                                source="remark"
                                parse={v => v}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.taskPathTemplateMerge.stepSize"
                                source="stepSize"
                            />
                        </Stack>
                    </Grid>
                    <Grid item xs={12} md={4}>
                        <Typography variant="h6" gutterBottom>
                            {translate('common.edit.title.common')}
                        </Typography>
                        <StatusSelectInput />
                        <Box mt="2em" />
                        <MemoInput />
                    </Grid>
                </Grid>
            </SimpleForm>
        </Edit >
    )
}
export default TaskPathTemplateMergeEdit;
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeList.jsx
New file
@@ -0,0 +1,182 @@
import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
import { useNavigate } from 'react-router-dom';
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,
} from 'react-admin';
import { Box, Typography, Card, Stack } from '@mui/material';
import { styled } from '@mui/material/styles';
import TaskPathTemplateMergeCreate from "./TaskPathTemplateMergeCreate";
import TaskPathTemplateMergePanel from "./TaskPathTemplateMergePanel";
import EmptyData from "../components/EmptyData";
import MyCreateButton from "../components/MyCreateButton";
import MyExportButton from '../components/MyExportButton';
import PageDrawer from "../components/PageDrawer";
import MyField from "../components/MyField";
import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
import * as Common from '@/utils/common';
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
    '& .css-1vooibu-MuiSvgIcon-root': {
        height: '.9em'
    },
    '& .RaDatagrid-row': {
        cursor: 'auto'
    },
    '& .column-name': {
    },
    '& .opt': {
        width: 200
    },
}));
const filters = [
    <SearchInput source="condition" alwaysOn />,
    <DateInput label='common.time.after' source="timeStart" alwaysOn />,
    <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
    <TextInput source="templateCode" label="table.field.taskPathTemplateMerge.templateCode" />,
    <TextInput source="templateName" label="table.field.taskPathTemplateMerge.templateName" />,
    <TextInput source="sourceType" label="table.field.taskPathTemplateMerge.sourceType" />,
    <TextInput source="targetType" label="table.field.taskPathTemplateMerge.targetType" />,
    <TextInput source="conditionExpression" label="table.field.taskPathTemplateMerge.conditionExpression" />,
    <TextInput source="conditionDesc" label="table.field.taskPathTemplateMerge.conditionDesc" />,
    <NumberInput source="version" label="table.field.taskPathTemplateMerge.version" />,
    <NumberInput source="isCurrent" label="table.field.taskPathTemplateMerge.isCurrent" />,
    <DateInput source="effectiveTime" label="table.field.taskPathTemplateMerge.effectiveTime" />,
    <DateInput source="expireTime" label="table.field.taskPathTemplateMerge.expireTime" />,
    <NumberInput source="priority" label="table.field.taskPathTemplateMerge.priority" />,
    <NumberInput source="timeoutMinutes" label="table.field.taskPathTemplateMerge.timeoutMinutes" />,
    <NumberInput source="maxRetryTimes" label="table.field.taskPathTemplateMerge.maxRetryTimes" />,
    <NumberInput source="retryIntervalSeconds" label="table.field.taskPathTemplateMerge.retryIntervalSeconds" />,
    <TextInput source="remark" label="table.field.taskPathTemplateMerge.remark" />,
    <NumberInput source="stepSize" label="table.field.taskPathTemplateMerge.stepSize" />,
    <TextInput label="common.field.memo" source="memo" />,
    <SelectInput
        label="common.field.status"
        source="status"
        choices={[
            { id: '1', name: 'common.enums.statusTrue' },
            { id: '0', name: 'common.enums.statusFalse' },
        ]}
        resettable
    />,
]
const TaskPathTemplateMergeList = () => {
    const translate = useTranslate();
    const [createDialog, setCreateDialog] = useState(false);
    const [drawerVal, setDrawerVal] = useState(false);
    return (
        <Box display="flex">
            <List
                sx={{
                    flexGrow: 1,
                    transition: (theme) =>
                        theme.transitions.create(['all'], {
                            duration: theme.transitions.duration.enteringScreen,
                        }),
                    marginRight: !!drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0,
                }}
                title={"menu.taskPathTemplateMerge"}
                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
                filters={filters}
                sort={{ field: "create_time", order: "desc" }}
                actions={(
                    <TopToolbar>
                        <FilterButton />
                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
                        <SelectColumnsButton preferenceKey='taskPathTemplateMerge' />
                        <MyExportButton />
                    </TopToolbar>
                )}
                perPage={DEFAULT_PAGE_SIZE}
            >
                <StyledDatagrid
                    preferenceKey='taskPathTemplateMerge'
                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
                    rowClick={(id, resource, record) => false}
                    expand={() => <TaskPathTemplateMergePanel />}
                    expandSingle={true}
                    omit={['id', 'createTime', 'createBy', 'memo']}
                >
                    <NumberField source="id" />
                    <TextField source="templateCode" label="table.field.taskPathTemplateMerge.templateCode" />
                    <TextField source="templateName" label="table.field.taskPathTemplateMerge.templateName" />
                    <TextField source="sourceType" label="table.field.taskPathTemplateMerge.sourceType" />
                    <TextField source="targetType" label="table.field.taskPathTemplateMerge.targetType" />
                    <TextField source="conditionExpression" label="table.field.taskPathTemplateMerge.conditionExpression" />
                    <TextField source="conditionDesc" label="table.field.taskPathTemplateMerge.conditionDesc" />
                    <NumberField source="version" label="table.field.taskPathTemplateMerge.version" />
                    <NumberField source="isCurrent" label="table.field.taskPathTemplateMerge.isCurrent" />
                    <DateField source="effectiveTime" label="table.field.taskPathTemplateMerge.effectiveTime" showTime />
                    <DateField source="expireTime" label="table.field.taskPathTemplateMerge.expireTime" showTime />
                    <NumberField source="priority" label="table.field.taskPathTemplateMerge.priority" />
                    <NumberField source="timeoutMinutes" label="table.field.taskPathTemplateMerge.timeoutMinutes" />
                    <NumberField source="maxRetryTimes" label="table.field.taskPathTemplateMerge.maxRetryTimes" />
                    <NumberField source="retryIntervalSeconds" label="table.field.taskPathTemplateMerge.retryIntervalSeconds" />
                    <TextField source="remark" label="table.field.taskPathTemplateMerge.remark" />
                    <NumberField source="stepSize" label="table.field.taskPathTemplateMerge.stepSize" />
                    <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}>
                        <TextField source="nickname" />
                    </ReferenceField>
                    <DateField source="updateTime" label="common.field.updateTime" showTime />
                    <ReferenceField source="createBy" label="common.field.createBy" reference="user" link={false} sortable={false}>
                        <TextField source="nickname" />
                    </ReferenceField>
                    <DateField source="createTime" label="common.field.createTime" showTime />
                    <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">
                        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                        <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
                    </WrapperField>
                </StyledDatagrid>
            </List>
            <TaskPathTemplateMergeCreate
                open={createDialog}
                setOpen={setCreateDialog}
            />
            <PageDrawer
                title='TaskPathTemplateMerge Detail'
                drawerVal={drawerVal}
                setDrawerVal={setDrawerVal}
            >
            </PageDrawer>
        </Box>
    )
}
export default TaskPathTemplateMergeList;
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergePanel.jsx
New file
@@ -0,0 +1,147 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import { Box, Card, CardContent, Grid, Typography, Tooltip } from '@mui/material';
import {
    useTranslate,
    useRecordContext,
} from 'react-admin';
import PanelTypography from "../components/PanelTypography";
import * as Common from '@/utils/common'
const TaskPathTemplateMergePanel = () => {
    const record = useRecordContext();
    if (!record) return null;
    const translate = useTranslate();
    return (
        <>
            <Card sx={{ width: { xs: 300, sm: 500, md: 600, lg: 800 }, margin: 'auto' }}>
                <CardContent>
                    <Grid container spacing={2}>
                        <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'space-between' }}>
                            <Typography variant="h6" gutterBottom align="left" sx={{
                                maxWidth: { xs: '100px', sm: '180px', md: '260px', lg: '360px' },
                                whiteSpace: 'nowrap',
                                overflow: 'hidden',
                                textOverflow: 'ellipsis',
                            }}>
                                {Common.camelToPascalWithSpaces(translate('table.field.taskPathTemplateMerge.id'))}: {record.id}
                            </Typography>
                            {/*  inherit, primary, secondary, textPrimary, textSecondary, error */}
                            <Typography variant="h6" gutterBottom align="right" >
                                ID: {record.id}
                            </Typography>
                        </Grid>
                    </Grid>
                    <Grid container spacing={2}>
                        <Grid item xs={12} container alignContent="flex-end">
                            <Typography variant="caption" color="textSecondary" sx={{ wordWrap: 'break-word', wordBreak: 'break-all' }}>
                                {Common.camelToPascalWithSpaces(translate('common.field.memo'))}:{record.memo}
                            </Typography>
                        </Grid>
                    </Grid>
                    <Box height={20}>&nbsp;</Box>
                    <Grid container spacing={2}>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.templateCode"
                                property={record.templateCode}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.templateName"
                                property={record.templateName}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.sourceType"
                                property={record.sourceType}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.targetType"
                                property={record.targetType}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.conditionExpression"
                                property={record.conditionExpression}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.conditionDesc"
                                property={record.conditionDesc}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.version"
                                property={record.version}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.isCurrent"
                                property={record.isCurrent}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.effectiveTime"
                                property={record.effectiveTime$}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.expireTime"
                                property={record.expireTime$}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.priority"
                                property={record.priority}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.timeoutMinutes"
                                property={record.timeoutMinutes}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.maxRetryTimes"
                                property={record.maxRetryTimes}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.retryIntervalSeconds"
                                property={record.retryIntervalSeconds}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.remark"
                                property={record.remark}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.taskPathTemplateMerge.stepSize"
                                property={record.stepSize}
                            />
                        </Grid>
                    </Grid>
                </CardContent>
            </Card >
        </>
    );
};
export default TaskPathTemplateMergePanel;
rsf-admin/src/page/taskPathTemplateMerge/index.jsx
New file
@@ -0,0 +1,18 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
    ListGuesser,
    EditGuesser,
    ShowGuesser,
} from "react-admin";
import TaskPathTemplateMergeList from "./TaskPathTemplateMergeList";
import TaskPathTemplateMergeEdit from "./TaskPathTemplateMergeEdit";
export default {
    list: TaskPathTemplateMergeList,
    edit: TaskPathTemplateMergeEdit,
    show: ShowGuesser,
    recordRepresentation: (record) => {
        return `${record.id}`
    }
};
rsf-admin/src/page/warehouseAreas/WarehouseAreasCreate.jsx
@@ -127,6 +127,11 @@
                    parse={v => v}
                    validate={[required()]}
                  />                
                  <NumberInput
                    label="table.field.warehouseAreas.sort"
                    source="sort"
                    parse={v => v}
                  />
                  {/* <SelectInput
                    label="table.field.warehouseAreas.flagLabelMange"
                    source="flagLabelMange"
rsf-admin/src/page/warehouseAreas/WarehouseAreasEdit.jsx
@@ -115,6 +115,13 @@
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.warehouseAreas.sort"
                                source="sort"
                                parse={v => v}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <SelectInput
                                label="table.field.warehouseAreas.flagMix"
                                source="flagMix"
rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx
@@ -164,6 +164,7 @@
                    <TextField source="code" label="table.field.warehouseAreas.code" />
                    <TextField source="name" label="table.field.warehouseAreas.name" />                   
                    <TextField source="type$" label="table.field.warehouseAreas.type"/>
                    <NumberField source="sort" label="table.field.warehouseAreas.sort" />
                    <TextField source="shipperId$" label="table.field.warehouseAreas.shipperId" />
                    <NumberField source="supplierId" label="table.field.warehouseAreas.supplierId" />
                    <TextField source="flagMix$" label="table.field.warehouseAreas.flagMix" sortable={false} />
rsf-admin/src/page/warehouseAreas/WarehouseAreasPanel.jsx
@@ -60,6 +60,12 @@
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.warehouseAreas.sort"
                                property={record.sort}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.warehouseAreas.shipperId" 
                                property={record.shipperId$}
                            />
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/MissionTransferStationController.java
New file
@@ -0,0 +1,26 @@
package com.vincent.rsf.openApi.controller;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Api("任务中转站")
@Slf4j
@RequestMapping("/mission")
public class MissionTransferStationController {
    @ApiOperation("任务总控")
    @PostMapping("/task/master/control")
    public CommonResponse missionMasterControl(@RequestBody Object objParams) {
        //判断需要下发系统
        //判断下发方式
        //返回结果
        return CommonResponse.ok();
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/PdaOtherController.java
@@ -45,4 +45,33 @@
        return pdaOtherService.inspectConfirm2(generalParam);
    }
    @RequestMapping(value = "/other/station/page")
    public R stationPage(@RequestParam String staNo,
                          @RequestParam(defaultValue = "1") Integer curr,
                          @RequestParam(defaultValue = "5") Integer limit) {
        return pdaOtherService.stationPage(staNo,curr,limit);
    }
    @PostMapping(value = "/other/staOperate")
    public R staOperate(@RequestBody PdaGeneralParam generalParam) {
        return pdaOtherService.staOperate(generalParam,getLoginUser());
    }
    @RequestMapping(value = "/other/loc/page")
    public R locPage(@RequestParam String locNo,
                         @RequestParam(defaultValue = "1") Integer curr,
                         @RequestParam(defaultValue = "5") Integer limit) {
        return pdaOtherService.locPage(locNo,curr,limit);
    }
    @PostMapping(value = "/other/locOperate")
    public R locOperate(@RequestBody PdaGeneralParam generalParam) {
        return pdaOtherService.locOperate(generalParam,getLoginUser());
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java
@@ -13,4 +13,7 @@
    //盘点库存修改
    public static String CHECK_LOCITEM_UPDATE = "/rsf-open-api/erp/check/locitem/update";
    //待下发任务发送至中转站
    public static String MISSION_TRANSFER_STATION = "/rsf-open-api/mission/task/master/control";
}
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java
@@ -19,4 +19,11 @@
    private List<LocItem> matnrList;
    /**
     * 站点操作类型:1.解禁 2.禁用 3.释放
     */
    private Integer staOperateType;
    private String locNo;
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/PdaOtherService.java
@@ -2,6 +2,7 @@
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.server.api.entity.params.PdaGeneralParam;
import com.vincent.rsf.server.system.entity.User;
public interface PdaOtherService {
    R transferPage(String orderNo, Integer curr, Integer limit);
@@ -11,4 +12,12 @@
    R inspectConfirm(PdaGeneralParam generalParam);
    R inspectConfirm2(PdaGeneralParam generalParam);
    R stationPage(String staNo, Integer curr, Integer limit);
    R staOperate(PdaGeneralParam generalParam, User user);
    R locPage(String locNo, Integer curr, Integer limit);
    R locOperate(PdaGeneralParam generalParam, User loginUser);
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/AgvServiceImpl.java
@@ -327,7 +327,7 @@
            throw new CoolException("站点状态不为空闲");
        }
        if (basStation.getType().equals(StationTypeEnum.STATION_TYPE_MUTI.type)) {
            throw new CoolException("站点为光电站点,禁止呼叫AGV");
            throw new CoolException("站点为智能站点,禁止呼叫AGV");
        }
        List<String> areaList = JSONObject.parseArray(basStation.getCrossZoneArea(), String.class);
@@ -345,7 +345,7 @@
            for (BasContainer container : containers) {
                String codeType = container.getCodeType();  // 获取正则表达式
                if (barcode.matches(codeType)) {  // 判断条码是否符合这个正则
                    List<Integer> areaList2 = container.getAreas();
                    List<Integer> areaList2 = container.getAreasIds();
                    if (!areaList2.contains(Integer.valueOf(area))) {
                        matches2 = false;
                        continue;
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
@@ -141,7 +141,7 @@
            for (BasContainer container : containers) {
                String codeType = container.getCodeType();  // 获取正则表达式
                if (barcode.matches(codeType)) {  // 判断条码是否符合这个正则
                    List<Integer> areaList2 = container.getAreas();
                    List<Integer> areaList2 = container.getAreasIds();
                    if (!areaList2.contains(Integer.parseInt(area))) {
                        matches2 = false;
                        continue;
@@ -242,7 +242,7 @@
            }
            Task task = new Task();
            task.setTaskCode(ruleCode)
                    .setTaskStatus(TaskStsType.GENERATE_IN.id)
                    .setTaskStatus(TaskStsType.MISSION_INITIAL.id)
                    .setTaskType(TaskType.TASK_TYPE_EMPTY_IN.type)
                    .setWarehType(WarehType.WAREHOUSE_TYPE_AGV.val)//lsh待修改
                    .setTargLoc(targetLoc)
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
@@ -1026,7 +1026,7 @@
                        .map(WarehouseRoleMenu::getMenuId)
                        .collect(Collectors.toSet());
                // 获取 areaList 并转换为 Long 类型的 Set
                List<Integer> areaList = container.getAreas();
                List<Integer> areaList = container.getAreasIds();
                Set<Long> areaSet = new HashSet<>();
                if (areaList != null) {
                    areaList.forEach(area -> areaSet.add(area.longValue()));
@@ -1066,7 +1066,7 @@
                        .map(WarehouseRoleMenu::getMenuId)
                        .collect(Collectors.toSet());
                // 获取 areaList 并转换为 Long 类型的 Set
                List<Integer> areaList = container.getAreas();
                List<Integer> areaList = container.getAreasIds();
                Set<Long> areaSet = new HashSet<>();
                if (areaList != null) {
                    areaList.forEach(area -> areaSet.add(area.longValue()));
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOtherServiceImpl.java
@@ -7,17 +7,22 @@
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.api.entity.params.PdaGeneralParam;
import com.vincent.rsf.server.api.service.PdaOtherService;
import com.vincent.rsf.server.manager.entity.BasStation;
import com.vincent.rsf.server.manager.entity.Loc;
import com.vincent.rsf.server.manager.entity.LocItem;
import com.vincent.rsf.server.manager.entity.Transfer;
import com.vincent.rsf.server.manager.enums.LocStatusType;
import com.vincent.rsf.server.manager.enums.LocStsType;
import com.vincent.rsf.server.manager.service.BasStationService;
import com.vincent.rsf.server.manager.service.LocItemService;
import com.vincent.rsf.server.manager.service.LocService;
import com.vincent.rsf.server.manager.service.TransferService;
import com.vincent.rsf.server.system.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
@@ -30,6 +35,9 @@
    private LocService locService;
    @Autowired
    private LocItemService locItemService;
    @Autowired
    private BasStationService basStationService;
    @Override
    public R transferPage(String orderNo, Integer curr, Integer limit) {
@@ -113,4 +121,161 @@
        }
        return R.ok();
    }
    @Override
    public R stationPage(String staNo, Integer curr, Integer limit) {
        Page<BasStation> page = new Page<>(curr, limit);
        LambdaQueryWrapper<BasStation> basStationLambdaQueryWrapper = new LambdaQueryWrapper<>();
        basStationLambdaQueryWrapper.eq(!Cools.isEmpty(staNo), BasStation::getStationName, staNo);
        Page<BasStation> page1 = basStationService.page(page, basStationLambdaQueryWrapper);
        return R.ok(page1);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R staOperate(PdaGeneralParam generalParam, User user) {
        switch (generalParam.getStaOperateType()){
            case 1:
                stationDisabled(generalParam.getTransferStationNo(),user);
                break;
            case 2:
                stationLiftTheBan(generalParam.getTransferStationNo(),user);
                break;
            case 3:
                stationRelease(generalParam.getTransferStationNo(),user);
                break;
            default:
                break;
        }
        return R.ok();
    }
    @Transactional(rollbackFor = Exception.class)
    public void stationDisabled(String transferStationNo,User user){
        BasStation basStation = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, transferStationNo));
        if (Cools.isEmpty(basStation)) {
            throw new CoolException("未找到正确的站点");
        }
        if (basStation.getUseStatus().equals(LocStsType.LOC_STS_TYPE_S.type) || basStation.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) {
            throw new CoolException("当前站点正在工作");
        }
        basStation.setUseStatus(LocStsType.LOC_STS_TYPE_X.type);
        basStation.setUpdateBy(user.getId());
        basStation.setUpdateTime(new Date());
        basStationService.updateById(basStation);
    }
    @Transactional(rollbackFor = Exception.class)
    public void stationLiftTheBan(String transferStationNo,User user){
        BasStation basStation = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, transferStationNo));
        if (Cools.isEmpty(basStation)) {
            throw new CoolException("未找到正确的站点");
        }
        if (basStation.getUseStatus().equals(LocStsType.LOC_STS_TYPE_S.type) || basStation.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) {
            throw new CoolException("当前站点正在工作");
        }
        basStation.setUseStatus(Cools.isEmpty(basStation.getBarcode())?LocStsType.LOC_STS_TYPE_O.type:LocStsType.LOC_STS_TYPE_F.type);
        basStation.setUpdateBy(user.getId());
        basStation.setUpdateTime(new Date());
        basStationService.updateById(basStation);
    }
    @Transactional(rollbackFor = Exception.class)
    public void stationRelease(String transferStationNo,User user){
        BasStation basStation = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, transferStationNo));
        if (Cools.isEmpty(basStation)) {
            throw new CoolException("未找到正确的站点");
        }
        if (basStation.getUseStatus().equals(LocStsType.LOC_STS_TYPE_S.type) || basStation.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) {
            throw new CoolException("当前站点正在工作");
        }
        basStation.setUseStatus(LocStsType.LOC_STS_TYPE_O.type);
        basStation.setBarcode(null);
        basStation.setUpdateBy(user.getId());
        basStation.setUpdateTime(new Date());
        basStationService.updateById(basStation);
    }
    @Override
    public R locPage(String locNo, Integer curr, Integer limit) {
        Page<Loc> page = new Page<>(curr, limit);
        LambdaQueryWrapper<Loc> basStationLambdaQueryWrapper = new LambdaQueryWrapper<>();
        basStationLambdaQueryWrapper.eq(!Cools.isEmpty(locNo), Loc::getCode, locNo);
        Page<Loc> page1 = locService.page(page, basStationLambdaQueryWrapper);
        return R.ok(page1);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R locOperate(PdaGeneralParam generalParam, User user) {
        switch (generalParam.getStaOperateType()){
            case 1:
                locDisabled(generalParam.getLocNo(),user);
                break;
            case 2:
                locLiftTheBan(generalParam.getLocNo(),user);
                break;
            case 3:
                locRelease(generalParam.getLocNo(),user);
                break;
            default:
                break;
        }
        return R.ok();
    }
    @Transactional(rollbackFor = Exception.class)
    public void locDisabled(String transferStationNo,User user){
        Loc lco = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, transferStationNo));
        if (Cools.isEmpty(lco)) {
            throw new CoolException("未找到正确的库位");
        }
        if (lco.getUseStatus().equals(LocStsType.LOC_STS_TYPE_S.type) || lco.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) {
            throw new CoolException("当前站点正在工作");
        }
        lco.setUseStatus(LocStsType.LOC_STS_TYPE_X.type);
        lco.setUpdateBy(user.getId());
        lco.setUpdateTime(new Date());
        locService.updateById(lco);
    }
    @Transactional(rollbackFor = Exception.class)
    public void locLiftTheBan(String transferStationNo,User user){
        Loc lco = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, transferStationNo));
        if (Cools.isEmpty(lco)) {
            throw new CoolException("未找到正确的库位");
        }
        if (lco.getUseStatus().equals(LocStsType.LOC_STS_TYPE_S.type) || lco.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) {
            throw new CoolException("当前库位正在工作");
        }
        lco.setUseStatus(Cools.isEmpty(lco.getBarcode())?LocStsType.LOC_STS_TYPE_O.type:LocStsType.LOC_STS_TYPE_F.type);
        lco.setUpdateBy(user.getId());
        lco.setUpdateTime(new Date());
        locService.updateById(lco);
    }
    @Transactional(rollbackFor = Exception.class)
    public void locRelease(String transferStationNo,User user){
        Loc lco = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, transferStationNo));
        if (Cools.isEmpty(lco)) {
            throw new CoolException("未找到正确的库位");
        }
        if (lco.getUseStatus().equals(LocStsType.LOC_STS_TYPE_S.type) || lco.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) {
            throw new CoolException("当前库位正在工作");
        }
        lco.setUseStatus(LocStsType.LOC_STS_TYPE_O.type);
        lco.setBarcode(null);
        lco.setUpdateBy(user.getId());
        lco.setUpdateTime(new Date());
        locService.updateById(lco);
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java
@@ -274,7 +274,7 @@
        if (Cools.isEmpty(loc)){
            throw new CoolException("未查询到符合条件的托盘");
        }
        //生成盘点任务参数
        //生成任务参数
        LocToTaskParams locToTaskParams = new LocToTaskParams();
        locToTaskParams.setType(Constants.TASK_TYPE_OUT_STOCK_EMPTY)
                .setSiteNo(basStation.getStationName())
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java
@@ -929,22 +929,25 @@
                return R.error("查询条件不能为空");
            }
            // 将ERP参数映射为Java实体字段名(驼峰格式),PageParam会自动转换为数据库字段名(下划线格式)
            // 将ERP参数映射为数据库字段名(下划线格式)
            Map<String, Object> queryMap = new HashMap<>();
            // 从实体类中提取查询条件,映射为数据库字段名
            // 从实体类中提取查询条件,映射为真实的数据库字段名
            if (StringUtils.isNotBlank(condition.getLocId())) {
                queryMap.put("locCode", condition.getLocId());
                queryMap.put("loc_code", condition.getLocId());
            }
            if (StringUtils.isNotBlank(condition.getMatNr())) {
                queryMap.put("matnrCode", condition.getMatNr());
                queryMap.put("matnr_code", condition.getMatNr());
            }
            if (StringUtils.isNotBlank(condition.getPlanNo())) {
                queryMap.put("trackCode", condition.getPlanNo());
                queryMap.put("track_code", condition.getPlanNo());
            }
            if (StringUtils.isNotBlank(condition.getBatch())) {
                queryMap.put("batch", condition.getBatch());
            }
            // 注意:orderNo 和 wareHouseId 不在 LocItem 表中,需要通过其他方式查询
            // orderNo 已在后面单独处理(plat_order_code 和 plat_work_code)
            // wareHouseId 需要通过 Loc 表关联查询,已在后面单独处理
            BaseParam baseParam = new BaseParam();
            baseParam.syncMap(queryMap);
@@ -953,10 +956,27 @@
            QueryWrapper<LocItem> wrapper = pageParam.buildWrapper(false);
            // 订单号/工单号/MES工单号
            // 订单号可能存储在:1) LocItem.plat_order_code 2) LocItem.plat_work_code 3) WkOrder.code (通过orderId关联)
            if (StringUtils.isNotBlank(condition.getOrderNo())) {
                String orderNo = condition.getOrderNo();
                wrapper.and(w -> w.eq("plat_order_code", orderNo)
                        .or().eq("plat_work_code", orderNo));
                // 先查询WkOrder表,获取匹配的订单ID列表
                LambdaQueryWrapper<WkOrder> orderWrapper = new LambdaQueryWrapper<>();
                orderWrapper.eq(WkOrder::getCode, orderNo);
                List<WkOrder> matchingOrders = asnOrderService.list(orderWrapper);
                List<Long> matchingOrderIds = matchingOrders.stream()
                        .map(WkOrder::getId)
                        .collect(Collectors.toList());
                // 构建订单号查询条件:LocItem表的plat_order_code、plat_work_code,或通过orderId关联WkOrder.code
                wrapper.and(w -> {
                    w.eq("plat_order_code", orderNo)
                     .or().eq("plat_work_code", orderNo);
                    // 如果找到了匹配的订单,也查询orderId
                    if (!matchingOrderIds.isEmpty()) {
                        w.or().in("order_id", matchingOrderIds);
                    }
                });
            }
            // 物料组(需要通过物料表关联查询)
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -31,6 +31,8 @@
import com.vincent.rsf.server.manager.service.impl.LocServiceImpl;
import com.vincent.rsf.server.system.constant.SerialRuleCode;
import com.vincent.rsf.server.manager.enums.LocStsType;
import com.vincent.rsf.server.system.entity.User;
import com.vincent.rsf.server.system.service.impl.UserServiceImpl;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -82,6 +84,8 @@
    private RestTemplate restTemplate;
    @Autowired
    private RemotesInfoProperties.RcsApi rcsApi;
    @Autowired
    private UserServiceImpl userService;
    @Override
@@ -201,7 +205,7 @@
                            String targetSite, String sourceSiteNo, Long loginUserId) {
        Task task = new Task();
        task.setTaskCode(ruleCode)
                .setTaskStatus(TaskStsType.GENERATE_IN.id)
                .setTaskStatus(TaskStsType.MISSION_INITIAL.id)
                .setTaskType(TaskType.TASK_TYPE_IN.type)
                .setWarehType(WarehType.WAREHOUSE_TYPE_CRN.val)
                .setTargLoc(targetLoc)
@@ -471,7 +475,7 @@
        }
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, params.getSeqNum()));
        if (Objects.isNull(task)) {
            throw new CoolException("任务不存在可已结束!!");
            throw new CoolException("任务不存在可以结束!!");
        }
        /**料箱搬运中, 修改站点状态*/
@@ -991,7 +995,7 @@
            throw new CoolException(params.getSourceStaNo()+"站点不存在!!");
        }
        if (!basStation.getType().equals(StationTypeEnum.STATION_TYPE_MUTI.type)) {
            throw new CoolException(params.getSourceStaNo()+"站点非光电站点!!请使用PDA绑定入库");
            throw new CoolException(params.getSourceStaNo()+"站点非智能站点!!请使用PDA绑定入库");
        }
        Task one = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, params.getBarcode()));
        if (!Cools.isEmpty(one)) {
@@ -1011,7 +1015,13 @@
        GenerateTaskParams taskParams = new GenerateTaskParams();
        taskParams.setWaitPakins(waitPakins)
                .setSiteId(basStation.getId());
        R r = taskService.generateTasksWcs(taskParams, 111L,params.getRowList());//lsh待修改  WCS用户信息
        User wcs = userService.getByUsername("wcs", 1L);
        Long wcsId = 1111L;
        if (!Cools.isEmpty(wcs)) {
            wcsId = wcs.getId();
        }
        R r = taskService.generateTasksWcs(taskParams, wcsId,params.getRowList());
        if (r.get("msg").equals("任务生成完毕!")) {
            one = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, params.getBarcode()));
            InTaskWcsReportParam inTaskWcsReportParam = new InTaskWcsReportParam();
@@ -1075,7 +1085,7 @@
        }
        if (params.getMsgType().equals(WcsMsgTypeEvent.TASK_COMPLETE.event)){
            if (!Cools.isEmpty(one)) {
                one.setTaskStatus(TaskStsType.GENERATE_IN.id);
                one.setTaskStatus(TaskStsType.MISSION_INITIAL.id);
                one.setOrgSite(one.getTargSite());
                if (!taskService.updateById(one)) {
//                    throw new CoolException("完成任务失败");
rsf-server/src/main/java/com/vincent/rsf/server/api/utils/LocUtils.java
@@ -380,7 +380,7 @@
                    continue;
                }
                String shallowLoc = LocUtils.getDeepLoc(locMast1.getCode());
                if ((ioType == TaskStsType.GENERATE_IN.id && deviceBind.getBeSimilar().equals("1"))) {
                if ((ioType == TaskStsType.MISSION_INITIAL.id && deviceBind.getBeSimilar().equals("1"))) {
                    //相似物料打开,判断深库位有没有货,没货就放深库位,有货就不操作
                    Loc locMast2 = locService.getOne(new LambdaQueryWrapper<Loc>()
                            .eq(Loc::getCode, shallowLoc)
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java
@@ -51,6 +51,10 @@
    @GetMapping("/basContainer/{id}")
    public R get(@PathVariable("id") Long id) {
        BasContainer basContainer = basContainerService.getById(id);
        // 确保返回的areas按sort字段排序
        if (basContainer != null) {
            basContainer.sortAreas();
        }
        return R.ok().add(basContainer);
    }
@@ -62,6 +66,10 @@
        basContainer.setCreateTime(new Date());
        basContainer.setUpdateBy(getLoginUserId());
        basContainer.setUpdateTime(new Date());
        // 确保areas按sort字段排序
        basContainer.sortAreas();
        BasContainer container = basContainerService.getOne(new LambdaQueryWrapper<BasContainer>().eq(BasContainer::getContainerType, basContainer.getContainerType()));
        if (null != container) {
            return R.error("该类型已被初始化");
@@ -78,6 +86,10 @@
    public R update(@RequestBody BasContainer basContainer) {
        basContainer.setUpdateBy(getLoginUserId());
        basContainer.setUpdateTime(new Date());
        // 确保areas按sort字段排序
        basContainer.sortAreas();
        if (!basContainerService.updateById(basContainer)) {
            return R.error("Update Fail");
        }
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasContainer.java
@@ -6,17 +6,16 @@
import java.util.Date;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.vincent.rsf.server.manager.utils.AreasDeserializer;
import com.vincent.rsf.server.manager.utils.AreasSerializer;
import com.vincent.rsf.server.manager.utils.AreasTypeHandler;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vincent.rsf.server.system.entity.DictData;
import com.vincent.rsf.server.system.service.DictDataService;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableLogic;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.vincent.rsf.framework.common.Cools;
@@ -24,8 +23,10 @@
import com.vincent.rsf.server.system.service.UserService;
import com.vincent.rsf.server.system.entity.User;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.stream.Collectors;
@Data
@Accessors(chain = true)
@@ -57,11 +58,14 @@
    private String codeType;
    /**
     * 可入库区
     * 可入库区(包含排序信息)
     * 格式: [{"id": 1, "sort": 1}, {"id": 2, "sort": 2}]
     */
    @ApiModelProperty(value = "可入库区")
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<Integer> areas;
    @ApiModelProperty(value = "可入库区(包含排序信息)")
    @TableField(typeHandler = AreasTypeHandler.class)
    @JsonDeserialize(using = AreasDeserializer.class)
    @JsonSerialize(using = AreasSerializer.class)
    private List<Map<String, Object>> areas;
    /**
     * 是否删除 1: 是 0: 否
@@ -118,7 +122,7 @@
    public BasContainer() {
    }
    public BasContainer(Long containerType, String codeType, List<Integer> areas, Integer deleted, Integer status,
    public BasContainer(Long containerType, String codeType, List<Map<String, Object>> areas, Integer deleted, Integer status,
            Integer tenantId, Long createBy, Date createTime, Long updateBy, Date updateTime, String memo) {
        this.containerType = containerType;
        this.codeType = codeType;
@@ -207,4 +211,42 @@
        }
    }
    /**
     * 获取排序后的库区ID列表(向后兼容方法)
     * @return 排序后的库区ID列表
     */
    public List<Integer> getAreasIds() {
        if (Cools.isEmpty(this.areas)) {
            return new ArrayList<>();
        }
        return this.areas.stream()
                .sorted((a, b) -> {
                    Integer sortA = a.get("sort") != null ? ((Number) a.get("sort")).intValue() : Integer.MAX_VALUE;
                    Integer sortB = b.get("sort") != null ? ((Number) b.get("sort")).intValue() : Integer.MAX_VALUE;
                    return sortA.compareTo(sortB);
                })
                .map(area -> {
                    Object id = area.get("id");
                    if (id instanceof Number) {
                        return ((Number) id).intValue();
                    }
                    return null;
                })
                .filter(id -> id != null)
                .collect(Collectors.toList());
    }
    /**
     * 对areas按sort字段进行排序
     */
    public void sortAreas() {
        if (this.areas != null && !this.areas.isEmpty()) {
            this.areas.sort((a, b) -> {
                Integer sortA = a.get("sort") != null ? ((Number) a.get("sort")).intValue() : Integer.MAX_VALUE;
                Integer sortB = b.get("sort") != null ? ((Number) b.get("sort")).intValue() : Integer.MAX_VALUE;
                return sortA.compareTo(sortB);
            });
        }
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasStation.java
@@ -7,6 +7,7 @@
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.vincent.rsf.server.manager.enums.StationTypeEnum;
import com.vincent.rsf.server.manager.service.WarehouseAreasService;
import com.vincent.rsf.server.system.entity.DictData;
@@ -26,7 +27,7 @@
import java.util.stream.Collectors;
@Data
@TableName("man_bas_station")
@TableName(value = "man_bas_station", autoResultMap = true)
public class BasStation implements Serializable {
    private static final long serialVersionUID = 1L;
@@ -171,6 +172,13 @@
    @TableField(exist = false)
    private List<Long> containerTypes;
    /**
     * 别名
     */
    @ApiModelProperty(value = "别名")
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<String> stationAlias;
    public BasStation() {
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WarehouseAreas.java
@@ -107,6 +107,12 @@
    private Integer status;
    /**
     * 排序字段
     */
    @ApiModelProperty(value= "排序字段")
    private Integer sort;
    /**
     * 是否删除 1: 是  0: 否  
     */
    @ApiModelProperty(value= "是否删除 1: 是  0: 否  ")
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/MissionStepType.java
New file
@@ -0,0 +1,29 @@
package com.vincent.rsf.server.manager.enums;
/**
 * @author Ryan
 * @version 1.0
 * @title TaskType
 * @description
 * @create 2025/3/29 17:02
 */
public enum MissionStepType {
    MISSION_STEP_TYPE_RESPONSE("RESPONSE", "响应"),
    MISSION_STEP_TYPE_RECEIVE("RECEIVE", "接收"),
    MISSION_STEP_TYPE_REQUEST("REQUEST", "请求"),
    MISSION_STEP_TYPE_BIND("BIND", "绑定"),
    MISSION_STEP_TYPE_UNBIND("UNBIND", "解绑"),
    MISSION_STEP_TYPE_VALIDATE("VALIDATE", "校验"),
    MISSION_STEP_TYPE_TRANSFORM("TRANSFORM", "转换"),
    MISSION_STEP_TYPE_OBTAIN("OBTAIN", "获取"),
    MISSION_STEP_TYPE_NO_EXECUTE("NO_EXECUTE", "越过"),
    ;
    public String type;
    public String desc;
    MissionStepType(String type, String desc) {
        this.type = type;
        this.desc = desc;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/StationTypeEnum.java
@@ -8,7 +8,7 @@
 */
public enum StationTypeEnum {
    //站点类型
    STATION_TYPE_MUTI(0, "光电站点"),
    STATION_TYPE_MUTI(0, "智能站点"),
    STATION_TYPE_NORMAL(1, "普通站点"),
            ;
    /**站点类型*/
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/TaskStsType.java
@@ -19,34 +19,56 @@
//    WCS_PUTAWAY_SUSPEND(13L, "入库任务挂起"),
    COMPLETE_IN("98", "入库完成"),
    REPORT_IN("99", "上报完成"),
    UPDATED_IN("100", "库存更新完成"),
    GENERATE_OUT("101", "创建出库任务"),
    WCS_EXECUTE_OUT("102", "WCS、RCS出库任务已下发"),
    WCS_EXECUTE_OUT_TOTE_LOAD("103", "WCS、RCS取箱完成"),
    WCS_EXECUTE_OUT_TOTE_UNLOAD("104", "WCS、RCS放箱完成"),
    WCS_EXECUTE_OUT_TASK_DONE("105", "WCS、RCS任务完成"),
    WCS_EXECUTE_OUT_ARRIVED("106", "WCS、RCS容器已到达"),
    WCS_EXECUTE_OUT_CONVEYOR("107", "WCS、RCS容器流动任务已下发"),
    AWAIT("196","等待确认"),
    GENERATE_WAVE_SEED("197", "等待容器到达"),
    COMPLETE_OUT("198", "出库完成"),
    WAVE_SEED("199", "播种中/盘点中/待确认"),
    UPDATED_OUT("200", "库存更新完成"),
    MISSION_TEMPLATE_EXECUTE_WCS_ONE1("1001", "1001.WCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_WCS_ONE2("1002", "1002.WCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_ONE1("1003", "1003.RCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_ONE2("1004", "1004.RCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_TWO1("1005", "1005.RCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_TWO2("1006", "1006.RCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_WCS_TWO1("1007", "1007.WCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_WCS_TWO2("1008", "1008.WCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_WCS_THREE1("1009", "1009.WCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_WCS_THREE2("1010", "1010.WCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_WCS_FOUR1("1011", "1011.WCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_WCS_FOUR2("1012", "1012.WCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_WCS_FIVE1("1013", "1013.WCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_WCS_FIVE2("1014", "1014.WCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_THREE1("1015", "1015.RCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_THREE2("1016", "1016.RCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_WCS_SIX1("1017", "1017.WCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_WCS_SIX2("1018", "1018.WCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_WCS_SEVEN1("1019", "1019.WCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_WCS_SEVEN2("1020", "1020.WCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_FOUR1("1021", "1021.RCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_FOUR2("1022", "1022.RCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_FIVE1("1023", "1023.RCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_FIVE2("1024", "1024.RCS作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_WCS_EIGHT1("1025", "1025.WCS请求下发中"),
    MISSION_TEMPLATE_EXECUTE_WCS_EIGHT2("1026", "1026.WCS作业中等待上报完成"),
    MISSION_TEMPLATE_NO_EXECUTE1("1027", "1027.不执行任务"),
    MISSION_TEMPLATE_WEIGHING_ONE2("1028", "1028.称重作业中等待上报完成"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_ONE3("1029", "1029.RCS绑定数据"),
    MISSION_TEMPLATE_EXECUTE_HK_RCS_ONE4("1030", "1030.RCS解绑数据"),
    MISSION_TRANSFER("9999", "9999.任务完成中"),
    ;
    public Integer id;
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/TaskType.java
@@ -19,6 +19,7 @@
    TASK_TYPE_PICK_AGAIN_OUT("103", "拣料出库"),
    TASK_TYPE_MERGE_OUT("104", "并板出库"),
    TASK_TYPE_CHECK_OUT("107", "盘点出库"),
    TASK_TYPE_CROSS_DOCKING_OUT("109", "越库"),
    TASK_TYPE_EMPTY_OUT("110", "空板出库"),
    ;
    public Integer type;
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/CheckOrderSchedules.java
@@ -51,7 +51,7 @@
    public void genReCheck() {
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .select(Task::getId)
                .eq(Task::getTaskStatus, TaskStsType.GENERATE_OUT.id)
                .eq(Task::getTaskStatus, TaskStsType.MISSION_INITIAL.id)
                .eq(Task::getTaskType, TaskType.TASK_TYPE_CHECK_OUT.type));
        if (tasks.isEmpty()) {
            return;
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskMissionSchedules.java
@@ -2,18 +2,13 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.manager.controller.params.LocToTaskParams;
import com.vincent.rsf.server.manager.entity.*;
import com.vincent.rsf.server.manager.enums.LocStsType;
import com.vincent.rsf.server.manager.enums.StationTypeEnum;
import com.vincent.rsf.framework.common.SnowflakeIdWorker;
import com.vincent.rsf.server.manager.entity.Loc;
import com.vincent.rsf.server.manager.entity.Task;
import com.vincent.rsf.server.manager.enums.TaskStsType;
import com.vincent.rsf.server.manager.enums.TaskType;
import com.vincent.rsf.server.manager.service.LocItemService;
import com.vincent.rsf.server.manager.service.LocService;
import com.vincent.rsf.server.manager.service.TaskService;
import com.vincent.rsf.server.manager.service.impl.BasContainerServiceImpl;
import com.vincent.rsf.server.manager.service.impl.WarehouseAreasServiceImpl;
import com.vincent.rsf.server.system.constant.GlobalConfigCode;
import com.vincent.rsf.server.system.entity.*;
import com.vincent.rsf.server.system.service.ConfigService;
@@ -47,31 +42,27 @@
    @Autowired
    private ConfigService configService;
    @Autowired
    private WarehouseAreasServiceImpl warehouseAreasService;
    @Autowired
    private LocService locService;
    @Autowired
    private LocItemService locItemService;
    private TaskPathTemplateMergeServiceImpl taskPathTemplateMergeService;
    @Autowired
    private BasContainerServiceImpl basContainerService;
    private TaskInstanceServiceImpl taskInstanceService;
    @Autowired
    private TaskPathTemplateServiceImpl taskPathTemplateService;
    @Autowired
    private TaskPathTemplateNodeServiceImpl taskPathTemplateNodeService;
    @Autowired
    private TaskInstanceServiceImpl taskInstanceService;
    @Autowired
    private TaskInstanceNodeServiceImpl taskInstanceNodeService;
    @Autowired
    private SubsystemFlowTemplateServiceImpl subsystemFlowTemplateService;
    @Autowired
    private FlowStepLogServiceImpl flowStepLogService;
    @Autowired
    private FlowStepTemplateServiceImpl flowStepTemplateService;
    @Autowired
    private FlowStepInstanceServiceImpl flowStepInstanceService;
    private TaskInstanceNodeServiceImpl taskInstanceNodeService;
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
    @Autowired
    private FlowInstanceServiceImpl flowInstanceService;
    @Autowired
    private FlowStepInstanceServiceImpl flowStepInstanceService;
    /**
     * @author Munch D. Luffy
@@ -79,14 +70,13 @@
     * @description: 初始任务规划路径
     * @version 1.0
     */
    @Scheduled(cron = "0/2 * * * * ?")
    @Scheduled(cron = "0/1 * * * * ?")
    @Transactional(rollbackFor = Exception.class)
    public void missionTemplate() throws Exception {
        Config config = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_TEMPLATE_PLANNING_STEP_FLOW));
        if (!Boolean.parseBoolean(config.getVal())) {
            return;
        }
        List<Task> taskList = taskService.list(new LambdaQueryWrapper<Task>().eq(Task::getTaskStatus, TaskStsType.MISSION_INITIAL.id));
        if (Objects.isNull(taskList)) {
            return;
@@ -112,9 +102,9 @@
                sou = loc.getAreaId().toString();
                end = task.getTargSite();
            }
            List<TaskPathTemplate> taskPathTemplates = taskPathTemplateService.list(new LambdaQueryWrapper<TaskPathTemplate>().eq(TaskPathTemplate::getSourceType, sou).eq(TaskPathTemplate::getTargetType, end));
            if (Objects.isNull(taskPathTemplates) || taskPathTemplates.isEmpty()) {
                List<TaskPathTemplate> list = taskPathTemplateService.list(new LambdaQueryWrapper<>());
            List<TaskPathTemplateMerge> taskPathTemplateMergeList = taskPathTemplateMergeService.list(new LambdaQueryWrapper<TaskPathTemplateMerge>().eq(TaskPathTemplateMerge::getSourceType, sou).eq(TaskPathTemplateMerge::getTargetType, end));
            if (Objects.isNull(taskPathTemplateMergeList) || taskPathTemplateMergeList.isEmpty()) {
                List<TaskPathTemplateMerge> list = taskPathTemplateMergeService.list(new LambdaQueryWrapper<TaskPathTemplateMerge>().eq(TaskPathTemplateMerge::getStepSize,1));
                if (!Cools.isEmpty(list)) {
                    List<String[]> stationList = new ArrayList<>();
                    list.forEach(taskPathTemplate -> {
@@ -122,16 +112,146 @@
                    });
                    List<Long> longs = RouteWmsStepFlow.routeGet(stationList, sou, end);
                    if (longs != null && !longs.isEmpty()) {
                        TaskPathTemplate taskPathTemplate = new TaskPathTemplate();
                        taskPathTemplate.setTemplateCode(sou+"===>"+end);
                        taskPathTemplate.setTemplateName(sou+"===>"+end);
                        taskPathTemplate.setSourceType(sou);
                        taskPathTemplate.setTargetType(end);
                        taskPathTemplateService.save(taskPathTemplate);
                        TaskPathTemplateMerge taskPathTemplateMerge = new TaskPathTemplateMerge();
                        taskPathTemplateMerge.setTemplateCode(sou+"===>"+end);
                        taskPathTemplateMerge.setTemplateName(sou+"===>"+end);
                        taskPathTemplateMerge.setSourceType(sou);
                        taskPathTemplateMerge.setTargetType(end);
                        List<Integer> longList = new ArrayList<>();
                        for (Long id : longs) {
                            TaskPathTemplateMerge one = taskPathTemplateMergeService.getOne(new LambdaQueryWrapper<TaskPathTemplateMerge>().eq(TaskPathTemplateMerge::getId, id));
                            longList.addAll(one.getConditionExpression());
                        }
                        taskPathTemplateMerge.setConditionExpression(longList);
                        taskPathTemplateMerge.setStepSize(longList.size());
                        taskPathTemplateMergeService.save(taskPathTemplateMerge);
                    }
                    System.out.println("任务:"+task.getTaskCode()+"查询步序为:"+longs);
//                    System.out.println("任务:"+task.getTaskCode()+"查询步序为:"+longs);
                }
            } else {
                //生成实际路径
                boolean actualPath = generateActualPath(task, taskPathTemplateMergeList.get(0));
                if (!actualPath) {
                    log.error("生成实际路径失败");
                }
            }
        });
    }
    public boolean generateActualPath(Task task,TaskPathTemplateMerge taskPathTemplateMerge) {
        boolean actualPath = false;
        List<Integer> conditionExpression = taskPathTemplateMerge.getConditionExpression();
        Integer i = 0;//it顺序
        Integer j = 0;//suT顺序
        // 用于存储所有FlowStepTemplate
        List<FlowStepTemplate> allFlowStepTemplates = new ArrayList<>();
        // 第一遍遍历:收集所有的FlowStepTemplate
        for (Integer id : conditionExpression) {
            TaskPathTemplate taskPathTemplate = taskPathTemplateService.getById(id);
            if (taskPathTemplate == null) {
                return false;
            }
            List<TaskPathTemplateNode> taskPathTemplateNodeList = taskPathTemplateNodeService.list(
                    new LambdaQueryWrapper<TaskPathTemplateNode>()
                            .eq(TaskPathTemplateNode::getTemplateId, taskPathTemplate.getId()));
            for (TaskPathTemplateNode taskPathTemplateNode : taskPathTemplateNodeList) {
                List<SubsystemFlowTemplate> subsystemFlowTemplateList = subsystemFlowTemplateService.list(
                        new LambdaQueryWrapper<SubsystemFlowTemplate>()
                                .eq(SubsystemFlowTemplate::getFlowCode, taskPathTemplateNode.getNodeCode())
                                .eq(SubsystemFlowTemplate::getSystemCode, taskPathTemplateNode.getSystemCode()));
                for (SubsystemFlowTemplate subsystemFlowTemplate : subsystemFlowTemplateList) {
                    List<FlowStepTemplate> flowStepTemplateList = flowStepTemplateService.list(
                            new LambdaQueryWrapper<FlowStepTemplate>()
                                    .eq(FlowStepTemplate::getFlowId, subsystemFlowTemplate.getId()));
                    allFlowStepTemplates.addAll(flowStepTemplateList);
                }
            }
        }
        TaskInstance taskInstance = new TaskInstance(taskPathTemplateMerge,task);
        boolean save = taskInstanceService.save(taskInstance);
        if (!save) {
            return false;
        }
        // 当前处理的FlowStepTemplate在全局列表中的索引
        int globalIndex = 0;
        for (Integer id : conditionExpression) {
            TaskPathTemplate taskPathTemplate = taskPathTemplateService.getById(id);
            if (taskPathTemplate == null) {
                return false;
            }
            List<TaskPathTemplateNode> taskPathTemplateNodeList = taskPathTemplateNodeService.list(
                    new LambdaQueryWrapper<TaskPathTemplateNode>()
                            .eq(TaskPathTemplateNode::getTemplateId, taskPathTemplate.getId()));
            for (TaskPathTemplateNode taskPathTemplateNode : taskPathTemplateNodeList) {
                TaskInstanceNode taskInstanceNode = new TaskInstanceNode(taskPathTemplateNode);
                taskInstanceNode.setTaskId(taskInstance.getId());
                taskInstanceNode.setTaskNo(taskInstance.getTaskNo());
                i++;
                taskInstanceNode.setNodeOrder(i);
                taskInstanceNode.setNodeCode(String.valueOf(snowflakeIdWorker.nextId()));
                taskInstanceNodeService.save(taskInstanceNode);
                List<SubsystemFlowTemplate> subsystemFlowTemplateList = subsystemFlowTemplateService.list(
                        new LambdaQueryWrapper<SubsystemFlowTemplate>()
                                .eq(SubsystemFlowTemplate::getFlowCode, taskPathTemplateNode.getNodeCode())
                                .eq(SubsystemFlowTemplate::getSystemCode, taskPathTemplateNode.getSystemCode()));
                for (SubsystemFlowTemplate subsystemFlowTemplate : subsystemFlowTemplateList) {
                    FlowInstance flowInstance = new FlowInstance(subsystemFlowTemplate);
                    flowInstance.setFlowInstanceNo(String.valueOf(snowflakeIdWorker.nextId()));
                    flowInstance.setTaskId(taskInstance.getId());
                    flowInstance.setTaskNo(taskInstance.getTaskNo());
                    flowInstance.setNodeInstanceId(taskInstanceNode.getId());
                    flowInstance.setNodeCode(String.valueOf(snowflakeIdWorker.nextId()));
                    flowInstanceService.save(flowInstance);
                    List<FlowStepTemplate> flowStepTemplateList = flowStepTemplateService.list(
                            new LambdaQueryWrapper<FlowStepTemplate>()
                                    .eq(FlowStepTemplate::getFlowId, subsystemFlowTemplate.getId()));
                    for (FlowStepTemplate flowStepTemplate : flowStepTemplateList) {
                        j++;
                        FlowStepInstance flowStepInstance = new FlowStepInstance(flowStepTemplate);
                        flowStepInstance.setFlowInstanceId(flowInstance.getId());
                        flowStepInstance.setFlowInstanceNo(flowInstance.getFlowInstanceNo());
                        flowStepInstance.setStepOrder(j);
                        flowStepInstance.setStepCode(String.valueOf(snowflakeIdWorker.nextId()));
                        flowStepInstance.setWmsNowTaskStatus(flowStepTemplate.getWmsNowTaskStatus());
                        // 判断是否是最后一个
                        if (globalIndex < allFlowStepTemplates.size() - 1) {
                            // 不是最后一个,取下一个的WmsNowTaskStatus
                            FlowStepTemplate nextFlowStep = allFlowStepTemplates.get(globalIndex + 1);
                            flowStepInstance.setWmsNextTaskStatus(nextFlowStep.getWmsNowTaskStatus());
                            if (globalIndex == 0){
                                task.setTaskStatus(flowStepTemplate.getWmsNowTaskStatus());
                                flowStepInstance.setStatus((short)1);
                            }
                        } else {
                            // 是最后一个,设置为9999
                            flowStepInstance.setWmsNextTaskStatus(9999);
                        }
                        flowStepInstanceService.save(flowStepInstance);
                        globalIndex++; // 更新全局索引
                    }
                }
            }
        }
        task.setDeviceSiteId(taskInstance.getId());
        taskService.updateById(task);
        return actualPath;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
@@ -26,8 +26,12 @@
import com.vincent.rsf.server.manager.service.impl.LocServiceImpl;
import com.vincent.rsf.server.system.constant.GlobalConfigCode;
import com.vincent.rsf.server.system.constant.SerialRuleCode;
import com.vincent.rsf.server.system.entity.Config;
import com.vincent.rsf.server.system.entity.*;
import com.vincent.rsf.server.system.service.ConfigService;
import com.vincent.rsf.server.system.service.impl.FlowInstanceServiceImpl;
import com.vincent.rsf.server.system.service.impl.FlowStepInstanceServiceImpl;
import com.vincent.rsf.server.system.service.impl.TaskInstanceNodeServiceImpl;
import com.vincent.rsf.server.system.service.impl.TaskInstanceServiceImpl;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import com.vincent.rsf.server.system.utils.SystemAuthUtils;
import lombok.extern.slf4j.Slf4j;
@@ -93,6 +97,176 @@
    private RemotesInfoProperties.RcsApi rcsApi;
    @Autowired
    private BasStationService basStationService;
    @Autowired
    private FlowStepInstanceServiceImpl flowStepInstanceService;
    @Autowired
    private FlowInstanceServiceImpl flowInstanceService;
    @Autowired
    private TaskInstanceNodeServiceImpl taskInstanceNodeService;
    @Autowired
    private TaskInstanceServiceImpl taskInstanceService;
    /**
     * 任务下发:请求
     */
    @Scheduled(cron = "0/2 * * * * ?  ")
    @Transactional(rollbackFor = Exception.class)
    public void missionTaskEXECUTE() {
        List<String> typeList = Arrays.asList(
                MissionStepType.MISSION_STEP_TYPE_REQUEST.type,
                MissionStepType.MISSION_STEP_TYPE_UNBIND.type,
                MissionStepType.MISSION_STEP_TYPE_BIND.type,
                MissionStepType.MISSION_STEP_TYPE_OBTAIN.type,
                MissionStepType.MISSION_STEP_TYPE_NO_EXECUTE.type);
        List<FlowStepInstance> flowStepInstanceList = flowStepInstanceService.list(new LambdaQueryWrapper<FlowStepInstance>()
                .eq(FlowStepInstance::getStatus, 1).in(FlowStepInstance::getStepType,typeList));
        for (FlowStepInstance flowStepInstance : flowStepInstanceList) {
            FlowInstance flowInstance = flowInstanceService.getById(flowStepInstance.getFlowInstanceId());
            if (Cools.isEmpty(flowInstance)) { continue;}
            TaskInstanceNode taskInstanceNode = taskInstanceNodeService.getById(flowInstance.getNodeInstanceId());
            if (Cools.isEmpty(taskInstanceNode)) { continue;}
            TaskInstance taskInstance = taskInstanceService.getById(flowInstance.getTaskId());
            if (Cools.isEmpty(taskInstance)) { continue;}
            Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, taskInstance.getTaskNo()));
            if (Cools.isEmpty(task)) { continue;}
            if (!task.getTaskStatus().equals(flowStepInstance.getWmsNowTaskStatus())) {
                log.error("任务号:"+task.getTaskCode()+"的任务状态与执行档案任务状态:"+flowStepInstance.getWmsNowTaskStatus()+"不一致!!!");
                continue;
            }
            if (flowStepInstance.getStepType().equals(MissionStepType.MISSION_STEP_TYPE_NO_EXECUTE.type)) {
                /**基础配置链接*/
                log.info("任务越过, 请求参数: {}", JSONObject.toJSONString(flowStepInstance));
                try {
                    task.setTaskStatus(flowStepInstance.getWmsNextTaskStatus());
                    flowStepInstance.setStatus((short)3);
                    flowStepInstanceService.updateById(flowStepInstance);
                    taskService.updateById(task);
                    if (flowStepInstance.getWmsNextTaskStatus() != 9999) {
                        FlowStepInstance nextFlowStepInstance = flowStepInstanceService.getOne(new LambdaQueryWrapper<FlowStepInstance>()
                                .eq(FlowStepInstance::getFlowInstanceId, flowStepInstance.getFlowInstanceId())
                                .eq(FlowStepInstance::getFlowInstanceNo, flowStepInstance.getFlowInstanceNo())
                                .eq(FlowStepInstance::getStepOrder, flowStepInstance.getStepOrder() + 1)
                                .eq(FlowStepInstance::getWmsNowTaskStatus, flowStepInstance.getWmsNextTaskStatus()));
                        nextFlowStepInstance.setStatus((short)1);
                        flowStepInstanceService.updateById(nextFlowStepInstance);
                    }
                } catch (Exception e) {
                    throw new CoolException(e.getMessage());
                }
            } else {
                /**任务下发接口*/
                String pubTakUrl = rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.MISSION_TRANSFER_STATION;
                /**基础配置链接*/
                log.info("任务下发,请求地址: {}, 请求参数: {}", pubTakUrl, JSONObject.toJSONString(""));
                HttpHeaders headers = new HttpHeaders();
                headers.add("Content-Type", "application/json");
                headers.add("api-version", "v2.0");
                HttpEntity httpEntity = new HttpEntity(flowStepInstance, headers);
                ResponseEntity<String> exchange = restTemplate.exchange(pubTakUrl, HttpMethod.POST, httpEntity, String.class);
                log.info("任务下发后,响应结果: {}", exchange);
                if (Objects.isNull(exchange.getBody())) {
                    throw new CoolException("任务下发失败!!,返回参数为空!!!");
                } else {
                    try {
                        ObjectMapper objectMapper = new ObjectMapper();
                        objectMapper.coercionConfigDefaults()
                                .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
                        CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                        if (result.getCode() == 200) {
                            task.setTaskStatus(flowStepInstance.getWmsNextTaskStatus());
                            flowStepInstance.setStatus((short)3);
                            if (flowStepInstance.getStepType().equals(MissionStepType.MISSION_STEP_TYPE_OBTAIN.type)) {
                                //获取数据解析
                                //录入
                            }
                            flowStepInstanceService.updateById(flowStepInstance);
                            taskService.updateById(task);
                            if (flowStepInstance.getWmsNextTaskStatus() != 9999) {
                                FlowStepInstance nextFlowStepInstance = flowStepInstanceService.getOne(new LambdaQueryWrapper<FlowStepInstance>()
                                        .eq(FlowStepInstance::getFlowInstanceId, flowStepInstance.getFlowInstanceId())
                                        .eq(FlowStepInstance::getFlowInstanceNo, flowStepInstance.getFlowInstanceNo())
                                        .eq(FlowStepInstance::getStepOrder, flowStepInstance.getStepOrder() + 1)
                                        .eq(FlowStepInstance::getWmsNowTaskStatus, flowStepInstance.getWmsNextTaskStatus()));
                                nextFlowStepInstance.setStatus((short)1);
                                flowStepInstanceService.updateById(nextFlowStepInstance);
                            }
                        } else {
                            flowStepInstance.setRetryTimes(flowStepInstance.getRetryTimes() + 1);
                            if (flowStepInstance.getRetryTimes()>5){
                                flowStepInstance.setStatus((short)4);
                                flowStepInstanceService.updateById(flowStepInstance);
                                log.error("任务下发失败,重试次数大于等于五次,标记为失败!!!");
                            } else {
                                flowStepInstanceService.updateById(flowStepInstance);
                                log.error("任务下发失败,等待重试....");
                            }
                        }
                    } catch (JsonProcessingException e) {
                        throw new CoolException(e.getMessage());
                    }
                }
            }
        }
    }
    /**
     * @param
     * @return
     * @author Ryan
     * @description 完成入库,更新为对应状态
     * @time 2026/02/02 12:45
     */
    @Scheduled(cron = "0/3 * * * * ?")
    public void completeStock() throws Exception {
        completeInStock();
        complateOutStock();
        completeStock9999();
    }
    public void completeStock9999() throws Exception {
        try{
            List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>().eq(Task::getTaskStatus, TaskStsType.MISSION_TRANSFER.id).select(Task::getId));
            if (tasks.isEmpty()) {
                return;
            }
            for (Task task : tasks) {
                if (task.getTaskType().equals(TaskType.TASK_TYPE_IN.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_PICK_IN.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_EMPTY_IN.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_MERGE_IN.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                    task.setTaskStatus(TaskStsType.COMPLETE_IN.id);
                } else if (task.getTaskType().equals(TaskType.TASK_TYPE_OUT.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_MERGE_OUT.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_CROSS_DOCKING_OUT.type)
                        || task.getTaskType().equals(TaskType.TASK_TYPE_EMPTY_OUT.type)) {
                    task.setTaskStatus(TaskStsType.COMPLETE_OUT.id);
                }
                taskService.updateById(task);
            }
//            List<Long> longSet = tasks.stream().map(Task::getId).collect(Collectors.toList());
//            List<Task> vaildTasks = taskService.list(new LambdaQueryWrapper<Task>().in(Task::getId, longSet));
//            taskService.complateInTask(vaildTasks);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
    /**
     * @param
@@ -101,15 +275,19 @@
     * @description 完成入库,更新库存
     * @time 2025/4/2 12:37
     */
    @Scheduled(cron = "0/3 * * * * ?")
//    @Scheduled(cron = "0/3 * * * * ?")
    public void completeInStock() throws Exception {
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>().eq(Task::getTaskStatus, TaskStsType.COMPLETE_IN.id).select(Task::getId));
        if (tasks.isEmpty()) {
            return;
        try{
            List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>().eq(Task::getTaskStatus, TaskStsType.COMPLETE_IN.id).select(Task::getId));
            if (tasks.isEmpty()) {
                return;
            }
            List<Long> longSet = tasks.stream().map(Task::getId).collect(Collectors.toList());
            List<Task> vaildTasks = taskService.list(new LambdaQueryWrapper<Task>().in(Task::getId, longSet));
            taskService.complateInTask(vaildTasks);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        List<Long> longSet = tasks.stream().map(Task::getId).collect(Collectors.toList());
        List<Task> vaildTasks = taskService.list(new LambdaQueryWrapper<Task>().in(Task::getId, longSet));
        taskService.complateInTask(vaildTasks);
    }
    /**
@@ -118,18 +296,22 @@
     * @description: 完成出库任务,更新库存
     * @version 1.0
     */
    @Scheduled(cron = "0/5 * * * * ?  ")
//    @Scheduled(cron = "0/5 * * * * ?  ")
    @Transactional(rollbackFor = Exception.class)
    public void complateOutStock() throws Exception {
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .eq(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id)
                .select(Task::getId));
        if (tasks.isEmpty()) {
            return;
        try{
            List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                    .eq(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id)
                    .select(Task::getId));
            if (tasks.isEmpty()) {
                return;
            }
            List<Long> longSet = tasks.stream().map(Task::getId).collect(Collectors.toList());
            List<Task> vaildTasks = taskService.list(new LambdaQueryWrapper<Task>().in(Task::getId, longSet));
            taskService.completeTask(vaildTasks);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        List<Long> longSet = tasks.stream().map(Task::getId).collect(Collectors.toList());
        List<Task> vaildTasks = taskService.list(new LambdaQueryWrapper<Task>().in(Task::getId, longSet));
        taskService.completeTask(vaildTasks);
//        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>()
//                .eq(TaskItem::getWkType, OrderWorkType.ORDER_WORK_TYPE_STOCK_TERANSFER.type)
@@ -193,6 +375,7 @@
            }
        });
//        Set<Long> taskIds = taskItems.stream().map(TaskItem::getTaskId).collect(Collectors.toSet());
//        List<Task> tasks = taskService.listByIds(taskIds);
//        if (!tasks.isEmpty()) {
@@ -235,102 +418,102 @@
//            });
//        }
    }
    /**
     * 非光电站点任务下发
     */
    @Scheduled(cron = "0/5 * * * * ?  ")
    @Transactional(rollbackFor = Exception.class)
    public void pubTaskToWcs() {
        Long loginUserId = SystemAuthUtils.getLoginUserId();
        List<Integer> list = Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_LOC_MOVE.type, TaskType.TASK_TYPE_EMPTY_IN.type
                , TaskType.TASK_TYPE_CHECK_IN.type, TaskType.TASK_TYPE_MERGE_IN.type, TaskType.TASK_TYPE_EMPTY_OUT.type, TaskType.TASK_TYPE_PICK_IN.type,
                TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type, TaskType.TASK_TYPE_MERGE_OUT.type);
        List<Integer> integers = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .in(Task::getTaskType, list)
                .in(Task::getTaskStatus, integers).last("limit 1")
                .orderByDesc(Task::getSort));
        for (Task task : tasks) {
            /**移库不做站点操作*/
            if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>()
                        .eq(BasStation::getStationName,
                        task.getTaskStatus().equals(TaskStsType.GENERATE_IN.id) ? task.getOrgSite() : task.getTargSite()));
                if (Cools.isEmpty(station)){
                    log.info("非光电站点任务下发:站点信息异常,任务信息:"+ JSON.toJSONString(task));
                    continue;
                }
                if (station.getType().equals(StationTypeEnum.STATION_TYPE_MUTI.type)) {
                    continue;
                }
            }
            /**下发普通站点任务,报错回滚,不再往下执行*/
            pubTaskToWcs(task);
        }
    }
    /**
     * @author Ryan
     * @date 2025/9/4
     * @description: 光电站点任务下发
     * @version 1.0
     */
    @Scheduled(cron = "0/5 * * * * ?  ")
    @Transactional(rollbackFor = Exception.class)
    public void taskToWCS() throws Exception {
        Long loginUserId = SystemAuthUtils.getLoginUserId();
        List<Integer> list = Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_LOC_MOVE.type, TaskType.TASK_TYPE_EMPTY_IN.type
                , TaskType.TASK_TYPE_CHECK_IN.type, TaskType.TASK_TYPE_MERGE_IN.type, TaskType.TASK_TYPE_EMPTY_OUT.type,
                TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type, TaskType.TASK_TYPE_MERGE_OUT.type);
        List<Integer> integers = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .in(Task::getTaskType, list)
                .in(Task::getTaskStatus, integers)
                .orderByDesc(Task::getSort));
        for (Task task : tasks) {
            /**移库不做站点操作*/
            if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName,
                        task.getTaskStatus().equals(TaskStsType.GENERATE_IN.id) ? task.getOrgSite() : task.getTargSite()));
                /**过滤掉普通站点任务*/
                if (station.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
                    continue;
                }
                Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getBarcode, task.getBarcode()));
                if (Objects.isNull(loc)) {
                    continue;
                }
                //判断是否深库位
                if (!LocUtils.isShallowLoc(loc.getCode())) {
                    //获取深库位对应的浅库位
                    String shallowLoc = LocUtils.getShallowLoc(loc.getCode());
                    if (StringUtils.isBlank(shallowLoc)) {
                        continue;
                    }
                    Loc shalloc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, shallowLoc));
                    if (Objects.isNull(shalloc) || !shalloc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type)) {
                        //如果浅库位不在库跳出循环
                        continue;
                    }
                    LocToTaskParams params = new LocToTaskParams();
                    params.setOrgLoc(shallowLoc).setType(TaskType.TASK_TYPE_LOC_MOVE.type + "");
                    //生成移库任务
                    Task moveTask = locItemService.genMoveTask(params, loginUserId);
                    moveTask.setSort(!Objects.isNull(task.getSort()) ? task.getSort() + 1 : Constants.TASK_SORT_DEFAULT_VALUE + 1);
                    if (!taskService.updateById(moveTask)) {
                        throw new Exception("任务优先级更新失败!!");
                    }
                }
            }
            /**下发任务*/
            try {
                pubTaskToWcs(task);
            } catch (Exception e) {
                log.error("任务下发失败!!", e);
            }
        }
    }
//
//    /**
//     * 非智能站点任务下发
//     */
//    @Scheduled(cron = "0/5 * * * * ?  ")
//    @Transactional(rollbackFor = Exception.class)
//    public void pubTaskToWcs() {
//        Long loginUserId = SystemAuthUtils.getLoginUserId();
//        List<Integer> list = Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_LOC_MOVE.type, TaskType.TASK_TYPE_EMPTY_IN.type
//                , TaskType.TASK_TYPE_CHECK_IN.type, TaskType.TASK_TYPE_MERGE_IN.type, TaskType.TASK_TYPE_EMPTY_OUT.type, TaskType.TASK_TYPE_PICK_IN.type,
//                TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type, TaskType.TASK_TYPE_MERGE_OUT.type);
//        List<Integer> integers = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
//        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
//                .in(Task::getTaskType, list)
//                .in(Task::getTaskStatus, integers).last("limit 1")
//                .orderByDesc(Task::getSort));
//        for (Task task : tasks) {
//            /**移库不做站点操作*/
//            if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
//                BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>()
//                        .eq(BasStation::getStationName,
//                        task.getTaskStatus().equals(TaskStsType.GENERATE_IN.id) ? task.getOrgSite() : task.getTargSite()));
//                if (Cools.isEmpty(station)){
//                    log.info("非智能站点任务下发:站点信息异常,任务信息:"+ JSON.toJSONString(task));
//                    continue;
//                }
//                if (station.getType().equals(StationTypeEnum.STATION_TYPE_MUTI.type)) {
//                    continue;
//                }
//            }
//            /**下发普通站点任务,报错回滚,不再往下执行*/
//            pubTaskToWcs(task);
//        }
//    }
//
//    /**
//     * @author Ryan
//     * @date 2025/9/4
//     * @description: 智能站点任务下发
//     * @version 1.0
//     */
//    @Scheduled(cron = "0/5 * * * * ?  ")
//    @Transactional(rollbackFor = Exception.class)
//    public void taskToWCS() throws Exception {
//        Long loginUserId = SystemAuthUtils.getLoginUserId();
//        List<Integer> list = Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_LOC_MOVE.type, TaskType.TASK_TYPE_EMPTY_IN.type
//                , TaskType.TASK_TYPE_CHECK_IN.type, TaskType.TASK_TYPE_MERGE_IN.type, TaskType.TASK_TYPE_EMPTY_OUT.type,
//                TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type, TaskType.TASK_TYPE_MERGE_OUT.type);
//        List<Integer> integers = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
//        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
//                .in(Task::getTaskType, list)
//                .in(Task::getTaskStatus, integers)
//                .orderByDesc(Task::getSort));
//        for (Task task : tasks) {
//            /**移库不做站点操作*/
//            if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
//                BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName,
//                        task.getTaskStatus().equals(TaskStsType.GENERATE_IN.id) ? task.getOrgSite() : task.getTargSite()));
//                /**过滤掉普通站点任务*/
//                if (station.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
//                    continue;
//                }
//                Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getBarcode, task.getBarcode()));
//                if (Objects.isNull(loc)) {
//                    continue;
//                }
//                //判断是否深库位
//                if (!LocUtils.isShallowLoc(loc.getCode())) {
//                    //获取深库位对应的浅库位
//                    String shallowLoc = LocUtils.getShallowLoc(loc.getCode());
//                    if (StringUtils.isBlank(shallowLoc)) {
//                        continue;
//                    }
//                    Loc shalloc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, shallowLoc));
//                    if (Objects.isNull(shalloc) || !shalloc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type)) {
//                        //如果浅库位不在库跳出循环
//                        continue;
//                    }
//                    LocToTaskParams params = new LocToTaskParams();
//                    params.setOrgLoc(shallowLoc).setType(TaskType.TASK_TYPE_LOC_MOVE.type + "");
//                    //生成移库任务
//                    Task moveTask = locItemService.genMoveTask(params, loginUserId);
//                    moveTask.setSort(!Objects.isNull(task.getSort()) ? task.getSort() + 1 : Constants.TASK_SORT_DEFAULT_VALUE + 1);
//                    if (!taskService.updateById(moveTask)) {
//                        throw new Exception("任务优先级更新失败!!");
//                    }
//                }
//            }
//            /**下发任务*/
//            try {
//                pubTaskToWcs(task);
//            } catch (Exception e) {
//                log.error("任务下发失败!!", e);
//            }
//        }
//    }
    /**
     * 每五秒校验深库位是否为空,如果浅库位有货,将浅库位移至深库位
@@ -373,7 +556,7 @@
        BasStation station = null;
        if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
             station = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, task.getTargSite()));
            station = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, task.getTargSite()));
            if (Objects.isNull(station)) {
                throw new CoolException("站点不存在!!");
            }
@@ -383,7 +566,7 @@
        /**判断是否起点系统类型  非标准程序*/
        Loc locStart = null;
        if (task.getTaskType().equals(TaskType.TASK_TYPE_OUT.type) || task.getTaskType().equals(TaskType.TASK_TYPE_MERGE_OUT.type) ||
        task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type) ||  task.getTaskType().equals(TaskType.TASK_TYPE_EMPTY_OUT.type)) {
                task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type) || task.getTaskType().equals(TaskType.TASK_TYPE_EMPTY_OUT.type)) {
            locStart = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, task.getOrgLoc()));
            if (Objects.isNull(locStart)) {
                throw new CoolException("源库位不存在!!");
@@ -398,11 +581,11 @@
        /**判断是否起点系统类型  非标准程序*/
        if (locStart == null){
        if (locStart == null) {
            if (task.getTaskType().equals(TaskType.TASK_TYPE_IN.type) || task.getTaskType().equals(TaskType.TASK_TYPE_EMPTY_IN.type) ||
                    task.getTaskType().equals(TaskType.TASK_TYPE_PICK_IN.type) ||
                    task.getTaskType().equals(TaskType.TASK_TYPE_MERGE_IN.type) ||  task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)
                ) {
                    task.getTaskType().equals(TaskType.TASK_TYPE_MERGE_IN.type) || task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)
            ) {
                BasStation stationS = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, task.getOrgSite()));
                if (Objects.isNull(stationS)) {
                    throw new CoolException("源库位不存在!!");
@@ -425,7 +608,7 @@
        }
        /**判断是否光电站点,非光店站点需管控站点状态*/ //目标站点
        /**判断是否智能站点,非光店站点需管控站点状态*/ //目标站点
        if (!Objects.isNull(station) && station.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
            if (task.getTaskType() <= TaskType.TASK_TYPE_CHECK_IN.type && !task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
//                if (!station.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type)) {
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/CheckOrderServiceImpl.java
@@ -296,7 +296,7 @@
                        .setTargSite(checkItem.getSiteNo())
                        .setResource(TaskResouceType.TASK_RESOUCE_CHECK_TYPE.val)
                        .setTaskType(TaskType.TASK_TYPE_CHECK_OUT.type)
                        .setTaskStatus(TaskStsType.GENERATE_OUT.id)
                        .setTaskStatus(TaskStsType.MISSION_INITIAL.id)
                        .setCreateTime(new Date())
                        .setUpdateBy(loginUserId)
                        .setUpdateTime(new Date())
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java
@@ -98,7 +98,7 @@
                .setCreateBy(loginUserId)
                .setCreateTime(new Date())
                .setUpdateTime(new Date())
                .setTaskStatus(TaskStsType.GENERATE_OUT.id)
                .setTaskStatus(TaskStsType.MISSION_INITIAL.id)
                .setBarcode(loc.getBarcode())
                .setMemo(map.getMemo());
@@ -177,7 +177,7 @@
            //增加对备货单得判断
            Integer taskStatus = resouce.equals(TaskResouceType.TASK_RESOUCE_STOCK_UP.val)
                    ? TaskStsType.MISSION_INITIAL.id:TaskStsType.GENERATE_OUT.id;
                    ? TaskStsType.MISSION_INITIAL.id:TaskStsType.MISSION_INITIAL.id;
            Task moveTask = new Task();
            String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null);
@@ -411,7 +411,7 @@
                .setUpdateBy(loginUserId)
                .setSort(Constants.TASK_SORT_DEFAULT_VALUE)
                .setUpdateTime(new Date())
                .setTaskStatus(TaskStsType.GENERATE_IN.id)
                .setTaskStatus(TaskStsType.MISSION_INITIAL.id)
                .setBarcode(orgLoc.getBarcode())
                .setMemo(map.getMemo());
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -150,7 +150,7 @@
            }
            Task task = new Task();
            task.setTaskCode(ruleCode)
                    .setTaskStatus(TaskStsType.GENERATE_IN.id)
                    .setTaskStatus(TaskStsType.MISSION_INITIAL.id)
                    .setTaskType(TaskType.TASK_TYPE_IN.type)
                    .setWarehType(WarehType.WAREHOUSE_TYPE_AGV.val)
                    .setTargLoc(targetLoc)
@@ -366,7 +366,7 @@
            }
            Task task = new Task();
            task.setTaskCode(ruleCode)
                    .setTaskStatus(TaskStsType.GENERATE_IN.id)
                    .setTaskStatus(TaskStsType.MISSION_INITIAL.id)
                    .setTaskType(TaskType.TASK_TYPE_IN.type)
                    .setResource(TaskResouceType.TASK_RESOUCE_PAKIN_TYPE.val)
                    .setTargLoc(targetLoc)
@@ -509,7 +509,7 @@
            for (BasContainer container : containers) {
                String codeType = container.getCodeType();  // 获取正则表达式
                if (barcode.matches(codeType)) {  // 判断条码是否符合这个正则
                    List<Integer> areaList2 = container.getAreas();
                    List<Integer> areaList2 = container.getAreasIds();
                    if (!areaList2.contains(Integer.parseInt(area))) {
                        matches2 = false;
                        continue;
@@ -776,7 +776,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Task taskToTop(Long id, Long loginUserId) throws Exception {
        List<Integer> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Integer> longs = Arrays.asList(TaskStsType.MISSION_INITIAL.id, TaskStsType.MISSION_INITIAL.id);
        Task tasks = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getId, id).in(Task::getTaskStatus, longs));
        if (Objects.isNull(tasks)) {
            throw new CoolException("任务已处执行状态不可一键置顶!!");
@@ -800,7 +800,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Task operateComplete(Long id, Long loginUserId) {
        List<Integer> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Integer> longs = Arrays.asList(TaskStsType.MISSION_INITIAL.id, TaskStsType.MISSION_INITIAL.id);
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>()
                .eq(Task::getId, id)
                .in(Task::getTaskStatus, longs));
@@ -1071,7 +1071,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R removeTask(Long[] ids, Long loginUserId) {
        List<Integer> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Integer> longs = Arrays.asList(TaskStsType.MISSION_INITIAL.id, TaskStsType.MISSION_INITIAL.id);
        List<Integer> list = Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type,
                TaskType.TASK_TYPE_CHECK_OUT.type, TaskType.TASK_TYPE_EMPTY_IN.type, TaskType.TASK_TYPE_LOC_MOVE.type,
                TaskType.TASK_TYPE_EMPTY_OUT.type, TaskType.TASK_TYPE_MERGE_OUT.type);
@@ -1084,7 +1084,7 @@
        }
        for (Task task : tasks) {
            //取消移库任务
            if (task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type) && task.getTaskStatus().equals(TaskStsType.GENERATE_IN.id)) {
            if (task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type) && task.getTaskStatus().equals(TaskStsType.MISSION_INITIAL.id)) {
                if (!locService.update(new LambdaUpdateWrapper<Loc>()
                        .eq(Loc::getCode, task.getOrgLoc())
                        .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_F.type))) {
@@ -1312,7 +1312,7 @@
        task.setTaskCode(ruleCode)
                .setTaskType(type)
                .setBarcode(task.getBarcode())
                .setTaskStatus(TaskStsType.GENERATE_IN.id);
                .setTaskStatus(TaskStsType.MISSION_INITIAL.id);
        TaskInParam param = new TaskInParam();
        param.setSourceStaNo(task.getTargSite())
@@ -1693,7 +1693,7 @@
        if (Objects.isNull(ids) || ids.isEmpty()) {
            return R.error("任务编码不能为空!!");
        }
        List<Integer> integers = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Integer> integers = Arrays.asList(TaskStsType.MISSION_INITIAL.id, TaskStsType.MISSION_INITIAL.id);
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .in(Task::getId, ids)
                .in(Task::getTaskStatus, integers)
@@ -1739,7 +1739,7 @@
                }
            }
            /**判断是否光电站点,非光店站点需管控站点状态*/
            /**判断是否智能站点,非光店站点需管控站点状态*/
            if (!Objects.isNull(station) && station.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
                if (task.getTaskType() <= TaskType.TASK_TYPE_CHECK_IN.type && !task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                    station.setUseStatus(LocStsType.LOC_STS_TYPE_R.type);
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasDeserializer.java
New file
@@ -0,0 +1,68 @@
package com.vincent.rsf.server.manager.utils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 *
 * @author chen.lin
 * @time 2026-02-02
 * Areas 字段自定义反序列化器
 * 支持两种格式:
 * 1. [1, 2, 3] - 纯ID数组(向后兼容)
 * 2. [{"id": 1, "sort": 1}, {"id": 2, "sort": 2}] - 对象数组(新格式)
 *
 *
 */
public class AreasDeserializer extends JsonDeserializer<List<Map<String, Object>>> {
    @Override
    public List<Map<String, Object>> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonToken currentToken = p.getCurrentToken();
        // 处理 null 值
        if (currentToken == JsonToken.VALUE_NULL) {
            return new ArrayList<>();
        }
        // 处理数组
        if (currentToken == JsonToken.START_ARRAY) {
            List<Map<String, Object>> result = new ArrayList<>();
            JsonToken token = p.nextToken();
            while (token != null && token != JsonToken.END_ARRAY) {
                if (token == JsonToken.VALUE_NUMBER_INT) {
                    // 处理纯ID数组格式 [1, 2, 3]
                    int id = p.getIntValue();
                    Map<String, Object> area = new HashMap<>();
                    area.put("id", id);
                    area.put("sort", result.size() + 1); // 默认排序
                    result.add(area);
                    token = p.nextToken();
                } else if (token == JsonToken.START_OBJECT) {
                    // 处理对象数组格式 [{"id": 1, "sort": 1}]
                    ObjectMapper mapper = (ObjectMapper) p.getCodec();
                    @SuppressWarnings("unchecked")
                    Map<String, Object> area = mapper.readValue(p, Map.class);
                    result.add(area);
                    token = p.nextToken();
                } else {
                    token = p.nextToken();
                }
            }
            return result;
        }
        // 如果既不是数组也不是null,返回空列表
        return new ArrayList<>();
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasSerializer.java
New file
@@ -0,0 +1,57 @@
package com.vincent.rsf.server.manager.utils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author chen.lin
 * @time 2026-02-02
 * Areas 字段自定义序列化器
 * 将 List<Map<String, Object>> 序列化为 JSON 数组
 * 支持混合类型(Integer 和 Map)的向后兼容
 *
 */
public class AreasSerializer extends JsonSerializer<Object> {
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value == null) {
            gen.writeNull();
            return;
        }
        if (!(value instanceof List)) {
            gen.writeObject(value);
            return;
        }
        @SuppressWarnings("unchecked")
        List<Object> list = (List<Object>) value;
        gen.writeStartArray();
        for (Object item : list) {
            if (item instanceof Map) {
                // 已经是 Map 格式
                @SuppressWarnings("unchecked")
                Map<String, Object> map = (Map<String, Object>) item;
                gen.writeObject(map);
            } else if (item instanceof Number) {
                // 如果是 Number(向后兼容),转换为 Map 格式
                Map<String, Object> areaMap = new HashMap<>();
                areaMap.put("id", ((Number) item).intValue());
                areaMap.put("sort", 1); // 默认排序
                gen.writeObject(areaMap);
            } else {
                // 其他类型,尝试直接写入
                gen.writeObject(item);
            }
        }
        gen.writeEndArray();
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/AreasTypeHandler.java
New file
@@ -0,0 +1,115 @@
package com.vincent.rsf.server.manager.utils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author chen.lin
 * @time 2026-02-02
 * Areas 字段自定义 TypeHandler
 * 处理数据库和 Java 对象之间的转换
 * 支持两种格式:
 * 1. [1, 2, 3] - 纯ID数组(向后兼容)
 * 2. [{"id": 1, "sort": 1}, {"id": 2, "sort": 2}] - 对象数组(新格式)
 *
 */
@MappedTypes({List.class})
@MappedJdbcTypes(JdbcType.VARCHAR)
public class AreasTypeHandler extends BaseTypeHandler<List<Map<String, Object>>> {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final TypeReference<List<Object>> LIST_TYPE_REF = new TypeReference<List<Object>>() {};
    /**
     * 解析 JSON 字符串为 List<Map<String, Object>>
     */
    private List<Map<String, Object>> parse(String json) {
        if (json == null || json.trim().isEmpty()) {
            return new ArrayList<>();
        }
        try {
            // 先解析为 List<Object>
            List<Object> rawList = OBJECT_MAPPER.readValue(json, LIST_TYPE_REF);
            if (rawList == null || rawList.isEmpty()) {
                return new ArrayList<>();
            }
            List<Map<String, Object>> result = new ArrayList<>();
            // 遍历所有元素并转换
            for (int i = 0; i < rawList.size(); i++) {
                Object item = rawList.get(i);
                if (item instanceof Map) {
                    // 已经是对象数组格式 [{"id": 1, "sort": 1}]
                    @SuppressWarnings("unchecked")
                    Map<String, Object> map = (Map<String, Object>) item;
                    result.add(map);
                } else if (item instanceof Number) {
                    // 纯ID数组格式 [1, 2, 3],转换为对象数组
                    Map<String, Object> area = new HashMap<>();
                    area.put("id", ((Number) item).intValue());
                    area.put("sort", i + 1);
                    result.add(area);
                }
                // 忽略其他类型
            }
            return result;
        } catch (Exception e) {
            throw new RuntimeException("Failed to parse areas JSON: " + json, e);
        }
    }
    /**
     * 将 List<Map<String, Object>> 转换为 JSON 字符串
     */
    private String toJson(List<Map<String, Object>> obj) {
        if (obj == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.writeValueAsString(obj);
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize areas to JSON", e);
        }
    }
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<Map<String, Object>> parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, toJson(parameter));
    }
    @Override
    public List<Map<String, Object>> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String json = rs.getString(columnName);
        return parse(json);
    }
    @Override
    public List<Map<String, Object>> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String json = rs.getString(columnIndex);
        return parse(json);
    }
    @Override
    public List<Map<String, Object>> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String json = cs.getString(columnIndex);
        return parse(json);
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/TaskPathTemplateMergeController.java
New file
@@ -0,0 +1,110 @@
package com.vincent.rsf.server.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.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.system.entity.TaskPathTemplateMerge;
import com.vincent.rsf.server.system.service.TaskPathTemplateMergeService;
import com.vincent.rsf.server.system.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
@RestController
public class TaskPathTemplateMergeController extends BaseController {
    @Autowired
    private TaskPathTemplateMergeService taskPathTemplateMergeService;
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:list')")
    @PostMapping("/taskPathTemplateMerge/page")
    public R page(@RequestBody Map<String, Object> map) {
        BaseParam baseParam = buildParam(map, BaseParam.class);
        PageParam<TaskPathTemplateMerge, BaseParam> pageParam = new PageParam<>(baseParam, TaskPathTemplateMerge.class);
        return R.ok().add(taskPathTemplateMergeService.page(pageParam, pageParam.buildWrapper(true)));
    }
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:list')")
    @PostMapping("/taskPathTemplateMerge/list")
    public R list(@RequestBody Map<String, Object> map) {
        return R.ok().add(taskPathTemplateMergeService.list());
    }
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:list')")
    @PostMapping({"/taskPathTemplateMerge/many/{ids}", "/taskPathTemplateMerges/many/{ids}"})
    public R many(@PathVariable Long[] ids) {
        return R.ok().add(taskPathTemplateMergeService.listByIds(Arrays.asList(ids)));
    }
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:list')")
    @GetMapping("/taskPathTemplateMerge/{id}")
    public R get(@PathVariable("id") Long id) {
        return R.ok().add(taskPathTemplateMergeService.getById(id));
    }
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:save')")
    @OperationLog("Create 物料权限")
    @PostMapping("/taskPathTemplateMerge/save")
    public R save(@RequestBody TaskPathTemplateMerge taskPathTemplateMerge) {
        taskPathTemplateMerge.setCreateBy(getLoginUserId());
        taskPathTemplateMerge.setCreateTime(new Date());
        taskPathTemplateMerge.setUpdateBy(getLoginUserId());
        taskPathTemplateMerge.setUpdateTime(new Date());
        if (!taskPathTemplateMergeService.save(taskPathTemplateMerge)) {
            return R.error("Save Fail");
        }
        return R.ok("Save Success").add(taskPathTemplateMerge);
    }
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:update')")
    @OperationLog("Update 物料权限")
    @PostMapping("/taskPathTemplateMerge/update")
    public R update(@RequestBody TaskPathTemplateMerge taskPathTemplateMerge) {
        taskPathTemplateMerge.setUpdateBy(getLoginUserId());
        taskPathTemplateMerge.setUpdateTime(new Date());
        if (!taskPathTemplateMergeService.updateById(taskPathTemplateMerge)) {
            return R.error("Update Fail");
        }
        return R.ok("Update Success").add(taskPathTemplateMerge);
    }
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:remove')")
    @OperationLog("Delete 物料权限")
    @PostMapping("/taskPathTemplateMerge/remove/{ids}")
    public R remove(@PathVariable Long[] ids) {
        if (!taskPathTemplateMergeService.removeByIds(Arrays.asList(ids))) {
            return R.error("Delete Fail");
        }
        return R.ok("Delete Success").add(ids);
    }
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:list')")
    @PostMapping("/taskPathTemplateMerge/query")
    public R query(@RequestParam(required = false) String condition) {
        List<KeyValVo> vos = new ArrayList<>();
        LambdaQueryWrapper<TaskPathTemplateMerge> wrapper = new LambdaQueryWrapper<>();
        if (!Cools.isEmpty(condition)) {
            wrapper.like(TaskPathTemplateMerge::getId, condition);
        }
        taskPathTemplateMergeService.page(new Page<>(1, 30), wrapper).getRecords().forEach(
                item -> vos.add(new KeyValVo(item.getId(), item.getId()))
        );
        return R.ok().add(vos);
    }
    @PreAuthorize("hasAuthority('system:taskPathTemplateMerge:list')")
    @PostMapping("/taskPathTemplateMerge/export")
    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
        ExcelUtil.build(ExcelUtil.create(taskPathTemplateMergeService.list(), TaskPathTemplateMerge.class), response);
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowInstance.java
@@ -183,6 +183,12 @@
    private Date updateTime;
    public FlowInstance() {}
    public FlowInstance(SubsystemFlowTemplate subsystemFlowTemplate) {
        this.flowTemplateId = subsystemFlowTemplate.getId();
        this.flowTemplateCode = subsystemFlowTemplate.getFlowCode();
        this.templateVersion = subsystemFlowTemplate.getVersion();
    }
    public FlowInstance(String flowInstanceNo,Long taskId,String taskNo,Long nodeInstanceId,String nodeCode,Long flowTemplateId,String flowTemplateCode,Integer templateVersion,Short status,String currentStepCode,Integer currentStepOrder,String executeParams,String executeResult,String errorCode,String errorMessage,Date startTime,Date endTime,Date timeoutAt,Integer durationSeconds,Integer retryTimes,Date lastRetryTime,String contextData,Date createTime,Date updateTime) {
        this.flowInstanceNo = flowInstanceNo;
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepInstance.java
@@ -77,9 +77,9 @@
    private Long stepTemplateId;
    /**
     * 状态:0-待执行 1-执行中 2-成功 3-失败 4-跳过 5-超时
     * 状态:0-排队中 1-待执行 2-执行中 3-执行成功 4-执行失败 5-已跳过 6-已取消
     */
    @ApiModelProperty(value= "状态:0-待执行 1-执行中 2-成功 3-失败 4-跳过 5-超时")
    @ApiModelProperty(value= "状态:0-排队中 1-待执行 2-执行中 3-执行成功 4-执行失败 5-已跳过 6-已取消")
    private Short status;
    /**
@@ -146,7 +146,18 @@
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    @ApiModelProperty(value= "wms下一步任务类型ID")
    private Integer wmsNextTaskStatus;
    @ApiModelProperty(value= "wms当前任务类型ID")
    private Integer wmsNowTaskStatus;
    public FlowStepInstance() {}
    public FlowStepInstance(FlowStepTemplate  flowStepTemplate) {
        this.stepTemplateId = flowStepTemplate.getId();
        this.stepName = flowStepTemplate.getStepName();
        this.stepType = flowStepTemplate.getStepType();
    }
    public FlowStepInstance(Long flowInstanceId,String flowInstanceNo,Integer stepOrder,String stepCode,String stepName,String stepType,Long stepTemplateId,Short status,String executeResult,String errorCode,String errorMessage,Date startTime,Date endTime,Integer durationSeconds,String inputData,String outputData,Integer retryTimes,Date createTime,Date updateTime) {
        this.flowInstanceId = flowInstanceId;
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/FlowStepTemplate.java
@@ -134,6 +134,9 @@
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    @ApiModelProperty(value= "wms下一步任务状态ID")
    private Integer wmsNowTaskStatus;
    public FlowStepTemplate() {}
    public FlowStepTemplate(Long flowId,String flowCode,Integer stepOrder,String stepCode,String stepName,String stepType,String actionType,String actionConfig,String inputMapping,String outputMapping,String conditionExpression,Short skipOnFail,Short retryEnabled,String retryConfig,Integer timeoutSeconds,Long createBy,Long updateBy,Date createTime,Date updateTime) {
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstance.java
@@ -2,6 +2,8 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import com.vincent.rsf.server.manager.entity.Task;
import org.springframework.format.annotation.DateTimeFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -243,6 +245,17 @@
    private Date updateTime;
    public TaskInstance() {}
    public TaskInstance(TaskPathTemplateMerge taskPathTemplateMerge, Task task) {
        this.templateId = taskPathTemplateMerge.getId();
        this.templateCode = taskPathTemplateMerge.getTemplateCode();
        this.templateVersion = taskPathTemplateMerge.getVersion();
        this.sourceCode = taskPathTemplateMerge.getSourceType();
        this.targetCode = taskPathTemplateMerge.getTargetType();
//        this.sourceInfo = taskPathTemplateMerge.getSourceType();
//        this.targetInfo = taskPathTemplateMerge.getTargetType();
        this.taskNo = task.getTaskCode();
        this.bizType = task.getTaskType$();
    }
    public TaskInstance(String taskNo,String bizNo,String bizType,Long templateId,String templateCode,Integer templateVersion,String sourceInfo,String targetInfo,String sourceCode,String targetCode,String plannedPath,String actualPath,Short priority,Date timeoutAt,Short status,String currentNodeCode,String currentNodeName,Integer totalNodes,Integer completedNodes,Double progressRate,Integer estimatedDurationMinutes,Integer actualDurationMinutes,Date startTime,Date endTime,String resultCode,String resultMessage,String resultData,Integer retryTimes,Date lastRetryTime,String extParams,String remark,Long createBy,Long updateBy,Date createTime,Date updateTime) {
        this.taskNo = taskNo;
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskInstanceNode.java
@@ -93,9 +93,9 @@
    private String executeParams;
    /**
     * 状态:0-待执行 1-执行中 2-执行成功 3-执行失败 4-已跳过 5-已取消
     * 状态:0-排队中 1-待执行 2-执行中 3-执行成功 4-执行失败 5-已跳过 6-已取消
     */
    @ApiModelProperty(value= "状态:0-待执行 1-执行中 2-执行成功 3-执行失败 4-已跳过 5-已取消")
    @ApiModelProperty(value= "状态:0-排队中 1-待执行 2-执行中 3-执行成功 4-执行失败 5-已跳过 6-已取消")
    private Short status;
    /**
@@ -177,6 +177,12 @@
    private Date updateTime;
    public TaskInstanceNode() {}
    public TaskInstanceNode(TaskPathTemplateNode taskPathTemplateNode) {
        this.nodeName = taskPathTemplateNode.getNodeName();
        this.nodeType = taskPathTemplateNode.getNodeType();
        this.systemCode = taskPathTemplateNode.getSystemCode();
        this.systemName = taskPathTemplateNode.getSystemName();
    }
    public TaskInstanceNode(Long taskId,String taskNo,Integer nodeOrder,String nodeCode,String nodeName,String nodeType,String systemCode,String systemName,String executeParams,Short status,String executeResult,String errorCode,String errorMessage,Date estimatedStartTime,Date actualStartTime,Date actualEndTime,Date timeoutAt,Integer durationSeconds,Integer retryTimes,Integer maxRetryTimes,String dependsOnNodes,Date createTime,Date updateTime) {
        this.taskId = taskId;
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/TaskPathTemplateMerge.java
New file
@@ -0,0 +1,234 @@
package com.vincent.rsf.server.system.entity;
import java.text.SimpleDateFormat;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableLogic;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.SpringUtils;
import com.vincent.rsf.server.system.service.UserService;
import com.vincent.rsf.server.system.entity.User;
import java.io.Serializable;
import java.util.Date;
@Data
@Accessors(chain = true)
@TableName(value = "mission_task_path_template_merge", autoResultMap = true)
public class TaskPathTemplateMerge implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 主键ID
     */
    @ApiModelProperty(value= "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 模板编码,唯一标识
     */
    @ApiModelProperty(value= "模板编码,唯一标识")
    private String templateCode;
    /**
     * 模板名称
     */
    @ApiModelProperty(value= "模板名称")
    private String templateName;
    /**
     * 起点类型/起点系统标识
     */
    @ApiModelProperty(value= "起点类型/起点系统标识")
    private String sourceType;
    /**
     * 终点类型/终点系统标识
     */
    @ApiModelProperty(value= "终点类型/终点系统标识")
    private String targetType;
    /**
     * 条件表达式(JSON),用于更复杂的匹配
     */
    @ApiModelProperty(value = "条件表达式(JSON),用于更复杂的匹配")
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<Integer> conditionExpression;
    /**
     * 条件描述,人工可读
     */
    @ApiModelProperty(value= "条件描述,人工可读")
    private String conditionDesc;
    /**
     * 版本号
     */
    @ApiModelProperty(value= "版本号")
    private Integer version;
    /**
     * 是否为当前生效版本 0-否 1-是
     */
    @ApiModelProperty(value= "是否为当前生效版本 0-否 1-是")
    private Short isCurrent;
    /**
     * 生效时间
     */
    @ApiModelProperty(value= "生效时间")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date effectiveTime;
    /**
     * 失效时间
     */
    @ApiModelProperty(value= "失效时间")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date expireTime;
    /**
     * 优先级 1-10,数字越小优先级越高
     */
    @ApiModelProperty(value= "优先级 1-10,数字越小优先级越高")
    private Short priority;
    /**
     * 整体超时时间(分钟)
     */
    @ApiModelProperty(value= "整体超时时间(分钟)")
    private Integer timeoutMinutes;
    /**
     * 最大重试次数
     */
    @ApiModelProperty(value= "最大重试次数")
    private Integer maxRetryTimes;
    /**
     * 重试间隔(秒)
     */
    @ApiModelProperty(value= "重试间隔(秒)")
    private Integer retryIntervalSeconds;
    /**
     * 状态 0-禁用 1-启用
     */
    @ApiModelProperty(value= "状态 0-禁用 1-启用")
    private Short status;
    /**
     * 备注
     */
    @ApiModelProperty(value= "备注")
    private String remark;
    /**
     * 创建人
     */
    @ApiModelProperty(value= "创建人")
    private Long createBy;
    /**
     * 更新人
     */
    @ApiModelProperty(value= "更新人")
    private Long updateBy;
    /**
     * 创建时间
     */
    @ApiModelProperty(value= "创建时间")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    /**
     * 更新时间
     */
    @ApiModelProperty(value= "更新时间")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    /**
     * 步序长度
     */
    @ApiModelProperty(value= "步序长度")
    private Integer stepSize;
    /**
     * 租户
     */
    @ApiModelProperty(value= "租户")
    private Long tenantId;
    /**
     * 是否删除 1: 是  0: 否
     */
    @ApiModelProperty(value= "是否删除 1: 是  0: 否  ")
    @TableLogic
    private Long deleted;
    public TaskPathTemplateMerge() {}
    public String getEffectiveTime$(){
        if (Cools.isEmpty(this.effectiveTime)){
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.effectiveTime);
    }
    public String getExpireTime$(){
        if (Cools.isEmpty(this.expireTime)){
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.expireTime);
    }
    public String getCreateTime$(){
        if (Cools.isEmpty(this.createTime)){
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
    }
    public String getUpdateTime$(){
        if (Cools.isEmpty(this.updateTime)){
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
    }
    public Boolean getStatusBool(){
        if (null == this.status){ return null; }
        switch (this.status){
            case 1:
                return true;
            case 0:
                return false;
            default:
                return null;
        }
    }
    public String[] route(){
        return new String[]{this.sourceType, this.targetType, this.stepSize.toString(), this.id.toString()};
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/TaskPathTemplateMergeMapper.java
New file
@@ -0,0 +1,12 @@
package com.vincent.rsf.server.system.mapper;
import com.vincent.rsf.server.system.entity.TaskPathTemplateMerge;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface TaskPathTemplateMergeMapper extends BaseMapper<TaskPathTemplateMerge> {
}
rsf-server/src/main/java/com/vincent/rsf/server/system/service/TaskPathTemplateMergeService.java
New file
@@ -0,0 +1,8 @@
package com.vincent.rsf.server.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.server.system.entity.TaskPathTemplateMerge;
public interface TaskPathTemplateMergeService extends IService<TaskPathTemplateMerge> {
}
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TaskPathTemplateMergeServiceImpl.java
New file
@@ -0,0 +1,12 @@
package com.vincent.rsf.server.system.service.impl;
import com.vincent.rsf.server.system.mapper.TaskPathTemplateMergeMapper;
import com.vincent.rsf.server.system.entity.TaskPathTemplateMerge;
import com.vincent.rsf.server.system.service.TaskPathTemplateMergeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service("taskPathTemplateMergeService")
public class TaskPathTemplateMergeServiceImpl extends ServiceImpl<TaskPathTemplateMergeMapper, TaskPathTemplateMerge> implements TaskPathTemplateMergeService {
}
rsf-server/src/main/java/taskPathTemplateMerge.sql
New file
@@ -0,0 +1,37 @@
-- save taskPathTemplateMerge record
-- mysql
insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.taskPathTemplateMerge', '0', '/system/taskPathTemplateMerge', 'taskPathTemplateMerge', '0' , '0', '1' , '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query 项目实际路径', '', '1', 'system:taskPathTemplateMerge:list', '0', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create 项目实际路径', '', '1', 'system:taskPathTemplateMerge:save', '1', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update 项目实际路径', '', '1', 'system:taskPathTemplateMerge:update', '2', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete 项目实际路径', '', '1', 'system:taskPathTemplateMerge:remove', '3', '1', '1');
-- locale menu name
taskPathTemplateMerge: 'TaskPathTemplateMerge',
-- locale field
taskPathTemplateMerge: {
    templateCode: "templateCode",
    templateName: "templateName",
    sourceType: "sourceType",
    targetType: "targetType",
    conditionExpression: "conditionExpression",
    conditionDesc: "conditionDesc",
    version: "version",
    isCurrent: "isCurrent",
    effectiveTime: "effectiveTime",
    expireTime: "expireTime",
    priority: "priority",
    timeoutMinutes: "timeoutMinutes",
    maxRetryTimes: "maxRetryTimes",
    retryIntervalSeconds: "retryIntervalSeconds",
    remark: "remark",
    stepSize: "stepSize",
},
-- ResourceContent
import taskPathTemplateMerge from './taskPathTemplateMerge';
case 'taskPathTemplateMerge':
    return taskPathTemplateMerge;
rsf-server/src/main/resources/mapper/system/TaskPathTemplateMergeMapper.xml
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vincent.rsf.server.system.mapper.TaskPathTemplateMergeMapper">
</mapper>
version/db/man_warehouse_areas_add_sort.sql
New file
@@ -0,0 +1,12 @@
-- 为 man_warehouse_areas 表添加 sort 排序字段
-- @author chen.lin
-- @time 2026-02-02
-- 添加 sort 字段,默认值为 NULL,允许为空
ALTER TABLE `man_warehouse_areas`
ADD COLUMN `sort` INT(11) NULL DEFAULT NULL COMMENT '排序字段' AFTER `status`;
-- 为现有数据设置默认排序值(使用 id 作为初始排序值)
UPDATE `man_warehouse_areas`
SET `sort` = `id`
WHERE `sort` IS NULL;