Selaa lähdekoodia

feat(s7): add sales delivery notice readonly list

- 新增 api/S7SalesDeliveryNotice/list:ASNBOLShipperMaster + DepartmentMaster 直读
- 业务过滤 ShType='SH' AND Typed<>'S' AND IsActive=1
- tenant_id 过滤(TenantId>0 追加,<=0 不过滤);Domain 仅用于 DepartmentMaster join
- salesDeliveryNoticeList.vue 接真实接口(查询/重置/分页/空态/日期格式化)
- 空态先行,ASNBOLShipperMaster/Detail 均 0 行
- 无 MDP/stg/dwd、无 SQL、无菜单/功能码变更
- 无明细、无新增/编辑/删除、无发货确认/出库/库存扣减、无打印/标签/导出
- 未改 S1 AsnShipperService / S1 ship_trans
- 版本 server 1.0.230 / Web 2.4.225
YY968XX 1 viikko sitten
vanhempi
commit
a9a73eb6b3

+ 1 - 1
Web/package.json

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

+ 42 - 0
Web/src/views/aidop/s7/api/salesDeliveryNotice.ts

@@ -0,0 +1,42 @@
+import service from '/@/utils/request';
+import { withAidopTenantParams } from '../../api/aidopTenant';
+
+export interface SalesDeliveryNoticePaged<T> {
+	total: number;
+	page: number;
+	pageSize: number;
+	list: T[];
+}
+
+// ── S7 销售发货通知(头列表,只读)──
+// 数据源 aidopdev.ASNBOLShipperMaster ⋈ DepartmentMaster(ShType='SH' AND Typed<>'S' AND IsActive=1),sql.txt 口径。
+export interface SalesDeliveryNoticeRow {
+	/** 主键 RecID */
+	recId?: number;
+	/** 发货单号 Id */
+	deliveryNo?: string;
+	/** 销售单号 OrdNbr */
+	salesOrderNo?: string;
+	/** 部门 Department + ' ' + DepartmentMaster.Descr */
+	dept?: string;
+	/** 发货日期 ShipDate */
+	deliveryDate?: string;
+	/** 状态 Status(raw) */
+	status?: string;
+	/** 备注 Remark */
+	remark?: string;
+	/** 客户 SoldTo(保留,不展示) */
+	soldTo?: string;
+	/** 公司域 Domain(保留,不展示) */
+	domain?: string;
+	/** 发货确认标志 IsConfirm(保留,不展示) */
+	isConfirm?: number;
+	/** 租户 tenant_id(保留,不展示) */
+	tenantId?: number;
+}
+
+export function fetchSalesDeliveryNoticeList(params: any) {
+	return service
+		.get<SalesDeliveryNoticePaged<SalesDeliveryNoticeRow>>('/api/S7SalesDeliveryNotice/list', { params: withAidopTenantParams(params) })
+		.then((r) => r.data);
+}

+ 46 - 5
Web/src/views/aidop/s7/finished-outbound/salesDeliveryNoticeList.vue

@@ -28,11 +28,13 @@
 			</el-form-item>
 		</el-form>
 
-		<el-table :data="rows" row-key="id" v-loading="loading" border stripe>
+		<el-table :data="rows" row-key="recId" v-loading="loading" border stripe>
 			<el-table-column prop="deliveryNo" label="发货单号" min-width="160" show-overflow-tooltip resizable />
 			<el-table-column prop="salesOrderNo" label="销售单号" min-width="160" show-overflow-tooltip resizable />
 			<el-table-column prop="dept" label="部门" min-width="140" show-overflow-tooltip resizable />
-			<el-table-column prop="deliveryDate" label="发货日期" min-width="150" resizable />
+			<el-table-column prop="deliveryDate" label="发货日期" min-width="150" resizable>
+				<template #default="{ row }">{{ fmtDate(row.deliveryDate) }}</template>
+			</el-table-column>
 			<el-table-column prop="status" label="状态" min-width="110" resizable />
 			<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip resizable />
 			<template #empty>
@@ -47,15 +49,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="aidopS7FinishedOutboundSalesShipmentNotice">
-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 { fetchSalesDeliveryNoticeList, type SalesDeliveryNoticeRow } from '../api/salesDeliveryNotice';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '销售发货通知');
@@ -72,15 +78,46 @@ const query = reactive({
 
 const deliveryDateRange = ref<[string, string] | null>(null);
 
-// 本批次为只读 UI 骨架,尚无真实销售发货通知接口:渲染空表,不发请求、不造数据。
+// S7 销售发货通知 · 只读头列表(S7-SALES-DELIVERY-NOTICE-READONLY-LIST-1)。
+// 数据源 aidopdev.ASNBOLShipperMaster ⋈ DepartmentMaster,后端 api/S7SalesDeliveryNotice。
+// 只读:无新增/编辑/删除/发货确认/出库/库存扣减/打印/标签/导出/明细/详情。ASNBOLShipperMaster 0 行 → 空态先行。
 const loading = ref(false);
-const rows = ref<Record<string, unknown>[]>([]);
+const rows = ref<SalesDeliveryNoticeRow[]>([]);
 const total = ref(0);
 
+function fmtDate(v?: string | null) {
+	if (!v) return '';
+	return String(v).slice(0, 10);
+}
+
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchSalesDeliveryNoticeList({
+			id: query.shipmentNo,
+			ordNbr: query.salesOrderNo,
+			department: query.dept,
+			shipDateFrom: query.deliveryDateStart,
+			shipDateTo: query.deliveryDateEnd,
+			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.deliveryDateStart = deliveryDateRange.value?.[0] || '';
 	query.deliveryDateEnd = deliveryDateRange.value?.[1] || '';
 	query.page = 1;
+	loadList();
 }
 
 function resetQuery() {
@@ -91,7 +128,11 @@ function resetQuery() {
 	query.deliveryDateStart = '';
 	query.deliveryDateEnd = '';
 	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.229</AssemblyVersion>
-    <FileVersion>1.0.229</FileVersion>
-    <Version>1.0.229</Version>
+    <AssemblyVersion>1.0.230</AssemblyVersion>
+    <FileVersion>1.0.230</FileVersion>
+    <Version>1.0.230</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 82 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/FinishedWarehouse/Dto/SalesDeliveryNoticeDto.cs

@@ -0,0 +1,82 @@
+namespace Admin.NET.Plugin.AiDOP.FinishedWarehouse.Dto;
+
+/// <summary>
+/// S7 销售发货通知(头列表)查询入参(只读)。
+///
+/// 数据源:aidopdev.ASNBOLShipperMaster a LEFT JOIN DepartmentMaster b,忠实复刻证据 sql.txt 口径:
+/// 业务过滤 a.ShType='SH' AND a.Typed&lt;&gt;'S' AND a.IsActive=1;join b ON a.Department=b.Department AND a.Domain=b.Domain。
+/// 头列表粒度(一行一 ASNBOLShipperMaster)。ASNBOLShipperMaster 有 tenant_id:TenantId&gt;0 时追加 a.tenant_id=@TenantId,
+/// TenantId&lt;=0 时不加租户过滤;Domain 仅用于 DepartmentMaster join,不做 Domain 占位过滤。
+/// 本查询仅 SELECT:不做明细/详情/发货确认/出库/库存事务/新增编辑删除。
+/// </summary>
+public class SalesDeliveryNoticeListInput
+{
+    /// <summary>页码(从 1 开始)</summary>
+    public int Page { get; set; } = 1;
+
+    /// <summary>每页条数</summary>
+    public int PageSize { get; set; } = 10;
+
+    /// <summary>货运单号/发货单号(ASNBOLShipperMaster.Id,模糊匹配)</summary>
+    public string? Id { get; set; }
+
+    /// <summary>销售单号(ASNBOLShipperMaster.OrdNbr,模糊匹配)</summary>
+    public string? OrdNbr { get; set; }
+
+    /// <summary>部门(ASNBOLShipperMaster.Department 或 DepartmentMaster.Descr,模糊匹配)</summary>
+    public string? Department { get; set; }
+
+    /// <summary>发货日期起(ASNBOLShipperMaster.ShipDate,含当日,yyyy-MM-dd)</summary>
+    public string? ShipDateFrom { get; set; }
+
+    /// <summary>发货日期止(ASNBOLShipperMaster.ShipDate,含当日,yyyy-MM-dd)</summary>
+    public string? ShipDateTo { get; set; }
+
+    /// <summary>排序字段(白名单:deliveryNo/salesOrderNo/dept/deliveryDate/status/remark)</summary>
+    public string? OrderBy { get; set; }
+
+    /// <summary>排序方向(asc/desc,非法回落 desc)</summary>
+    public string? OrderDir { get; set; }
+
+    /// <summary>租户 ID(前端 withAidopTenantParams 注入;&gt;0 时按 a.tenant_id 过滤,&lt;=0 不过滤)</summary>
+    public long? TenantId { get; set; }
+}
+
+/// <summary>
+/// S7 销售发货通知(头列表)行(只读)。字段对齐前端列 prop(camelCase),源列见注释(sql.txt / ASNBOLShipperMaster)。
+/// </summary>
+public class SalesDeliveryNoticeListRow
+{
+    /// <summary>主键(ASNBOLShipperMaster.RecID)</summary>
+    public int? RecId { get; set; }
+
+    /// <summary>发货单号(ASNBOLShipperMaster.Id)</summary>
+    public string? DeliveryNo { get; set; }
+
+    /// <summary>销售单号(ASNBOLShipperMaster.OrdNbr)</summary>
+    public string? SalesOrderNo { get; set; }
+
+    /// <summary>部门(Department + ' ' + DepartmentMaster.Descr)</summary>
+    public string? Dept { get; set; }
+
+    /// <summary>发货日期(ASNBOLShipperMaster.ShipDate)</summary>
+    public DateTime? DeliveryDate { get; set; }
+
+    /// <summary>状态(ASNBOLShipperMaster.Status,raw 展示)</summary>
+    public string? Status { get; set; }
+
+    /// <summary>备注(ASNBOLShipperMaster.Remark)</summary>
+    public string? Remark { get; set; }
+
+    /// <summary>客户(ASNBOLShipperMaster.SoldTo,DTO 保留,前端不展示)</summary>
+    public string? SoldTo { get; set; }
+
+    /// <summary>公司域(ASNBOLShipperMaster.Domain,DTO 保留,前端不展示)</summary>
+    public string? Domain { get; set; }
+
+    /// <summary>发货确认标志(ASNBOLShipperMaster.IsConfirm,DTO 保留,前端不展示)</summary>
+    public int? IsConfirm { get; set; }
+
+    /// <summary>租户 ID(ASNBOLShipperMaster.tenant_id,DTO 保留,前端不展示)</summary>
+    public long? TenantId { get; set; }
+}

+ 131 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/FinishedWarehouse/SalesDeliveryNoticeService.cs

@@ -0,0 +1,131 @@
+using Admin.NET.Plugin.AiDOP.FinishedWarehouse.Dto;
+
+namespace Admin.NET.Plugin.AiDOP.FinishedWarehouse;
+
+/// <summary>
+/// S7 销售发货通知(头列表)只读 list 服务。
+///
+/// 数据源:aidopdev.ASNBOLShipperMaster a LEFT JOIN DepartmentMaster b,忠实复刻证据 sql.txt:
+/// 业务过滤 a.ShType='SH' AND a.Typed&lt;&gt;'S' AND a.IsActive=1;join b ON a.Department=b.Department AND a.Domain=b.Domain。
+/// 头列表粒度(一行一 ASNBOLShipperMaster)。口径:发货单号=Id;销售单号=OrdNbr;
+/// 部门=Department+' '+DepartmentMaster.Descr;发货日期=ShipDate;状态=Status(raw);备注=Remark。
+/// 排序默认 ShipDate DESC, RecID DESC。
+///
+/// tenant 策略:ASNBOLShipperMaster 有 tenant_id,TenantId&gt;0 时追加 a.tenant_id=@TenantId,TenantId&lt;=0 不过滤;
+/// 不使用 Domain 占位过滤(Domain 仅用于 DepartmentMaster join)。
+///
+/// 本服务仅 SELECT:无新增/编辑/删除/发货确认/出库/库存事务/状态流转/打印/导出/明细/详情。
+/// 明细表 ASNBOLShipperDetail 与 S1 AsnShipperService 均不在本服务范围。
+/// </summary>
+[ApiDescriptionSettings(Order = 320, Description = "销售发货通知")]
+[Route("api/S7SalesDeliveryNotice")]
+[AllowAnonymous]
+[NonUnify]
+public class SalesDeliveryNoticeService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+
+    public SalesDeliveryNoticeService(ISqlSugarClient db)
+    {
+        _db = db;
+    }
+
+    /// <summary>排序字段白名单(前端 prop → 物理列),杜绝 orderBy 注入。</summary>
+    private static readonly Dictionary<string, string> OrderByWhitelist = new(StringComparer.OrdinalIgnoreCase)
+    {
+        ["deliveryNo"] = "a.Id",
+        ["salesOrderNo"] = "a.OrdNbr",
+        ["dept"] = "a.Department",
+        ["deliveryDate"] = "a.ShipDate",
+        ["status"] = "a.Status",
+        ["remark"] = "a.Remark",
+    };
+
+    /// <summary>
+    /// 销售发货通知列表(只读分页查询)。
+    /// </summary>
+    [DisplayName("销售发货通知列表")]
+    [HttpGet("list")]
+    public async Task<object> GetList([FromQuery] SalesDeliveryNoticeListInput input)
+    {
+        var page = input.Page <= 0 ? 1 : input.Page;
+        var pageSize = input.PageSize <= 0 ? 10 : (input.PageSize > 200 ? 200 : input.PageSize);
+        var offset = (page - 1) * pageSize;
+
+        var where = new List<string> { "a.ShType = 'SH'", "a.Typed <> 'S'", "a.IsActive = 1" };
+        var pars = new List<SugarParameter>();
+
+        if (input.TenantId.HasValue && input.TenantId.Value > 0)
+        {
+            where.Add("a.tenant_id = @TenantId");
+            pars.Add(new SugarParameter("@TenantId", input.TenantId.Value));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Id))
+        {
+            where.Add("a.Id LIKE @Id");
+            pars.Add(new SugarParameter("@Id", $"%{input.Id.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.OrdNbr))
+        {
+            where.Add("a.OrdNbr LIKE @OrdNbr");
+            pars.Add(new SugarParameter("@OrdNbr", $"%{input.OrdNbr.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Department))
+        {
+            where.Add("(a.Department LIKE @Department OR b.Descr LIKE @Department)");
+            pars.Add(new SugarParameter("@Department", $"%{input.Department.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.ShipDateFrom))
+        {
+            where.Add("a.ShipDate >= @ShipDateFrom");
+            pars.Add(new SugarParameter("@ShipDateFrom", $"{input.ShipDateFrom.Trim()} 00:00:00"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.ShipDateTo))
+        {
+            where.Add("a.ShipDate <= @ShipDateTo");
+            pars.Add(new SugarParameter("@ShipDateTo", $"{input.ShipDateTo.Trim()} 23:59:59"));
+        }
+
+        var whereSql = string.Join(" AND ", where);
+
+        // 排序:白名单回落,方向 asc/desc 回落 desc;默认 ShipDate DESC, RecID DESC。
+        string orderSql;
+        if (!string.IsNullOrWhiteSpace(input.OrderBy) && OrderByWhitelist.TryGetValue(input.OrderBy.Trim(), out var col))
+        {
+            var dir = string.Equals(input.OrderDir?.Trim(), "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
+            orderSql = $"{col} {dir}, a.RecID DESC";
+        }
+        else
+        {
+            orderSql = "a.ShipDate DESC, a.RecID DESC";
+        }
+
+        var total = await _db.Ado.GetIntAsync(
+            $"SELECT COUNT(1) FROM ASNBOLShipperMaster a LEFT JOIN DepartmentMaster b ON a.Department = b.Department AND a.Domain = b.Domain WHERE {whereSql}",
+            pars);
+
+        var list = await _db.Ado.SqlQueryAsync<SalesDeliveryNoticeListRow>(
+            $"""
+            SELECT
+                a.RecID                                              AS RecId,
+                a.Id                                                 AS DeliveryNo,
+                a.OrdNbr                                             AS SalesOrderNo,
+                TRIM(CONCAT(IFNULL(a.Department, ''), ' ', IFNULL(b.Descr, ''))) AS Dept,
+                a.ShipDate                                           AS DeliveryDate,
+                a.Status                                             AS Status,
+                a.Remark                                             AS Remark,
+                a.SoldTo                                             AS SoldTo,
+                a.Domain                                             AS Domain,
+                a.IsConfirm                                          AS IsConfirm,
+                a.tenant_id                                          AS TenantId
+            FROM ASNBOLShipperMaster a
+            LEFT JOIN DepartmentMaster b ON a.Department = b.Department AND a.Domain = b.Domain
+            WHERE {whereSql}
+            ORDER BY {orderSql}
+            LIMIT {pageSize} OFFSET {offset}
+            """,
+            pars);
+
+        return new { total, page, pageSize, list };
+    }
+}