Bläddra i källkod

feat(s7): 生产入库单列表只读查询接口 + 前端接线

S7 生产入库管理/生产入库单列表:新增 ProductionInbound 只读查询,忠实复刻证据 sql.txt(NbrMaster⋈NbrDetail Type='WOI' IsActive=1 join DepartmentMaster/ItemMaster,明细粒度);口径 SQL 内定义(单据号=Nbr,入库日期=Date,状态=Status,入库类型=Remark,入库数量=QtyRec,ERP工单号=Address,相关单号=OrdNbr);前端既有骨架接线(接口/日期区间/分页/日期格式化/空态),17列/查询项已对齐字段配置图未改。数据源 aidopdev 只读,无 DDL/无写路径。

chore: bump version Web 2.4.221 / server 1.0.226
YY968XX 1 månad sedan
förälder
incheckning
38c4ca5b1f

+ 1 - 1
Web/package.json

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

+ 53 - 0
Web/src/views/aidop/s7/api/productionInbound.ts

@@ -0,0 +1,53 @@
+import service from '/@/utils/request';
+import { withAidopTenantParams } from '../../api/aidopTenant';
+
+export interface ProductionInboundPaged<T> {
+	total: number;
+	page: number;
+	pageSize: number;
+	list: T[];
+}
+
+// ── S7 生产入库单列表(只读,数据源 aidopdev.NbrMaster+NbrDetail Type='WOI',sql.txt 口径)──
+export interface ProductionInboundRow {
+	/** 入库单号 Nbr */
+	inboundNo?: string;
+	/** 项次 Line */
+	lineNo?: number;
+	/** 入库日期 Date */
+	inboundDate?: string;
+	/** 状态 Status */
+	status?: string;
+	/** 入库类型 Remark */
+	inboundType?: string;
+	/** 生产线 ProdLine */
+	productionLine?: string;
+	/** 工单号 WorkOrd */
+	workOrderNo?: string;
+	/** ERP工单号 Address */
+	erpWorkOrderNo?: string;
+	/** 物料编码 ItemNum */
+	materialCode?: string;
+	/** 物料名称 ItemMaster.Descr */
+	materialName?: string;
+	/** 规格型号 ItemMaster.Descr1 */
+	spec?: string;
+	/** 入库位 LocationTo */
+	inboundLocation?: string;
+	/** 批次 LotSerial */
+	batch?: string;
+	/** 数量 QtyRec */
+	qty?: number;
+	/** 单位 UM */
+	unit?: string;
+	/** 相关单号 OrdNbr */
+	relatedBillNo?: string;
+	/** 创建时间 CreateTime */
+	createTime?: string;
+}
+
+export function fetchProductionInboundList(params: any) {
+	return service
+		.get<ProductionInboundPaged<ProductionInboundRow>>('/api/S7ProductionInbound/list', { params: withAidopTenantParams(params) })
+		.then((r) => r.data);
+}

+ 54 - 8
Web/src/views/aidop/s7/production-receipt/productionInboundList.vue

@@ -31,10 +31,12 @@
 			</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>
 			<el-table-column prop="inboundNo" label="入库单号" min-width="150" show-overflow-tooltip resizable />
 			<el-table-column prop="lineNo" label="项次" min-width="90" resizable />
-			<el-table-column prop="inboundDate" label="入库日期" min-width="150" resizable />
+			<el-table-column prop="inboundDate" label="入库日期" min-width="150" resizable>
+				<template #default="{ row }">{{ fmtDate(row.inboundDate) }}</template>
+			</el-table-column>
 			<el-table-column prop="status" label="状态" min-width="100" resizable />
 			<el-table-column prop="inboundType" label="入库类型" min-width="120" resizable />
 			<el-table-column prop="productionLine" label="生产线" min-width="120" show-overflow-tooltip resizable />
@@ -48,7 +50,9 @@
 			<el-table-column prop="qty" label="数量" min-width="100" resizable />
 			<el-table-column prop="unit" label="单位" min-width="90" resizable />
 			<el-table-column prop="relatedBillNo" label="相关单号" min-width="150" show-overflow-tooltip resizable />
-			<el-table-column prop="createTime" label="创建时间" min-width="170" resizable />
+			<el-table-column prop="createTime" label="创建时间" min-width="170" resizable>
+				<template #default="{ row }">{{ fmtDateTime(row.createTime) }}</template>
+			</el-table-column>
 			<template #empty>
 				<el-empty description="暂无数据" />
 			</template>
@@ -61,15 +65,19 @@
 				: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="aidopS7ProductionReceiptOrderList">
-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 { fetchProductionInboundList, type ProductionInboundRow } from '../api/productionInbound';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '生产入库单列表');
@@ -87,15 +95,49 @@ const query = reactive({
 
 const inboundDateRange = ref<[string, string] | null>(null);
 
-// 本批次为只读 UI 骨架,尚无真实生产入库单接口:渲染空表,不发请求、不造数据。
 const loading = ref(false);
-const rows = ref<Record<string, unknown>[]>([]);
+const rows = ref<ProductionInboundRow[]>([]);
 const total = ref(0);
 
+function fmtDate(v?: string | null) {
+	if (!v) return '';
+	return String(v).slice(0, 10);
+}
+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 {
+		query.inboundDateStart = inboundDateRange.value?.[0] || '';
+		query.inboundDateEnd = inboundDateRange.value?.[1] || '';
+		const data = await fetchProductionInboundList({
+			inboundNo: query.inboundNo,
+			workOrderNo: query.workOrderNo,
+			erpWorkOrderNo: query.erpWorkOrderNo,
+			batch: query.batch,
+			inboundDateStart: query.inboundDateStart,
+			inboundDateEnd: query.inboundDateEnd,
+			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.inboundDateStart = inboundDateRange.value?.[0] || '';
-	query.inboundDateEnd = inboundDateRange.value?.[1] || '';
 	query.page = 1;
+	loadList();
 }
 
 function resetQuery() {
@@ -107,7 +149,11 @@ function resetQuery() {
 	query.inboundDateStart = '';
 	query.inboundDateEnd = '';
 	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.225</AssemblyVersion>
-    <FileVersion>1.0.225</FileVersion>
-    <Version>1.0.225</Version>
+    <AssemblyVersion>1.0.226</AssemblyVersion>
+    <FileVersion>1.0.226</FileVersion>
+    <Version>1.0.226</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 96 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/FinishedWarehouse/Dto/ProductionInboundDto.cs

@@ -0,0 +1,96 @@
+namespace Admin.NET.Plugin.AiDOP.FinishedWarehouse.Dto;
+
+/// <summary>
+/// S7 生产入库单列表 查询入参(只读)。
+///
+/// 数据源:aidopdev.NbrMaster ⋈ NbrDetail(Type='WOI' 生产入库、IsActive=1),按证据 sql.txt 口径
+/// join DepartmentMaster / ItemMaster;明细粒度(一行一 NbrDetail)。
+/// Domain 由 GeneralizedCodeMaster(SystemConfig/CompanyCode) 解析,默认 8010。
+/// NbrMaster 无 tenant_id;TenantId 仅为前端 withAidopTenantParams 注入的透传参数,本查询不据其过滤。
+/// </summary>
+public class ProductionInboundListInput
+{
+    /// <summary>页码(从 1 开始)</summary>
+    public int Page { get; set; } = 1;
+
+    /// <summary>每页条数</summary>
+    public int PageSize { get; set; } = 10;
+
+    /// <summary>入库单号(NbrMaster.Nbr,模糊匹配)</summary>
+    public string? InboundNo { get; set; }
+
+    /// <summary>工单号(NbrDetail.WorkOrd,模糊匹配)</summary>
+    public string? WorkOrderNo { get; set; }
+
+    /// <summary>ERP工单号(NbrDetail.Address,模糊匹配)</summary>
+    public string? ErpWorkOrderNo { get; set; }
+
+    /// <summary>批次(NbrDetail.LotSerial,模糊匹配)</summary>
+    public string? Batch { get; set; }
+
+    /// <summary>入库日期起(NbrMaster.Date,含当日,yyyy-MM-dd)</summary>
+    public string? InboundDateStart { get; set; }
+
+    /// <summary>入库日期止(NbrMaster.Date,含当日,yyyy-MM-dd)</summary>
+    public string? InboundDateEnd { get; set; }
+
+    /// <summary>租户 ID(前端 withAidopTenantParams 注入;NbrMaster 无租户列,本查询忽略)</summary>
+    public long? TenantId { get; set; }
+}
+
+/// <summary>
+/// S7 生产入库单列表 行(只读)。字段对齐前端列 prop(camelCase),源列见注释(sql.txt / NbrMaster+NbrDetail Type='WOI')。
+/// </summary>
+public class ProductionInboundListRow
+{
+    /// <summary>入库单号(NbrMaster.Nbr)</summary>
+    public string? InboundNo { get; set; }
+
+    /// <summary>项次(NbrDetail.Line)</summary>
+    public int? LineNo { get; set; }
+
+    /// <summary>入库日期(NbrMaster.Date)</summary>
+    public DateTime? InboundDate { get; set; }
+
+    /// <summary>状态(NbrMaster.Status)</summary>
+    public string? Status { get; set; }
+
+    /// <summary>入库类型(NbrMaster.Remark)</summary>
+    public string? InboundType { get; set; }
+
+    /// <summary>生产线(NbrDetail.ProdLine)</summary>
+    public string? ProductionLine { get; set; }
+
+    /// <summary>工单号(NbrDetail.WorkOrd)</summary>
+    public string? WorkOrderNo { get; set; }
+
+    /// <summary>ERP工单号(NbrDetail.Address)</summary>
+    public string? ErpWorkOrderNo { get; set; }
+
+    /// <summary>物料编码(NbrDetail.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>入库位(NbrDetail.LocationTo)</summary>
+    public string? InboundLocation { get; set; }
+
+    /// <summary>批次(NbrDetail.LotSerial)</summary>
+    public string? Batch { get; set; }
+
+    /// <summary>数量(NbrDetail.QtyRec,入库数量)</summary>
+    public decimal? Qty { get; set; }
+
+    /// <summary>单位(ItemMaster.UM)</summary>
+    public string? Unit { get; set; }
+
+    /// <summary>相关单号(NbrDetail.OrdNbr)</summary>
+    public string? RelatedBillNo { get; set; }
+
+    /// <summary>创建时间(NbrDetail.CreateTime)</summary>
+    public DateTime? CreateTime { get; set; }
+}

+ 122 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/FinishedWarehouse/ProductionInboundService.cs

@@ -0,0 +1,122 @@
+using Admin.NET.Plugin.AiDOP.FinishedWarehouse.Dto;
+
+namespace Admin.NET.Plugin.AiDOP.FinishedWarehouse;
+
+/// <summary>
+/// S7 生产入库单列表 只读 list 服务。
+///
+/// 数据源:aidopdev.NbrMaster ⋈ NbrDetail(Type='WOI' 生产入库、IsActive=1),忠实复刻证据 sql.txt:
+/// LEFT JOIN DepartmentMaster(部门)、ItemMaster(名称/规格/单位),明细粒度(一行一 NbrDetail)。
+/// 口径由 SQL 内定义:单据号=Nbr;入库日期=Date;状态=Status;入库类型=Remark;入库数量=QtyRec;
+/// ERP工单号=Address;相关单号=OrdNbr。排序 Nbr DESC, Line。
+/// Domain 由 GeneralizedCodeMaster(SystemConfig/CompanyCode) 解析,默认 8010。
+///
+/// 本服务仅 SELECT:无新增/编辑/删除/入库写入/库存事务/状态流转;NbrMaster 无 tenant_id,不做租户过滤。
+/// </summary>
+[ApiDescriptionSettings(Order = 310, Description = "生产入库单列表")]
+[Route("api/S7ProductionInbound")]
+[AllowAnonymous]
+[NonUnify]
+public class ProductionInboundService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+
+    public ProductionInboundService(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] ProductionInboundListInput 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> { "n.Domain = @Domain", "n.Type = 'WOI'", "n.IsActive = 1" };
+        var pars = new List<SugarParameter> { new("@Domain", domain) };
+
+        if (!string.IsNullOrWhiteSpace(input.InboundNo))
+        {
+            where.Add("n.Nbr LIKE @InboundNo");
+            pars.Add(new SugarParameter("@InboundNo", $"%{input.InboundNo.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.WorkOrderNo))
+        {
+            where.Add("d.WorkOrd LIKE @WorkOrderNo");
+            pars.Add(new SugarParameter("@WorkOrderNo", $"%{input.WorkOrderNo.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.ErpWorkOrderNo))
+        {
+            where.Add("d.Address LIKE @ErpWorkOrderNo");
+            pars.Add(new SugarParameter("@ErpWorkOrderNo", $"%{input.ErpWorkOrderNo.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Batch))
+        {
+            where.Add("d.LotSerial LIKE @Batch");
+            pars.Add(new SugarParameter("@Batch", $"%{input.Batch.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.InboundDateStart))
+        {
+            where.Add("n.Date >= @InboundDateStart");
+            pars.Add(new SugarParameter("@InboundDateStart", $"{input.InboundDateStart.Trim()} 00:00:00"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.InboundDateEnd))
+        {
+            where.Add("n.Date <= @InboundDateEnd");
+            pars.Add(new SugarParameter("@InboundDateEnd", $"{input.InboundDateEnd.Trim()} 23:59:59"));
+        }
+
+        var whereSql = string.Join(" AND ", where);
+
+        var total = await _db.Ado.GetIntAsync(
+            $"SELECT COUNT(1) FROM NbrMaster n LEFT JOIN NbrDetail d ON n.Domain = d.Domain AND n.Nbr = d.Nbr WHERE {whereSql}",
+            pars);
+
+        var list = await _db.Ado.SqlQueryAsync<ProductionInboundListRow>(
+            $"""
+            SELECT
+                n.Nbr          AS InboundNo,
+                d.Line         AS LineNo,
+                n.Date         AS InboundDate,
+                n.Status       AS Status,
+                n.Remark       AS InboundType,
+                d.ProdLine     AS ProductionLine,
+                d.WorkOrd      AS WorkOrderNo,
+                d.Address      AS ErpWorkOrderNo,
+                d.ItemNum      AS MaterialCode,
+                i.Descr        AS MaterialName,
+                i.Descr1       AS Spec,
+                d.LocationTo   AS InboundLocation,
+                d.LotSerial    AS Batch,
+                d.QtyRec       AS Qty,
+                i.UM           AS Unit,
+                d.OrdNbr       AS RelatedBillNo,
+                d.CreateTime   AS CreateTime
+            FROM NbrMaster n
+            LEFT JOIN NbrDetail d ON n.Domain = d.Domain AND n.Nbr = d.Nbr
+            LEFT JOIN ItemMaster i ON d.Domain = i.Domain AND d.ItemNum = i.ItemNum
+            WHERE {whereSql}
+            ORDER BY n.Nbr DESC, d.Line
+            LIMIT {pageSize} OFFSET {offset}
+            """,
+            pars);
+
+        return new { total, page, pageSize, list };
+    }
+}