| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- using Admin.NET.Plugin.AiDOP.Manufacturing.Dto;
- namespace Admin.NET.Plugin.AiDOP.Manufacturing;
- /// <summary>
- /// S6 生产指令单 只读消费服务(Phase 1A)。
- ///
- /// 数据源:数据中台标准层 mdp_std_work_order_schedule(S2/Production 从 aidopdev.WorkOrdMaster
- /// 贴源 → mdp_stg_schedule → 标准化而来;specification 由 S2 转换层 LEFT JOIN ItemMaster.Descr1 enrich)。
- ///
- /// 只读铁律:本服务只 SELECT mdp_std_*,绝不直连 WorkOrdMaster / ItemMaster / mes_morder,绝不回退源库、绝不写库。
- ///
- /// 租户隔离(P0):租户只来自认证后 JWT(AidopTenantScope.ResolveOrThrow),忽略前端传入的任何 tenantId;
- /// COUNT / PAGE / DETAIL 三处 SQL 都显式带 tenant_id = @TenantId(mdp_std 实体非 ITenantIdFilter,不依赖框架自动过滤)。
- /// 工厂隔离:当前 S6 体系只可靠解析租户、无稳定工厂作用域 → FACTORY_SCOPE = PARTIAL(tenant-only,不硬编码 factory_id)。
- /// </summary>
- [ApiDescriptionSettings(Order = 305, Description = "生产指令单")]
- [Route("api/S6ProductionInstruction")]
- [NonUnify]
- public class S6ProductionInstructionService : IDynamicApiController, ITransient
- {
- private readonly ISqlSugarClient _db;
- private readonly UserManager _userManager;
- public S6ProductionInstructionService(ISqlSugarClient db, UserManager userManager)
- {
- _db = db;
- _userManager = userManager;
- }
- /// <summary>严格可信租户解析,见 <see cref="Infrastructure.AidopTenantScope.ResolveOrThrow"/>。</summary>
- private long ResolveTenantOrThrow() => Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
- private const string SelectColumns = """
- m.id AS Id,
- m.work_order AS WorkOrderNo,
- m.lot_serial AS LotSerial,
- m.item_code AS ItemCode,
- m.item_name AS ItemName,
- m.specification AS Specification,
- m.drawing_no AS DrawingNo,
- m.project AS Project,
- m.qty_ordered AS OrderQty,
- m.qty_completed AS CompletedQty,
- m.order_date AS OrderDate,
- m.due_date AS DueDate,
- m.status AS Status,
- m.work_order_type AS WorkOrderType
- """;
- /// <summary>
- /// 生产指令单列表(只读真分页)。
- /// </summary>
- [DisplayName("生产指令单列表")]
- [HttpGet("page")]
- public async Task<object> GetPage([FromQuery] S6ProductionInstructionInput input)
- {
- var page = input.Page <= 0 ? 1 : input.Page;
- var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
- var offset = (page - 1) * pageSize;
- var where = new List<string> { "1=1" };
- var pars = new List<SugarParameter>();
- // 强制租户过滤:mdp_std_work_order_schedule 非 ITenantIdFilter,隔离全靠显式 SQL 谓词。
- var tid = ResolveTenantOrThrow();
- where.Add("m.tenant_id = @TenantId");
- pars.Add(new SugarParameter("@TenantId", tid));
- if (!string.IsNullOrWhiteSpace(input.WorkOrderNo))
- {
- where.Add("m.work_order LIKE @WorkOrderNo");
- pars.Add(new SugarParameter("@WorkOrderNo", $"%{input.WorkOrderNo.Trim()}%"));
- }
- if (!string.IsNullOrWhiteSpace(input.LotSerial))
- {
- where.Add("m.lot_serial LIKE @LotSerial");
- pars.Add(new SugarParameter("@LotSerial", $"%{input.LotSerial.Trim()}%"));
- }
- if (!string.IsNullOrWhiteSpace(input.ItemCode))
- {
- where.Add("m.item_code LIKE @ItemCode");
- pars.Add(new SugarParameter("@ItemCode", $"%{input.ItemCode.Trim()}%"));
- }
- if (!string.IsNullOrWhiteSpace(input.Specification))
- {
- where.Add("m.specification LIKE @Specification");
- pars.Add(new SugarParameter("@Specification", $"%{input.Specification.Trim()}%"));
- }
- if (!string.IsNullOrWhiteSpace(input.Status))
- {
- where.Add("m.status = @Status");
- pars.Add(new SugarParameter("@Status", input.Status.Trim()));
- }
- if (!string.IsNullOrWhiteSpace(input.OrderDateStart))
- {
- where.Add("m.order_date >= @OrderDateStart");
- pars.Add(new SugarParameter("@OrderDateStart", input.OrderDateStart.Trim()));
- }
- if (!string.IsNullOrWhiteSpace(input.OrderDateEnd))
- {
- // 含止日当天:order_date < 止日 + 1 天
- where.Add("m.order_date < DATE_ADD(@OrderDateEnd, INTERVAL 1 DAY)");
- pars.Add(new SugarParameter("@OrderDateEnd", input.OrderDateEnd.Trim()));
- }
- var whereSql = string.Join(" AND ", where);
- var total = await _db.Ado.GetIntAsync(
- $"SELECT COUNT(1) FROM mdp_std_work_order_schedule m WHERE {whereSql}", pars);
- var list = total == 0
- ? new List<S6ProductionInstructionRow>()
- : await _db.Ado.SqlQueryAsync<S6ProductionInstructionRow>(
- $"""
- SELECT
- {SelectColumns}
- FROM mdp_std_work_order_schedule m
- WHERE {whereSql}
- ORDER BY m.order_date DESC, m.work_order DESC
- LIMIT {pageSize} OFFSET {offset}
- """,
- pars);
- return new { total, page, pageSize, list };
- }
- /// <summary>
- /// 生产指令单详情(只读,按工单编号)。查不到 / 跨租户 → 返回 null(不泄漏“无权限”信息)。
- /// </summary>
- [DisplayName("生产指令单详情")]
- [HttpGet("detail")]
- public async Task<S6ProductionInstructionRow?> GetDetail([FromQuery] string workOrderNo)
- {
- if (string.IsNullOrWhiteSpace(workOrderNo)) return null;
- var tid = ResolveTenantOrThrow();
- var pars = new List<SugarParameter>
- {
- new("@TenantId", tid),
- new("@WorkOrderNo", workOrderNo.Trim())
- };
- return await _db.Ado.SqlQuerySingleAsync<S6ProductionInstructionRow>(
- $"""
- SELECT
- {SelectColumns}
- FROM mdp_std_work_order_schedule m
- WHERE m.tenant_id = @TenantId AND m.work_order = @WorkOrderNo
- LIMIT 1
- """,
- pars);
- }
- }
|