Browse Source

feat(s5): 盘点结果查询只读查询接口 + 前端对齐证据接线

S5z 库存数据/盘点结果查询:新增 StocktakeResult 只读查询,忠实复刻证据 sql.txt(MissedPrint IsTag=1 盘点标签 join ItemMaster);盘点口径 SQL 内定义(账面=Qty,实盘=CompQty,差异=CompQty-Qty,盘点状态=CompQty≠Qty?有差异:正常,状态=Status=Q?冻结:'',实际库位=FirmString2,实际货架=FirmString4);前端骨架列(初盘/重盘/冻结)与证据明显不一致,按 sql.txt+字段配置图重排 16 列 + 物料编码/盘点状态查询项 + 接线。数据源 aidopdev 只读,无 DDL/无写路径。

chore: bump version Web 2.4.219 / server 1.0.224
YY968XX 5 days ago
parent
commit
6d2eb33797

+ 1 - 1
Web/package.json

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

+ 51 - 0
Web/src/views/aidop/s5/api/stocktakeResult.ts

@@ -0,0 +1,51 @@
+import service from '/@/utils/request';
+import { withAidopTenantParams } from '../../api/aidopTenant';
+
+export interface StocktakeResultPaged<T> {
+	total: number;
+	page: number;
+	pageSize: number;
+	list: T[];
+}
+
+// ── S5 盘点结果查询(只读,数据源 aidopdev.MissedPrint IsTag=1,sql.txt 口径)──
+export interface StocktakeResultRow {
+	/** 物料编码 ItemNum */
+	materialCode?: string;
+	/** 物料名称 ItemMaster.Descr */
+	materialName?: string;
+	/** 规格型号 ItemMaster.Descr1 */
+	spec?: string;
+	/** 库位 Location */
+	location?: string;
+	/** 货架 Shelf */
+	shelf?: string;
+	/** 实际库位 FirmString2 */
+	actLocation?: string;
+	/** 实际货架 FirmString4 */
+	actShelf?: string;
+	/** 批次号 LotSerial */
+	batchNo?: string;
+	/** 单位 UM */
+	unit?: string;
+	/** 标签数量(账面)Qty */
+	tagQty?: number;
+	/** 实盘数量 CompQty */
+	countQty?: number;
+	/** 差异数量 CompQty-Qty */
+	qtyDiff?: number;
+	/** 状态 Status='Q'?'冻结':'' */
+	status?: string;
+	/** 盘点状态 CompQty≠Qty?'有差异':'正常' */
+	invStatus?: string;
+	/** 盘点人 UpdateUser */
+	countUser?: string;
+	/** 盘点时间 UpdateTime */
+	countTime?: string;
+}
+
+export function fetchStocktakeResultList(params: any) {
+	return service
+		.get<StocktakeResultPaged<StocktakeResultRow>>('/api/S5StocktakeResult/list', { params: withAidopTenantParams(params) })
+		.then((r) => r.data);
+}

+ 70 - 17
Web/src/views/aidop/s5/inventory/stocktakeResultList.vue

@@ -1,8 +1,19 @@
 <template>
 	<AidopDemoShell :title="pageTitle" subtitle="只读列表">
+		<!--
+			S5 盘点结果查询 · 只读页(S5z-STOCKTAKE-RESULT-1)。
+			数据源:aidopdev.MissedPrint(盘点标签 IsTag=1),sql.txt 口径 join ItemMaster;只读,无 mock。
+			列/口径来自证据 sql.txt + 字段配置图(账面=Qty/实盘=CompQty/差异=CompQty-Qty/盘点状态=CompQty≠Qty?有差异:正常)。
+		-->
 		<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
-			<el-form-item label="盘点名称">
-				<el-select v-model="query.stocktakeName" placeholder="请选择" clearable style="width: 220px" />
+			<el-form-item label="物料编码">
+				<el-input v-model="query.materialCode" clearable style="width: 200px" />
+			</el-form-item>
+			<el-form-item label="盘点状态">
+				<el-select v-model="query.invStatus" placeholder="请选择" clearable style="width: 160px">
+					<el-option label="有差异" value="有差异" />
+					<el-option label="正常" value="正常" />
+				</el-select>
 			</el-form-item>
 			<el-form-item>
 				<el-button type="primary" @click="doSearch">查询</el-button>
@@ -10,20 +21,26 @@
 			</el-form-item>
 		</el-form>
 
-		<el-table :data="rows" row-key="id" v-loading="loading" border stripe>
-			<el-table-column prop="billNo" label="单号" min-width="150" show-overflow-tooltip resizable />
+		<el-table :data="rows" v-loading="loading" border stripe>
+			<el-table-column type="index" label="#" width="55" align="center" />
 			<el-table-column prop="materialCode" label="物料编码" min-width="140" show-overflow-tooltip resizable />
 			<el-table-column prop="materialName" label="物料名称" min-width="160" show-overflow-tooltip resizable />
 			<el-table-column prop="spec" label="规格型号" min-width="140" show-overflow-tooltip resizable />
-			<el-table-column prop="location" label="库位" min-width="110" show-overflow-tooltip resizable />
+			<el-table-column prop="location" label="库位" min-width="100" show-overflow-tooltip resizable />
 			<el-table-column prop="shelf" label="货架" min-width="110" show-overflow-tooltip resizable />
-			<el-table-column prop="unit" label="单位" min-width="90" resizable />
-			<el-table-column prop="firstCountQty" label="初盘量" min-width="110" resizable />
-			<el-table-column prop="recountQty" label="重盘量" min-width="110" resizable />
-			<el-table-column prop="frozenQty" label="冻结数量" min-width="110" resizable />
-			<el-table-column prop="firstRecountDiff" label="初盘与重盘差异" min-width="140" resizable />
-			<el-table-column prop="recountFrozenDiff" label="重盘与冻结差异" min-width="140" resizable />
-			<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip resizable />
+			<el-table-column prop="actLocation" label="实际库位" min-width="110" show-overflow-tooltip resizable />
+			<el-table-column prop="actShelf" label="实际货架" min-width="110" show-overflow-tooltip resizable />
+			<el-table-column prop="batchNo" label="批次号" min-width="140" show-overflow-tooltip resizable />
+			<el-table-column prop="unit" label="单位" width="80" resizable />
+			<el-table-column prop="tagQty" label="标签数量" width="100" align="right" resizable />
+			<el-table-column prop="countQty" label="实盘数量" width="100" align="right" resizable />
+			<el-table-column prop="qtyDiff" label="差异数量" width="100" align="right" resizable />
+			<el-table-column prop="status" label="状态" width="80" resizable />
+			<el-table-column prop="invStatus" label="盘点状态" width="100" resizable />
+			<el-table-column prop="countUser" label="盘点人" width="110" show-overflow-tooltip resizable />
+			<el-table-column prop="countTime" label="盘点时间" width="170" resizable>
+				<template #default="{ row }">{{ fmtDateTime(row.countTime) }}</template>
+			</el-table-column>
 			<template #empty>
 				<el-empty description="暂无数据" />
 			</template>
@@ -36,38 +53,74 @@
 				: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="aidopS5InventoryStocktakeResult">
-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 { fetchStocktakeResultList, type StocktakeResultRow } from '../api/stocktakeResult';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '盘点结果查询');
 
 const query = reactive({
-	stocktakeName: '',
+	materialCode: '',
+	invStatus: '',
 	page: 1,
 	pageSize: 10,
 });
 
-// 本批次为只读 UI 骨架,尚无真实盘点结果接口:渲染空表,不发请求、不造数据。
 const loading = ref(false);
-const rows = ref<Record<string, unknown>[]>([]);
+const rows = ref<StocktakeResultRow[]>([]);
 const total = ref(0);
 
+function fmtDateTime(v?: string | null) {
+	if (!v) return '';
+	const s = String(v);
+	return s.length > 19 ? s.slice(0, 19).replace('T', ' ') : s.replace('T', ' ');
+}
+
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchStocktakeResultList({
+			materialCode: query.materialCode,
+			invStatus: query.invStatus,
+			page: query.page,
+			pageSize: query.pageSize,
+		});
+		rows.value = data.list || [];
+		total.value = data.total || 0;
+	} catch (e: any) {
+		rows.value = [];
+		total.value = 0;
+		ElMessage.error(e?.message || '加载盘点结果查询列表失败');
+	} finally {
+		loading.value = false;
+	}
+}
+
 function doSearch() {
 	query.page = 1;
+	loadList();
 }
 
 function resetQuery() {
-	query.stocktakeName = '';
+	query.materialCode = '';
+	query.invStatus = '';
 	query.page = 1;
+	loadList();
 }
+
+onMounted(() => 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.222</AssemblyVersion>
-    <FileVersion>1.0.222</FileVersion>
-    <Version>1.0.222</Version>
+    <AssemblyVersion>1.0.224</AssemblyVersion>
+    <FileVersion>1.0.224</FileVersion>
+    <Version>1.0.224</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 82 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/Dto/StocktakeResultDto.cs

@@ -0,0 +1,82 @@
+namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
+
+/// <summary>
+/// S5 盘点结果查询 列表查询入参(只读)。
+///
+/// 数据源:aidopdev.MissedPrint(盘点标签 IsTag=1),按证据 sql.txt 口径 join ItemMaster(名称/规格/单位)。
+/// 盘点口径由 SQL 内定义:账面(标签数量)=Qty;盘点(实盘数量)=CompQty;差异数量=CompQty-Qty;
+/// 盘点状态=CompQty≠Qty?'有差异':'正常';状态=Status='Q'?'冻结':'';实际库位=FirmString2;实际货架=FirmString4。
+/// Domain 由 GeneralizedCodeMaster(SystemConfig/CompanyCode) 解析,默认 8010。
+/// MissedPrint 无 tenant_id;TenantId 仅为前端 withAidopTenantParams 注入的透传参数,本查询不据其过滤。
+/// </summary>
+public class StocktakeResultListInput
+{
+    /// <summary>页码(从 1 开始)</summary>
+    public int Page { get; set; } = 1;
+
+    /// <summary>每页条数</summary>
+    public int PageSize { get; set; } = 10;
+
+    /// <summary>物料编码(MissedPrint.ItemNum,模糊匹配)</summary>
+    public string? MaterialCode { get; set; }
+
+    /// <summary>盘点状态(有差异 / 正常,按 CompQty 与 Qty 是否相等过滤)</summary>
+    public string? InvStatus { get; set; }
+
+    /// <summary>租户 ID(前端 withAidopTenantParams 注入;MissedPrint 无租户列,本查询忽略)</summary>
+    public long? TenantId { get; set; }
+}
+
+/// <summary>
+/// S5 盘点结果查询 列表行(只读)。字段对齐前端列 prop(camelCase),源列见注释(sql.txt / MissedPrint IsTag=1)。
+/// </summary>
+public class StocktakeResultListRow
+{
+    /// <summary>物料编码(MissedPrint.ItemNum)</summary>
+    public string? MaterialCode { get; set; }
+
+    /// <summary>物料名称(ItemMaster.Descr)</summary>
+    public string? MaterialName { get; set; }
+
+    /// <summary>规格型号(ItemMaster.Descr1)</summary>
+    public string? Spec { get; set; }
+
+    /// <summary>库位(MissedPrint.Location)</summary>
+    public string? Location { get; set; }
+
+    /// <summary>货架(MissedPrint.Shelf)</summary>
+    public string? Shelf { get; set; }
+
+    /// <summary>实际库位(MissedPrint.FirmString2)</summary>
+    public string? ActLocation { get; set; }
+
+    /// <summary>实际货架(MissedPrint.FirmString4)</summary>
+    public string? ActShelf { get; set; }
+
+    /// <summary>批次号(MissedPrint.LotSerial)</summary>
+    public string? BatchNo { get; set; }
+
+    /// <summary>单位(ItemMaster.UM)</summary>
+    public string? Unit { get; set; }
+
+    /// <summary>标签数量(账面,MissedPrint.Qty)</summary>
+    public decimal? TagQty { get; set; }
+
+    /// <summary>实盘数量(MissedPrint.CompQty)</summary>
+    public decimal? CountQty { get; set; }
+
+    /// <summary>差异数量(CompQty-Qty)</summary>
+    public decimal? QtyDiff { get; set; }
+
+    /// <summary>状态(Status='Q'?'冻结':'')</summary>
+    public string? Status { get; set; }
+
+    /// <summary>盘点状态(CompQty≠Qty?'有差异':'正常')</summary>
+    public string? InvStatus { get; set; }
+
+    /// <summary>盘点人(MissedPrint.UpdateUser)</summary>
+    public string? CountUser { get; set; }
+
+    /// <summary>盘点时间(MissedPrint.UpdateTime)</summary>
+    public DateTime? CountTime { get; set; }
+}

+ 102 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/StocktakeResultService.cs

@@ -0,0 +1,102 @@
+using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
+
+namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
+
+/// <summary>
+/// S5 盘点结果查询 只读 list 服务。
+///
+/// 数据源:aidopdev.MissedPrint(盘点标签 IsTag=1),忠实复刻证据 sql.txt:
+/// LEFT JOIN ItemMaster(名称/规格/单位),过滤 m.Domain=@Domain AND m.IsTag=1。
+/// 盘点口径由 SQL 内定义(非脑补):
+///   账面(标签数量)=Qty;实盘数量=CompQty;差异数量=CompQty-Qty;
+///   盘点状态=CompQty≠Qty?'有差异':'正常';状态=Status='Q'?'冻结':'';实际库位=FirmString2;实际货架=FirmString4。
+/// Domain 由 GeneralizedCodeMaster(SystemConfig/CompanyCode) 解析,默认 8010。排序 UpdateTime DESC, RecID DESC。
+///
+/// 本服务仅 SELECT:无新增/编辑/删除/库存事务/状态流转;MissedPrint 无 tenant_id,不做租户过滤。
+/// </summary>
+[ApiDescriptionSettings(Order = 304, Description = "盘点结果查询")]
+[Route("api/S5StocktakeResult")]
+[AllowAnonymous]
+[NonUnify]
+public class StocktakeResultService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+
+    public StocktakeResultService(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] StocktakeResultListInput 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.IsTag = 1" };
+        var pars = new List<SugarParameter> { new("@Domain", domain) };
+
+        if (!string.IsNullOrWhiteSpace(input.MaterialCode))
+        {
+            where.Add("m.ItemNum LIKE @MaterialCode");
+            pars.Add(new SugarParameter("@MaterialCode", $"%{input.MaterialCode.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.InvStatus))
+        {
+            var s = input.InvStatus.Trim();
+            if (s == "有差异")
+                where.Add("m.CompQty <> m.Qty");
+            else if (s == "正常")
+                where.Add("m.CompQty = m.Qty");
+        }
+
+        var whereSql = string.Join(" AND ", where);
+
+        var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM MissedPrint m WHERE {whereSql}", pars);
+
+        var list = await _db.Ado.SqlQueryAsync<StocktakeResultListRow>(
+            $"""
+            SELECT
+                m.ItemNum      AS MaterialCode,
+                i.Descr        AS MaterialName,
+                i.Descr1       AS Spec,
+                m.Location     AS Location,
+                m.Shelf        AS Shelf,
+                m.FirmString2  AS ActLocation,
+                m.FirmString4  AS ActShelf,
+                m.LotSerial    AS BatchNo,
+                i.UM           AS Unit,
+                m.Qty          AS TagQty,
+                m.CompQty      AS CountQty,
+                (m.CompQty - m.Qty) AS QtyDiff,
+                (CASE WHEN m.Status = 'Q' THEN '冻结' ELSE '' END) AS Status,
+                (CASE WHEN m.CompQty <> m.Qty THEN '有差异' ELSE '正常' END) AS InvStatus,
+                m.UpdateUser   AS CountUser,
+                m.UpdateTime   AS CountTime
+            FROM MissedPrint m
+            LEFT JOIN ItemMaster i ON m.Domain = i.Domain AND m.ItemNum = i.ItemNum
+            WHERE {whereSql}
+            ORDER BY m.UpdateTime DESC, m.RecID DESC
+            LIMIT {pageSize} OFFSET {offset}
+            """,
+            pars);
+
+        return new { total, page, pageSize, list };
+    }
+}