From bcf39531dcfaea85297312405fbecc7eebccf7d0 Mon Sep 17 00:00:00 2001
From: skyouc
Date: 星期五, 25 四月 2025 09:33:21 +0800
Subject: [PATCH] #新增  1. 新增波次列表及详情  2. 新增立库站点绑定

---
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java   |   63 +
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java                    |  291 +++++
 rsf-admin/src/page/wave/WaveEdit.jsx                                                            |  130 ++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceBindServiceImpl.java |   12 
 rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java                         |    6 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/DeviceBindService.java          |    8 
 rsf-admin/src/page/orders/outStock/OutOrderModal.jsx                                            |   34 
 rsf-admin/src/page/deviceBind/DeviceBindEdit.jsx                                                |  146 ++
 rsf-server/src/main/java/waveItem.sql                                                           |   35 
 rsf-admin/src/page/orders/outStock/OutOrderList.jsx                                             |   32 
 rsf-admin/src/i18n/en.js                                                                        |   17 
 rsf-admin/src/page/ResourceContent.js                                                           |    3 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java       |   12 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/OutStockService.java            |    2 
 rsf-server/src/main/resources/mapper/manager/WaveMapper.xml                                     |    5 
 rsf-admin/src/page/wave/WavePanel.jsx                                                           |   87 +
 rsf-admin/src/page/waveItem/WaveItemPanel.jsx                                                   |  135 ++
 rsf-admin/src/page/wave/WaveCreate.jsx                                                          |  158 ++
 rsf-server/src/main/resources/mapper/manager/DeviceBindMapper.xml                               |    5 
 rsf-admin/src/page/basicInfo/deviceSite/DeviceSiteList.jsx                                      |    5 
 rsf-admin/src/page/basicInfo/deviceSite/InitModal.jsx                                           |   26 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java      |   11 
 rsf-admin/src/page/waveItem/index.jsx                                                           |   18 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveItemService.java            |    8 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceSiteServiceImpl.java |   25 
 rsf-admin/src/page/waveItem/WaveItemList.jsx                                                    |  178 +++
 rsf-admin/src/page/deviceBind/DeviceBindCreate.jsx                                              |  174 +++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceBind.java                  |  133 ++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceSite.java                  |    6 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java                |    8 
 rsf-server/src/main/resources/mapper/manager/WaveItemMapper.xml                                 |    5 
 rsf-admin/src/i18n/zh.js                                                                        |   27 
 rsf-admin/src/page/deviceBind/DeviceBindPanel.jsx                                               |  111 +
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java                        |  249 ++++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/WaveMapper.java                  |   12 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java          |  108 +
 rsf-admin/src/page/wave/index.jsx                                                               |   18 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveItemController.java      |  110 +
 rsf-admin/src/page/wave/WaveList.jsx                                                            |  174 +++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/WaveItemMapper.java              |   12 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/params/DeviceSiteParame.java |    5 
 rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java             |    5 
 rsf-server/src/main/java/wave.sql                                                               |   27 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/WaveExceStatus.java               |   27 
 rsf-admin/src/page/basicInfo/loc/LocList.jsx                                                    |   16 
 rsf-server/src/main/java/deviceBind.sql                                                         |   31 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveItemServiceImpl.java   |   12 
 rsf-admin/src/page/deviceBind/DeviceBindList.jsx                                                |  170 ++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/DeviceBindMapper.java            |   12 
 rsf-admin/src/page/deviceBind/index.jsx                                                         |   18 
 rsf-admin/src/page/waveItem/WaveItemEdit.jsx                                                    |  176 +++
 rsf-admin/src/page/waveItem/WaveItemCreate.jsx                                                  |  204 +++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/DeviceBindController.java    |  110 +
 53 files changed, 3,350 insertions(+), 62 deletions(-)

diff --git a/rsf-admin/src/i18n/en.js b/rsf-admin/src/i18n/en.js
index 0f29dff..d81da4f 100644
--- a/rsf-admin/src/i18n/en.js
+++ b/rsf-admin/src/i18n/en.js
@@ -70,6 +70,7 @@
             loadMore: 'Load More Data',
             complete: 'Complete',
             deprecate: 'Deprecate',
+            inputPlaceholder: 'Use commas to separate',
             resend: 'RESEND',
             selected: 'selected',
             batch: 'batch'
@@ -175,6 +176,7 @@
         outStockItem: 'Out Stock Item',
         inStockPoces: 'In Stock Pocess',
         outStockPoces: 'Out Stock Pocess',
+        deviceBind: 'Device Bind',
     },
     table: {
         field: {
@@ -616,6 +618,7 @@
             },
             deliveryItem: {
                 deliveryId: "deliveryId",
+                deliveryCode: "Delivery Code",
                 platItemId: "platItemId",
                 matnrCode: "matnrCode",
                 matnrName: "matnrName",
@@ -667,7 +670,6 @@
                 deviceCode: "deviceCode",
                 deviceSite: "deviceSite",
                 flagInit: "flagInit",
-                sites: "sites",
             },
             waitPakin: {
                 code: "code",
@@ -833,6 +835,18 @@
                 model: "model",
                 fieldsIndex: "fieldsIndex",
             },
+            deviceBind: {
+                currentRow: "currentRow",
+                startRow: "startRow",
+                endRow: "endRow",
+                deviceQty: "deviceQty",
+                startDeviceNo: "startDeviceNo",
+                endDeviceNo: "endDeviceNo",
+                staList: "staList",
+                typeId: "typeId",
+                beSimilar: "beSimilar",
+                emptySimilar: "emptySimilar",
+            },
         }
     },
     page: {
@@ -944,6 +958,7 @@
         asnCreate: "Create By Order",
         createTask: "createTask",
         recover: "recover",
+        createWave: "Create Wave",
         order: 'Orders',
 
     },
diff --git a/rsf-admin/src/i18n/zh.js b/rsf-admin/src/i18n/zh.js
index 44b724e..82b5bb0 100644
--- a/rsf-admin/src/i18n/zh.js
+++ b/rsf-admin/src/i18n/zh.js
@@ -58,6 +58,7 @@
             collapse: '鎶樺彔',
             collapseAll: '鍏ㄩ儴鎶樺彔',
             scope: '鏉冮檺',
+            inputPlaceholder: '绔欑偣鍙~澶氫釜锛屼腑闂翠互鑻辨枃閫楀彿鍖哄垎锛�,锛�',
             import: {
                 title: '瀵煎叆',
                 stop: '鍋滄瀵煎叆',
@@ -176,6 +177,7 @@
         outStockItem: '鍑哄簱鍗曟槑缁�',
         inStockPoces: '鍏ュ簱娴佺▼',
         outStockPoces: '鍑哄簱娴佺▼',
+        deviceBind: '璁惧缁戝畾',
     },
     table: {
         field: {
@@ -361,8 +363,8 @@
             loc: {
                 warehouseId: "鎵�灞炰粨搴�",
                 areaId: "鎵�灞炲簱鍖�",
-                code: "搴撲綅缂栫爜",
-                type: "搴撲綅绫诲瀷",
+                code: "缂栫爜",
+                type: "绫诲瀷",
                 name: "鍚嶇О",
                 flagLogic: "铏氭嫙搴撲綅",
                 fucAtrrs: "鍔熻兘灞炴��",
@@ -661,6 +663,7 @@
             },
             deliveryItem: {
                 deliveryId: "涓诲崟ID",
+                deliveryCode: "鍗曞彿",
                 platItemId: "琛屽彿",
                 matnrCode: "鐗╂枡缂栫爜",
                 matnrName: "鐗╂枡鍚嶇О",
@@ -704,15 +707,14 @@
             },
             deviceSite: {
                 type: "鍏ュ嚭搴撶被鍨�",
-                site: "绔欑偣缂栧彿",
+                site: "浣滀笟绔欑偣",
                 name: "鍚嶇О",
-                wcsCode: "wcs绔欑偣缂栧彿",
+                target: "鐩爣绔欑偣",
                 label: "绔欑偣鏍囩",
                 device: "绔嬪簱绫诲瀷",
                 deviceCode: "璁惧缂栧彿",
                 deviceSite: "璁惧绔欑偣",
                 flagInit: "鏄惁鍒濆鍖�",
-                sites: "浣滀笟绔欑偣",
             },
             waitPakin: {
                 code: "缂栫爜",
@@ -878,6 +880,18 @@
                 model: "鍨嬪彿",
                 fieldsIndex: "鍔ㄦ�佺储寮�",
             },
+            deviceBind: {
+                currentRow: "褰撳墠鎺掑彿",
+                startRow: "璧峰鎺掑彿",
+                endRow: "缁堟鎺掑彿",
+                deviceQty: "璁惧鏁伴噺",
+                startDeviceNo: "璧峰璁惧鍙�",
+                endDeviceNo: "缁堟璁惧鍙�",
+                staList: "绔欑偣闆�",
+                typeId: "搴撳尯绫诲瀷",
+                beSimilar: "鐩镐技寮�鍏�",
+                emptySimilar: "绌烘澘闈犺繎寮�鍏�",
+            },
         }
     },
     page: {
@@ -991,7 +1005,8 @@
         complete: "瀹屾垚",
         close: "鍏抽棴",
         asnCreate: "閫氳繃鍗曟嵁鍒涘缓",
-        createTask: "鐢熸垚浠诲姟",
+        createTask: "涓嬪彂浠诲姟",
+        createWave: "鐢熸垚娉㈡",
         recover: "缁х画鏀惰揣",
     },
 };
diff --git a/rsf-admin/src/page/ResourceContent.js b/rsf-admin/src/page/ResourceContent.js
index 5dd2787..b3271cf 100644
--- a/rsf-admin/src/page/ResourceContent.js
+++ b/rsf-admin/src/page/ResourceContent.js
@@ -43,6 +43,7 @@
 import stock from './orders/stock';
 import delivery from './orders/delivery';
 import outStock from './orders/outStock';
+import deviceBind from './deviceBind';
 
 
 const ResourceContent = (node) => {
@@ -125,6 +126,8 @@
             return delivery;
         case 'outStock':
             return outStock;
+        case 'deviceBind':
+            return deviceBind;
         default:
             return {
                 list: ListGuesser,
diff --git a/rsf-admin/src/page/basicInfo/deviceSite/DeviceSiteList.jsx b/rsf-admin/src/page/basicInfo/deviceSite/DeviceSiteList.jsx
index 21321b5..706513c 100644
--- a/rsf-admin/src/page/basicInfo/deviceSite/DeviceSiteList.jsx
+++ b/rsf-admin/src/page/basicInfo/deviceSite/DeviceSiteList.jsx
@@ -148,18 +148,17 @@
                     preferenceKey='deviceSite'
                     bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
                     rowClick={(id, resource, record) => false}
-                    omit={['id', 'createTime', 'createBy', 'memo']}
+                    omit={['id', 'createTime', 'createBy', 'memo', 'label']}
                 >
                     <NumberField source="id" />
                     <TextField source="name" label="table.field.deviceSite.name" />
                     <NumberField source="type$" label="table.field.deviceSite.type" />
                     <TextField source="site" label="table.field.deviceSite.site" />
-                    <TextField source="wcsCode" label="table.field.deviceSite.wcsCode" />
+                    <TextField source="target" label="table.field.deviceSite.target" />
                     <TextField source="label" label="table.field.deviceSite.label" />
                     <TextField source="device$" label="table.field.deviceSite.device" />
                     <TextField source="deviceCode" label="table.field.deviceSite.deviceCode" />
                     <TextField source="deviceSite" label="table.field.deviceSite.deviceSite" />
-
                     <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}>
                         <TextField source="nickname" />
                     </ReferenceField>
diff --git a/rsf-admin/src/page/basicInfo/deviceSite/InitModal.jsx b/rsf-admin/src/page/basicInfo/deviceSite/InitModal.jsx
index fbd1634..163c261 100644
--- a/rsf-admin/src/page/basicInfo/deviceSite/InitModal.jsx
+++ b/rsf-admin/src/page/basicInfo/deviceSite/InitModal.jsx
@@ -95,7 +95,7 @@
 
 
     return (
-        <Dialog open={open} maxWidth="md" fullWidth>
+        <Dialog open={open} maxWidth="lg" fullWidth>
             <Form onSubmit={handleSubmit}>
                 <DialogCloseButton onClose={handleClose} />
                 <DialogTitle>{translate('toolbar.siteInit')}</DialogTitle>
@@ -119,8 +119,6 @@
                                     dictTypeCode="sys_device_type"
                                 />
                             </Grid>
-
-
                             <Grid item xs={4}>
                                 <TextInput
                                     label={translate("table.field.deviceSite.deviceCode")}
@@ -129,25 +127,33 @@
                                     type="number"
                                 />
                             </Grid>
-
                             <Grid item xs={4}>
                                 <TextInput
                                     label={translate("table.field.deviceSite.deviceSite")}
                                     name="deviceSites"
+                                    placeholder={translate('common.action.inputPlaceholder')}
                                     size="small"
-                                    type="number"
+                                // type="number"
                                 />
                             </Grid>
-
                             <Grid item xs={4}>
                                 <TextInput
-                                    label={translate("table.field.deviceSite.sites")}
-                                    name="sites"
+                                    label={translate("table.field.deviceSite.site")}
+                                    name="site"
+                                    placeholder={translate('common.action.inputPlaceholder')}
                                     size="small"
-                                    type="number"
+                                // type="number"
                                 />
                             </Grid>
-
+                            <Grid item xs={4}>
+                                <TextInput
+                                    label={translate("table.field.deviceSite.target")}
+                                    name="target"
+                                    placeholder={translate('common.action.inputPlaceholder')}
+                                    size="small"
+                                // type="number"
+                                />
+                            </Grid>
                             <Grid item xs={4}>
                                 <SelectInput
                                     label="table.field.deviceSite.flagInit"
diff --git a/rsf-admin/src/page/basicInfo/loc/LocList.jsx b/rsf-admin/src/page/basicInfo/loc/LocList.jsx
index 3484d69..ef686d0 100644
--- a/rsf-admin/src/page/basicInfo/loc/LocList.jsx
+++ b/rsf-admin/src/page/basicInfo/loc/LocList.jsx
@@ -66,20 +66,20 @@
     '& .column-name': {
     },
     '& .opt': {
-        width: 200
+        width: 180
     },
-    '& .RaDatagrid-headerCell': {
-        textAlign: 'left'
-    },
-    '& .RaDatagrid-rowCell': {
-        textAlign: 'left'
-    },
+    // '& .RaDatagrid-headerCell': {
+    //     textAlign: 'left'
+    // },
+    // '& .RaDatagrid-rowCell': {
+    //     textAlign: 'left'
+    // },
     '& .type .MuiTypography-root': {
         whiteSpace: 'nowrap',
         overflow: 'hidden',
         textOverflow: 'ellipsis',
         display: 'block',
-        width: '200px',
+        width: 'auto',
     },
 }));
 
diff --git a/rsf-admin/src/page/deviceBind/DeviceBindCreate.jsx b/rsf-admin/src/page/deviceBind/DeviceBindCreate.jsx
new file mode 100644
index 0000000..101d8d4
--- /dev/null
+++ b/rsf-admin/src/page/deviceBind/DeviceBindCreate.jsx
@@ -0,0 +1,174 @@
+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 DeviceBindCreate = (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}>
+                                    <NumberInput
+                                        label="table.field.deviceBind.currentRow"
+                                        source="currentRow"
+                                        autoFocus
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.deviceBind.startRow"
+                                        source="startRow"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.deviceBind.endRow"
+                                        source="endRow"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.deviceBind.deviceQty"
+                                        source="deviceQty"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.deviceBind.startDeviceNo"
+                                        source="startDeviceNo"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.deviceBind.endDeviceNo"
+                                        source="endDeviceNo"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.deviceBind.staList"
+                                        source="staList"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.deviceBind.typeId"
+                                        source="typeId"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.deviceBind.beSimilar"
+                                        source="beSimilar"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.deviceBind.emptySimilar"
+                                        source="emptySimilar"
+                                        parse={v => v}
+                                    />
+                                </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 DeviceBindCreate;
diff --git a/rsf-admin/src/page/deviceBind/DeviceBindEdit.jsx b/rsf-admin/src/page/deviceBind/DeviceBindEdit.jsx
new file mode 100644
index 0000000..9bd771b
--- /dev/null
+++ b/rsf-admin/src/page/deviceBind/DeviceBindEdit.jsx
@@ -0,0 +1,146 @@
+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 DeviceBindEdit = () => {
+    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}>
+                            <NumberInput
+                                label="table.field.deviceBind.currentRow"
+                                source="currentRow"
+                                autoFocus
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.deviceBind.startRow"
+                                source="startRow"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.deviceBind.endRow"
+                                source="endRow"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.deviceBind.deviceQty"
+                                source="deviceQty"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.deviceBind.startDeviceNo"
+                                source="startDeviceNo"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.deviceBind.endDeviceNo"
+                                source="endDeviceNo"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.deviceBind.staList"
+                                source="staList"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.deviceBind.typeId"
+                                source="typeId"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.deviceBind.beSimilar"
+                                source="beSimilar"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.deviceBind.emptySimilar"
+                                source="emptySimilar"
+                                parse={v => v}
+                            />
+                        </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 DeviceBindEdit;
diff --git a/rsf-admin/src/page/deviceBind/DeviceBindList.jsx b/rsf-admin/src/page/deviceBind/DeviceBindList.jsx
new file mode 100644
index 0000000..7b47945
--- /dev/null
+++ b/rsf-admin/src/page/deviceBind/DeviceBindList.jsx
@@ -0,0 +1,170 @@
+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 DeviceBindCreate from "./DeviceBindCreate";
+import DeviceBindPanel from "./DeviceBindPanel";
+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 />,
+
+    <NumberInput source="currentRow" label="table.field.deviceBind.currentRow" />,
+    <NumberInput source="startRow" label="table.field.deviceBind.startRow" />,
+    <NumberInput source="endRow" label="table.field.deviceBind.endRow" />,
+    <NumberInput source="deviceQty" label="table.field.deviceBind.deviceQty" />,
+    <NumberInput source="startDeviceNo" label="table.field.deviceBind.startDeviceNo" />,
+    <NumberInput source="endDeviceNo" label="table.field.deviceBind.endDeviceNo" />,
+    <TextInput source="staList" label="table.field.deviceBind.staList" />,
+    <NumberInput source="typeId" label="table.field.deviceBind.typeId" />,
+    <TextInput source="beSimilar" label="table.field.deviceBind.beSimilar" />,
+    <TextInput source="emptySimilar" label="table.field.deviceBind.emptySimilar" />,
+
+    <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 DeviceBindList = () => {
+    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.deviceBind"}
+                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+                filters={filters}
+                sort={{ field: "create_time", order: "desc" }}
+                actions={(
+                    <TopToolbar>
+                        <FilterButton />
+                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+                        <SelectColumnsButton preferenceKey='deviceBind' />
+                        <MyExportButton />
+                    </TopToolbar>
+                )}
+                perPage={DEFAULT_PAGE_SIZE}
+            >
+                <StyledDatagrid
+                    preferenceKey='deviceBind'
+                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
+                    rowClick={(id, resource, record) => false}
+                    expand={() => <DeviceBindPanel />}
+                    expandSingle={true}
+                    omit={['id', 'createTime', 'createBy', 'memo']}
+                >
+                    <NumberField source="id" />
+                    <NumberField source="currentRow" label="table.field.deviceBind.currentRow" />
+                    <NumberField source="startRow" label="table.field.deviceBind.startRow" />
+                    <NumberField source="endRow" label="table.field.deviceBind.endRow" />
+                    <NumberField source="deviceQty" label="table.field.deviceBind.deviceQty" />
+                    <NumberField source="startDeviceNo" label="table.field.deviceBind.startDeviceNo" />
+                    <NumberField source="endDeviceNo" label="table.field.deviceBind.endDeviceNo" />
+                    <TextField source="staList" label="table.field.deviceBind.staList" />
+                    <NumberField source="typeId" label="table.field.deviceBind.typeId" />
+                    <TextField source="beSimilar" label="table.field.deviceBind.beSimilar" />
+                    <TextField source="emptySimilar" label="table.field.deviceBind.emptySimilar" />
+
+                    <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>
+            <DeviceBindCreate
+                open={createDialog}
+                setOpen={setCreateDialog}
+            />
+            <PageDrawer
+                title='DeviceBind Detail'
+                drawerVal={drawerVal}
+                setDrawerVal={setDrawerVal}
+            >
+            </PageDrawer>
+        </Box>
+    )
+}
+
+export default DeviceBindList;
diff --git a/rsf-admin/src/page/deviceBind/DeviceBindPanel.jsx b/rsf-admin/src/page/deviceBind/DeviceBindPanel.jsx
new file mode 100644
index 0000000..fa74506
--- /dev/null
+++ b/rsf-admin/src/page/deviceBind/DeviceBindPanel.jsx
@@ -0,0 +1,111 @@
+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 DeviceBindPanel = () => {
+    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.deviceBind.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.deviceBind.currentRow" 
+                                property={record.currentRow}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.startRow" 
+                                property={record.startRow}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.endRow" 
+                                property={record.endRow}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.deviceQty" 
+                                property={record.deviceQty}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.startDeviceNo" 
+                                property={record.startDeviceNo}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.endDeviceNo" 
+                                property={record.endDeviceNo}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.staList" 
+                                property={record.staList}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.typeId" 
+                                property={record.typeId}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.beSimilar" 
+                                property={record.beSimilar}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.deviceBind.emptySimilar" 
+                                property={record.emptySimilar}
+                            />
+                        </Grid>
+
+                    </Grid>
+                </CardContent>
+            </Card >
+        </>
+    );
+};
+
+export default DeviceBindPanel;
diff --git a/rsf-admin/src/page/deviceBind/index.jsx b/rsf-admin/src/page/deviceBind/index.jsx
new file mode 100644
index 0000000..08c2f9e
--- /dev/null
+++ b/rsf-admin/src/page/deviceBind/index.jsx
@@ -0,0 +1,18 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    ListGuesser,
+    EditGuesser,
+    ShowGuesser,
+} from "react-admin";
+
+import DeviceBindList from "./DeviceBindList";
+import DeviceBindEdit from "./DeviceBindEdit";
+
+export default {
+    list: DeviceBindList,
+    edit: DeviceBindEdit,
+    show: ShowGuesser,
+    recordRepresentation: (record) => {
+        return `${record.id}`
+    }
+};
diff --git a/rsf-admin/src/page/orders/outStock/OutOrderList.jsx b/rsf-admin/src/page/orders/outStock/OutOrderList.jsx
index 8c1bb08..58bb153 100644
--- a/rsf-admin/src/page/orders/outStock/OutOrderList.jsx
+++ b/rsf-admin/src/page/orders/outStock/OutOrderList.jsx
@@ -55,6 +55,7 @@
 import OutOrderCreate from "./OutOrderCreate";
 import AddIcon from '@mui/icons-material/Add';
 import OutOrderModal from "./OutOrderModal";
+import PublicIcon from '@mui/icons-material/Public';
 
 const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
   '& .css-1vooibu-MuiSvgIcon-root': {
@@ -141,7 +142,7 @@
             <MyCreateButton onClick={() => { setManualDialog(true) }} />
             <SelectColumnsButton preferenceKey='outStock' />
             <ImportButton value={'asnOrderItem'} />
-            <MyExportButton />
+            {/* <MyExportButton /> */}
           </TopToolbar>
         )}
         perPage={DEFAULT_PAGE_SIZE}
@@ -151,6 +152,7 @@
           preferenceKey='outStock'
           bulkActionButtons={
             <>
+              <PublicTaskButton />
               <MyExportButton />
               <BulkDeleteButton mutationMode={OPERATE_MODE}
               />
@@ -201,6 +203,25 @@
 }
 export default OutOrderList;
 
+
+const PublicTaskButton = () => {
+  const record = useRecordContext();
+  const { selectedIds, onUnselectItems } = useListContext();
+
+  const pubClick = (event) => {
+    event.stopPropagation();
+    onUnselectItems();
+
+  }
+
+  return (
+    <Button
+      onClick={pubClick}
+      label={"toolbar.createWave"}
+      startIcon={<PublicIcon />}
+    />);
+}
+
 const MyButton = ({ setCreateDialog, setmodalType }) => {
   const record = useRecordContext();
   const handleEditClick = (btn) => {
@@ -227,8 +248,16 @@
   const refresh = useRefresh();
 
   const createByOrder = async (event) => {
+    const {selectedIds, onUnselectItems} = useListContext();
     event.stopPropagation();
     setCreateDialog(true);
+    const res = await request.post(`/outStock/generate/wave`, { ids: selectedIds });
+    if (res?.data?.code === 200) {
+      notify(res.data.msg);
+    } else {
+      notify(res.data.msg);
+    }
+
   }
 
   return (
@@ -241,7 +270,6 @@
   const record = useRecordContext();
   const notify = useNotify();
   const refresh = useRefresh();
-  console.log(record);
 
   const cancelOrder = async () => {
     const { data: { code, data, msg } } = await request.get(`/outStock/cancel/${record?.id}`);
diff --git a/rsf-admin/src/page/orders/outStock/OutOrderModal.jsx b/rsf-admin/src/page/orders/outStock/OutOrderModal.jsx
index 95b48a6..5bc8cc3 100644
--- a/rsf-admin/src/page/orders/outStock/OutOrderModal.jsx
+++ b/rsf-admin/src/page/orders/outStock/OutOrderModal.jsx
@@ -54,14 +54,6 @@
     '& .RaDatagrid-row': {
         cursor: 'auto'
     },
-    '& .column-name': {
-    },
-    '& .opt': {
-        width: 220
-    },
-    '& .wkType': {
-        width: 110
-    },
     '& .status': {
         width: 90
     },
@@ -102,9 +94,10 @@
         const { filterValues, setFilters, refetch } = useListContext('deliveryItem');
         const [formValues, setFormValues] = useState(filterValues);
         const handleChange = (event) => {
+            if (event.target == undefined || event.target == null) {return}
             setFormValues(formValues => ({
                 ...formValues,
-                [event.target.name]: event.target.value
+              [event.target.name]: event.target.value
             }));
         };
 
@@ -125,10 +118,22 @@
                     </Stack>
                     <Stack>
                         <TextInput
+                            source="deliveryCode"
+                            label="table.field.deliveryItem.deliveryCode"
+                            defaultValue={params?.matnrName}
+                            onChange={handleChange}
+                            resettable
+
+                        />
+                    </Stack>
+                    <Stack>
+                        <TextInput
                             source="matnrName"
                             label="table.field.deliveryItem.matnrName"
                             defaultValue={params?.matnrName}
                             onChange={handleChange}
+                            resettable
+
                         />
                     </Stack>
                     <Stack>
@@ -136,6 +141,7 @@
                             source="matnrCode"
                             label="table.field.deliveryItem.matnrCode"
                             defaultValue={params?.matnrCode}
+                            resettable
                             onChange={handleChange} />
                     </Stack>
                     <Stack>
@@ -143,6 +149,7 @@
                             source="splrName"
                             label="table.field.deliveryItem.splrName"
                             defaultValue={params?.splrName}
+                            resettable
                             onChange={handleChange} />
                     </Stack>
                 </Grid>
@@ -162,7 +169,7 @@
             aria-hidden
             fullWidth
             disableRestoreFocus
-            maxWidth="lg"
+            maxWidth="xl"
         >
             <DialogTitle id="form-dialog-title" sx={{
                 position: 'sticky',
@@ -200,14 +207,16 @@
                             perPage={DEFAULT_PAGE_SIZE}
                         >
                             <StyledDatagrid
+                                sx={{ height: '400' }}
                                 preferenceKey='deliveryItem'
-                                bulkActionButtons={<AddOutStockButton  setOpen={setOpen}/>}
+                                bulkActionButtons={<AddOutStockButton setOpen={setOpen} />}
                                 rowClick={(id, resource, record) => false}
                                 expand={false}
                                 expandSingle={true}
                                 omit={['id', 'createTime', 'createBy', 'memo', 'workQty', 'startTime', 'endTime', 'updateBy', 'createTime']}
                             >
                                 <NumberField source="id" />
+                                <TextField source="deliveryCode" label="table.field.deliveryItem.deliveryCode" />
                                 <TextField source="matnrCode" label="table.field.deliveryItem.matnrCode" />
                                 <TextField source="matnrName" label="table.field.deliveryItem.matnrName" />
                                 <TextField source="unit" label="table.field.deliveryItem.unit" />
@@ -236,8 +245,7 @@
     const { selectedIds, onUnselectItems } = useListContext();
     const notify = useNotify();
     const confirm = async (event) => {
-        console.log(selectedIds);
-        const res = await request.post(`/outStock/generate/orders`, {ids: selectedIds});
+        const res = await request.post(`/outStock/generate/orders`, { ids: selectedIds });
         if (res?.data?.code === 200) {
             notify(res.data.msg);
         } else {
diff --git a/rsf-admin/src/page/wave/WaveCreate.jsx b/rsf-admin/src/page/wave/WaveCreate.jsx
new file mode 100644
index 0000000..ad26794
--- /dev/null
+++ b/rsf-admin/src/page/wave/WaveCreate.jsx
@@ -0,0 +1,158 @@
+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 WaveCreate = (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.wave.code"
+                                        source="code"
+                                        parse={v => v}
+                                        autoFocus
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <SelectInput
+                                        label="table.field.wave.type"
+                                        source="type"
+                                        choices={[
+                                            { id: 0, name: '鎵嬪姩' },
+                                            { id: 1, name: '鑷姩' },
+                                        ]}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <SelectInput
+                                        label="table.field.wave.exceStatus"
+                                        source="exceStatus"
+                                        choices={[
+                                            { id: 0, name: '鍒濆鍖�' },
+                                            { id: 1, name: '鐢熸垚浠诲姟' },
+                                            { id: 2, name: '浠诲姟鎾' },
+                                            { id: 3, name: '瀹屾垚' },
+                                        ]}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.wave.anfme"
+                                        source="anfme"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.wave.qty"
+                                        source="qty"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.wave.orderNum"
+                                        source="orderNum"
+                                    />
+                                </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 WaveCreate;
diff --git a/rsf-admin/src/page/wave/WaveEdit.jsx b/rsf-admin/src/page/wave/WaveEdit.jsx
new file mode 100644
index 0000000..b26cc70
--- /dev/null
+++ b/rsf-admin/src/page/wave/WaveEdit.jsx
@@ -0,0 +1,130 @@
+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 WaveEdit = () => {
+    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.wave.code"
+                                source="code"
+                                parse={v => v}
+                                autoFocus
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <SelectInput
+                                label="table.field.wave.type"
+                                source="type"
+                                choices={[
+                                    { id: 0, name: '鎵嬪姩' },
+                                    { id: 1, name: '鑷姩' },
+                                ]}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <SelectInput
+                                label="table.field.wave.exceStatus"
+                                source="exceStatus"
+                                choices={[
+                                    { id: 0, name: '鍒濆鍖�' },
+                                    { id: 1, name: '鐢熸垚浠诲姟' },
+                                    { id: 2, name: '浠诲姟鎾' },
+                                    { id: 3, name: '瀹屾垚' },
+                                ]}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.wave.anfme"
+                                source="anfme"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.wave.qty"
+                                source="qty"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.wave.orderNum"
+                                source="orderNum"
+                            />
+                        </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 WaveEdit;
diff --git a/rsf-admin/src/page/wave/WaveList.jsx b/rsf-admin/src/page/wave/WaveList.jsx
new file mode 100644
index 0000000..1ee5b56
--- /dev/null
+++ b/rsf-admin/src/page/wave/WaveList.jsx
@@ -0,0 +1,174 @@
+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 WaveCreate from "./WaveCreate";
+import WavePanel from "./WavePanel";
+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="code" label="table.field.wave.code" />,
+    <SelectInput source="type" label="table.field.wave.type"
+        choices={[
+            { id: 0, name: '鎵嬪姩' },
+            { id: 1, name: '鑷姩' },
+        ]}
+    />,
+    <SelectInput source="exceStatus" label="table.field.wave.exceStatus"
+        choices={[
+            { id: 0, name: '鍒濆鍖�' },
+            { id: 1, name: '鐢熸垚浠诲姟' },
+            { id: 2, name: '浠诲姟鎾' },
+            { id: 3, name: '瀹屾垚' },
+        ]}
+    />,
+    <NumberInput source="anfme" label="table.field.wave.anfme" />,
+    <NumberInput source="qty" label="table.field.wave.qty" />,
+    <NumberInput source="orderNum" label="table.field.wave.orderNum" />,
+
+    <TextInput label="common.field.memo" source="memo" />,
+    <SelectInput
+        label="common.field.status"
+        source="status"
+        choices={[
+            { id: '1', name: 'common.enums.statusTrue' },
+            { id: '0', name: 'common.enums.statusFalse' },
+        ]}
+        resettable
+    />,
+]
+
+const WaveList = () => {
+    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.wave"}
+                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+                filters={filters}
+                sort={{ field: "create_time", order: "desc" }}
+                actions={(
+                    <TopToolbar>
+                        <FilterButton />
+                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+                        <SelectColumnsButton preferenceKey='wave' />
+                        <MyExportButton />
+                    </TopToolbar>
+                )}
+                perPage={DEFAULT_PAGE_SIZE}
+            >
+                <StyledDatagrid
+                    preferenceKey='wave'
+                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
+                    rowClick={(id, resource, record) => false}
+                    expand={() => <WavePanel />}
+                    expandSingle={true}
+                    omit={['id', 'createTime', 'createBy', 'memo']}
+                >
+                    <NumberField source="id" />
+                    <TextField source="code" label="table.field.wave.code" />
+                    <TextField source="type$" label="table.field.wave.type" sortable={false} />
+                    <TextField source="exceStatus$" label="table.field.wave.exceStatus" sortable={false} />
+                    <NumberField source="anfme" label="table.field.wave.anfme" />
+                    <NumberField source="qty" label="table.field.wave.qty" />
+                    <NumberField source="orderNum" label="table.field.wave.orderNum" />
+
+                    <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>
+            <WaveCreate
+                open={createDialog}
+                setOpen={setCreateDialog}
+            />
+            <PageDrawer
+                title='Wave Detail'
+                drawerVal={drawerVal}
+                setDrawerVal={setDrawerVal}
+            >
+            </PageDrawer>
+        </Box>
+    )
+}
+
+export default WaveList;
diff --git a/rsf-admin/src/page/wave/WavePanel.jsx b/rsf-admin/src/page/wave/WavePanel.jsx
new file mode 100644
index 0000000..311f963
--- /dev/null
+++ b/rsf-admin/src/page/wave/WavePanel.jsx
@@ -0,0 +1,87 @@
+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 WavePanel = () => {
+    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.wave.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.wave.code" 
+                                property={record.code}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.wave.type" 
+                                property={record.type$}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.wave.exceStatus" 
+                                property={record.exceStatus$}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.wave.anfme" 
+                                property={record.anfme}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.wave.qty" 
+                                property={record.qty}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.wave.orderNum" 
+                                property={record.orderNum}
+                            />
+                        </Grid>
+
+                    </Grid>
+                </CardContent>
+            </Card >
+        </>
+    );
+};
+
+export default WavePanel;
diff --git a/rsf-admin/src/page/wave/index.jsx b/rsf-admin/src/page/wave/index.jsx
new file mode 100644
index 0000000..c5e4655
--- /dev/null
+++ b/rsf-admin/src/page/wave/index.jsx
@@ -0,0 +1,18 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    ListGuesser,
+    EditGuesser,
+    ShowGuesser,
+} from "react-admin";
+
+import WaveList from "./WaveList";
+import WaveEdit from "./WaveEdit";
+
+export default {
+    list: WaveList,
+    edit: WaveEdit,
+    show: ShowGuesser,
+    recordRepresentation: (record) => {
+        return `${record.id}`
+    }
+};
diff --git a/rsf-admin/src/page/waveItem/WaveItemCreate.jsx b/rsf-admin/src/page/waveItem/WaveItemCreate.jsx
new file mode 100644
index 0000000..7e2c428
--- /dev/null
+++ b/rsf-admin/src/page/waveItem/WaveItemCreate.jsx
@@ -0,0 +1,204 @@
+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 WaveItemCreate = (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}>
+                                    <NumberInput
+                                        label="table.field.waveItem.waveId"
+                                        source="waveId"
+                                        autoFocus
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.waveCode"
+                                        source="waveCode"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.waveItem.matnrId"
+                                        source="matnrId"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.matnrName"
+                                        source="matnrName"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.matnrCode"
+                                        source="matnrCode"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.batch"
+                                        source="batch"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.splrBatch"
+                                        source="splrBatch"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.orderCode"
+                                        source="orderCode"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.waveItem.orderItemId"
+                                        source="orderItemId"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.unit"
+                                        source="unit"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.trackCode"
+                                        source="trackCode"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <TextInput
+                                        label="table.field.waveItem.fieldsIndex"
+                                        source="fieldsIndex"
+                                        parse={v => v}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.waveItem.anfme"
+                                        source="anfme"
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.waveItem.workQty"
+                                        source="workQty"
+                                    />
+                                </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 WaveItemCreate;
diff --git a/rsf-admin/src/page/waveItem/WaveItemEdit.jsx b/rsf-admin/src/page/waveItem/WaveItemEdit.jsx
new file mode 100644
index 0000000..a79b3ec
--- /dev/null
+++ b/rsf-admin/src/page/waveItem/WaveItemEdit.jsx
@@ -0,0 +1,176 @@
+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 WaveItemEdit = () => {
+    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}>
+                            <NumberInput
+                                label="table.field.waveItem.waveId"
+                                source="waveId"
+                                autoFocus
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.waveCode"
+                                source="waveCode"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.waveItem.matnrId"
+                                source="matnrId"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.matnrName"
+                                source="matnrName"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.matnrCode"
+                                source="matnrCode"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.batch"
+                                source="batch"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.splrBatch"
+                                source="splrBatch"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.orderCode"
+                                source="orderCode"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.waveItem.orderItemId"
+                                source="orderItemId"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.unit"
+                                source="unit"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.trackCode"
+                                source="trackCode"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <TextInput
+                                label="table.field.waveItem.fieldsIndex"
+                                source="fieldsIndex"
+                                parse={v => v}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.waveItem.anfme"
+                                source="anfme"
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.waveItem.workQty"
+                                source="workQty"
+                            />
+                        </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 WaveItemEdit;
diff --git a/rsf-admin/src/page/waveItem/WaveItemList.jsx b/rsf-admin/src/page/waveItem/WaveItemList.jsx
new file mode 100644
index 0000000..07d87f0
--- /dev/null
+++ b/rsf-admin/src/page/waveItem/WaveItemList.jsx
@@ -0,0 +1,178 @@
+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 WaveItemCreate from "./WaveItemCreate";
+import WaveItemPanel from "./WaveItemPanel";
+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 />,
+
+    <NumberInput source="waveId" label="table.field.waveItem.waveId" />,
+    <TextInput source="waveCode" label="table.field.waveItem.waveCode" />,
+    <NumberInput source="matnrId" label="table.field.waveItem.matnrId" />,
+    <TextInput source="matnrName" label="table.field.waveItem.matnrName" />,
+    <TextInput source="matnrCode" label="table.field.waveItem.matnrCode" />,
+    <TextInput source="batch" label="table.field.waveItem.batch" />,
+    <TextInput source="splrBatch" label="table.field.waveItem.splrBatch" />,
+    <TextInput source="orderCode" label="table.field.waveItem.orderCode" />,
+    <NumberInput source="orderItemId" label="table.field.waveItem.orderItemId" />,
+    <TextInput source="unit" label="table.field.waveItem.unit" />,
+    <TextInput source="trackCode" label="table.field.waveItem.trackCode" />,
+    <TextInput source="fieldsIndex" label="table.field.waveItem.fieldsIndex" />,
+    <NumberInput source="anfme" label="table.field.waveItem.anfme" />,
+    <NumberInput source="workQty" label="table.field.waveItem.workQty" />,
+
+    <TextInput label="common.field.memo" source="memo" />,
+    <SelectInput
+        label="common.field.status"
+        source="status"
+        choices={[
+            { id: '1', name: 'common.enums.statusTrue' },
+            { id: '0', name: 'common.enums.statusFalse' },
+        ]}
+        resettable
+    />,
+]
+
+const WaveItemList = () => {
+    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.waveItem"}
+                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+                filters={filters}
+                sort={{ field: "create_time", order: "desc" }}
+                actions={(
+                    <TopToolbar>
+                        <FilterButton />
+                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+                        <SelectColumnsButton preferenceKey='waveItem' />
+                        <MyExportButton />
+                    </TopToolbar>
+                )}
+                perPage={DEFAULT_PAGE_SIZE}
+            >
+                <StyledDatagrid
+                    preferenceKey='waveItem'
+                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
+                    rowClick={(id, resource, record) => false}
+                    expand={() => <WaveItemPanel />}
+                    expandSingle={true}
+                    omit={['id', 'createTime', 'createBy', 'memo']}
+                >
+                    <NumberField source="id" />
+                    <NumberField source="waveId" label="table.field.waveItem.waveId" />
+                    <TextField source="waveCode" label="table.field.waveItem.waveCode" />
+                    <NumberField source="matnrId" label="table.field.waveItem.matnrId" />
+                    <TextField source="matnrName" label="table.field.waveItem.matnrName" />
+                    <TextField source="matnrCode" label="table.field.waveItem.matnrCode" />
+                    <TextField source="batch" label="table.field.waveItem.batch" />
+                    <TextField source="splrBatch" label="table.field.waveItem.splrBatch" />
+                    <TextField source="orderCode" label="table.field.waveItem.orderCode" />
+                    <NumberField source="orderItemId" label="table.field.waveItem.orderItemId" />
+                    <TextField source="unit" label="table.field.waveItem.unit" />
+                    <TextField source="trackCode" label="table.field.waveItem.trackCode" />
+                    <TextField source="fieldsIndex" label="table.field.waveItem.fieldsIndex" />
+                    <NumberField source="anfme" label="table.field.waveItem.anfme" />
+                    <NumberField source="workQty" label="table.field.waveItem.workQty" />
+
+                    <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>
+            <WaveItemCreate
+                open={createDialog}
+                setOpen={setCreateDialog}
+            />
+            <PageDrawer
+                title='WaveItem Detail'
+                drawerVal={drawerVal}
+                setDrawerVal={setDrawerVal}
+            >
+            </PageDrawer>
+        </Box>
+    )
+}
+
+export default WaveItemList;
diff --git a/rsf-admin/src/page/waveItem/WaveItemPanel.jsx b/rsf-admin/src/page/waveItem/WaveItemPanel.jsx
new file mode 100644
index 0000000..ebea3a1
--- /dev/null
+++ b/rsf-admin/src/page/waveItem/WaveItemPanel.jsx
@@ -0,0 +1,135 @@
+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 WaveItemPanel = () => {
+    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.waveItem.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.waveItem.waveId" 
+                                property={record.waveId}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.waveCode" 
+                                property={record.waveCode}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.matnrId" 
+                                property={record.matnrId}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.matnrName" 
+                                property={record.matnrName}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.matnrCode" 
+                                property={record.matnrCode}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.batch" 
+                                property={record.batch}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.splrBatch" 
+                                property={record.splrBatch}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.orderCode" 
+                                property={record.orderCode}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.orderItemId" 
+                                property={record.orderItemId}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.unit" 
+                                property={record.unit}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.trackCode" 
+                                property={record.trackCode}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.fieldsIndex" 
+                                property={record.fieldsIndex}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.anfme" 
+                                property={record.anfme}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.waveItem.workQty" 
+                                property={record.workQty}
+                            />
+                        </Grid>
+
+                    </Grid>
+                </CardContent>
+            </Card >
+        </>
+    );
+};
+
+export default WaveItemPanel;
diff --git a/rsf-admin/src/page/waveItem/index.jsx b/rsf-admin/src/page/waveItem/index.jsx
new file mode 100644
index 0000000..a64a352
--- /dev/null
+++ b/rsf-admin/src/page/waveItem/index.jsx
@@ -0,0 +1,18 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    ListGuesser,
+    EditGuesser,
+    ShowGuesser,
+} from "react-admin";
+
+import WaveItemList from "./WaveItemList";
+import WaveItemEdit from "./WaveItemEdit";
+
+export default {
+    list: WaveItemList,
+    edit: WaveItemEdit,
+    show: ShowGuesser,
+    recordRepresentation: (record) => {
+        return `${record.id}`
+    }
+};
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
index cb213f0..18e29b0 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
@@ -15,15 +15,15 @@
         generator.frontendPrefixPath = "rsf-admin/";
 
         generator.sqlOsType = SqlOsType.MYSQL;
-        generator.url="192.168.4.24:3306/rsf";
+        generator.url="127.0.0.1:3306/rsf";
         generator.username="root";
         generator.password="34821015";
 //        generator.url="47.97.1.152:51433;databasename=jkasrs";
 //        generator.username="sa";
 //        generator.password="Zoneyung@zy56$";
 
-        generator.table="man_delivery_item";
-        generator.tableDesc="缁煎悎鍗曟嵁鏄庣粏";
+        generator.table="man_device_bind";
+        generator.tableDesc="绔嬩綋搴撶珯鐐圭粦瀹�";
         generator.packagePath="com.vincent.rsf.server.manager";
 
         generator.build();
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/DeviceBindController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/DeviceBindController.java
new file mode 100644
index 0000000..73b3473
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/DeviceBindController.java
@@ -0,0 +1,110 @@
+package com.vincent.rsf.server.manager.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.manager.entity.DeviceBind;
+import com.vincent.rsf.server.manager.service.DeviceBindService;
+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 DeviceBindController extends BaseController {
+
+    @Autowired
+    private DeviceBindService deviceBindService;
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:list')")
+    @PostMapping("/deviceBind/page")
+    public R page(@RequestBody Map<String, Object> map) {
+        BaseParam baseParam = buildParam(map, BaseParam.class);
+        PageParam<DeviceBind, BaseParam> pageParam = new PageParam<>(baseParam, DeviceBind.class);
+        return R.ok().add(deviceBindService.page(pageParam, pageParam.buildWrapper(true)));
+    }
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:list')")
+    @PostMapping("/deviceBind/list")
+    public R list(@RequestBody Map<String, Object> map) {
+        return R.ok().add(deviceBindService.list());
+    }
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:list')")
+    @PostMapping({"/deviceBind/many/{ids}", "/deviceBinds/many/{ids}"})
+    public R many(@PathVariable Long[] ids) {
+        return R.ok().add(deviceBindService.listByIds(Arrays.asList(ids)));
+    }
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:list')")
+    @GetMapping("/deviceBind/{id}")
+    public R get(@PathVariable("id") Long id) {
+        return R.ok().add(deviceBindService.getById(id));
+    }
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:save')")
+    @OperationLog("Create 绔嬩綋搴撶珯鐐圭粦瀹�")
+    @PostMapping("/deviceBind/save")
+    public R save(@RequestBody DeviceBind deviceBind) {
+        deviceBind.setCreateBy(getLoginUserId());
+        deviceBind.setCreateTime(new Date());
+        deviceBind.setUpdateBy(getLoginUserId());
+        deviceBind.setUpdateTime(new Date());
+        if (!deviceBindService.save(deviceBind)) {
+            return R.error("Save Fail");
+        }
+        return R.ok("Save Success").add(deviceBind);
+    }
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:update')")
+    @OperationLog("Update 绔嬩綋搴撶珯鐐圭粦瀹�")
+    @PostMapping("/deviceBind/update")
+    public R update(@RequestBody DeviceBind deviceBind) {
+        deviceBind.setUpdateBy(getLoginUserId());
+        deviceBind.setUpdateTime(new Date());
+        if (!deviceBindService.updateById(deviceBind)) {
+            return R.error("Update Fail");
+        }
+        return R.ok("Update Success").add(deviceBind);
+    }
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:remove')")
+    @OperationLog("Delete 绔嬩綋搴撶珯鐐圭粦瀹�")
+    @PostMapping("/deviceBind/remove/{ids}")
+    public R remove(@PathVariable Long[] ids) {
+        if (!deviceBindService.removeByIds(Arrays.asList(ids))) {
+            return R.error("Delete Fail");
+        }
+        return R.ok("Delete Success").add(ids);
+    }
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:list')")
+    @PostMapping("/deviceBind/query")
+    public R query(@RequestParam(required = false) String condition) {
+        List<KeyValVo> vos = new ArrayList<>();
+        LambdaQueryWrapper<DeviceBind> wrapper = new LambdaQueryWrapper<>();
+        if (!Cools.isEmpty(condition)) {
+            wrapper.like(DeviceBind::getId, condition);
+        }
+        deviceBindService.page(new Page<>(1, 30), wrapper).getRecords().forEach(
+                item -> vos.add(new KeyValVo(item.getId(), item.getId()))
+        );
+        return R.ok().add(vos);
+    }
+
+    @PreAuthorize("hasAuthority('manager:deviceBind:list')")
+    @PostMapping("/deviceBind/export")
+    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+        ExcelUtil.build(ExcelUtil.create(deviceBindService.list(), DeviceBind.class), response);
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java
index 3149703..ac580c4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java
@@ -203,5 +203,16 @@
         return outStockService.genOutStock(ids);
     }
 
+    @PreAuthorize("hasAuthority('manager:outStock:update')")
+    @ApiOperation("鍑哄簱鍗曠敓鎴愭尝娆�")
+    @PostMapping("/outStock/generate/wave")
+    public R generateWave(@RequestBody Map<String, Object> params) {
+        if (Objects.isNull(params)) {
+            return R.error("鍙傛暟涓嶈兘涓虹┖锛侊紒");
+        }
+        List<Long> ids = (List<Long>) params.get("ids");
+        return outStockService.generateWaves(ids);
+    }
+
 
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java
new file mode 100644
index 0000000..989b5d2
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java
@@ -0,0 +1,108 @@
+package com.vincent.rsf.server.manager.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.manager.entity.Wave;
+import com.vincent.rsf.server.manager.service.WaveService;
+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 WaveController extends BaseController {
+
+    @Autowired
+    private WaveService waveService;
+
+    @PreAuthorize("hasAuthority('manager:wave:list')")
+    @PostMapping("/wave/page")
+    public R page(@RequestBody Map<String, Object> map) {
+        BaseParam baseParam = buildParam(map, BaseParam.class);
+        PageParam<Wave, BaseParam> pageParam = new PageParam<>(baseParam, Wave.class);
+        return R.ok().add(waveService.page(pageParam, pageParam.buildWrapper(true)));
+    }
+
+    @PreAuthorize("hasAuthority('manager:wave:list')")
+    @PostMapping("/wave/list")
+    public R list(@RequestBody Map<String, Object> map) {
+        return R.ok().add(waveService.list());
+    }
+
+    @PreAuthorize("hasAuthority('manager:wave:list')")
+    @PostMapping({"/wave/many/{ids}", "/waves/many/{ids}"})
+    public R many(@PathVariable Long[] ids) {
+        return R.ok().add(waveService.listByIds(Arrays.asList(ids)));
+    }
+
+    @PreAuthorize("hasAuthority('manager:wave:list')")
+    @GetMapping("/wave/{id}")
+    public R get(@PathVariable("id") Long id) {
+        return R.ok().add(waveService.getById(id));
+    }
+
+    @PreAuthorize("hasAuthority('manager:wave:save')")
+    @OperationLog("Create 娉㈡鍗曟嵁")
+    @PostMapping("/wave/save")
+    public R save(@RequestBody Wave wave) {
+        wave.setCreateBy(getLoginUserId());
+        wave.setUpdateBy(getLoginUserId());
+        if (!waveService.save(wave)) {
+            return R.error("Save Fail");
+        }
+        return R.ok("Save Success").add(wave);
+    }
+
+    @PreAuthorize("hasAuthority('manager:wave:update')")
+    @OperationLog("Update 娉㈡鍗曟嵁")
+    @PostMapping("/wave/update")
+    public R update(@RequestBody Wave wave) {
+        wave.setUpdateBy(getLoginUserId());
+        wave.setUpdateTime(new Date());
+        if (!waveService.updateById(wave)) {
+            return R.error("Update Fail");
+        }
+        return R.ok("Update Success").add(wave);
+    }
+
+    @PreAuthorize("hasAuthority('manager:wave:remove')")
+    @OperationLog("Delete 娉㈡鍗曟嵁")
+    @PostMapping("/wave/remove/{ids}")
+    public R remove(@PathVariable Long[] ids) {
+        if (!waveService.removeByIds(Arrays.asList(ids))) {
+            return R.error("Delete Fail");
+        }
+        return R.ok("Delete Success").add(ids);
+    }
+
+    @PreAuthorize("hasAuthority('manager:wave:list')")
+    @PostMapping("/wave/query")
+    public R query(@RequestParam(required = false) String condition) {
+        List<KeyValVo> vos = new ArrayList<>();
+        LambdaQueryWrapper<Wave> wrapper = new LambdaQueryWrapper<>();
+        if (!Cools.isEmpty(condition)) {
+            wrapper.like(Wave::getId, condition);
+        }
+        waveService.page(new Page<>(1, 30), wrapper).getRecords().forEach(
+                item -> vos.add(new KeyValVo(item.getId(), item.getId()))
+        );
+        return R.ok().add(vos);
+    }
+
+    @PreAuthorize("hasAuthority('manager:wave:list')")
+    @PostMapping("/wave/export")
+    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+        ExcelUtil.build(ExcelUtil.create(waveService.list(), Wave.class), response);
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveItemController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveItemController.java
new file mode 100644
index 0000000..758f03e
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveItemController.java
@@ -0,0 +1,110 @@
+package com.vincent.rsf.server.manager.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.manager.entity.WaveItem;
+import com.vincent.rsf.server.manager.service.WaveItemService;
+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 WaveItemController extends BaseController {
+
+    @Autowired
+    private WaveItemService waveItemService;
+
+    @PreAuthorize("hasAuthority('manager:waveItem:list')")
+    @PostMapping("/waveItem/page")
+    public R page(@RequestBody Map<String, Object> map) {
+        BaseParam baseParam = buildParam(map, BaseParam.class);
+        PageParam<WaveItem, BaseParam> pageParam = new PageParam<>(baseParam, WaveItem.class);
+        return R.ok().add(waveItemService.page(pageParam, pageParam.buildWrapper(true)));
+    }
+
+    @PreAuthorize("hasAuthority('manager:waveItem:list')")
+    @PostMapping("/waveItem/list")
+    public R list(@RequestBody Map<String, Object> map) {
+        return R.ok().add(waveItemService.list());
+    }
+
+    @PreAuthorize("hasAuthority('manager:waveItem:list')")
+    @PostMapping({"/waveItem/many/{ids}", "/waveItems/many/{ids}"})
+    public R many(@PathVariable Long[] ids) {
+        return R.ok().add(waveItemService.listByIds(Arrays.asList(ids)));
+    }
+
+    @PreAuthorize("hasAuthority('manager:waveItem:list')")
+    @GetMapping("/waveItem/{id}")
+    public R get(@PathVariable("id") Long id) {
+        return R.ok().add(waveItemService.getById(id));
+    }
+
+    @PreAuthorize("hasAuthority('manager:waveItem:save')")
+    @OperationLog("Create 娉㈡鏄庣粏")
+    @PostMapping("/waveItem/save")
+    public R save(@RequestBody WaveItem waveItem) {
+        waveItem.setCreateBy(getLoginUserId());
+        waveItem.setCreateTime(new Date());
+        waveItem.setUpdateBy(getLoginUserId());
+        waveItem.setUpdateTime(new Date());
+        if (!waveItemService.save(waveItem)) {
+            return R.error("Save Fail");
+        }
+        return R.ok("Save Success").add(waveItem);
+    }
+
+    @PreAuthorize("hasAuthority('manager:waveItem:update')")
+    @OperationLog("Update 娉㈡鏄庣粏")
+    @PostMapping("/waveItem/update")
+    public R update(@RequestBody WaveItem waveItem) {
+        waveItem.setUpdateBy(getLoginUserId());
+        waveItem.setUpdateTime(new Date());
+        if (!waveItemService.updateById(waveItem)) {
+            return R.error("Update Fail");
+        }
+        return R.ok("Update Success").add(waveItem);
+    }
+
+    @PreAuthorize("hasAuthority('manager:waveItem:remove')")
+    @OperationLog("Delete 娉㈡鏄庣粏")
+    @PostMapping("/waveItem/remove/{ids}")
+    public R remove(@PathVariable Long[] ids) {
+        if (!waveItemService.removeByIds(Arrays.asList(ids))) {
+            return R.error("Delete Fail");
+        }
+        return R.ok("Delete Success").add(ids);
+    }
+
+    @PreAuthorize("hasAuthority('manager:waveItem:list')")
+    @PostMapping("/waveItem/query")
+    public R query(@RequestParam(required = false) String condition) {
+        List<KeyValVo> vos = new ArrayList<>();
+        LambdaQueryWrapper<WaveItem> wrapper = new LambdaQueryWrapper<>();
+        if (!Cools.isEmpty(condition)) {
+            wrapper.like(WaveItem::getId, condition);
+        }
+        waveItemService.page(new Page<>(1, 30), wrapper).getRecords().forEach(
+                item -> vos.add(new KeyValVo(item.getId(), item.getId()))
+        );
+        return R.ok().add(vos);
+    }
+
+    @PreAuthorize("hasAuthority('manager:waveItem:list')")
+    @PostMapping("/waveItem/export")
+    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+        ExcelUtil.build(ExcelUtil.create(waveItemService.list(), WaveItem.class), response);
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/params/DeviceSiteParame.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/params/DeviceSiteParame.java
index 6b915c8..68b6a76 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/params/DeviceSiteParame.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/params/DeviceSiteParame.java
@@ -20,7 +20,7 @@
     private List<Long> typeIds;
 
     @ApiModelProperty("浣滀笟绔欑偣")
-    private String sites;
+    private String site;
 
     @ApiModelProperty("璁惧缂栧彿")
     private String deviceCode;
@@ -28,4 +28,7 @@
     @ApiModelProperty("璁惧浣滀笟绔欑偣")
     private String deviceSites;
 
+    @ApiModelProperty("鐩爣绔欑偣")
+    private String target;
+
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceBind.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceBind.java
new file mode 100644
index 0000000..dc98990
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceBind.java
@@ -0,0 +1,133 @@
+package com.vincent.rsf.server.manager.entity;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+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
+@TableName("man_device_bind")
+public class DeviceBind implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @ApiModelProperty(value= "id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 褰撳墠鎺掑彿
+     */
+    @ApiModelProperty(value= "褰撳墠鎺掑彿")
+    private Integer currentRow;
+
+    /**
+     * 璧峰鎺掑彿
+     */
+    @ApiModelProperty(value= "璧峰鎺掑彿")
+    private Integer startRow;
+
+    /**
+     * 缁堟鎺掑彿
+     */
+    @ApiModelProperty(value= "缁堟鎺掑彿")
+    private Integer endRow;
+
+    /**
+     * 璁惧鏁伴噺
+     */
+    @ApiModelProperty(value= "璁惧鏁伴噺")
+    private Integer deviceQty;
+
+    /**
+     * 璧峰璁惧鍙�
+     */
+    @ApiModelProperty(value= "璧峰璁惧鍙�")
+    private Integer startDeviceNo;
+
+    /**
+     * 缁堟璁惧鍙�
+     */
+    @ApiModelProperty(value= "缁堟璁惧鍙�")
+    private Integer endDeviceNo;
+
+    /**
+     * 绔欑偣list
+     */
+    @ApiModelProperty(value= "绔欑偣list")
+    private String staList;
+
+    /**
+     * 搴撳尯绫诲瀷
+     */
+    @ApiModelProperty(value= "搴撳尯绫诲瀷")
+    private Integer typeId;
+
+    /**
+     * 鐗╂枡鐩镐技寮�鍏�
+     */
+    @ApiModelProperty(value= "鐗╂枡鐩镐技寮�鍏�")
+    private String beSimilar;
+
+    /**
+     * 绌烘澘闈犺繎寮�鍏�
+     */
+    @ApiModelProperty(value= "绌烘澘闈犺繎寮�鍏�")
+    private String emptySimilar;
+
+    public DeviceBind() {}
+
+    public DeviceBind(Integer currentRow,Integer startRow,Integer endRow,Integer deviceQty,Integer startDeviceNo,Integer endDeviceNo,String staList,Integer typeId,String beSimilar,String emptySimilar) {
+        this.currentRow = currentRow;
+        this.startRow = startRow;
+        this.endRow = endRow;
+        this.deviceQty = deviceQty;
+        this.startDeviceNo = startDeviceNo;
+        this.endDeviceNo = endDeviceNo;
+        this.staList = staList;
+        this.typeId = typeId;
+        this.beSimilar = beSimilar;
+        this.emptySimilar = emptySimilar;
+    }
+
+//    DeviceBind deviceBind = new DeviceBind(
+//            null,    // 褰撳墠鎺掑彿
+//            null,    // 璧峰鎺掑彿
+//            null,    // 缁堟鎺掑彿
+//            null,    // 璁惧鏁伴噺
+//            null,    // 璧峰璁惧鍙�
+//            null,    // 缁堟璁惧鍙�
+//            null,    // 绔欑偣list
+//            null,    // 搴撳尯绫诲瀷
+//            null,    // 鐗╂枡鐩镐技寮�鍏�
+//            null    // 绌烘澘闈犺繎寮�鍏�
+//    );
+
+
+
+    public Boolean getStatusBool(){
+        if (null == this.status){ return null; }
+        switch (this.status){
+            case 1:
+                return true;
+            case 0:
+                return false;
+            default:
+                return null;
+        }
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceSite.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceSite.java
index 5293829..2bca82a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceSite.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/DeviceSite.java
@@ -64,8 +64,8 @@
     /**
      * wcs绔欑偣缂栧彿
      */
-    @ApiModelProperty(value= "wcs绔欑偣缂栧彿")
-    private String wcsCode;
+    @ApiModelProperty(value= "鐩爣浣嶇疆")
+    private String target;
 
     /**
      * 绔欑偣鏍囩
@@ -154,7 +154,7 @@
         this.type = type;
         this.site = site;
         this.name = name;
-        this.wcsCode = wcsCode;
+        this.target = wcsCode;
         this.label = label;
         this.device = device;
         this.deviceCode = deviceCode;
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java
new file mode 100644
index 0000000..c9dadf0
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java
@@ -0,0 +1,249 @@
+package com.vincent.rsf.server.manager.entity;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+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 com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+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
+@TableName("man_wave")
+@Accessors(chain = true)
+public class Wave 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 code;
+
+    /**
+     * 娉㈡绫诲瀷 0: 鎵嬪姩  1: 鑷姩  
+     */
+    @ApiModelProperty(value= "娉㈡绫诲瀷 0: 鎵嬪姩  1: 鑷姩  ")
+    private Short type;
+
+    /**
+     * 娉㈡鐘舵�� 0: 鍒濆鍖�  1: 鐢熸垚浠诲姟  2: 浠诲姟鎾  3: 瀹屾垚  
+     */
+    @ApiModelProperty(value= "娉㈡鐘舵�� 0: 鍒濆鍖�  1: 鐢熸垚浠诲姟  2: 浠诲姟鎾  3: 瀹屾垚  ")
+    private Short exceStatus;
+
+    /**
+     * 鍗曟嵁鏁伴噺
+     */
+    @ApiModelProperty(value= "鍗曟嵁鏁伴噺")
+    private Double anfme;
+
+    /**
+     * 宸插畬鎴愭暟閲�
+     */
+    @ApiModelProperty(value= "宸插畬鎴愭暟閲�")
+    private Double qty;
+
+    /**
+     * 鍗曟嵁鏁伴噺
+     */
+    @ApiModelProperty(value= "鍗曟嵁鏁伴噺")
+    private Integer orderNum;
+
+    /**
+     * 鐘舵�� 1: 姝e父  0: 绂佺敤  
+     */
+    @ApiModelProperty(value= "鐘舵�� 1: 姝e父  0: 绂佺敤  ")
+    private Integer status;
+
+    /**
+     * 鎵�灞炴満鏋�
+     */
+    @ApiModelProperty(value= "鎵�灞炴満鏋�")
+    private Long tenantId;
+
+    /**
+     * 娣诲姞鏃堕棿
+     */
+    @ApiModelProperty(value= "娣诲姞鏃堕棿")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     * 娣诲姞浜哄憳
+     */
+    @ApiModelProperty(value= "娣诲姞浜哄憳")
+    private Long createBy;
+
+    /**
+     * 淇敼鏃堕棿
+     */
+    @ApiModelProperty(value= "淇敼鏃堕棿")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /**
+     * 淇敼浜哄憳
+     */
+    @ApiModelProperty(value= "淇敼浜哄憳")
+    private Long updateBy;
+
+    /**
+     * 鏄惁鍒犻櫎 1: 鏄�  0: 鍚�  
+     */
+    @ApiModelProperty(value= "鏄惁鍒犻櫎 1: 鏄�  0: 鍚�  ")
+    @TableLogic
+    private Integer deleted;
+
+    /**
+     * 澶囨敞
+     */
+    @ApiModelProperty(value= "澶囨敞")
+    private String memo;
+
+    public Wave() {}
+
+    public Wave(String code,Short type,Short exceStatus,Double anfme,Double qty,Integer orderNum,Integer status,Long tenantId,Date createTime,Long createBy,Date updateTime,Long updateBy,Integer deleted,String memo) {
+        this.code = code;
+        this.type = type;
+        this.exceStatus = exceStatus;
+        this.anfme = anfme;
+        this.qty = qty;
+        this.orderNum = orderNum;
+        this.status = status;
+        this.tenantId = tenantId;
+        this.createTime = createTime;
+        this.createBy = createBy;
+        this.updateTime = updateTime;
+        this.updateBy = updateBy;
+        this.deleted = deleted;
+        this.memo = memo;
+    }
+
+//    Wave wave = new Wave(
+//            null,    // 娉㈡鍙�
+//            null,    // 娉㈡绫诲瀷
+//            null,    // 娉㈡鐘舵��
+//            null,    // 鍗曟嵁鏁伴噺
+//            null,    // 宸插畬鎴愭暟閲�
+//            null,    // 鍗曟嵁鏁伴噺
+//            null,    // 鐘舵��
+//            null,    // 鎵�灞炴満鏋�
+//            null,    // 娣诲姞鏃堕棿
+//            null,    // 娣诲姞浜哄憳
+//            null,    // 淇敼鏃堕棿
+//            null,    // 淇敼浜哄憳
+//            null,    // 鏄惁鍒犻櫎
+//            null    // 澶囨敞
+//    );
+
+    public String getType$(){
+        if (null == this.type){ return null; }
+        switch (this.type){
+            case 0:
+                return "鎵嬪姩";
+            case 1:
+                return "鑷姩";
+            default:
+                return String.valueOf(this.type);
+        }
+    }
+
+    public String getExceStatus$(){
+        if (null == this.exceStatus){ return null; }
+        switch (this.exceStatus){
+            case 0:
+                return "鍒濆鍖�";
+            case 1:
+                return "鐢熸垚浠诲姟";
+            case 2:
+                return "浠诲姟鎾";
+            case 3:
+                return "瀹屾垚";
+            default:
+                return String.valueOf(this.exceStatus);
+        }
+    }
+
+    public String getStatus$(){
+        if (null == this.status){ return null; }
+        switch (this.status){
+            case 1:
+                return "姝e父";
+            case 0:
+                return "绂佺敤";
+            default:
+                return String.valueOf(this.status);
+        }
+    }
+
+    public String getCreateTime$(){
+        if (Cools.isEmpty(this.createTime)){
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+    }
+
+    public String getCreateBy$(){
+        UserService service = SpringUtils.getBean(UserService.class);
+        User user = service.getById(this.createBy);
+        if (!Cools.isEmpty(user)){
+            return String.valueOf(user.getNickname());
+        }
+        return null;
+    }
+
+    public String getUpdateTime$(){
+        if (Cools.isEmpty(this.updateTime)){
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+    }
+
+    public String getUpdateBy$(){
+        UserService service = SpringUtils.getBean(UserService.class);
+        User user = service.getById(this.updateBy);
+        if (!Cools.isEmpty(user)){
+            return String.valueOf(user.getNickname());
+        }
+        return null;
+    }
+
+
+
+    public Boolean getStatusBool(){
+        if (null == this.status){ return null; }
+        switch (this.status){
+            case 1:
+                return true;
+            case 0:
+                return false;
+            default:
+                return null;
+        }
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java
new file mode 100644
index 0000000..36d7e30
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaveItem.java
@@ -0,0 +1,291 @@
+package com.vincent.rsf.server.manager.entity;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+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("man_wave_item")
+public class WaveItem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * ID
+     */
+    @ApiModelProperty(value= "ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 娉㈡ID
+     */
+    @ApiModelProperty(value= "娉㈡ID")
+    private Long waveId;
+
+    /**
+     * 娉㈡鍙�
+     */
+    @ApiModelProperty(value= "娉㈡鍙�")
+    private String waveCode;
+
+    /**
+     * 鍟嗗搧ID
+     */
+    @ApiModelProperty(value= "鍟嗗搧ID")
+    private Long matnrId;
+
+    /**
+     * 鐗╂枡鍚嶇О
+     */
+    @ApiModelProperty(value= "鐗╂枡鍚嶇О")
+    private String matnrName;
+
+    /**
+     * 鍟嗗搧缂栧彿
+     */
+    @ApiModelProperty(value= "鍟嗗搧缂栧彿")
+    private String matnrCode;
+
+    /**
+     * 鎵瑰彿
+     */
+    @ApiModelProperty(value= "鎵瑰彿")
+    private String batch;
+
+    /**
+     * 渚涘簲鍟嗘壒娆�
+     */
+    @ApiModelProperty(value= "渚涘簲鍟嗘壒娆�")
+    private String splrBatch;
+
+    /**
+     * 鍗曟嵁缂栫爜
+     */
+    @ApiModelProperty(value= "鍗曟嵁缂栫爜")
+    private String orderCode;
+
+    /**
+     * 鍗曟嵁ID
+     */
+    @ApiModelProperty(value= "鍗曟嵁ID")
+    private Long orderItemId;
+
+    @ApiModelProperty("璁㈠崟ID")
+    private Long orderId;
+
+    /**
+     * 鍗曚綅
+     */
+    @ApiModelProperty(value= "鍗曚綅")
+    private String unit;
+
+    /**
+     * 璺熻釜鐮�
+     */
+    @ApiModelProperty(value= "璺熻釜鐮�")
+    private String trackCode;
+
+    /**
+     * 鍟嗗搧搴撳瓨绱㈠紩
+     */
+    @ApiModelProperty(value= "鍟嗗搧搴撳瓨绱㈠紩")
+    private String fieldsIndex;
+
+    /**
+     * 鏁伴噺
+     */
+    @ApiModelProperty(value= "鏁伴噺")
+    private Double anfme;
+
+    @ApiModelProperty("宸插畬鎴愭暟閲�")
+    private Double qty;
+
+    /**
+     * 宸ヤ綔鏁伴噺
+     */
+    @ApiModelProperty(value= "宸ヤ綔鏁伴噺")
+    private Double workQty;
+
+    /**
+     * 鎵�灞炴満鏋�
+     */
+    @ApiModelProperty(value= "鎵�灞炴満鏋�")
+    private Long tenantId;
+
+    /**
+     * 鐘舵�� 1: 姝e父  0: 绂佺敤  
+     */
+    @ApiModelProperty(value= "鐘舵�� 1: 姝e父  0: 绂佺敤  ")
+    private Integer status;
+
+    /**
+     * 鏄惁鍒犻櫎 1: 鏄�  0: 鍚�  
+     */
+    @ApiModelProperty(value= "鏄惁鍒犻櫎 1: 鏄�  0: 鍚�  ")
+    @TableLogic
+    private Integer deleted;
+
+    /**
+     * 娣诲姞鏃堕棿
+     */
+    @ApiModelProperty(value= "娣诲姞鏃堕棿")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     * 娣诲姞浜哄憳
+     */
+    @ApiModelProperty(value= "娣诲姞浜哄憳")
+    private Long createBy;
+
+    /**
+     * 淇敼鏃堕棿
+     */
+    @ApiModelProperty(value= "淇敼鏃堕棿")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /**
+     * 淇敼浜哄憳
+     */
+    @ApiModelProperty(value= "淇敼浜哄憳")
+    private Long updateBy;
+
+    /**
+     * 澶囨敞
+     */
+    @ApiModelProperty(value= "澶囨敞")
+    private String memo;
+
+    public WaveItem() {}
+
+    public WaveItem(Long waveId,String waveCode,Long matnrId,String matnrName,String matnrCode,String batch,String splrBatch,String orderCode,Long orderItemId,String unit,String trackCode,String fieldsIndex,Double anfme,Double workQty,Long tenantId,Integer status,Integer deleted,Date createTime,Long createBy,Date updateTime,Long updateBy,String memo) {
+        this.waveId = waveId;
+        this.waveCode = waveCode;
+        this.matnrId = matnrId;
+        this.matnrName = matnrName;
+        this.matnrCode = matnrCode;
+        this.batch = batch;
+        this.splrBatch = splrBatch;
+        this.orderCode = orderCode;
+        this.orderItemId = orderItemId;
+        this.unit = unit;
+        this.trackCode = trackCode;
+        this.fieldsIndex = fieldsIndex;
+        this.anfme = anfme;
+        this.workQty = workQty;
+        this.tenantId = tenantId;
+        this.status = status;
+        this.deleted = deleted;
+        this.createTime = createTime;
+        this.createBy = createBy;
+        this.updateTime = updateTime;
+        this.updateBy = updateBy;
+        this.memo = memo;
+    }
+
+//    WaveItem waveItem = new WaveItem(
+//            null,    // 娉㈡ID
+//            null,    // 娉㈡鍙�
+//            null,    // 鍟嗗搧ID
+//            null,    // 鐗╂枡鍚嶇О
+//            null,    // 鍟嗗搧缂栧彿
+//            null,    // 鎵瑰彿
+//            null,    // 渚涘簲鍟嗘壒娆�
+//            null,    // 鍗曟嵁缂栫爜
+//            null,    // 鍗曟嵁ID
+//            null,    // 鍗曚綅
+//            null,    // 璺熻釜鐮�
+//            null,    // 鍟嗗搧搴撳瓨绱㈠紩
+//            null,    // 鏁伴噺
+//            null,    // 宸ヤ綔鏁伴噺
+//            null,    // 鎵�灞炴満鏋�
+//            null,    // 鐘舵��
+//            null,    // 鏄惁鍒犻櫎
+//            null,    // 娣诲姞鏃堕棿
+//            null,    // 娣诲姞浜哄憳
+//            null,    // 淇敼鏃堕棿
+//            null,    // 淇敼浜哄憳
+//            null    // 澶囨敞
+//    );
+
+    public String getStatus$(){
+        if (null == this.status){ return null; }
+        switch (this.status){
+            case 1:
+                return "姝e父";
+            case 0:
+                return "绂佺敤";
+            default:
+                return String.valueOf(this.status);
+        }
+    }
+
+    public String getCreateTime$(){
+        if (Cools.isEmpty(this.createTime)){
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+    }
+
+    public String getCreateBy$(){
+        UserService service = SpringUtils.getBean(UserService.class);
+        User user = service.getById(this.createBy);
+        if (!Cools.isEmpty(user)){
+            return String.valueOf(user.getNickname());
+        }
+        return null;
+    }
+
+    public String getUpdateTime$(){
+        if (Cools.isEmpty(this.updateTime)){
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+    }
+
+    public String getUpdateBy$(){
+        UserService service = SpringUtils.getBean(UserService.class);
+        User user = service.getById(this.updateBy);
+        if (!Cools.isEmpty(user)){
+            return String.valueOf(user.getNickname());
+        }
+        return null;
+    }
+
+
+
+    public Boolean getStatusBool(){
+        if (null == this.status){ return null; }
+        switch (this.status){
+            case 1:
+                return true;
+            case 0:
+                return false;
+            default:
+                return null;
+        }
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/WaveExceStatus.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/WaveExceStatus.java
new file mode 100644
index 0000000..a590c90
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/WaveExceStatus.java
@@ -0,0 +1,27 @@
+package com.vincent.rsf.server.manager.enums;
+
+/**
+ * @author Ryan
+ * @version 1.0
+ * @title WaveExceStatus
+ * @description
+ * @create 2025/4/24 17:56
+ */
+public enum WaveExceStatus {
+
+    //娉㈡鎵ц鐘舵��
+    WAVE_EXCE_STATUS_INIT("0", "鍒濆鍖�"),
+    WAVE_EXCE_STATUS_TASK("1", "鐢熸垚浠诲姟"),
+    WAVE_EXCE_STATUS_SPEED("2", "鎾浠诲姟"),
+    WAVE_EXCE_STATUS_DONE("3", "瀹屾垚"),
+    ;
+
+    WaveExceStatus(String val, String desc) {
+        this.val = Short.parseShort(val);
+        this.desc = desc;
+    }
+
+    public Short val;
+
+    public String desc;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/DeviceBindMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/DeviceBindMapper.java
new file mode 100644
index 0000000..528719d
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/DeviceBindMapper.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.manager.mapper;
+
+import com.vincent.rsf.server.manager.entity.DeviceBind;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface DeviceBindMapper extends BaseMapper<DeviceBind> {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/WaveItemMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/WaveItemMapper.java
new file mode 100644
index 0000000..a1017cb
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/WaveItemMapper.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.manager.mapper;
+
+import com.vincent.rsf.server.manager.entity.WaveItem;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface WaveItemMapper extends BaseMapper<WaveItem> {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/WaveMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/WaveMapper.java
new file mode 100644
index 0000000..4d4bf1f
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/WaveMapper.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.manager.mapper;
+
+import com.vincent.rsf.server.manager.entity.Wave;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface WaveMapper extends BaseMapper<Wave> {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/DeviceBindService.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/DeviceBindService.java
new file mode 100644
index 0000000..91fae2b
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/DeviceBindService.java
@@ -0,0 +1,8 @@
+package com.vincent.rsf.server.manager.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.manager.entity.DeviceBind;
+
+public interface DeviceBindService extends IService<DeviceBind> {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/OutStockService.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/OutStockService.java
index 90450e4..22fcc64 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/OutStockService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/OutStockService.java
@@ -14,4 +14,6 @@
     R cancelOutOrder(String id);
 
     R genOutStock(List<Long> ids);
+
+    R generateWaves(List<Long> ids);
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveItemService.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveItemService.java
new file mode 100644
index 0000000..bd9a838
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveItemService.java
@@ -0,0 +1,8 @@
+package com.vincent.rsf.server.manager.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.manager.entity.WaveItem;
+
+public interface WaveItemService extends IService<WaveItem> {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java
new file mode 100644
index 0000000..0005369
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WaveService.java
@@ -0,0 +1,8 @@
+package com.vincent.rsf.server.manager.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.manager.entity.Wave;
+
+public interface WaveService extends IService<Wave> {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceBindServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceBindServiceImpl.java
new file mode 100644
index 0000000..b5a861b
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceBindServiceImpl.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.manager.service.impl;
+
+import com.vincent.rsf.server.manager.mapper.DeviceBindMapper;
+import com.vincent.rsf.server.manager.entity.DeviceBind;
+import com.vincent.rsf.server.manager.service.DeviceBindService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service("deviceBindService")
+public class DeviceBindServiceImpl extends ServiceImpl<DeviceBindMapper, DeviceBind> implements DeviceBindService {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceSiteServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceSiteServiceImpl.java
index 2a1a189..9f28a03 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceSiteServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/DeviceSiteServiceImpl.java
@@ -37,25 +37,32 @@
         if (Objects.isNull(param.getDeviceSites()) || StringUtils.isBlank(param.getDeviceSites())) {
             throw new CoolException("鍒濆鍖栧け璐ワ細 璁惧浣滀笟绔欑偣涓嶈兘涓虹┖锛侊紒");
         }
-        if (Objects.isNull(param.getSites()) || StringUtils.isBlank(param.getSites())) {
+        if (Objects.isNull(param.getSite()) || StringUtils.isBlank(param.getSite())) {
             throw new CoolException("鍒濆鍖栧け璐�: 浣滀笟绔欑偣涓嶈兘涓虹┖锛侊紒");
         }
         if (Objects.isNull(param.getTypeIds()) || param.getTypeIds().isEmpty()) {
             throw new CoolException("鍒濆鍖栧け璐ワ細 浣滀笟绫诲瀷涓嶈兘涓虹┖锛侊紒");
         }
-        List<String> sites = Arrays.asList(StringUtils.split(param.getSites(), ","));
+        if (Objects.isNull(param.getTarget()) || param.getTarget().isEmpty()) {
+            throw new CoolException("鍒濆鍖栧け璐ワ細 鐩爣绔欑偣涓嶈兘涓虹┖锛侊紒");
+        }
+        List<String> sites = Arrays.asList(StringUtils.split(param.getSite(), ","));
         List<String> dvSites = Arrays.asList(StringUtils.split(param.getDeviceSites(), ","));
+        List<String> targets = Arrays.asList(StringUtils.split(param.getTarget(), ","));
         List<DeviceSite> deviceSites =  new ArrayList<>();
         for (String site : sites) {
             for (String deviceSite : dvSites) {
                 for (Long id : param.getTypeIds()) {
-                    DeviceSite site1 = new DeviceSite();
-                    site1.setType(id + "")
-                            .setSite(site)
-                            .setDevice(param.getDeviceType())
-                            .setDeviceSite(deviceSite)
-                            .setDeviceCode(param.getDeviceCode());
-                    deviceSites.add(site1);
+                    for (String target : targets) {
+                        DeviceSite site1 = new DeviceSite();
+                        site1.setType(id + "")
+                                .setSite(site)
+                                .setDevice(param.getDeviceType())
+                                .setDeviceSite(deviceSite)
+                                .setTarget(target)
+                                .setDeviceCode(param.getDeviceCode());
+                        deviceSites.add(site1);
+                    }
                 }
             }
         }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
index 3b19c58..af8c2d1 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -13,6 +13,7 @@
 import com.vincent.rsf.server.manager.controller.params.BatchUpdateParam;
 import com.vincent.rsf.server.manager.entity.*;
 import com.vincent.rsf.server.manager.enums.AsnExceStatus;
+import com.vincent.rsf.server.manager.enums.WaveExceStatus;
 import com.vincent.rsf.server.manager.mapper.AsnOrderMapper;
 import com.vincent.rsf.server.manager.mapper.PurchaseMapper;
 import com.vincent.rsf.server.manager.service.*;
@@ -45,15 +46,16 @@
     private AsnOrderLogService asnOrderLogService;
     @Autowired
     private AsnOrderItemLogService asnOrderItemLogService;
-
     @Autowired
     private DeliveryItemService deliveryItemService;
-
     @Autowired
     private DeliveryService deliveryService;
     @Autowired
     private MatnrService matnrService;
-
+    @Autowired
+    private WaveService waveService;
+    @Autowired
+    private WaveItemService waveItemService;
 
 
     /**
@@ -157,6 +159,7 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public R cancelOutOrder(String id) {
+        //TODO 鍑哄簱鍗曞彇娑堟祦绋嬶紝QMS锛堝崟鎹彇娑堬級->DO鍗�->鍑哄簱鍗�->娉㈡->鍒ゆ柇鏄惁鍏ㄥ崟鎹�->鍏ㄥ崟鎹笅鍙戝彇娑堜换鍔¤嚦WCS,闈炲叏鍗曟暟鎹彇娑堝垹闄ゆ祦绋嬫墍鏈夊叧鑱旀暟鎹�
         if (Cools.isEmpty(id)) {
             throw new CoolException("鍙傛暟涓嶈兘涓虹┖锛侊紒");
         }
@@ -238,4 +241,58 @@
         });
         return R.ok();
     }
+
+    /**
+     * @param
+     * @return
+     * @author Ryan
+     * @description 鐢熸垚娉㈡
+     * @time 2025/4/24 15:04
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R generateWaves(List<Long> ids) {
+        if (Objects.isNull(ids) || ids.isEmpty()) {
+            throw new CoolException("鍙傛暟涓嶈兘涓虹┖锛侊紒");
+        }
+        List<AsnOrder> orders = this.listByIds(ids);
+        if (orders.isEmpty()) {
+            throw new CoolException("鍗曟嵁涓嶅瓨鍦紒锛�");
+        }
+        double sum = orders.stream().mapToDouble(AsnOrder::getAnfme).sum();
+        Wave wave = new Wave();
+        String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_WAVE_TYPE, null);
+        if (Objects.isNull(ruleCode) || StringUtils.isBlank(ruleCode)) {
+            throw new CoolException("缂栫爜瑙勫垯閿欒锛氳瑕佹煡鐪嬨�孲YS_WAVE_TYPE銆嶆槸鍚﹁缃垚鍔燂紒锛�");
+        }
+        wave.setOrderNum(ids.size())
+                .setType(Short.parseShort("1"))
+                .setCode(ruleCode)
+                .setExceStatus(WaveExceStatus.WAVE_EXCE_STATUS_INIT.val)
+                .setAnfme(sum);
+        if (!waveService.save(wave)) {
+            throw new CoolException("娉㈡淇濆瓨澶辫触锛侊紒");
+        }
+        List<Long> list = orders.stream().map(AsnOrder::getId).collect(Collectors.toList());
+        List<WaveItem> items = new ArrayList<>();
+        List<AsnOrderItem> orderItems = asnOrderItemService
+                .list(new LambdaQueryWrapper<AsnOrderItem>()
+                        .in(AsnOrderItem::getAsnId, list));
+
+
+        for (AsnOrderItem item : orderItems) {
+            WaveItem waveItem = new WaveItem();
+            BeanUtils.copyProperties(item, waveItem);
+            waveItem.setWaveId(wave.getId())
+                    .setWaveCode(wave.getCode())
+                    .setOrderId(item.getAsnId())
+                    .setOrderCode(item.getAsnCode())
+                    .setOrderItemId(item.getId());
+            items.add(waveItem);
+        }
+        if (!waveItemService.saveBatch(items)) {
+            throw new CoolException("娉㈡鏄庣粏淇濆瓨澶辫触锛侊紒");
+        }
+        return R.ok("鎿嶄綔瀹屾垚锛侊紒");
+    }
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveItemServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveItemServiceImpl.java
new file mode 100644
index 0000000..a18d44b
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveItemServiceImpl.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.manager.service.impl;
+
+import com.vincent.rsf.server.manager.mapper.WaveItemMapper;
+import com.vincent.rsf.server.manager.entity.WaveItem;
+import com.vincent.rsf.server.manager.service.WaveItemService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service("waveItemService")
+public class WaveItemServiceImpl extends ServiceImpl<WaveItemMapper, WaveItem> implements WaveItemService {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
new file mode 100644
index 0000000..d323911
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.manager.service.impl;
+
+import com.vincent.rsf.server.manager.mapper.WaveMapper;
+import com.vincent.rsf.server.manager.entity.Wave;
+import com.vincent.rsf.server.manager.service.WaveService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service("waveService")
+public class WaveServiceImpl extends ServiceImpl<WaveMapper, Wave> implements WaveService {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java
index d0ad509..3546c5c 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/SerialRuleCode.java
@@ -69,4 +69,9 @@
      */
     public final static String SYS_OUT_STOCK_CODE = "sys_out_stock_code";
 
+    /**
+     * 娉㈡绫诲瀷
+     */
+    public final static String SYS_WAVE_TYPE = "sys_wave_type";
+
 }
diff --git a/rsf-server/src/main/java/deviceBind.sql b/rsf-server/src/main/java/deviceBind.sql
new file mode 100644
index 0000000..6ef331b
--- /dev/null
+++ b/rsf-server/src/main/java/deviceBind.sql
@@ -0,0 +1,31 @@
+-- save deviceBind record
+-- mysql
+insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.deviceBind', '0', '/manager/deviceBind', 'deviceBind', '0' , '0', '1' , '1');
+
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query 绔嬩綋搴撶珯鐐圭粦瀹�', '', '1', 'manager:deviceBind:list', '0', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create 绔嬩綋搴撶珯鐐圭粦瀹�', '', '1', 'manager:deviceBind:save', '1', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update 绔嬩綋搴撶珯鐐圭粦瀹�', '', '1', 'manager:deviceBind:update', '2', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete 绔嬩綋搴撶珯鐐圭粦瀹�', '', '1', 'manager:deviceBind:remove', '3', '1', '1');
+
+-- locale menu name
+deviceBind: 'DeviceBind',
+
+-- locale field
+deviceBind: {
+    currentRow: "currentRow",
+    startRow: "startRow",
+    endRow: "endRow",
+    deviceQty: "deviceQty",
+    startDeviceNo: "startDeviceNo",
+    endDeviceNo: "endDeviceNo",
+    staList: "staList",
+    typeId: "typeId",
+    beSimilar: "beSimilar",
+    emptySimilar: "emptySimilar",
+},
+
+-- ResourceContent
+import deviceBind from './deviceBind';
+
+case 'deviceBind':
+    return deviceBind;
diff --git a/rsf-server/src/main/java/wave.sql b/rsf-server/src/main/java/wave.sql
new file mode 100644
index 0000000..4fcff71
--- /dev/null
+++ b/rsf-server/src/main/java/wave.sql
@@ -0,0 +1,27 @@
+-- save wave record
+-- mysql
+insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.wave', '0', '/manager/wave', 'wave', '0' , '0', '1' , '1');
+
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query 娉㈡鍗曟嵁', '', '1', 'manager:wave:list', '0', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create 娉㈡鍗曟嵁', '', '1', 'manager:wave:save', '1', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update 娉㈡鍗曟嵁', '', '1', 'manager:wave:update', '2', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete 娉㈡鍗曟嵁', '', '1', 'manager:wave:remove', '3', '1', '1');
+
+-- locale menu name
+wave: 'Wave',
+
+-- locale field
+wave: {
+    code: "code",
+    type: "type",
+    exceStatus: "exceStatus",
+    anfme: "anfme",
+    qty: "qty",
+    orderNum: "orderNum",
+},
+
+-- ResourceContent
+import wave from './wave';
+
+case 'wave':
+    return wave;
diff --git a/rsf-server/src/main/java/waveItem.sql b/rsf-server/src/main/java/waveItem.sql
new file mode 100644
index 0000000..48f35e9
--- /dev/null
+++ b/rsf-server/src/main/java/waveItem.sql
@@ -0,0 +1,35 @@
+-- save waveItem record
+-- mysql
+insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.waveItem', '0', '/manager/waveItem', 'waveItem', '0' , '0', '1' , '1');
+
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query 娉㈡鏄庣粏', '', '1', 'manager:waveItem:list', '0', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create 娉㈡鏄庣粏', '', '1', 'manager:waveItem:save', '1', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update 娉㈡鏄庣粏', '', '1', 'manager:waveItem:update', '2', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete 娉㈡鏄庣粏', '', '1', 'manager:waveItem:remove', '3', '1', '1');
+
+-- locale menu name
+waveItem: 'WaveItem',
+
+-- locale field
+waveItem: {
+    waveId: "waveId",
+    waveCode: "waveCode",
+    matnrId: "matnrId",
+    matnrName: "matnrName",
+    matnrCode: "matnrCode",
+    batch: "batch",
+    splrBatch: "splrBatch",
+    orderCode: "orderCode",
+    orderItemId: "orderItemId",
+    unit: "unit",
+    trackCode: "trackCode",
+    fieldsIndex: "fieldsIndex",
+    anfme: "anfme",
+    workQty: "workQty",
+},
+
+-- ResourceContent
+import waveItem from './waveItem';
+
+case 'waveItem':
+    return waveItem;
diff --git a/rsf-server/src/main/resources/mapper/manager/DeviceBindMapper.xml b/rsf-server/src/main/resources/mapper/manager/DeviceBindMapper.xml
new file mode 100644
index 0000000..510e068
--- /dev/null
+++ b/rsf-server/src/main/resources/mapper/manager/DeviceBindMapper.xml
@@ -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.manager.mapper.DeviceBindMapper">
+
+</mapper>
diff --git a/rsf-server/src/main/resources/mapper/manager/WaveItemMapper.xml b/rsf-server/src/main/resources/mapper/manager/WaveItemMapper.xml
new file mode 100644
index 0000000..0d37cfe
--- /dev/null
+++ b/rsf-server/src/main/resources/mapper/manager/WaveItemMapper.xml
@@ -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.manager.mapper.WaveItemMapper">
+
+</mapper>
diff --git a/rsf-server/src/main/resources/mapper/manager/WaveMapper.xml b/rsf-server/src/main/resources/mapper/manager/WaveMapper.xml
new file mode 100644
index 0000000..6a4849e
--- /dev/null
+++ b/rsf-server/src/main/resources/mapper/manager/WaveMapper.xml
@@ -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.manager.mapper.WaveMapper">
+
+</mapper>

--
Gitblit v1.9.1