ソースを参照

feat(s5): 暂收在检列表只读查询接口 + 前端接线

S5z 库存数据/暂收在检:新增 PendingInspection 只读聚合查询(list/status-options/supplier-options),忠实复刻存储过程 pr_WMS_GetMissedTempQty(MissedPrint Status='I' 暂收待检 join qms检验/ItemMaster/SuppMaster,按 Supply/PurOrd/PurLine/ShipperNbr/ItemNum/LotSerial 聚合);前端既有骨架接线(接真实接口/检验状态·供应商下拉/分页/合计/真实空态),未改列结构样式。数据源 aidopdev 只读,无 DDL、无写路径。

chore: bump version Web 2.4.215 / server 1.0.220
YY968XX 1 週間 前
コミット
d8422d5606

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
   "name": "admin.net",
   "type": "module",
-  "version": "2.4.214",
+  "version": "2.4.215",
   "packageManager": "pnpm@10.32.1",
   "lastBuildTime": "2026.03.15",
   "description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 63 - 0
Web/src/views/aidop/s5/api/pendingInspection.ts

@@ -0,0 +1,63 @@
+import service from '/@/utils/request';
+import { withAidopTenantParams } from '../../api/aidopTenant';
+
+export interface PagedWithTotalQty<T> {
+	total: number;
+	page: number;
+	pageSize: number;
+	list: T[];
+	grandTotalQty: number;
+}
+
+// ── S5 暂收在检列表(只读,数据源 aidopdev.MissedPrint Status='I',存储过程 pr_WMS_GetMissedTempQty 口径)──
+export interface PendingInspectionRow {
+	/** 要货令单号 PurOrd */
+	demandNo?: string;
+	/** 要货令行号 PurLine */
+	demandLineNo?: number;
+	/** 物料编码 ItemNum */
+	materialCode?: string;
+	/** 物料名称 Descr */
+	materialName?: string;
+	/** 批次号 LotSerial */
+	batchNo?: string;
+	/** 数量 SUM(Qty) */
+	qty?: number;
+	/** 单位 UM */
+	unit?: string;
+	/** 待检货架 Shelf */
+	pendingShelf?: string;
+	/** 送货单号 ShipperNbr */
+	deliveryNo?: string;
+	/** 检验单号 CheckNo */
+	inspectNo?: string;
+	/** 检验状态 FINSPECTSTATUS */
+	inspectStatus?: string;
+	/** 供应商 Supply */
+	supplier?: string;
+	/** 供应商简称 SortName */
+	supplierShortName?: string;
+}
+
+export interface PendingInspectionStatusOption {
+	val?: string;
+}
+
+export interface PendingInspectionSupplierOption {
+	val?: string;
+	label?: string;
+}
+
+export function fetchPendingInspectionList(params: any) {
+	return service
+		.get<PagedWithTotalQty<PendingInspectionRow>>('/api/S5PendingInspection/list', { params: withAidopTenantParams(params) })
+		.then((r) => r.data);
+}
+
+export function fetchPendingInspectionStatusOptions() {
+	return service.get<PendingInspectionStatusOption[]>('/api/S5PendingInspection/status-options').then((r) => r.data);
+}
+
+export function fetchPendingInspectionSupplierOptions() {
+	return service.get<PendingInspectionSupplierOption[]>('/api/S5PendingInspection/supplier-options').then((r) => r.data);
+}

+ 76 - 6
Web/src/views/aidop/s5/inventory/pendingInspectionList.vue

@@ -14,10 +14,14 @@
 				<el-input v-model="query.materialCode" clearable style="width: 200px" />
 			</el-form-item>
 			<el-form-item label="检验状态">
-				<el-select v-model="query.inspectStatus" placeholder="请选择" clearable style="width: 200px" />
+				<el-select v-model="query.inspectStatus" placeholder="请选择" clearable style="width: 200px">
+					<el-option v-for="o in statusOptions" :key="o.val" :label="o.val" :value="o.val!" />
+				</el-select>
 			</el-form-item>
 			<el-form-item label="供应商">
-				<el-select v-model="query.supplier" placeholder="请选择" clearable style="width: 200px" />
+				<el-select v-model="query.supplier" placeholder="请选择" clearable filterable style="width: 200px">
+					<el-option v-for="o in supplierOptions" :key="o.val" :label="o.label ? `${o.val} ${o.label}` : o.val" :value="o.val!" />
+				</el-select>
 			</el-form-item>
 			<el-form-item>
 				<el-button type="primary" @click="doSearch">查询</el-button>
@@ -25,7 +29,7 @@
 			</el-form-item>
 		</el-form>
 
-		<el-table :data="rows" row-key="id" v-loading="loading" border stripe>
+		<el-table :data="rows" v-loading="loading" border stripe show-summary :summary-method="summaryMethod">
 			<el-table-column prop="demandNo" label="要货令单号" min-width="150" show-overflow-tooltip resizable />
 			<el-table-column prop="demandLineNo" label="要货令行号" min-width="120" resizable />
 			<el-table-column prop="materialCode" label="物料编码" min-width="140" show-overflow-tooltip resizable />
@@ -51,15 +55,26 @@
 				:total="total"
 				:page-sizes="[10, 20, 50]"
 				layout="total, sizes, prev, pager, next"
+				@current-change="loadList"
+				@size-change="loadList"
 			/>
 		</div>
 	</AidopDemoShell>
 </template>
 
 <script setup lang="ts" name="aidopS5InventoryPendingInspection">
-import { computed, reactive, ref } from 'vue';
+import { computed, onActivated, onMounted, reactive, ref } from 'vue';
 import { useRoute } from 'vue-router';
+import { ElMessage } from 'element-plus';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
+import {
+	fetchPendingInspectionList,
+	fetchPendingInspectionStatusOptions,
+	fetchPendingInspectionSupplierOptions,
+	type PendingInspectionRow,
+	type PendingInspectionStatusOption,
+	type PendingInspectionSupplierOption,
+} from '../api/pendingInspection';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '暂收在检列表');
@@ -75,13 +90,61 @@ const query = reactive({
 	pageSize: 10,
 });
 
-// 本批次为只读 UI 骨架,尚无真实暂收在检接口:渲染空表,不发请求、不造数据。
 const loading = ref(false);
-const rows = ref<Record<string, unknown>[]>([]);
+const rows = ref<PendingInspectionRow[]>([]);
 const total = ref(0);
+const grandTotalQty = ref(0);
+const statusOptions = ref<PendingInspectionStatusOption[]>([]);
+const supplierOptions = ref<PendingInspectionSupplierOption[]>([]);
+
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchPendingInspectionList({
+			demandNo: query.demandNo,
+			shelf: query.shelf,
+			batchNo: query.batchNo,
+			materialCode: query.materialCode,
+			inspectStatus: query.inspectStatus,
+			supplier: query.supplier,
+			page: query.page,
+			pageSize: query.pageSize,
+		});
+		rows.value = data.list || [];
+		total.value = data.total || 0;
+		grandTotalQty.value = data.grandTotalQty || 0;
+	} catch (e: any) {
+		rows.value = [];
+		total.value = 0;
+		grandTotalQty.value = 0;
+		ElMessage.error(e?.message || '加载暂收在检列表失败');
+	} finally {
+		loading.value = false;
+	}
+}
+
+async function loadOptions() {
+	try {
+		const [st, sup] = await Promise.all([fetchPendingInspectionStatusOptions(), fetchPendingInspectionSupplierOptions()]);
+		statusOptions.value = st || [];
+		supplierOptions.value = sup || [];
+	} catch {
+		statusOptions.value = [];
+		supplierOptions.value = [];
+	}
+}
+
+function summaryMethod({ columns }: { columns: { property?: string }[] }) {
+	return columns.map((col, idx) => {
+		if (idx === 0) return '合计';
+		if (col.property === 'qty') return String(grandTotalQty.value ?? 0);
+		return '';
+	});
+}
 
 function doSearch() {
 	query.page = 1;
+	loadList();
 }
 
 function resetQuery() {
@@ -92,7 +155,14 @@ function resetQuery() {
 	query.inspectStatus = '';
 	query.supplier = '';
 	query.page = 1;
+	loadList();
 }
+
+onMounted(() => {
+	loadOptions();
+	loadList();
+});
+onActivated(() => loadList());
 </script>
 
 <style scoped lang="scss">

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET ͨ��Ȩ�޿���ƽ̨</Description>
-    <AssemblyVersion>1.0.219</AssemblyVersion>
-    <FileVersion>1.0.219</FileVersion>
-    <Version>1.0.219</Version>
+    <AssemblyVersion>1.0.220</AssemblyVersion>
+    <FileVersion>1.0.220</FileVersion>
+    <Version>1.0.220</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 101 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/Dto/PendingInspectionDto.cs

@@ -0,0 +1,101 @@
+namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
+
+/// <summary>
+/// S5 暂收在检列表 查询入参(只读)。
+///
+/// 数据源:aidopdev.MissedPrint(Status='I' 暂收待检、Qty>0、Location 空),按存储过程
+/// pr_WMS_GetMissedTempQty 口径 join qms 检验单 / ItemMaster / SuppMaster 聚合。
+/// MissedPrint 无 tenant_id 列;Domain 由 GeneralizedCodeMaster(SystemConfig/CompanyCode) 解析,
+/// TenantId 仅为前端 withAidopTenantParams 注入的透传参数,本查询不据其过滤。
+/// </summary>
+public class PendingInspectionListInput
+{
+    /// <summary>页码(从 1 开始)</summary>
+    public int Page { get; set; } = 1;
+
+    /// <summary>每页条数</summary>
+    public int PageSize { get; set; } = 10;
+
+    /// <summary>要货令单号(MissedPrint.PurOrd,模糊匹配)</summary>
+    public string? DemandNo { get; set; }
+
+    /// <summary>货架(MissedPrint.Shelf,模糊匹配)</summary>
+    public string? Shelf { get; set; }
+
+    /// <summary>批次号(MissedPrint.LotSerial,模糊匹配)</summary>
+    public string? BatchNo { get; set; }
+
+    /// <summary>物料编码(MissedPrint.ItemNum,模糊匹配)</summary>
+    public string? MaterialCode { get; set; }
+
+    /// <summary>检验状态(qms_qcp_insappnentry.FINSPECTSTATUS,精确匹配,下拉选择)</summary>
+    public string? InspectStatus { get; set; }
+
+    /// <summary>供应商(MissedPrint.Supply,精确匹配,下拉选择)</summary>
+    public string? Supplier { get; set; }
+
+    /// <summary>租户 ID(前端 withAidopTenantParams 注入;MissedPrint 无租户列,本查询忽略)</summary>
+    public long? TenantId { get; set; }
+}
+
+/// <summary>
+/// S5 暂收在检列表 行(只读)。字段对齐前端列 prop(camelCase),源列见注释(存储过程 pr_WMS_GetMissedTempQty)。
+/// </summary>
+public class PendingInspectionListRow
+{
+    /// <summary>要货令单号(MissedPrint.PurOrd)</summary>
+    public string? DemandNo { get; set; }
+
+    /// <summary>要货令行号(MissedPrint.PurLine)</summary>
+    public int? DemandLineNo { get; set; }
+
+    /// <summary>物料编码(MissedPrint.ItemNum)</summary>
+    public string? MaterialCode { get; set; }
+
+    /// <summary>物料名称(ItemMaster.Descr)</summary>
+    public string? MaterialName { get; set; }
+
+    /// <summary>批次号(MissedPrint.LotSerial)</summary>
+    public string? BatchNo { get; set; }
+
+    /// <summary>数量(SUM(MissedPrint.Qty))</summary>
+    public decimal? Qty { get; set; }
+
+    /// <summary>单位(ItemMaster.UM)</summary>
+    public string? Unit { get; set; }
+
+    /// <summary>待检货架(MAX(MissedPrint.Shelf))</summary>
+    public string? PendingShelf { get; set; }
+
+    /// <summary>送货单号(MissedPrint.ShipperNbr)</summary>
+    public string? DeliveryNo { get; set; }
+
+    /// <summary>检验单号(MAX(MissedPrint.CheckNo))</summary>
+    public string? InspectNo { get; set; }
+
+    /// <summary>检验状态(MAX(qms_qcp_insappnentry.FINSPECTSTATUS),clfs='3' 时追加 -挑选使用)</summary>
+    public string? InspectStatus { get; set; }
+
+    /// <summary>供应商(MissedPrint.Supply)</summary>
+    public string? Supplier { get; set; }
+
+    /// <summary>供应商简称(SuppMaster.SortName)</summary>
+    public string? SupplierShortName { get; set; }
+}
+
+/// <summary>检验状态下拉选项(qms_qcp_insappnentry.FINSPECTSTATUS distinct)。</summary>
+public class PendingInspectionStatusOption
+{
+    /// <summary>检验状态值</summary>
+    public string? Val { get; set; }
+}
+
+/// <summary>供应商下拉选项(暂收在检集合内 distinct Supply + SuppMaster.SortName)。</summary>
+public class PendingInspectionSupplierOption
+{
+    /// <summary>供应商编码(MissedPrint.Supply)</summary>
+    public string? Val { get; set; }
+
+    /// <summary>供应商简称(SuppMaster.SortName)</summary>
+    public string? Label { get; set; }
+}

+ 177 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/PendingInspectionService.cs

@@ -0,0 +1,177 @@
+using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
+
+namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
+
+/// <summary>
+/// S5 暂收在检列表 只读 list 服务。
+///
+/// 数据源:aidopdev.MissedPrint(Status='I' 暂收待检、Qty>0、Location 空),忠实复刻旧系统存储过程
+/// pr_WMS_GetMissedTempQty:LEFT JOIN qms_qcp_inspecapplyn/qms_qcp_insappnentry 取检验状态,按
+/// Supply/PurOrd/PurLine/ShipperNbr/ItemNum/LotSerial 聚合(MAX Shelf/SUM Qty/MAX CheckNo/MAX FINSPECTSTATUS),
+/// 再 LEFT JOIN ItemMaster(描述/单位)、SuppMaster(简称),clfs='3' 追加“-挑选使用”。
+/// Domain 由 GeneralizedCodeMaster(SystemConfig/CompanyCode) 解析,默认 8010。
+///
+/// 本服务仅 SELECT:无新增/编辑/删除/库存事务/状态流转;MissedPrint 无 tenant_id,不做租户过滤。
+/// </summary>
+[ApiDescriptionSettings(Order = 301, Description = "暂收在检列表")]
+[Route("api/S5PendingInspection")]
+[AllowAnonymous]
+[NonUnify]
+public class PendingInspectionService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+
+    public PendingInspectionService(ISqlSugarClient db)
+    {
+        _db = db;
+    }
+
+    /// <summary>解析公司域(GeneralizedCodeMaster SystemConfig/CompanyCode,默认 8010)。</summary>
+    private async Task<string> ResolveDomainAsync()
+    {
+        var list = await _db.Ado.SqlQueryAsync<string>(
+            "SELECT Domain FROM GeneralizedCodeMaster WHERE FldName='SystemConfig' AND Val='CompanyCode' ORDER BY RecID LIMIT 1");
+        var domain = list.FirstOrDefault();
+        return string.IsNullOrWhiteSpace(domain) ? "8010" : domain;
+    }
+
+    /// <summary>
+    /// 暂收在检列表(只读分页查询)。
+    /// </summary>
+    [DisplayName("暂收在检列表")]
+    [HttpGet("list")]
+    public async Task<object> GetList([FromQuery] PendingInspectionListInput input)
+    {
+        var page = input.Page <= 0 ? 1 : input.Page;
+        var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
+        var offset = (page - 1) * pageSize;
+
+        var domain = await ResolveDomainAsync();
+
+        var where = new List<string>
+        {
+            "m.Domain = @Domain",
+            "m.Status = 'I'",
+            "m.Qty > 0",
+            "IFNULL(m.Location,'') = ''",
+        };
+        var pars = new List<SugarParameter> { new("@Domain", domain) };
+
+        if (!string.IsNullOrWhiteSpace(input.DemandNo))
+        {
+            where.Add("m.PurOrd LIKE @DemandNo");
+            pars.Add(new SugarParameter("@DemandNo", $"%{input.DemandNo.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Shelf))
+        {
+            where.Add("m.Shelf LIKE @Shelf");
+            pars.Add(new SugarParameter("@Shelf", $"%{input.Shelf.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.BatchNo))
+        {
+            where.Add("m.LotSerial LIKE @BatchNo");
+            pars.Add(new SugarParameter("@BatchNo", $"%{input.BatchNo.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.MaterialCode))
+        {
+            where.Add("m.ItemNum LIKE @MaterialCode");
+            pars.Add(new SugarParameter("@MaterialCode", $"%{input.MaterialCode.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.InspectStatus))
+        {
+            where.Add("d.FINSPECTSTATUS = @InspectStatus");
+            pars.Add(new SugarParameter("@InspectStatus", input.InspectStatus.Trim()));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Supplier))
+        {
+            where.Add("m.Supply = @Supplier");
+            pars.Add(new SugarParameter("@Supplier", input.Supplier.Trim()));
+        }
+
+        var whereSql = string.Join(" AND ", where);
+
+        // 复刻存储过程 @tbLocDet 聚合层
+        var groupedInner =
+            $"""
+            SELECT m.Supply, m.PurOrd, m.PurLine, m.ItemNum, m.ShipperNbr, m.LotSerial,
+                   MAX(m.Shelf) AS Shelf, SUM(m.Qty) AS Qty, MAX(m.CheckNo) AS CheckNo,
+                   MAX(IFNULL(d.FINSPECTSTATUS,'')) AS FINSPECTSTATUS
+            FROM MissedPrint m
+            LEFT JOIN qms_qcp_inspecapplyn q ON m.CheckNo = q.FBILLNO
+            LEFT JOIN qms_qcp_insappnentry d ON q.id = d.glid
+            WHERE {whereSql}
+            GROUP BY m.Supply, m.PurOrd, m.PurLine, m.ShipperNbr, m.ItemNum, m.LotSerial
+            """;
+
+        var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM ({groupedInner}) t", pars);
+
+        var grandList = await _db.Ado.SqlQueryAsync<decimal>($"SELECT IFNULL(SUM(t.Qty),0) FROM ({groupedInner}) t", pars);
+        var grandTotalQty = grandList.FirstOrDefault();
+
+        var list = await _db.Ado.SqlQueryAsync<PendingInspectionListRow>(
+            $"""
+            SELECT
+                t.PurOrd     AS DemandNo,
+                t.PurLine    AS DemandLineNo,
+                t.ItemNum    AS MaterialCode,
+                i.Descr      AS MaterialName,
+                t.LotSerial  AS BatchNo,
+                t.Qty        AS Qty,
+                i.UM         AS Unit,
+                t.Shelf      AS PendingShelf,
+                t.ShipperNbr AS DeliveryNo,
+                t.CheckNo    AS InspectNo,
+                CONCAT(t.FINSPECTSTATUS,
+                    CASE WHEN (SELECT MAX(ib.clfs) FROM qms_qcp_inspbill ib WHERE ib.lydjbh = t.CheckNo) = '3'
+                         THEN '-挑选使用' ELSE '' END) AS InspectStatus,
+                t.Supply     AS Supplier,
+                s.SortName   AS SupplierShortName
+            FROM ({groupedInner}) t
+            LEFT JOIN ItemMaster i ON i.Domain = @Domain AND t.ItemNum = i.ItemNum
+            LEFT JOIN SuppMaster s ON s.Domain = @Domain AND t.Supply = s.Supp
+            ORDER BY t.Supply, t.PurOrd, t.PurLine
+            LIMIT {pageSize} OFFSET {offset}
+            """,
+            pars);
+
+        return new { total, page, pageSize, list, grandTotalQty };
+    }
+
+    /// <summary>
+    /// 检验状态下拉选项(qms_qcp_insappnentry.FINSPECTSTATUS distinct,只读)。
+    /// </summary>
+    [DisplayName("暂收在检检验状态选项")]
+    [HttpGet("status-options")]
+    public async Task<object> GetStatusOptions()
+    {
+        var list = await _db.Ado.SqlQueryAsync<PendingInspectionStatusOption>(
+            """
+            SELECT DISTINCT FINSPECTSTATUS AS Val
+            FROM qms_qcp_insappnentry
+            WHERE IFNULL(FINSPECTSTATUS,'') <> ''
+            ORDER BY FINSPECTSTATUS
+            """);
+        return list;
+    }
+
+    /// <summary>
+    /// 供应商下拉选项(暂收在检集合内 distinct Supply + SuppMaster.SortName,只读)。
+    /// </summary>
+    [DisplayName("暂收在检供应商选项")]
+    [HttpGet("supplier-options")]
+    public async Task<object> GetSupplierOptions()
+    {
+        var domain = await ResolveDomainAsync();
+        var list = await _db.Ado.SqlQueryAsync<PendingInspectionSupplierOption>(
+            """
+            SELECT DISTINCT m.Supply AS Val, s.SortName AS Label
+            FROM MissedPrint m
+            LEFT JOIN SuppMaster s ON s.Domain = @Domain AND m.Supply = s.Supp
+            WHERE m.Domain = @Domain AND m.Status = 'I' AND m.Qty > 0 AND IFNULL(m.Location,'') = ''
+              AND IFNULL(m.Supply,'') <> ''
+            ORDER BY m.Supply
+            """,
+            new List<SugarParameter> { new("@Domain", domain) });
+        return list;
+    }
+}