From bb7dd1f513149ecd2887895c807861fdd06a43f6 Mon Sep 17 00:00:00 2001
From: chen.lin <1442464845@qq.com>
Date: 星期三, 11 二月 2026 15:29:27 +0800
Subject: [PATCH] 展示库存明细

---
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java |   68 ++++++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java          |    2 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java    |   10 +
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java                   |   14 +
 rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java              |   10 
 rsf-admin/src/layout/index.jsx                                                                |    7 
 rsf-admin/src/page/orders/outStock/SelectMatnrModal.jsx                                       |   78 ++++---
 rsf-admin/src/page/components/PageEditDrawer.jsx                                              |    4 
 rsf-admin/src/page/locItem/LocItemList.jsx                                                    |    1 
 rsf-admin/src/page/orders/outStock/OutOrderModal.jsx                                          |   59 ++++-
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocItemController.java     |   16 +
 rsf-admin/src/context/TabDialogStateContext.jsx                                               |   75 +++++++
 rsf-admin/src/i18n/zh.js                                                                      |    6 
 rsf-admin/src/layout/TabsBar.jsx                                                              |   24 +-
 rsf-admin/src/page/orders/outStock/OutOrderList.jsx                                           |   70 ++++++
 rsf-admin/src/i18n/en.js                                                                      |    4 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/LocStsType.java                 |    9 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/OutStockService.java          |   10 +
 rsf-admin/src/page/orders/outStock/OutStockPublic.jsx                                         |    7 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MatnrServiceImpl.java    |   11 +
 rsf-server/src/main/resources/mapper/manager/LocItemMapper.xml                                |   20 ++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Matnr.java                     |   15 +
 rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/LocItemMapper.java             |    9 
 rsf-admin/src/page/work/outBound/OutBoundList.jsx                                             |    1 
 rsf-admin/src/page/orders/outStock/MatnrInfoModal.jsx                                         |   68 ++++--
 25 files changed, 502 insertions(+), 96 deletions(-)

diff --git a/rsf-admin/src/context/TabDialogStateContext.jsx b/rsf-admin/src/context/TabDialogStateContext.jsx
new file mode 100644
index 0000000..3fb249d
--- /dev/null
+++ b/rsf-admin/src/context/TabDialogStateContext.jsx
@@ -0,0 +1,75 @@
+import React, { createContext, useCallback, useRef, useContext, useEffect } from 'react';
+
+const STORAGE_KEY = 'rsf_tab_dialog_state';
+
+// 鎸夎矾寰勫瓨鍌ㄥ脊绐楃姸鎬�
+const TabDialogStateContext = createContext(null);
+
+// 瑙勮寖鍖栬矾寰勪负銆岃祫婧愬垪琛ㄣ�嶈矾寰�
+export const getDialogStatePath = (pathnameOrLocation) => {
+  const pathname = typeof pathnameOrLocation === 'object' && pathnameOrLocation?.pathname != null
+    ? pathnameOrLocation.pathname
+    : String(pathnameOrLocation || '');
+  if (!pathname || pathname === '/') return pathname || '/';
+  const segments = pathname.replace(/^\//, '').split('/').filter(Boolean);
+  const first = segments[0];
+  return first ? `/${first}` : pathname;
+};
+
+const readFromStorage = () => {
+  try {
+    const raw = typeof window !== 'undefined' ? window.localStorage.getItem(STORAGE_KEY) : null;
+    if (raw) {
+      const parsed = JSON.parse(raw);
+      return typeof parsed === 'object' && parsed !== null ? parsed : {};
+    }
+  } catch (_) {}
+  return {};
+};
+
+const writeToStorage = (data) => {
+  try {
+    if (typeof window !== 'undefined') {
+      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+    }
+  } catch (_) {}
+};
+
+export const TabDialogStateProvider = ({ children }) => {
+  const storeRef = useRef({});
+
+  useEffect(() => {
+    storeRef.current = readFromStorage();
+  }, []);
+
+  const getDialogState = useCallback((path) => {
+    const fromRef = storeRef.current[path];
+    if (fromRef !== undefined) return fromRef;
+    const fromStorage = readFromStorage()[path];
+    if (fromStorage !== undefined) {
+      storeRef.current[path] = fromStorage;
+      return fromStorage;
+    }
+    return undefined;
+  }, []);
+
+  const setDialogState = useCallback((path, state) => {
+    storeRef.current[path] = state;
+    const all = readFromStorage();
+    all[path] = state;
+    writeToStorage(all);
+  }, []);
+
+  const value = { getDialogState, setDialogState };
+  return (
+    <TabDialogStateContext.Provider value={value}>
+      {children}
+    </TabDialogStateContext.Provider>
+  );
+};
+
+export const useTabDialogState = () => {
+  const ctx = useContext(TabDialogStateContext);
+  if (!ctx) return null;
+  return ctx;
+};
diff --git a/rsf-admin/src/i18n/en.js b/rsf-admin/src/i18n/en.js
index 962a2dd..feb2d8a 100644
--- a/rsf-admin/src/i18n/en.js
+++ b/rsf-admin/src/i18n/en.js
@@ -374,6 +374,7 @@
                 purUnit: "purchaseUnit",
                 stockUnit: "stockUnit",
                 stockLevel: "stockLeval",
+                stockQty: "Stock Qty",
                 isLabelMange: "isLabelMange",
                 safeQty: "safetyQty",
                 minQty: "minQty",
@@ -459,7 +460,8 @@
                 flagLabelMange: "FlagLabelMange",
                 locAttrs: "LocAttrs",
                 useStatus: 'useStatus',
-                locAreaId: 'locAreaId'
+                locAreaId: 'locAreaId',
+                locCode: 'Loc Code'
             },
             locType: {
                 uuid: "uuid",
diff --git a/rsf-admin/src/i18n/zh.js b/rsf-admin/src/i18n/zh.js
index 9631af5..c7a17eb 100644
--- a/rsf-admin/src/i18n/zh.js
+++ b/rsf-admin/src/i18n/zh.js
@@ -398,6 +398,7 @@
                 purUnit: "閲囪喘鍗曚綅",
                 stockUnit: "搴撳瓨鍗曚綅",
                 stockLevel: "ABC鍒嗙被",
+                stockQty: "搴撳瓨鏁伴噺",
                 isLabelMange: "鏍囩绠$悊",
                 safeQty: "瀹夊叏鍊�",
                 minQty: "鏈�灏忓��",
@@ -491,7 +492,8 @@
                 startLev: "璧峰灞�",
                 startRow: "璧峰鎺�",
                 useStatus: '搴撲綅鐘舵��',
-                locAreaId: '閫昏緫鍒嗗尯'
+                locAreaId: '閫昏緫鍒嗗尯',
+                locCode: '搴撲綅'
             },
             stockStatistic: {
                 id: "id",
@@ -1413,6 +1415,8 @@
             closeRight: '鍏抽棴鍙充晶鏍囩',
             closeOthers: '鍏抽棴鍏朵粬鏍囩',
             closeAll: '鍏抽棴鎵�鏈夋爣绛�',
+            sort: '鎺掑簭',
+            bulk_actions: '%{smart_count} 鏉¤閫変腑 |||| %{smart_count} 鏉¤閫変腑',
         },
         page: {
             empty_with_filters: '浣跨敤褰撳墠杩囨护鏉′欢鏈壘鍒扮粨鏋溿��',
diff --git a/rsf-admin/src/layout/TabsBar.jsx b/rsf-admin/src/layout/TabsBar.jsx
index 3043ade..b4641ae 100644
--- a/rsf-admin/src/layout/TabsBar.jsx
+++ b/rsf-admin/src/layout/TabsBar.jsx
@@ -321,14 +321,18 @@
     const handleContextMenu = (event, tab) => {
         event.preventDefault();
         event.stopPropagation();
-        setContextMenu(
-            contextMenu === null
-                ? {
-                      mouseX: event.clientX + 2,
-                      mouseY: event.clientY - 6,
-                  }
-                : null
-        );
+        if (!tab) return;
+        const tabObj = tabs.find(t => t.path === tab.path || isSameResource(t.path, tab.path));
+        if (!tabObj) return;
+        const hasItems = tabObj.closable ||
+            canCloseLeftForTab(tab.path) ||
+            canCloseRightForTab(tab.path) ||
+            canCloseOthersForTab(tab.path);
+        if (!hasItems) return;
+        setContextMenu({
+            mouseX: event.clientX + 2,
+            mouseY: event.clientY - 6,
+        });
         setContextMenuTab(tab);
     };
 
@@ -551,6 +555,8 @@
                         ? { top: contextMenu.mouseY, left: contextMenu.mouseX }
                         : undefined
                 }
+                disableScrollLock
+                ModalProps={{ disablePortal: true }}
                 PaperProps={{
                     sx: {
                         minWidth: 120,
@@ -564,7 +570,6 @@
                     sx: { py: 0 },
                 }}
             >
-                {/* 鍏抽棴褰撳墠鏍囩
                 {contextMenuTab && contextMenuTab.closable && (
                     <MenuItem
                         onClick={handleCloseCurrentTab}
@@ -578,7 +583,6 @@
                         {t('ra.action.close', '鍏抽棴褰撳墠鏍囩')}
                     </MenuItem>
                 )}
-                */}
                 {contextMenuTab && canCloseLeftForTab(contextMenuTab.path) && (
                     <MenuItem
                         onClick={handleCloseLeftTabs}
diff --git a/rsf-admin/src/layout/index.jsx b/rsf-admin/src/layout/index.jsx
index 68e420c..2298066 100644
--- a/rsf-admin/src/layout/index.jsx
+++ b/rsf-admin/src/layout/index.jsx
@@ -4,6 +4,7 @@
 import { MyMenu } from './MyMenu';
 import TabsBar from './TabsBar';
 import { Box } from '@mui/material';
+import { TabDialogStateProvider } from '@/context/TabDialogStateContext';
 
 const LayoutContent = ({ children }) => {
   const [sidebarIsOpen] = useSidebarState();
@@ -15,7 +16,7 @@
       top: 48,
       left: sidebarWidth + 5,
       right: 0,
-      zIndex: 1400, // 楂樹簬 Dialog/Modal(1300)锛岄�氳繃 Portal 鎸傚埌 body 鎵嶈兘鐩栦綇寮圭獥
+      zIndex: 1200, // 浣庝簬 Dialog/Modal 涓� Select/Menu(1300)锛岄伩鍏嶆爣绛鹃〉閬洊涓嬪彂绐楀彛鍐呯殑涓嬫媺锛堝鍑哄簱绛栫暐锛氭晥鐜囦紭鍏�/鍏堣繘鍏堝嚭锛�
       transition: (theme) =>
         theme.transitions.create('left', {
           easing: theme.transitions.easing.sharp,
@@ -47,7 +48,9 @@
       }}
     >
       {createPortal(tabsBarEl, document.body)}
-      {children}
+      <TabDialogStateProvider>
+        {children}
+      </TabDialogStateProvider>
       <CheckForApplicationUpdate />
     </RALayout>
   );
diff --git a/rsf-admin/src/page/components/PageEditDrawer.jsx b/rsf-admin/src/page/components/PageEditDrawer.jsx
index 82d770d..13b96ac 100644
--- a/rsf-admin/src/page/components/PageEditDrawer.jsx
+++ b/rsf-admin/src/page/components/PageEditDrawer.jsx
@@ -42,8 +42,8 @@
                             </Stack>
                         </Box>
                     </Card>
-                    <Card sx={{mt: '1em'}}>
-                        <Box>
+                    <Card sx={{ mt: '1em' }}>
+                        <Box sx={{ pt: 2 }}>
                             {children}
                         </Box>
                     </Card>
diff --git a/rsf-admin/src/page/locItem/LocItemList.jsx b/rsf-admin/src/page/locItem/LocItemList.jsx
index 9b3e38b..dda2fd0 100644
--- a/rsf-admin/src/page/locItem/LocItemList.jsx
+++ b/rsf-admin/src/page/locItem/LocItemList.jsx
@@ -146,6 +146,7 @@
                 <NumberField source="id" />,
                 <NumberField source="locId" label="table.field.locItem.locId" />,
                 <TextField source="locCode" label="table.field.locItem.locCode" />,
+                <TextField source="locUseStatus$" label="table.field.loc.useStatus" />,
                 <NumberField source="matnrId" label="table.field.locItem.matnrId" />,
                 <TextField source="maktx" label="table.field.locItem.maktx" />,
                 <TextField source="matnrCode" label="table.field.locItem.matnrCode" />,
diff --git a/rsf-admin/src/page/orders/outStock/MatnrInfoModal.jsx b/rsf-admin/src/page/orders/outStock/MatnrInfoModal.jsx
index e51d215..e3d652a 100644
--- a/rsf-admin/src/page/orders/outStock/MatnrInfoModal.jsx
+++ b/rsf-admin/src/page/orders/outStock/MatnrInfoModal.jsx
@@ -10,7 +10,11 @@
     Box,
     Button,
     Paper,
-    styled
+    styled,
+    Select,
+    MenuItem,
+    FormControl,
+    InputLabel
 } from '@mui/material';
 import DialogCloseButton from "../../components/DialogCloseButton";
 import { EDIT_MODE, DEFAULT_START_PAGE, DEFAULT_PAGE_SIZE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting';
@@ -32,7 +36,7 @@
         }
     };
 
-    const [formData, setFormData] = useState({});
+    const [formData, setFormData] = useState({ locUseStatus: 'F' });
     const [tableData, setTableData] = useState([]);
     const [dyFields, setDyFields] = useState([]);
     const [selectedRows, setSelectedRows] = useState([]);
@@ -41,7 +45,8 @@
     const [isLoading, setIsLoading] = useState(false);
     const handleChange = (e) => {
         const { name, value } = e.target;
-        setFormData(() => ({
+        setFormData((prev) => ({
+            ...prev,
             [name]: value
         }));
     };
@@ -50,50 +55,53 @@
         setFormData({
             name: null,
             code: null,
-            groupId: null
+            groupId: null,
+            locUseStatus: 'F'
         })
     }
 
     const handleSubmit = () => {
-        const hasarr = data.map(el => +el.matnrId)
-        const selectedData = selectedRows.filter(item => !hasarr.includes(item)).map(id => (tableData.find(row => row.id === id)));
-        const value = selectedData.map((el => {
+        const hasarr = data.map(el => +el.matnrId);
+        const selectedData = selectedRows
+            .filter((item) => !hasarr.includes(item))
+            .map((id) => tableData.find((row) => row.id === id))
+            .filter(Boolean);
+        const deduped = [...new Map(selectedData.map((s) => [s.id, s])).values()];
+        const value = deduped.map((el, i) => {
             const dynamicFields = dyFields.reduce((acc, item) => {
                 acc[item.fields] = el['extendFields']?.[item.fields] || '';
                 return acc;
             }, {});
             return {
+                _rowKey: `new_${Date.now()}_${i}`,
                 matnrId: el.id,
                 maktx: el.name,
                 matnrCode: el.code,
                 stockUnit: el.stockUnit || '',
                 purUnit: el.purchaseUnit || '',
                 ...dynamicFields
-            }
-        }))
+            };
+        });
         setData([...data, ...value]);
         setOpen(false);
         reset();
     };
 
     const getData = async () => {
-        setIsLoading(true)
-        console.log(page);
-        const res = await request.post(`/matnr/page`, {
+        setIsLoading(true);
+        const res = await request.post(`/outStock/matnr/page`, {
             ...formData,
             current: page?.page,
             pageSize: page?.pageSize,
             orderBy: "create_time desc"
         });
         if (res?.data?.code === 200) {
-            setTableData(res.data.data.records);
-            setRowCount(res.data?.data?.total);
-
+            setTableData(res.data.data.records || []);
+            setRowCount(res.data?.data?.total ?? 0);
         } else {
-            notify(res.data.msg);
+            notify(res.data?.msg || '鏌ヨ澶辫触');
         }
-        setIsLoading(false)
-
+        setIsLoading(false);
     };
 
     useEffect(() => {
@@ -145,7 +153,7 @@
                                 size="small"
                             />
                         </Grid>
-                        <Grid item md={4}>
+                        <Grid item md={3}>
                             <TreeSelectInput
                                 label="table.field.matnr.groupId"
                                 value={formData.groupId}
@@ -154,6 +162,24 @@
                                 name="groupId"
                                 onChange={handleChange}
                             />
+                        </Grid>
+                        <Grid item md={3}>
+                            <FormControl size="small" fullWidth variant="filled">
+                                <InputLabel>{translate('table.field.loc.useStatus')}</InputLabel>
+                                <Select
+                                    name="locUseStatus"
+                                    value={formData.locUseStatus ?? ''}
+                                    onChange={handleChange}
+                                    label={translate('table.field.loc.useStatus')}
+                                >
+                                    <MenuItem value="">鍏ㄩ儴</MenuItem>
+                                    {(JSON.parse(localStorage.getItem('sys_dicts')) || [])
+                                        .filter((d) => d.dictTypeCode === 'sys_loc_use_stas')
+                                        .map((d) => (
+                                            <MenuItem key={d.value} value={d.value}>{d.label}</MenuItem>
+                                        ))}
+                                </Select>
+                            </FormControl>
                         </Grid>
                     </Grid>
                 </Box>
@@ -195,7 +221,6 @@
     const notify = useNotify();
 
     const [columns, setColumns] = useState([
-        // { field: 'id', headerName: 'ID', width: 100 },
         { field: 'name', headerName: translate('table.field.matnr.name'), width: 300 },
         { field: 'code', headerName: translate('table.field.matnr.code'), width: 200 },
         { field: 'groupId$', headerName: translate('table.field.matnr.groupId'), width: 100 },
@@ -207,6 +232,9 @@
         { field: 'unit', headerName: translate('table.field.matnr.unit'), width: 100 },
         { field: 'purchaseUnit', headerName: translate('table.field.matnr.purUnit'), width: 100 },
         { field: 'stockUnit', headerName: translate('table.field.matnr.stockUnit'), width: 100 },
+        { field: 'stockQty', headerName: translate('table.field.matnr.stockQty') || '搴撳瓨鏁伴噺', width: 110, type: 'number', valueFormatter: (v) => (v != null ? Number(v) : 0) },
+        { field: 'locUseStatus$', headerName: translate('table.field.loc.useStatus'), width: 120 },
+        { field: 'locCodes$', headerName: translate('table.field.loc.locCode'), width: 180, flex: 1 },
         { field: 'stockLeval$', headerName: translate('table.field.matnr.stockLevel'), width: 100, sortable: false },
     ])
 
diff --git a/rsf-admin/src/page/orders/outStock/OutOrderList.jsx b/rsf-admin/src/page/orders/outStock/OutOrderList.jsx
index 8934fe5..944d128 100644
--- a/rsf-admin/src/page/orders/outStock/OutOrderList.jsx
+++ b/rsf-admin/src/page/orders/outStock/OutOrderList.jsx
@@ -1,5 +1,6 @@
-import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
+import React, { useState, useRef, useEffect, useLayoutEffect, useMemo, useCallback } from "react";
 import { useLocation, useNavigate } from 'react-router-dom';
+import { useTabDialogState, getDialogStatePath } from '@/context/TabDialogStateContext';
 import {
   List,
   DatagridConfigurable,
@@ -83,10 +84,44 @@
 }));
 
 const OutOrderList = (props) => {
+  const location = useLocation();
+  const tabDialogState = useTabDialogState();
 
   const dicts = JSON.parse(localStorage.getItem('sys_dicts'))?.filter(dict => (dict.dictTypeCode == 'sys_business_type')) || [];
-  const [createDialog, setCreateDialog] = useState(false);
+  const [createDialog, setCreateDialogInner] = useState(false);
   const [manualDialog, setManualDialog] = useState(false);
+
+  const pathKey = getDialogStatePath(
+    location.pathname === '/' && typeof window !== 'undefined' && window.location?.hash
+      ? (window.location.hash.replace(/^#/, '') || '/')
+      : location.pathname
+  );
+
+  const setCreateDialog = useCallback((open) => {
+    setCreateDialogInner(open);
+    if (tabDialogState) {
+      const current = tabDialogState.getDialogState(pathKey) || {};
+      tabDialogState.setDialogState(pathKey, { ...current, createDialogOpen: open });
+    }
+  }, [pathKey, tabDialogState]);
+
+  const saveCreateDialogForm = useCallback((formData) => {
+    if (tabDialogState) {
+      const current = tabDialogState.getDialogState(pathKey) || {};
+      tabDialogState.setDialogState(pathKey, { ...current, createDialogOpen: true, createDialogForm: formData });
+    }
+  }, [pathKey, tabDialogState]);
+
+  const savedCreateFormData = tabDialogState?.getDialogState(pathKey)?.createDialogForm;
+
+  useLayoutEffect(() => {
+    if (tabDialogState) {
+      const saved = tabDialogState.getDialogState(pathKey);
+      if (saved?.createDialogOpen) {
+        setCreateDialogInner(true);
+      }
+    }
+  }, [pathKey, tabDialogState]);
   const [drawerVal, setDrawerVal] = useState(false);
   const [waveRule, setWaveRule] = useState(false);
   const [selectIds, setSelectIds] = useState([]);
@@ -193,11 +228,15 @@
           <BillStatusField cellClassName="status" source="exceStatus" label="table.field.outStock.exceStatus" />
           <TextField source="memo" label="common.field.memo" sortable={false} />
           <WrapperField cellClassName="opt" label="common.field.opt" >
-            <MyButton setCreateDialog={setManualDialog} setmodalType={setmodalType} />
-            <EditButton label="toolbar.detail" icon={(<DetailsIcon />)}></EditButton>
+            <OutOrderRowActions
+              setCreateDialog={setManualDialog}
+              setmodalType={setmodalType}
+              setDrawerVal={setDrawerVal}
+              drawerVal={drawerVal}
+              setSelect={setSelect}
+            />
             <CancelButton />
             <CompleteButton />
-            <PublicButton setDrawerVal={setDrawerVal} drawerVal={drawerVal} setSelect={setSelect} />
           </WrapperField>
         </StyledDatagrid>
       </List>
@@ -216,6 +255,8 @@
         setOpen={setCreateDialog}
         preview={preview}
         setPreview={setPreview}
+        initialFormData={savedCreateFormData}
+        saveFormData={saveCreateDialogForm}
       />
       <OutStockWaveDialog open={waveRule} setOpen={setWaveRule} onClose={closeDialog} />
       <OutOrderPreview open={preview} setOpen={setPreview} />
@@ -276,6 +317,25 @@
   )
 }
 
+/** 鍑哄簱鍗曟墽琛岀姸鎬侊細10=鍒濆鍖栵紙浠呮鐘舵�佹樉绀虹紪杈�/璇︽儏锛� */
+const OUT_STOCK_EXCE_STATUS_INIT = 10;
+
+const OutOrderRowActions = ({ setCreateDialog, setmodalType, setDrawerVal, drawerVal, setSelect }) => {
+  const record = useRecordContext();
+  const isInit = record?.exceStatus === OUT_STOCK_EXCE_STATUS_INIT || record?.exceStatus === '10';
+  return (
+    <>
+      {isInit && (
+        <>
+          <MyButton setCreateDialog={setCreateDialog} setmodalType={setmodalType} />
+          <EditButton label="toolbar.detail" icon={(<DetailsIcon />)} />
+        </>
+      )}
+      <PublicButton setDrawerVal={setDrawerVal} drawerVal={drawerVal} setSelect={setSelect} />
+    </>
+  );
+};
+
 const MyButton = ({ setCreateDialog, setmodalType }) => {
   const record = useRecordContext();
   const handleEditClick = (btn) => {
diff --git a/rsf-admin/src/page/orders/outStock/OutOrderModal.jsx b/rsf-admin/src/page/orders/outStock/OutOrderModal.jsx
index a12c340..67594a4 100644
--- a/rsf-admin/src/page/orders/outStock/OutOrderModal.jsx
+++ b/rsf-admin/src/page/orders/outStock/OutOrderModal.jsx
@@ -1,5 +1,5 @@
 import { Dialog, DialogActions, DialogContent, DialogTitle, Box, LinearProgress } from "@mui/material";
-import React, { useState, useRef, useEffect, useMemo } from "react";
+import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
 import {
     List,
     DatagridConfigurable,
@@ -94,31 +94,64 @@
 ]
 
 const OutOrderModal = (props) => {
-    const { open, setOpen, preview, setPreview, record } = props;
+    const { open, setOpen, preview, setPreview, record, initialFormData, saveFormData } = props;
     const [drawerVal, setDrawerVal] = useState(false);
-    const [params, setParams] = useState({});
+    const [params, setParams] = useState(() => initialFormData?.params ?? {});
     const [select, setSelect] = useState([]);
     const translate = useTranslate();
     const refresh = useRefresh();
+
+    useEffect(() => {
+        if (open && initialFormData?.params) {
+            setParams(initialFormData.params);
+        }
+    }, [open, initialFormData?.params]);
+
     const handleClose = (event, reason) => {
         if (reason !== "backdropClick") {
             setOpen(false);
         }
     };
 
+    useEffect(() => {
+        if (open && params && Object.keys(params).length > 0 && saveFormData) {
+            saveFormData({ params, formValues: params });
+        }
+    }, [open, params, saveFormData]);
+
+    const handleFormValuesChange = useCallback((formValues) => {
+        if (saveFormData) {
+            saveFormData({ params: formValues, formValues });
+        }
+    }, [saveFormData]);
+
     const CustomFilter = () => {
-        const { filterValues, setFilters, refetch } = useListContext();
-        const [formValues, setFormValues] = useState(filterValues);
+        const { filterValues } = useListContext();
+        const initialFormValues = initialFormData?.formValues ?? initialFormData?.params ?? params ?? filterValues;
+        const [formValues, setFormValues] = useState(initialFormValues || {});
+        const initializedRef = useRef(false);
+        useEffect(() => {
+            if (open && (initialFormData?.formValues || initialFormData?.params) && !initializedRef.current) {
+                const init = initialFormData?.formValues ?? initialFormData?.params ?? {};
+                setFormValues(init);
+                initializedRef.current = true;
+            }
+            if (!open) initializedRef.current = false;
+        }, [open, initialFormData?.formValues, initialFormData?.params]);
+
         const handleChange = (event) => {
             if (event.target == undefined || event.target == null) { return }
-            setFormValues(formValues => ({
+            const next = {
                 ...formValues,
                 [event.target.name]: event.target.value,
-            }));
+            };
+            setFormValues(next);
+            handleFormValuesChange(next);
         };
 
         const handleSubmit = (event) => {
-            setParams(formValues)
+            setParams(formValues);
+            if (saveFormData) saveFormData({ params: formValues, formValues });
         };
 
         return (
@@ -129,14 +162,14 @@
                             source="condition"
                             label="common.action.search"
                             resettable
-                            defaultValue={params?.condition}
+                            value={formValues?.condition ?? ''}
                             onChange={handleChange} />
                     </Stack>
                     <Stack>
                         <TextInput
                             source="deliveryCode"
                             label="table.field.deliveryItem.deliveryCode"
-                            defaultValue={params?.deliveryCode}
+                            value={formValues?.deliveryCode ?? ''}
                             onChange={handleChange}
                             resettable
                         />
@@ -145,7 +178,7 @@
                         <TextInput
                             source="maktx"
                             label="table.field.deliveryItem.matnrName"
-                            defaultValue={params?.maktx}
+                            value={formValues?.maktx ?? ''}
                             onChange={handleChange}
                             resettable
                         />
@@ -154,7 +187,7 @@
                         <TextInput
                             source="matnrCode"
                             label="table.field.deliveryItem.matnrCode"
-                            defaultValue={params?.matnrCode}
+                            value={formValues?.matnrCode ?? ''}
                             resettable
                             onChange={handleChange} />
                     </Stack>
@@ -162,7 +195,7 @@
                         <TextInput
                             source="splrName"
                             label="table.field.deliveryItem.splrName"
-                            defaultValue={params?.splrName}
+                            value={formValues?.splrName ?? ''}
                             resettable
                             onChange={handleChange} />
                     </Stack>
diff --git a/rsf-admin/src/page/orders/outStock/OutStockPublic.jsx b/rsf-admin/src/page/orders/outStock/OutStockPublic.jsx
index 1877e2f..2046043 100644
--- a/rsf-admin/src/page/orders/outStock/OutStockPublic.jsx
+++ b/rsf-admin/src/page/orders/outStock/OutStockPublic.jsx
@@ -444,15 +444,18 @@
 
     const OutStockAnfme = React.memo(function OutStockAnfme(props) {
         const { value } = props;
+        const num = Number(value);
+        const hasStock = typeof num === 'number' && !Number.isNaN(num) && num > 1e-6;
         return (
-            value > 0 ?
+            hasStock ? (
                 <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                     <span>{value}</span>
                 </Box>
-                :
+            ) : (
                 <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                     <span style={{ color: 'red' }}>{translate('common.edit.title.insuffInventory')}</span>
                 </Box>
+            )
         );
     });
 
diff --git a/rsf-admin/src/page/orders/outStock/SelectMatnrModal.jsx b/rsf-admin/src/page/orders/outStock/SelectMatnrModal.jsx
index 75f916f..e3c59a2 100644
--- a/rsf-admin/src/page/orders/outStock/SelectMatnrModal.jsx
+++ b/rsf-admin/src/page/orders/outStock/SelectMatnrModal.jsx
@@ -122,38 +122,36 @@
     }
 
     const handleSubmit = async () => {
-        setFinally()
-        setDisabled(true)
-
-        if (asnId === 0) {
-            const parmas = {
-                "orders": formData,
-                "items": tabelData,
-            }
-            const res = await request.post(`/outStock/items/save`, parmas);
-            if (res?.data?.code === 200) {
-                setOpen(false);
-                refresh();
-                resetData()
+        setFinally();
+        setDisabled(true);
+        try {
+            if (asnId === 0) {
+                const parmas = { "orders": formData, "items": tabelData };
+                const res = await request.post(`/outStock/items/save`, parmas);
+                if (res?.data?.code === 200) {
+                    setOpen(false);
+                    refresh();
+                    resetData();
+                } else {
+                    notify(res?.data?.msg || '淇濆瓨澶辫触', { type: 'error' });
+                }
             } else {
-                notify(res.data.msg);
+                const parmas = { "orders": formData, "items": tabelData };
+                const res = await request.post(`/outStock/items/update`, parmas);
+                if (res?.data?.code === 200) {
+                    setOpen(false);
+                    refresh();
+                    resetData();
+                } else {
+                    notify(res?.data?.msg || '淇濆瓨澶辫触', { type: 'error' });
+                }
             }
-        } else {
-            const parmas = {
-                "orders": formData,
-                "items": tabelData,
-            }
-            const res = await request.post(`/outStock/items/update`, parmas);
-            if (res?.data?.code === 200) {
-                setOpen(false);
-                refresh();
-                resetData()
-            } else {
-                notify(res.data.msg);
-            }
+        } catch (error) {
+            const msg = error?.response?.data?.msg || error?.message || '淇濆瓨澶辫触锛岃閲嶈瘯';
+            notify(msg, { type: 'error' });
+        } finally {
+            setDisabled(false);
         }
-        setDisabled(false)
-
     };
 
 
@@ -187,9 +185,12 @@
 
     const [selectedRows, setSelectedRows] = useState([]);
 
+    const getRowId = (row) => (row.id != null ? row.id : row._rowKey) ?? row.matnrId;
+
     const handleDeleteItem = () => {
-        const newTableData = _.filter(tabelData, (item) => !selectedRows.includes(item.matnrId));
+        const newTableData = tabelData.filter((item) => !selectedRows.includes(getRowId(item)));
         setTableData(newTableData);
+        setSelectedRows([]);
     }
 
     return (
@@ -278,7 +279,7 @@
                         </Stack>
                     </Box>
                     <Box sx={{ mt: 2 }}>
-                        <AsnOrderModalTable tabelData={tabelData} setTableData={setTableData} asnId={asnId} selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef}></AsnOrderModalTable>
+                        <AsnOrderModalTable tabelData={tabelData} setTableData={setTableData} asnId={asnId} selectedRows={selectedRows} setSelectedRows={setSelectedRows} tableRef={tableRef} getRowId={getRowId}></AsnOrderModalTable>
                     </Box>
                 </DialogContent>
                 <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
@@ -410,7 +411,7 @@
 
 
 
-const AsnOrderModalTable = ({ tabelData, setTableData, asnId, selectedRows, setSelectedRows, tableRef }) => {
+const AsnOrderModalTable = ({ tabelData, setTableData, asnId, selectedRows, setSelectedRows, tableRef, getRowId: getRowIdProp }) => {
     const translate = useTranslate();
     const notify = useNotify();
 
@@ -530,17 +531,20 @@
     }
 
 
+    const getRowId = getRowIdProp || ((row) => (row.id != null ? row.id : row._rowKey) ?? row.matnrId);
+
     const handleDelete = (row) => {
-        const newData = _.filter(cdata.current, (item) => item.matnrId !== row.matnrId);
+        const rowId = getRowId(row);
+        const newData = cdata.current.filter((item) => getRowId(item) !== rowId);
         setTableData(newData);
     };
 
 
     const processRowUpdate = (newRow, oldRow) => {
         const rows = tabelData.map((r) =>
-            r.matnrId === newRow.matnrId ? { ...newRow } : r
-        )
-        setTableData(rows)
+            getRowId(r) === getRowId(oldRow) ? { ...newRow } : r
+        );
+        setTableData(rows);
         return newRow;
     };
 
@@ -557,7 +561,7 @@
                 rows={tabelData}
                 columns={columns}
                 disableRowSelectionOnClick
-                getRowId={(row) => row.matnrId ? row.matnrId : row.id}
+                getRowId={(row) => getRowId(row)}
                 disableColumnFilter
                 disableColumnSelector
                 disableColumnSorting
diff --git a/rsf-admin/src/page/work/outBound/OutBoundList.jsx b/rsf-admin/src/page/work/outBound/OutBoundList.jsx
index 1831c4f..f36d6f0 100644
--- a/rsf-admin/src/page/work/outBound/OutBoundList.jsx
+++ b/rsf-admin/src/page/work/outBound/OutBoundList.jsx
@@ -49,6 +49,7 @@
     Card,
 } from '@mui/material';
 import { EDIT_MODE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting';
+import _ from 'lodash';
 import ConfirmButton from "../../components/ConfirmButton";
 import TreeSelectInput from "@/page/components/TreeSelectInput";
 import { DataGrid, useGridApiRef } from '@mui/x-data-grid';
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocItemController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocItemController.java
index fc8bddd..2fb3cbe 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocItemController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocItemController.java
@@ -13,6 +13,7 @@
 import com.vincent.rsf.server.common.domain.PageParam;
 import com.vincent.rsf.server.common.utils.FieldsUtils;
 import com.vincent.rsf.server.manager.controller.params.LocToTaskParams;
+import com.vincent.rsf.server.manager.entity.Loc;
 import com.vincent.rsf.server.manager.entity.LocItem;
 import com.vincent.rsf.server.manager.entity.ViewStockManage;
 import com.vincent.rsf.server.manager.enums.TaskResouceType;
@@ -53,6 +54,14 @@
                 Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex());
                 record.setExtendFields(fields);
             }
+            // 濉厖搴撲綅鐘舵�侊紝渚夸簬鍒楄〃灞曠ず
+            if (record.getLocId() != null) {
+                Loc loc = locService.getById(record.getLocId());
+                if (loc != null) {
+                    record.setLocUseStatus(loc.getUseStatus());
+                    record.setLocUseStatus$(loc.getUseStatus$());
+                }
+            }
         }
         page.setRecords(records);
 
@@ -81,6 +90,13 @@
                 Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex());
                 record.setExtendFields(fields);
             }
+            if (record.getLocId() != null) {
+                Loc loc = locService.getById(record.getLocId());
+                if (loc != null) {
+                    record.setLocUseStatus(loc.getUseStatus());
+                    record.setLocUseStatus$(loc.getUseStatus$());
+                }
+            }
         }
         page.setRecords(records);
 
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 9d4c3a2..e754263 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
@@ -13,6 +13,7 @@
 import com.vincent.rsf.server.manager.controller.params.OrderOutTaskParam;
 import com.vincent.rsf.server.manager.controller.params.OutStockToTaskParams;
 import com.vincent.rsf.server.manager.entity.DeliveryItem;
+import com.vincent.rsf.server.manager.entity.Matnr;
 import com.vincent.rsf.server.manager.entity.WkOrder;
 import com.vincent.rsf.server.manager.entity.WkOrderItem;
 import com.vincent.rsf.server.manager.enums.OrderType;
@@ -93,6 +94,15 @@
         return R.ok().add(outStockService.getById(id));
     }
 
+    @PreAuthorize("hasAuthority('manager:outStock:list')")
+    @PostMapping("/outStock/matnr/page")
+    @ApiOperation("鍑哄簱鍗曢�夌墿鏂欏垎椤碉紙鏀寔搴撲綅鐘舵�佺瓫閫夈�佸簱瀛樻暟閲忎笌搴撲綅鐘舵�佸睍绀猴級")
+    public R pageMatnr(@RequestBody Map<String, Object> map) {
+        BaseParam baseParam = buildParam(map, BaseParam.class);
+        PageParam<Matnr, BaseParam> pageParam = new PageParam<>(baseParam, Matnr.class);
+        return R.ok().add(outStockService.pageMatnrForOutStock(pageParam, map));
+    }
+
     @PreAuthorize("hasAuthority('manager:outStock:save')")
     @OperationLog("Create 鍑哄簱鍗曟嵁")
     @PostMapping("/outStock/save")
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java
index 917aaf5..bea0103 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/LocItem.java
@@ -107,6 +107,20 @@
     private Integer channel;
 
     /**
+     * 搴撲綅鐘舵�侊紙鏉ヨ嚜鍏宠仈搴撲綅锛岄潪琛ㄥ瓧娈碉級
+     */
+    @ApiModelProperty("搴撲綅鐘舵��")
+    @TableField(exist = false)
+    private String locUseStatus;
+
+    /**
+     * 搴撲綅鐘舵�佹樉绀哄�硷紙鏉ヨ嚜鍏宠仈搴撲綅锛岄潪琛ㄥ瓧娈碉級
+     */
+    @ApiModelProperty("搴撲綅鐘舵�佹樉绀�")
+    @TableField(exist = false)
+    private String locUseStatus$;
+
+    /**
      * 鐗╂枡鍚嶇О
      */
     @ApiModelProperty(value= "鐗╂枡鍚嶇О")
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Matnr.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Matnr.java
index 69c696a..1cf77fb 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Matnr.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Matnr.java
@@ -231,6 +231,21 @@
     @TableField(exist = false)
     private Map<String, String> extendFields;
 
+    /** 搴撳瓨鏁伴噺锛堝嚭搴撻�夌墿鏂欐椂鐢卞簱浣嶆眹鎬伙紝闈炶〃瀛楁锛� */
+    @ApiModelProperty("搴撳瓨鏁伴噺")
+    @TableField(exist = false)
+    private Double stockQty;
+
+    /** 搴撲綅鐘舵�佸睍绀猴紙鍑哄簱閫夌墿鏂欐椂锛岄潪琛ㄥ瓧娈碉級 */
+    @ApiModelProperty("搴撲綅鐘舵��")
+    @TableField(exist = false)
+    private String locUseStatus$;
+
+    /** 搴撲綅灞曠ず锛堝嚭搴撻�夌墿鏂欐椂锛屾湁搴撳瓨鐨勫簱浣嶇紪鐮侊紝閫楀彿鍒嗛殧锛岄潪琛ㄥ瓧娈碉級 */
+    @ApiModelProperty("搴撲綅")
+    @TableField(exist = false)
+    private String locCodes$;
+
     /**
      * 绉熸埛
      */
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/LocStsType.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/LocStsType.java
index d081a1a..bc459b8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/LocStsType.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/LocStsType.java
@@ -35,6 +35,15 @@
         return null;
     }
 
+    /** 鏍规嵁鐘舵�佺爜(type)鍙栨弿杩帮紝濡� F -> 鍦ㄥ簱 */
+    public static String getDescByType(String type) {
+        if (type == null || type.isEmpty()) return type;
+        for (LocStsType value : LocStsType.values()) {
+            if (type.trim().equals(value.type)) return value.desc;
+        }
+        return type;
+    }
+
     /**
      * @author Ryan
      * @date 2025/8/28
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/LocItemMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/LocItemMapper.java
index 574ebca..4c42b43 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/LocItemMapper.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/LocItemMapper.java
@@ -9,10 +9,19 @@
 import org.springframework.stereotype.Repository;
 
 import java.util.List;
+import java.util.Map;
 
 @Mapper
 @Repository
 public interface LocItemMapper extends BaseMapper<LocItem> {
 
     List<LocItem> listByMatnr(@Param("type") String type, @Param("channel") String channel, @Param(Constants.WRAPPER) LambdaQueryWrapper<LocItem> matnr);
+
+    /**
+     * 鎸夌墿鏂橧D姹囨�诲簱浣嶅簱瀛樻暟閲忥紱鍙�夋寜搴撲綅鐘舵�佽繃婊ゃ��
+     * @param matnrIds 鐗╂枡ID鍒楄〃
+     * @param locUseStatus 搴撲綅鐘舵�侊紝涓虹┖鍒欎笉杩囨护
+     * @return 姣忚: matnrId, stockQty, locStatuses(閫楀彿鍒嗛殧鐨勫簱浣嶇姸鎬侊紝浠呭綋 locUseStatus 涓虹┖鏃惰繑鍥�)
+     */
+    List<Map<String, Object>> listStockByMatnrIds(@Param("matnrIds") List<Long> matnrIds, @Param("locUseStatus") String locUseStatus);
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
index e3858e1..eab7be5 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
@@ -248,7 +248,7 @@
     /**
      * 闈炲厜鐢电珯鐐逛换鍔′笅鍙�
      */
-    @Scheduled(cron = "0/55 * * * * ?  ")
+    @Scheduled(cron = "0/35 * * * * ?  ")
     @Transactional(rollbackFor = Exception.class)
     public void pubTaskToWcs() {
         log.info("瀹氭椂浠诲姟寮�濮嬫墽琛岋細浠诲姟涓嬪彂鍒癛CS");
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 13e0d9a..c842084 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
@@ -1,5 +1,8 @@
 package com.vincent.rsf.server.manager.service;
 
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.manager.entity.Matnr;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.vincent.rsf.framework.common.R;
 import com.vincent.rsf.server.manager.controller.params.AsnOrderAndItemsParams;
@@ -11,9 +14,16 @@
 import com.vincent.rsf.server.manager.entity.DeliveryItem;
 
 import java.util.List;
+import java.util.Map;
 
 public interface OutStockService extends IService<WkOrder> {
 
+    /**
+     * 鍑哄簱鍗曢�夌墿鏂欏垎椤碉細鏀寔鎸夊簱浣嶇姸鎬佺瓫閫夛紝骞惰繑鍥炲簱瀛樻暟閲忋�佸簱浣嶇姸鎬佸睍绀�
+     */
+    PageParam<Matnr, BaseParam> pageMatnrForOutStock(PageParam<Matnr, BaseParam> pageParam, Map<String, Object> params);
+
+
     R cancelOutOrder(String id);
 
     R genOutStock(List<DeliveryItem> ids, Long loginUserId);
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MatnrServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MatnrServiceImpl.java
index 34c5ea3..8b61546 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MatnrServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MatnrServiceImpl.java
@@ -25,6 +25,7 @@
 import com.vincent.rsf.server.system.constant.SerialRuleCode;
 import com.vincent.rsf.server.system.service.FieldsService;
 import com.vincent.rsf.server.system.utils.SerialRuleUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -158,8 +159,18 @@
             }
 
         }
+        // locUseStatus 浠呯敤浜庝笅鏂� EXISTS 瀛愭煡璇紝涓嶈兘浣滀负 man_matnr 琛ㄥ瓧娈靛弬涓� buildWrapper
+        Object locUseStatus = params.get("locUseStatus");
+        if (pageParam.getWhere() != null && pageParam.getWhere().getMap() != null) {
+            pageParam.getWhere().getMap().remove("locUseStatus");
+        }
         QueryWrapper<Matnr> queryWrapper = pageParam.buildWrapper(true);
         queryWrapper.in(!longs.isEmpty(),"group_id", longs);
+        // 鍑哄簱閫夌墿鏂欙細鎸夊簱浣嶇姸鎬佺瓫閫夛紙浠呭睍绀哄湪璇ュ簱浣嶇姸鎬佷笅鏈夊簱瀛樼殑鐗╂枡锛�
+        if (locUseStatus != null && StringUtils.isNotBlank(locUseStatus.toString())) {
+            String useStatus = locUseStatus.toString().replace("'", "''");
+            queryWrapper.apply("EXISTS (SELECT 1 FROM man_loc_item li INNER JOIN man_loc l ON li.loc_id = l.id WHERE li.matnr_id = man_matnr.id AND l.use_status = '" + useStatus + "')");
+        }
 
         FieldsUtils.setFieldsFilters(queryWrapper,pageParam,Matnr.class);
         /**鎷兼帴鎵╁睍瀛楁*/
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 2e999d3..a42985b 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
@@ -8,12 +8,16 @@
 import com.vincent.rsf.framework.exception.CoolException;
 import com.vincent.rsf.server.api.utils.LocUtils;
 import com.vincent.rsf.server.common.constant.Constants;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
 import com.vincent.rsf.server.manager.controller.dto.ExistDto;
 import com.vincent.rsf.server.manager.controller.dto.OrderOutItemDto;
 import com.vincent.rsf.server.manager.controller.params.*;
 import com.vincent.rsf.server.manager.enums.*;
+import com.vincent.rsf.server.manager.entity.Matnr;
 import com.vincent.rsf.server.manager.entity.*;
 import com.vincent.rsf.server.manager.mapper.AsnOrderMapper;
+import com.vincent.rsf.server.manager.mapper.LocItemMapper;
 import com.vincent.rsf.server.manager.service.*;
 import com.vincent.rsf.server.manager.utils.LocManageUtil;
 import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil;
@@ -83,7 +87,71 @@
     private WaveOrderRelaServiceImpl waveOrderRelaService;
     @Autowired
     private TaskItemService taskItemService;
+    @Autowired
+    private LocItemMapper locItemMapper;
 
+    @Override
+    public PageParam<Matnr, BaseParam> pageMatnrForOutStock(PageParam<Matnr, BaseParam> pageParam, Map<String, Object> params) {
+        PageParam<Matnr, BaseParam> page = matnrService.getMatnrPage(pageParam, params);
+        List<Matnr> records = page.getRecords();
+        if (records == null || records.isEmpty()) {
+            return page;
+        }
+        List<Long> matnrIds = records.stream().map(Matnr::getId).collect(Collectors.toList());
+        String locUseStatus = params.get("locUseStatus") != null ? params.get("locUseStatus").toString() : null;
+        List<Map<String, Object>> stockList = locItemMapper.listStockByMatnrIds(matnrIds, locUseStatus);
+        Map<Long, Double> stockQtyMap = new HashMap<>();
+        Map<Long, String> locStatusDescMap = new HashMap<>();
+        Map<Long, String> locCodesMap = new HashMap<>();
+        for (Map<String, Object> row : stockList) {
+            Long matnrId = getLong(row, "matnrId", "matnrid");
+            if (matnrId == null) continue;
+            Object qty = getAny(row, "stockQty", "stockqty");
+            double v = qty instanceof Number ? ((Number) qty).doubleValue() : 0d;
+            stockQtyMap.put(matnrId, v);
+            String locCodes = getStr(row, "locCodes", "loccodes");
+            if (locCodes != null && !locCodes.isEmpty()) {
+                locCodesMap.put(matnrId, locCodes);
+            }
+            String locStatuses = getStr(row, "locStatuses", "locstatuses");
+            if (locStatuses != null && !locStatuses.isEmpty()) {
+                String desc = Arrays.stream(locStatuses.split(","))
+                        .map(String::trim)
+                        .map(LocStsType::getDescByType)
+                        .collect(Collectors.joining(","));
+                locStatusDescMap.put(matnrId, desc);
+            } else if (locUseStatus != null && !locUseStatus.isEmpty()) {
+                locStatusDescMap.put(matnrId, LocStsType.getDescByType(locUseStatus));
+            }
+        }
+        for (Matnr record : records) {
+            record.setStockQty(stockQtyMap.getOrDefault(record.getId(), 0d));
+            record.setLocUseStatus$(locStatusDescMap.get(record.getId()));
+            record.setLocCodes$(locCodesMap.get(record.getId()));
+        }
+        return page;
+    }
+
+    private static Long getLong(Map<String, Object> map, String... keys) {
+        Object v = getAny(map, keys);
+        if (v == null) return null;
+        if (v instanceof Long) return (Long) v;
+        if (v instanceof Number) return ((Number) v).longValue();
+        try { return Long.parseLong(v.toString()); } catch (NumberFormatException e) { return null; }
+    }
+
+    private static String getStr(Map<String, Object> map, String... keys) {
+        Object v = getAny(map, keys);
+        return v != null ? v.toString() : null;
+    }
+
+    private static Object getAny(Map<String, Object> map, String... keys) {
+        for (String key : keys) {
+            Object v = map.get(key);
+            if (v != null) return v;
+        }
+        return null;
+    }
 
     /**
      * @param
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
index 5e73f65..ac3d112 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
@@ -96,7 +96,10 @@
     public static List<LocItem> getEfficiencyFirstItemList(String matnrCode, String splrBatch, Double anfme) {
         LambdaQueryWrapper<LocItem> locItemQueryWrapper = new LambdaQueryWrapper<>();
         locItemQueryWrapper.eq(LocItem::getMatnrCode, matnrCode);
-        locItemQueryWrapper.eq(StringUtils.isNotBlank(splrBatch), LocItem::getBatch, splrBatch);
+        // 鏈夋壒娆℃椂锛氬尮閰嶅簱浣嶆壒娆�=璁㈠崟鎵规 鎴� 搴撲綅鎵规涓虹┖锛堟棤鎵规搴撳瓨鍙弬涓庡垎閰嶏紝閬垮厤璇垽搴撳瓨涓嶈冻锛�
+        if (StringUtils.isNotBlank(splrBatch)) {
+            locItemQueryWrapper.and(w -> w.eq(LocItem::getBatch, splrBatch).or().isNull(LocItem::getBatch));
+        }
         String applySql = String.format(
                 "EXISTS (SELECT 1 FROM man_loc ml " +
                         "WHERE ml.use_status = '%s'" +
@@ -127,7 +130,10 @@
     public static List<LocItem> getFirstInFirstOutItemList(String matnrCode, String splrBatch, Double anfme) {
         LambdaQueryWrapper<LocItem> locItemQueryWrapper = new LambdaQueryWrapper<>();
         locItemQueryWrapper.eq(LocItem::getMatnrCode, matnrCode);
-        locItemQueryWrapper.eq(StringUtils.isNotEmpty(splrBatch), LocItem::getBatch, splrBatch);
+        // 鏈夋壒娆℃椂锛氬尮閰嶅簱浣嶆壒娆�=璁㈠崟鎵规 鎴� 搴撲綅鎵规涓虹┖锛堟棤鎵规搴撳瓨鍙弬涓庡垎閰嶏紝閬垮厤璇垽搴撳瓨涓嶈冻锛�
+        if (StringUtils.isNotBlank(splrBatch)) {
+            locItemQueryWrapper.and(w -> w.eq(LocItem::getBatch, splrBatch).or().isNull(LocItem::getBatch));
+        }
         //濡傛灉鎵规涓嶄负绌猴紝鎸夋壒娆″厛鍚庡嚭搴�
         if (StringUtils.isNotBlank(splrBatch)) {
             locItemQueryWrapper.orderByAsc(LocItem::getBatch);
diff --git a/rsf-server/src/main/resources/mapper/manager/LocItemMapper.xml b/rsf-server/src/main/resources/mapper/manager/LocItemMapper.xml
index 316b06d..ff677fc 100644
--- a/rsf-server/src/main/resources/mapper/manager/LocItemMapper.xml
+++ b/rsf-server/src/main/resources/mapper/manager/LocItemMapper.xml
@@ -34,4 +34,24 @@
             )t
             ${ew.customSqlSegment}
     </select>
+
+    <select id="listStockByMatnrIds" resultType="java.util.HashMap">
+        SELECT
+            li.matnr_id AS matnrId,
+            COALESCE(SUM(li.anfme), 0) AS stockQty,
+            GROUP_CONCAT(DISTINCT li.loc_code ORDER BY li.loc_code) AS locCodes
+        <if test="locUseStatus == null or locUseStatus == ''">
+            , GROUP_CONCAT(DISTINCT l.use_status ORDER BY l.use_status) AS locStatuses
+        </if>
+        FROM man_loc_item li
+        INNER JOIN man_loc l ON l.id = li.loc_id
+        WHERE li.matnr_id IN
+        <foreach collection="matnrIds" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+        <if test="locUseStatus != null and locUseStatus != ''">
+            AND l.use_status = #{locUseStatus}
+        </if>
+        GROUP BY li.matnr_id
+    </select>
 </mapper>

--
Gitblit v1.9.1