SupplierShipmentService.cs 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379
  1. using Admin.NET.Plugin.AiDOP.ProcurementExecution.Dto;
  2. using Admin.NET.Plugin.AiDOP.ProcurementExecution.Entity;
  3. using Yitter.IdGenerator;
  4. namespace Admin.NET.Plugin.AiDOP.ProcurementExecution;
  5. /// <summary>
  6. /// S4 供应商发货单(列表 + 表单)
  7. ///
  8. /// 【租户安全边界】租户一律经 <see cref="AidopTenantScope.ResolveOrThrow"/> 从认证后 JWT 解析,
  9. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退。
  10. /// <para>
  11. /// 原实现:类级 <c>[AllowAnonymous]</c> + 全类使用 <c>AidopTenantHelper.Resolve(App.HttpContext)</c>。
  12. /// 后者的解析顺序为 requestTenantId &gt; JWT claim &gt; <b>query 参数 tenantId</b> &gt; <b>DefaultTenantId 兜底</b>,
  13. /// 其自身注释即写明「允许前端指定租户且有默认回退,<b>不得用于业务数据的查询与写入</b>」。
  14. /// 两者叠加的实测后果:匿名(无 Token、无 Cookie)GET
  15. /// <c>/api/ProcurementExecution/supplier-shipment/list?tenantId=&lt;任意租户&gt;</c> 可读回该租户业务数据
  16. /// (797 返回 13 条、UAT 返回 2 条);同类的 HttpPost 写接口走同一条解析链。
  17. /// </para>
  18. /// <para>
  19. /// 本类的写 SQL 一律带 <c>tenant_id = @TenantId</c>:业务键 shddh/shdh 在库中无唯一索引,
  20. /// 一旦将来出现跨租户重复单号,仅按业务键定位会造成越权改写。
  21. /// </para>
  22. /// <para>
  23. /// 生成标签的 <c>INSERT INTO MissedPrint ... SELECT ... FROM scm_shbq</c> 另有两处缺陷一并修复:
  24. /// ① 未写 <c>tenant_id</c>,导致库中 152 行标签全部为 NULL、下游 008/013 无法按租户过滤;
  25. /// ② <c>LEFT JOIN ItemMaster ON ItemNum</c> 未限定租户,而 ItemMaster 有 663 个 ItemNum 跨租户重名
  26. /// (<c>(tenant_id, ItemNum)</c> 唯一、单独 ItemNum 不唯一),实测按现网 scm_shbq 数据:
  27. /// 797 的 50 行会放大成 82 行、UAT 的 24 行会放大成 48 行,且 ExpireDate 可能取自其它租户的物料主数据。
  28. /// 现补 <c>i.tenant_id / p.tenant_id / s.tenant_id = @TenantId</c>。
  29. /// </para>
  30. /// </summary>
  31. [ApiDescriptionSettings(Order = 321, Description = "S4供应商发货单")]
  32. [Route("api/ProcurementExecution")]
  33. [NonUnify]
  34. public class SupplierShipmentService : IDynamicApiController, ITransient
  35. {
  36. private readonly ISqlSugarClient _db;
  37. private readonly SqlSugarRepository<ScmShd> _masterRep;
  38. private readonly SqlSugarRepository<ScmShdzb> _detailRep;
  39. private readonly UserManager _userManager;
  40. private readonly DataPlatform.Wms.PurOrdWmsPushService _purOrdWmsPush;
  41. private const int ShpcSerialWidth = 3; // yyMMdd + 3位流水:260508001
  42. private const int LabelXhStart = 10001;
  43. public SupplierShipmentService(
  44. ISqlSugarClient db,
  45. SqlSugarRepository<ScmShd> masterRep,
  46. SqlSugarRepository<ScmShdzb> detailRep,
  47. UserManager userManager,
  48. DataPlatform.Wms.PurOrdWmsPushService purOrdWmsPush)
  49. {
  50. _db = db;
  51. _masterRep = masterRep;
  52. _detailRep = detailRep;
  53. _userManager = userManager;
  54. _purOrdWmsPush = purOrdWmsPush;
  55. }
  56. [DisplayName("供应商发货单列表")]
  57. [HttpGet("supplier-shipment/list")]
  58. public async Task<object> GetList([FromQuery] SupplierShipmentListInput input)
  59. {
  60. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  61. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  62. var conditions = new List<string> { "IFNULL(m.state, 1) <> 0" };
  63. if (!string.IsNullOrWhiteSpace(input.JhshrqFrom))
  64. {
  65. conditions.Add("m.jhshrq >= @jhshrqFrom");
  66. pars.Add(new SugarParameter("@jhshrqFrom", input.JhshrqFrom.Trim()));
  67. }
  68. if (!string.IsNullOrWhiteSpace(input.Gysmc))
  69. {
  70. conditions.Add("m.sh_purchase_name LIKE @gysmc");
  71. pars.Add(new SugarParameter("@gysmc", $"%{input.Gysmc.Trim()}%"));
  72. }
  73. if (!string.IsNullOrWhiteSpace(input.Shddh))
  74. {
  75. conditions.Add("m.shddh LIKE @shddh");
  76. pars.Add(new SugarParameter("@shddh", $"%{input.Shddh.Trim()}%"));
  77. }
  78. if (!string.IsNullOrWhiteSpace(input.Wldh))
  79. {
  80. conditions.Add("m.wldh LIKE @wldh");
  81. pars.Add(new SugarParameter("@wldh", $"%{input.Wldh.Trim()}%"));
  82. }
  83. if (!string.IsNullOrWhiteSpace(input.PoBill))
  84. {
  85. conditions.Add("m.po_bill LIKE @poBill");
  86. pars.Add(new SugarParameter("@poBill", $"%{input.PoBill.Trim()}%"));
  87. }
  88. if (!string.IsNullOrWhiteSpace(input.ShMaterialCode))
  89. {
  90. conditions.Add("m.sh_material_code LIKE @shMaterialCode");
  91. pars.Add(new SugarParameter("@shMaterialCode", $"%{input.ShMaterialCode.Trim()}%"));
  92. }
  93. if (!string.IsNullOrWhiteSpace(input.Shzt))
  94. {
  95. conditions.Add("m.shzt = @shzt");
  96. pars.Add(new SugarParameter("@shzt", input.Shzt.Trim()));
  97. }
  98. if (!string.IsNullOrWhiteSpace(input.Shpc))
  99. {
  100. conditions.Add("m.shpc LIKE @shpc");
  101. pars.Add(new SugarParameter("@shpc", $"%{input.Shpc.Trim()}%"));
  102. }
  103. var where = $" WHERE {string.Join(" AND ", conditions)} ";
  104. var orderBy = BuildListOrderBy(input.SortField, input.SortOrder);
  105. var offset = (input.Page - 1) * input.PageSize;
  106. var wrapped = $"{BuildListSql()} {where}";
  107. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(*) FROM ({wrapped}) t", pars);
  108. var list = await _db.Ado.SqlQueryAsync<SupplierShipmentListRow>(
  109. $"SELECT * FROM ({wrapped}) t {orderBy} LIMIT {input.PageSize} OFFSET {offset}", pars);
  110. return new { total, page = input.Page, pageSize = input.PageSize, list };
  111. }
  112. [DisplayName("获取发货单详情")]
  113. [HttpGet("supplier-shipment/{id:long}")]
  114. public async Task<object> GetDetail(long id)
  115. {
  116. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  117. var master = await _masterRep.GetFirstAsync(x => x.Id == id && x.TenantId == tenantId) ?? throw Oops.Oh("发货单不存在");
  118. var details = await _detailRep.AsQueryable().Where(x => x.Glid == id.ToString() && x.TenantId == tenantId).OrderBy(x => x.Hh).ToListAsync();
  119. return new
  120. {
  121. id = master.Id,
  122. shddh = master.Shddh,
  123. jhshrq = master.Jhshrq,
  124. wlsc = master.Wlsc,
  125. yjdhrq = master.Yjdhrq,
  126. shPurchaseName = master.ShPurchaseName,
  127. shPurchaseNum = master.ShPurchaseNum,
  128. wldh = master.Wldh,
  129. sfpc = master.Sfpc ?? 0,
  130. chbg = master.Chbg,
  131. pcsm = master.Pcsm,
  132. state = master.State ?? 1,
  133. details = details.Select(d => new
  134. {
  135. id = d.Id,
  136. hh = d.Hh,
  137. poBill = d.PoBill,
  138. poBillLine = d.PoBillLine,
  139. orderType = d.OrderType,
  140. shMaterialCode = d.ShMaterialCode,
  141. shMaterialName = d.ShMaterialName,
  142. th = d.Th,
  143. shDeliveryQuantity = d.ShDeliveryQuantity,
  144. bzsl = d.Bzsl,
  145. bqsl = d.Bqsl,
  146. shMaterialDw = d.ShMaterialDw,
  147. scrq = d.Scrq,
  148. scph = d.Scph,
  149. remarks = d.Remarks,
  150. djsl = d.Djsl,
  151. jybb = d.Jybb,
  152. jhdbh = d.Jhdbh
  153. })
  154. };
  155. }
  156. [DisplayName("发货单新增草稿")]
  157. [HttpGet("supplier-shipment/create-draft")]
  158. public async Task<object> GetCreateDraft([FromQuery] string ids)
  159. {
  160. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  161. var pars = new List<SugarParameter> { new("@ids", ids), new("@TenantId", tenantId) };
  162. var rows = await _db.Ado.SqlQueryAsync<SupplierShipmentDraftRow>(
  163. """
  164. SELECT
  165. CAST(IFNULL(ds.id, p.RecID) AS CHAR(50)) AS sourceId,
  166. ds.suppliercode AS gysdm,
  167. ds.supplier AS gysmc,
  168. p.PurOrd AS poBill,
  169. p.Line AS poBillLine,
  170. p.Potype AS orderType,
  171. p.ItemNum AS shMaterialCode,
  172. im.Descr AS shMaterialName,
  173. IFNULL(im.Drawing, p.Drawing) AS th,
  174. IFNULL(ds.SchedQty, p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) AS shDeliveryQuantity,
  175. p.StdPackQty AS bzsl,
  176. CASE
  177. WHEN IFNULL(p.StdPackQty, 0) > 0 THEN CEILING(IFNULL(ds.SchedQty, p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) / p.StdPackQty)
  178. ELSE 0
  179. END AS bqsl,
  180. p.UM AS shMaterialDw,
  181. (p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) AS djsl,
  182. ds.DSNum AS jhdbh,
  183. im.Descr1 AS shMaterialGgxh
  184. FROM PurOrdDetail p
  185. LEFT JOIN srm_polist_ds ds ON p.PurOrd = ds.ponumber AND p.Line = ds.poline AND ds.tenant_id = p.tenant_id
  186. LEFT JOIN ItemMaster im ON p.ItemNum = im.ItemNum AND (im.tenant_id = p.tenant_id OR p.tenant_id IS NULL)
  187. WHERE FIND_IN_SET(CAST(IFNULL(ds.id, p.RecID) AS CHAR(50)), REPLACE(IFNULL(@ids, ''), ' ', '')) > 0
  188. AND p.tenant_id = @TenantId
  189. ORDER BY p.PurOrd, p.Line
  190. """, pars);
  191. if (rows.Count == 0)
  192. throw Oops.Oh("未找到可生成的交货明细");
  193. return new
  194. {
  195. shddh = string.Empty,
  196. jhshrq = DateTime.Now.ToString("yyyy-MM-dd"),
  197. wlsc = string.Empty,
  198. yjdhrq = string.Empty,
  199. shPurchaseName = rows[0].Gysmc,
  200. shPurchaseNum = rows[0].Gysdm,
  201. wldh = string.Empty,
  202. sfpc = 0,
  203. chbg = string.Empty,
  204. pcsm = string.Empty,
  205. details = rows.Select((r, i) => new
  206. {
  207. id = (long?)null,
  208. hh = i + 1,
  209. poBill = r.PoBill,
  210. poBillLine = r.PoBillLine?.ToString(),
  211. orderType = r.OrderType,
  212. shMaterialCode = r.ShMaterialCode,
  213. shMaterialName = r.ShMaterialName,
  214. th = r.Th,
  215. shDeliveryQuantity = r.ShDeliveryQuantity,
  216. bzsl = r.Bzsl,
  217. bqsl = r.Bqsl ?? 0,
  218. shMaterialDw = r.ShMaterialDw,
  219. scrq = string.Empty,
  220. scph = string.Empty,
  221. remarks = string.Empty,
  222. djsl = r.Djsl,
  223. jybb = string.Empty,
  224. jhdbh = r.Jhdbh
  225. })
  226. };
  227. }
  228. [DisplayName("保存发货单")]
  229. [ApiDescriptionSettings(Name = "SaveSupplierShipment"), HttpPost("supplier-shipment/save")]
  230. public async Task<object> Save([FromBody] SupplierShipmentSaveInput input)
  231. {
  232. var now = DateTime.Now;
  233. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  234. var userId = _userManager.UserId.ToString();
  235. var userName = _userManager.Account ?? "system";
  236. var shipDate = string.IsNullOrWhiteSpace(input.Jhshrq) ? now.ToString("yyyy-MM-dd") : input.Jhshrq!.Trim();
  237. if (input.Id is null or 0)
  238. {
  239. var newId = YitIdHelper.NextId();
  240. var shddh = await AllocateShddhAsync(input.ShPurchaseNum ?? string.Empty);
  241. var entity = new ScmShd
  242. {
  243. Id = newId,
  244. Shddh = shddh,
  245. Jhshrq = shipDate,
  246. Wlsc = input.Wlsc,
  247. Yjdhrq = input.Yjdhrq,
  248. ShPurchaseName = input.ShPurchaseName,
  249. ShPurchaseNum = input.ShPurchaseNum,
  250. Wldh = input.Wldh,
  251. Sfpc = input.Sfpc ?? 0,
  252. Chbg = input.Chbg,
  253. Pcsm = input.Pcsm,
  254. State = 1,
  255. Shzt = "待收",
  256. Tjrid = userId,
  257. Tjrxm = userName,
  258. Tjrq = now.ToString("yyyy-MM-dd")
  259. };
  260. await _masterRep.InsertAsync(entity);
  261. await SaveDetailsAsync(newId, input.Details);
  262. return new { id = newId, message = "新增成功", shddh = entity.Shddh };
  263. }
  264. var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id.Value && x.TenantId == tenantId) ?? throw Oops.Oh("发货单不存在");
  265. master.Jhshrq = shipDate;
  266. master.Wlsc = input.Wlsc;
  267. master.Yjdhrq = input.Yjdhrq;
  268. master.ShPurchaseName = input.ShPurchaseName;
  269. master.ShPurchaseNum = input.ShPurchaseNum;
  270. master.Wldh = input.Wldh;
  271. master.Sfpc = input.Sfpc ?? 0;
  272. master.Chbg = input.Chbg;
  273. master.Pcsm = input.Pcsm;
  274. await _masterRep.UpdateAsync(master);
  275. await SaveDetailsAsync(input.Id.Value, input.Details);
  276. return new { id = input.Id, message = "编辑成功", shddh = master.Shddh };
  277. }
  278. [DisplayName("删除发货单")]
  279. [ApiDescriptionSettings(Name = "DeleteSupplierShipment"), HttpPost("supplier-shipment/delete")]
  280. public async Task<object> Delete([FromBody] SupplierShipmentDeleteInput input)
  281. {
  282. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  283. var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id && x.TenantId == tenantId) ?? throw Oops.Oh("发货单不存在");
  284. master.State = 0;
  285. await _masterRep.UpdateAsync(master);
  286. return new { message = "删除成功" };
  287. }
  288. [DisplayName("生成标签(预留)")]
  289. [HttpPost("supplier-shipment/generate-label")]
  290. public async Task<object> GenerateLabel([FromBody] SupplierShipmentOperationInput input)
  291. {
  292. if (input.Id <= 0)
  293. throw Oops.Oh("缺少发货单ID(id)");
  294. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  295. var userName = _userManager.Account ?? "system";
  296. var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id && x.TenantId == tenantId) ?? throw Oops.Oh("发货单不存在");
  297. if (string.IsNullOrWhiteSpace(master.Shddh))
  298. throw Oops.Oh("发货单缺少发货单编号(shddh)");
  299. var shddh = master.Shddh.Trim();
  300. var gysmc = master.ShPurchaseName ?? string.Empty;
  301. var gysdm = master.ShPurchaseNum ?? string.Empty;
  302. // Domain(工厂编码)
  303. var domain = await _db.Ado.GetStringAsync(
  304. "SELECT Domain FROM GeneralizedCodeMaster WHERE FldName='SystemConfig' AND Val='CompanyCode' AND tenant_id=@TenantId LIMIT 1",
  305. new List<SugarParameter> { new("@TenantId", tenantId) });
  306. domain = string.IsNullOrWhiteSpace(domain) ? string.Empty : domain.Trim();
  307. string? lockKey = null;
  308. try
  309. {
  310. var tran = await _db.Ado.UseTranAsync(async () =>
  311. {
  312. // 1) 归档旧标签
  313. await _db.Ado.ExecuteCommandAsync(
  314. """
  315. INSERT INTO scm_shbqhis
  316. (glid, sh_material_code, sh_material_name, sh_material_ggxh, sh_delivery_quantity,
  317. sh_material_dw, remarks, bzsl, order_type, po_billno, shdh, shdhh, scrq, scph, xh,
  318. gysdm, gysmc, po_billline, bbh, th, yt, ccrq, shpc, jhdbh, jhdhh, tenant_id)
  319. SELECT
  320. glid, sh_material_code, sh_material_name, sh_material_ggxh, sh_delivery_quantity,
  321. sh_material_dw, remarks, bzsl, order_type, po_billno, shdh, shdhh, scrq, scph, xh,
  322. gysdm, gysmc, po_billline, bbh, th, yt, ccrq, shpc, jhdbh, jhdhh, tenant_id
  323. FROM scm_shbq WHERE shdh=@shdh;
  324. """,
  325. new List<SugarParameter> { new("@shdh", shddh) });
  326. // 业务键 shdh 无唯一索引,必须叠加租户条件,否则跨租户重号时会删掉别人的标签
  327. await _db.Ado.ExecuteCommandAsync(
  328. "DELETE FROM scm_shbq WHERE shdh=@shdh AND tenant_id=@TenantId",
  329. new List<SugarParameter> { new("@shdh", shddh), new("@TenantId", tenantId) });
  330. // 2) 需要补 shpc 的明细(按物料+供应商批号去重)
  331. var needShpc = await _db.Ado.SqlQueryAsync<NeedShpcRow>(
  332. """
  333. SELECT DISTINCT
  334. b.sh_material_code AS wlbm,
  335. IFNULL(b.scph, '') AS scph
  336. FROM scm_shd a
  337. INNER JOIN scm_shdzb b ON CAST(a.id AS CHAR) = b.glid
  338. WHERE a.shddh = @shddh
  339. AND IFNULL(b.sh_material_code, '') <> ''
  340. AND IFNULL(b.shpc, '') = ''
  341. AND a.tenant_id = @TenantId;
  342. """,
  343. new List<SugarParameter> { new("@shddh", shddh), new("@TenantId", tenantId) });
  344. if (needShpc.Count > 0)
  345. {
  346. // 并发安全:按天加锁 + 取最大流水 + 批量生成
  347. var prefix = DateTime.Now.ToString("yyMMdd");
  348. lockKey = $"scm_shpc_seq_{prefix}";
  349. await AcquireMySqlLockAsync(lockKey, 10);
  350. var nextSerial = await GetNextDailySerialAsync(prefix, domain);
  351. var pairs = needShpc
  352. .OrderBy(x => x.Wlbm ?? string.Empty)
  353. .ThenBy(x => x.Scph ?? string.Empty)
  354. .Select((x, idx) => new
  355. {
  356. x.Wlbm,
  357. x.Scph,
  358. Shpc = $"{prefix}{(nextSerial + idx).ToString().PadLeft(ShpcSerialWidth, '0')}"
  359. })
  360. .ToList();
  361. // 更新 scm_shdzb.shpc(同一发货单下:物料+供应商批号相同的行统一批次号)
  362. foreach (var p in pairs)
  363. {
  364. await _db.Ado.ExecuteCommandAsync(
  365. """
  366. UPDATE scm_shdzb b
  367. INNER JOIN scm_shd a ON CAST(a.id AS CHAR) = b.glid
  368. SET b.shpc = @shpc
  369. WHERE a.shddh = @shddh
  370. AND b.sh_material_code = @wlbm
  371. AND IFNULL(b.scph, '') = IFNULL(@scph, '')
  372. AND IFNULL(b.shpc, '') = '';
  373. """,
  374. new List<SugarParameter>
  375. {
  376. new("@shpc", p.Shpc),
  377. new("@shddh", shddh),
  378. new("@wlbm", p.Wlbm ?? string.Empty),
  379. new("@scph", p.Scph ?? string.Empty),
  380. });
  381. }
  382. // 兼容:如果线上存在 scm_shdshph,则同步写入(仓库脚本里未包含该表)
  383. var shdshphExists = await TableExistsAsync("scm_shdshph");
  384. if (shdshphExists)
  385. {
  386. foreach (var p in pairs)
  387. {
  388. await _db.Ado.ExecuteCommandAsync(
  389. """
  390. INSERT INTO scm_shdshph (shpc, wlbm, scph, shdh, xh, gysbm, csrq)
  391. SELECT @shpc, @wlbm, @scph, @shdh, @xh, @gysdm, NOW()
  392. FROM DUAL
  393. WHERE NOT EXISTS (
  394. SELECT 1 FROM scm_shdshph
  395. WHERE shdh=@shdh AND wlbm=@wlbm AND IFNULL(scph,'')=IFNULL(@scph,'')
  396. );
  397. """,
  398. new List<SugarParameter>
  399. {
  400. new("@shpc", p.Shpc),
  401. new("@wlbm", p.Wlbm ?? string.Empty),
  402. new("@scph", p.Scph ?? string.Empty),
  403. new("@shdh", shddh),
  404. new("@xh", p.Shpc),
  405. new("@gysdm", gysdm),
  406. });
  407. }
  408. }
  409. // 推进 NbrControl.NextValue,确保续号(仅当没有历史 shpc 且使用了 NbrControl 作为起点时也能正确续)
  410. // 这里直接推进到“本批次最后一个流水号”,下次取号将从 NextValue+1 开始。
  411. var lastSerial = nextSerial + pairs.Count - 1;
  412. await BumpNbrControlNextValueAsync(domain, lastSerial);
  413. }
  414. // 3) 补 jhdhh(同一发货单:为空的明细按 id 递增,起始 max(jhdhh) or 1000)
  415. var shdid = await _db.Ado.GetLongAsync(
  416. "SELECT id FROM scm_shd WHERE shddh=@shddh LIMIT 1",
  417. new List<SugarParameter> { new("@shddh", shddh) });
  418. var startNo = await _db.Ado.GetIntAsync(
  419. "SELECT IFNULL(MAX(CAST(IFNULL(jhdhh,'') AS SIGNED)), 1000) FROM scm_shdzb WHERE glid=@glid",
  420. new List<SugarParameter> { new("@glid", shdid.ToString()) });
  421. var jhdhhNeed = await _db.Ado.SqlQueryAsync<IdOnlyRow>(
  422. "SELECT id FROM scm_shdzb WHERE glid=@glid AND IFNULL(jhdhh,'')='' ORDER BY id",
  423. new List<SugarParameter> { new("@glid", shdid.ToString()) });
  424. var seq = startNo;
  425. foreach (var r in jhdhhNeed)
  426. {
  427. seq++;
  428. await _db.Ado.ExecuteCommandAsync(
  429. "UPDATE scm_shdzb SET jhdhh=@jhdhh WHERE id=@id",
  430. new List<SugarParameter>
  431. {
  432. new("@jhdhh", seq.ToString()),
  433. new("@id", r.Id)
  434. });
  435. }
  436. // 4) 生成 scm_shbq(按 bqsl 拆分;每箱 bzsl,最后一箱余数)
  437. var sourceRows = await _db.Ado.SqlQueryAsync<LabelSourceRow>(
  438. """
  439. SELECT
  440. a.shddh AS shddh,
  441. b.id AS detailId,
  442. b.glid AS glid,
  443. b.sh_material_code AS wlbm,
  444. b.sh_material_name AS wlmc,
  445. b.sh_material_ggxh AS ggxh,
  446. b.sh_delivery_quantity AS shsl,
  447. b.sh_material_dw AS dw,
  448. b.remarks AS bz,
  449. b.bzsl AS bzsl,
  450. b.bqsl AS bqsl,
  451. b.order_type AS ddlx,
  452. b.po_bill AS ddh,
  453. CAST(IFNULL(b.po_billline, '0') AS SIGNED) AS PoBillLine,
  454. b.hh AS hh,
  455. b.scrq AS scrq,
  456. b.scph AS scph,
  457. IFNULL(im.Drawing, pd.Drawing) AS th,
  458. IFNULL(im.Rev, pd.Rev) AS bbh,
  459. a.sh_purchase_name AS gysmc,
  460. a.sh_purchase_num AS gysdm,
  461. pm.`Usage` AS PoUsage,
  462. b.ccrq AS ccrq,
  463. a.jhshrq AS jhshrq,
  464. b.jhdbh AS jhdbh,
  465. b.jhdhh AS jhdhh,
  466. b.shpc AS shpc
  467. FROM scm_shd a
  468. INNER JOIN scm_shdzb b ON CAST(a.id AS CHAR) = b.glid
  469. LEFT JOIN PurOrdMaster pm ON pm.PurOrd = b.po_bill
  470. LEFT JOIN PurOrdDetail pd ON pd.PurOrd = b.po_bill AND pd.Line = b.po_billline
  471. LEFT JOIN ItemMaster im ON im.ItemNum = b.sh_material_code
  472. WHERE a.shddh = @shddh
  473. AND a.tenant_id = @TenantId;
  474. """,
  475. new List<SugarParameter> { new("@shddh", shddh), new("@TenantId", tenantId) });
  476. foreach (var row in sourceRows)
  477. {
  478. var totalQty = row.Shsl ?? 0m;
  479. var packQty = row.Bzsl ?? 0m;
  480. var labelCnt = (int)Math.Max(0, row.Bqsl ?? 0m);
  481. if (totalQty <= 0 || labelCnt <= 0)
  482. continue;
  483. var xh = LabelXhStart;
  484. var remainQty = totalQty;
  485. var remainLabels = labelCnt;
  486. // 多箱:前 N-1 箱按包装数量
  487. while (remainLabels > 1)
  488. {
  489. await InsertLabelAsync(shddh, row, packQty, xh, gysdm, gysmc, tenantId);
  490. remainQty -= packQty;
  491. remainLabels--;
  492. xh++;
  493. }
  494. if (remainQty > 0)
  495. {
  496. await InsertLabelAsync(shddh, row, remainQty, xh, gysdm, gysmc, tenantId);
  497. }
  498. }
  499. // 5) 更新发货单状态
  500. await _db.Ado.ExecuteCommandAsync(
  501. "UPDATE scm_shd SET shzt='待收', state=2, dycs=0 WHERE shddh=@shddh AND tenant_id=@TenantId",
  502. new List<SugarParameter> { new("@shddh", shddh), new("@TenantId", tenantId) });
  503. // 6) MissedPrint:作废旧未打印(U)
  504. await _db.Ado.ExecuteCommandAsync(
  505. """
  506. UPDATE MissedPrint SET
  507. PurOrd = CONCAT('作废_', IFNULL(PurOrd, '')),
  508. BarCode = CONCAT(CAST(RecID AS CHAR(20)), '_', IFNULL(BarCode, '')),
  509. Status = 'C',
  510. RelatedBarCode = '',
  511. UpdateTime = NOW(),
  512. UpdateUser = @u
  513. WHERE tenant_id = @TenantId
  514. AND Domain = @domain
  515. AND ShipperNbr = @shddh
  516. AND Status = 'U';
  517. """,
  518. new List<SugarParameter>
  519. {
  520. new("@u", userName),
  521. new("@domain", domain),
  522. new("@shddh", shddh),
  523. new("@TenantId", tenantId),
  524. });
  525. // 7) MissedPrint:插入待打印(U)
  526. await _db.Ado.ExecuteCommandAsync(
  527. """
  528. INSERT INTO MissedPrint
  529. (
  530. Domain, Site, PrintTime,
  531. ItemNum, Descr, Product, Carton, OrdNbr,
  532. PackingQty, Qty, Location, Status,
  533. Supply, LotSerial, CartonQty, BarCode,
  534. MoldNum, SuppLotSerial, ShipperNbr, ShipperLine,
  535. ProdDate, PurOrd, PurLine, PurQty, WorkOrd,
  536. LabelFormat, StandItem, EffSize, GP12CheckedQty, NetWeight,
  537. Remark, CreateTime, UpdateTime, CreateUser, UpdateUser,
  538. LevelChar, PurOrdDetBatchNbr, FirmString5, ExpireDate,
  539. Printer, Company, Checker, Position,
  540. tenant_id
  541. )
  542. SELECT
  543. @domain AS Domain,
  544. @domain AS Site,
  545. NULL AS PrintTime,
  546. s.sh_material_code AS ItemNum,
  547. s.sh_material_name AS Descr,
  548. s.sh_material_ggxh AS Product,
  549. RIGHT(s.xh, 4) AS Carton,
  550. s.po_billno AS OrdNbr,
  551. s.bzsl AS PackingQty,
  552. s.sh_delivery_quantity AS Qty,
  553. NULL AS Location,
  554. 'U' AS Status,
  555. s.gysdm AS Supply,
  556. s.shpc AS LotSerial,
  557. 1 AS CartonQty,
  558. s.xh AS BarCode,
  559. NULL AS MoldNum,
  560. s.scph AS SuppLotSerial,
  561. s.shdh AS ShipperNbr,
  562. s.shdhh AS ShipperLine,
  563. STR_TO_DATE(s.scrq, '%Y-%m-%d') AS ProdDate,
  564. s.po_billno AS PurOrd,
  565. s.po_billline AS PurLine,
  566. s.sh_delivery_quantity AS PurQty,
  567. NULL AS WorkOrd,
  568. 'cl01' AS LabelFormat,
  569. s.po_billno AS StandItem,
  570. s.po_billline AS EffSize,
  571. s.sh_delivery_quantity AS GP12CheckedQty,
  572. 0 AS NetWeight,
  573. s.remarks AS Remark,
  574. NOW() AS CreateTime,
  575. NOW() AS UpdateTime,
  576. @u AS CreateUser,
  577. @u AS UpdateUser,
  578. s.bbh AS LevelChar,
  579. s.jhdbh AS PurOrdDetBatchNbr,
  580. p.ActiveRlseID1 AS FirmString5,
  581. CASE
  582. WHEN i.SuppWarranty = 1 THEN
  583. CASE
  584. WHEN UPPER(i.WarrantyCode)='D' THEN DATE_ADD(STR_TO_DATE(s.scrq, '%Y-%m-%d'), INTERVAL IFNULL(i.DaysBetweenPM,0) DAY)
  585. WHEN UPPER(i.WarrantyCode)='Y' THEN DATE_ADD(STR_TO_DATE(s.scrq, '%Y-%m-%d'), INTERVAL IFNULL(i.DaysBetweenPM,0) YEAR)
  586. WHEN UPPER(i.WarrantyCode)='M' THEN DATE_ADD(STR_TO_DATE(s.scrq, '%Y-%m-%d'), INTERVAL IFNULL(i.DaysBetweenPM,0) MONTH)
  587. ELSE DATE_ADD(STR_TO_DATE(s.scrq, '%Y-%m-%d'), INTERVAL IFNULL(i.DaysBetweenPM,0) DAY)
  588. END
  589. ELSE NULL
  590. END AS ExpireDate,
  591. '' AS Printer,
  592. '' AS Company,
  593. '' AS Checker,
  594. '' AS Position,
  595. @TenantId AS tenant_id
  596. FROM scm_shbq s
  597. LEFT JOIN PurOrdDetail p
  598. ON p.PurOrd = s.po_billno AND p.Line = s.po_billline
  599. AND p.tenant_id = @TenantId
  600. LEFT JOIN ItemMaster i
  601. ON i.ItemNum = s.sh_material_code
  602. AND i.tenant_id = @TenantId
  603. WHERE s.shdh = @shddh
  604. AND s.tenant_id = @TenantId;
  605. """,
  606. new List<SugarParameter>
  607. {
  608. new("@domain", domain),
  609. new("@u", userName),
  610. new("@shddh", shddh),
  611. new("@TenantId", tenantId),
  612. });
  613. });
  614. if (!tran.IsSuccess)
  615. {
  616. var msg = $"生成标签失败,{tran.ErrorMessage}";
  617. return new { success = false, msg, message = msg };
  618. }
  619. // 拆库后 WMS 读 165,采购链须随标签一并回写,否则扫码收货查不到订单
  620. var warnings = new List<string>();
  621. await _purOrdWmsPush.TryEnqueueShipmentChainSafeAsync(tenantId, shddh, warnings);
  622. var ok = "生成标签成功!";
  623. return new { success = true, msg = ok, message = ok, warnings };
  624. }
  625. finally
  626. {
  627. if (!string.IsNullOrWhiteSpace(lockKey))
  628. {
  629. await ReleaseMySqlLockAsync(lockKey);
  630. }
  631. }
  632. }
  633. private async Task InsertLabelAsync(string shddh, LabelSourceRow row, decimal qty, int xh, string gysdm, string gysmc, long tenantId)
  634. {
  635. var jhdhh3 = Right(row.Jhdhh, 3);
  636. var xh4 = Right(xh.ToString(), 4);
  637. var bar = $"{shddh}{jhdhh3}{xh4}";
  638. await _db.Ado.ExecuteCommandAsync(
  639. """
  640. INSERT INTO scm_shbq
  641. (glid, sh_material_code, sh_material_name, sh_material_ggxh, sh_delivery_quantity,
  642. sh_material_dw, remarks, bzsl, order_type, po_billno, shdh, shdhh, scrq, scph, xh,
  643. gysdm, gysmc, po_billline, bbh, th, yt, ccrq, shpc, jhdbh, jhdhh, tenant_id)
  644. VALUES
  645. (@glid, @wlbm, @wlmc, @ggxh, @qty,
  646. @dw, @bz, @bzsl, @ddlx, @ddh, @shdh, @hh, @scrq, @scph, @xh,
  647. @gysdm, @gysmc, @pohh, @bbh, @th, @yt, @ccrq, @shpc, @jhdbh, @jhdhh, @TenantId);
  648. """,
  649. new List<SugarParameter>
  650. {
  651. new("@glid", row.DetailId),
  652. new("@wlbm", row.Wlbm ?? string.Empty),
  653. new("@wlmc", row.Wlmc ?? string.Empty),
  654. new("@ggxh", row.Ggxh ?? string.Empty),
  655. new("@qty", qty),
  656. new("@dw", row.Dw ?? string.Empty),
  657. new("@bz", row.Bz ?? string.Empty),
  658. new("@bzsl", row.Bzsl ?? 0m),
  659. new("@ddlx", row.Ddlx ?? string.Empty),
  660. new("@ddh", row.Ddh ?? string.Empty),
  661. new("@shdh", shddh),
  662. new("@hh", row.Hh ?? 0),
  663. new("@scrq", row.Scrq ?? string.Empty),
  664. new("@scph", row.Scph ?? string.Empty),
  665. new("@xh", bar),
  666. new("@gysdm", gysdm),
  667. new("@gysmc", gysmc),
  668. new("@pohh", row.PoBillLine ?? 0),
  669. new("@bbh", row.Bbh ?? string.Empty),
  670. new("@th", row.Th ?? string.Empty),
  671. new("@yt", row.PoUsage ?? string.Empty),
  672. new("@ccrq", row.Ccrq ?? string.Empty),
  673. new("@shpc", row.Shpc ?? string.Empty),
  674. new("@jhdbh", row.Jhdbh ?? string.Empty),
  675. new("@jhdhh", row.Jhdhh ?? string.Empty),
  676. new("@TenantId", tenantId),
  677. });
  678. }
  679. private static string Right(string? s, int len)
  680. {
  681. if (string.IsNullOrEmpty(s)) return new string('0', len);
  682. var t = s.Trim();
  683. return t.Length <= len ? t.PadLeft(len, '0') : t[^len..];
  684. }
  685. private async Task AcquireMySqlLockAsync(string key, int timeoutSeconds)
  686. {
  687. var ok = await _db.Ado.GetIntAsync("SELECT GET_LOCK(@k, @t)", new List<SugarParameter>
  688. {
  689. new("@k", key),
  690. new("@t", timeoutSeconds),
  691. });
  692. if (ok != 1)
  693. throw Oops.Oh($"生成批次号锁等待超时({key})");
  694. }
  695. private Task ReleaseMySqlLockAsync(string key)
  696. => _db.Ado.ExecuteCommandAsync("SELECT RELEASE_LOCK(@k)", new List<SugarParameter> { new("@k", key) });
  697. /// <summary>
  698. /// 发货单号:供应商编码-yyyyMMdd-四位流水(同日同供应商递增,保存时分配)。
  699. /// </summary>
  700. private async Task<string> AllocateShddhAsync(string supplierCode)
  701. {
  702. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  703. var code = (supplierCode ?? string.Empty).Trim().Replace("-", string.Empty);
  704. if (string.IsNullOrEmpty(code))
  705. throw Oops.Oh("请先填写供应商编号,保存时将用于生成发货单号");
  706. var date = DateTime.Now.ToString("yyyyMMdd");
  707. var prefix = $"{code}-{date}-";
  708. if (prefix.Length > 240)
  709. throw Oops.Oh("供应商编码过长,无法生成发货单号");
  710. var lockKey = $"scm_shddh_{code}_{date}";
  711. if (lockKey.Length > 64)
  712. lockKey = lockKey[..64];
  713. await AcquireMySqlLockAsync(lockKey, 15);
  714. try
  715. {
  716. var maxSeq = await _db.Ado.GetIntAsync(
  717. """
  718. SELECT IFNULL(MAX(CAST(RIGHT(shddh, 4) AS UNSIGNED)), 0)
  719. FROM scm_shd
  720. WHERE shddh LIKE CONCAT(@pfx, '%')
  721. AND CHAR_LENGTH(shddh) = CHAR_LENGTH(@pfx) + 4
  722. AND RIGHT(shddh, 4) REGEXP '^[0-9]{4}$'
  723. AND tenant_id = @TenantId
  724. """,
  725. new List<SugarParameter> { new("@pfx", prefix), new("@TenantId", tenantId) });
  726. var next = maxSeq + 1;
  727. if (next > 9999)
  728. throw Oops.Oh("当日该供应商发货单流水号已超过 9999");
  729. return $"{prefix}{next:D4}";
  730. }
  731. finally
  732. {
  733. await ReleaseMySqlLockAsync(lockKey);
  734. }
  735. }
  736. private async Task<int> GetNextDailySerialAsync(string prefix, string domain)
  737. {
  738. // 从已存在的批次号中取当天最大流水:yyMMdd + 3位
  739. // 优先 scm_shdzb,其次 scm_shbq(兼容历史数据来源)
  740. var max1 = await _db.Ado.GetStringAsync(
  741. "SELECT MAX(shpc) FROM scm_shdzb WHERE shpc LIKE @pfx",
  742. new List<SugarParameter> { new("@pfx", $"{prefix}%") });
  743. var max2 = await _db.Ado.GetStringAsync(
  744. "SELECT MAX(shpc) FROM scm_shbq WHERE shpc LIKE @pfx",
  745. new List<SugarParameter> { new("@pfx", $"{prefix}%") });
  746. var max = string.CompareOrdinal(max1 ?? string.Empty, max2 ?? string.Empty) >= 0 ? max1 : max2;
  747. if (string.IsNullOrWhiteSpace(max) || max!.Length < prefix.Length + ShpcSerialWidth)
  748. {
  749. // 兜底:读取 NbrControl(NbrType='WoLot')的 NextValue + 1
  750. // 说明:有些环境可能会清理历史批次号表;此时用 NbrControl 保证连续。
  751. var nv = await _db.Ado.GetIntAsync(
  752. """
  753. SELECT IFNULL(NextValue, 0)
  754. FROM NbrControl
  755. WHERE NbrType='WoLot'
  756. AND (
  757. domain_code = @d
  758. OR Domain = @d
  759. OR IFNULL(@d,'') = ''
  760. )
  761. ORDER BY RecID
  762. LIMIT 1
  763. """,
  764. new List<SugarParameter> { new("@d", domain ?? string.Empty) });
  765. // 用户要求:NextValue+1 为下一流水号起点
  766. return Math.Max(1, nv + 1);
  767. }
  768. var suffix = max.Substring(prefix.Length);
  769. return int.TryParse(suffix, out var n) ? n + 1 : 1;
  770. }
  771. private async Task BumpNbrControlNextValueAsync(string domain, int lastSerial)
  772. {
  773. // 保守推进:仅当新值更大时更新,避免其它流程并发推进导致回退。
  774. await _db.Ado.ExecuteCommandAsync(
  775. """
  776. UPDATE NbrControl
  777. SET NextValue = CASE
  778. WHEN IFNULL(NextValue, 0) < @v THEN @v
  779. ELSE IFNULL(NextValue, 0)
  780. END,
  781. UpdateTime = NOW()
  782. WHERE NbrType = 'WoLot'
  783. AND (
  784. domain_code = @d
  785. OR Domain = @d
  786. OR IFNULL(@d,'') = ''
  787. );
  788. """,
  789. new List<SugarParameter>
  790. {
  791. new("@d", domain ?? string.Empty),
  792. new("@v", lastSerial),
  793. });
  794. }
  795. private async Task<bool> TableExistsAsync(string tableName)
  796. {
  797. var cnt = await _db.Ado.GetIntAsync(
  798. """
  799. SELECT COUNT(*)
  800. FROM information_schema.tables
  801. WHERE table_schema = DATABASE()
  802. AND table_name = @t
  803. """,
  804. new List<SugarParameter> { new("@t", tableName) });
  805. return cnt > 0;
  806. }
  807. private sealed class NeedShpcRow
  808. {
  809. public string? Wlbm { get; set; }
  810. public string? Scph { get; set; }
  811. }
  812. private sealed class IdOnlyRow
  813. {
  814. public long Id { get; set; }
  815. }
  816. private sealed class LabelSourceRow
  817. {
  818. public string? Shddh { get; set; }
  819. public long DetailId { get; set; }
  820. public string? Glid { get; set; }
  821. public string? Wlbm { get; set; }
  822. public string? Wlmc { get; set; }
  823. public string? Ggxh { get; set; }
  824. public decimal? Shsl { get; set; }
  825. public string? Dw { get; set; }
  826. public string? Bz { get; set; }
  827. public decimal? Bzsl { get; set; }
  828. public decimal? Bqsl { get; set; }
  829. public string? Ddlx { get; set; }
  830. public string? Ddh { get; set; }
  831. public int? PoBillLine { get; set; }
  832. public int? Hh { get; set; }
  833. public string? Scrq { get; set; }
  834. public string? Scph { get; set; }
  835. public string? Th { get; set; }
  836. public string? Bbh { get; set; }
  837. public string? PoUsage { get; set; }
  838. public string? Ccrq { get; set; }
  839. public string? Jhdbh { get; set; }
  840. public string? Jhdhh { get; set; }
  841. public string? Shpc { get; set; }
  842. }
  843. [DisplayName("送货单打印数据(模板:送货单)")]
  844. [HttpGet("supplier-shipment/shipping-note-data")]
  845. public async Task<object> GetShippingNotePrintData([FromQuery] string shddh)
  846. {
  847. if (string.IsNullOrWhiteSpace(shddh))
  848. throw Oops.Oh("缺少发货单编号(shddh)");
  849. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  850. var pars = new List<SugarParameter> { new("@shddh", shddh.Trim()), new("@TenantId", tenantId) };
  851. const string masterSql = """
  852. SELECT
  853. id,
  854. sh_purchase_id,
  855. sh_purchase_name AS gysmc,
  856. sh_purchase_num AS gysdm,
  857. sh_purchase_address,
  858. sh_purchase_lxr,
  859. sh_purchase_phone,
  860. client,
  861. delivery_Address,
  862. expected_consignee,
  863. consignee_phone,
  864. estimated_delivery_date,
  865. po_billno,
  866. shddh,
  867. jhshrq,
  868. tjrid,
  869. tjrxm,
  870. tjrq,
  871. scbq,
  872. wlsc,
  873. yjdhrq,
  874. wldh,
  875. sfpc,
  876. pcsm,
  877. chbg
  878. FROM scm_shd
  879. WHERE shddh = @shddh
  880. AND tenant_id = @TenantId
  881. LIMIT 1
  882. """;
  883. const string detailSql = """
  884. SELECT
  885. a.shddh,
  886. b.hh AS xh,
  887. b.po_bill AS ddbh,
  888. c.`Usage` AS ddlx,
  889. b.order_type AS lx1,
  890. b.sh_material_code AS wlbm,
  891. b.sh_material_name AS wlmc,
  892. b.sh_material_ggxh AS ggxh,
  893. b.sh_delivery_quantity AS shsl,
  894. b.sh_material_dw AS dw,
  895. b.remarks AS bz,
  896. b.scrq,
  897. b.scph,
  898. b.th
  899. FROM scm_shd a
  900. INNER JOIN scm_shdzb b ON CAST(a.id AS CHAR) = b.glid AND b.tenant_id = a.tenant_id
  901. LEFT JOIN (
  902. SELECT
  903. i.Drawing AS th,
  904. d.Rev AS bbh,
  905. d.Line,
  906. p.PurOrd,
  907. p.`Usage`
  908. FROM PurOrdMaster p
  909. LEFT JOIN PurOrdDetail d ON p.Potype = d.Potype AND p.PurOrd = d.PurOrd AND d.tenant_id = p.tenant_id
  910. LEFT JOIN ItemMaster i ON d.ItemNum = i.ItemNum AND (i.tenant_id = p.tenant_id OR p.tenant_id IS NULL)
  911. WHERE p.IsActive = 1
  912. AND p.tenant_id = @TenantId
  913. ) c ON c.PurOrd = b.po_bill AND CAST(c.Line AS CHAR) = TRIM(CAST(IFNULL(b.po_billline, '') AS CHAR))
  914. WHERE a.shddh = @shddh
  915. AND a.tenant_id = @TenantId
  916. ORDER BY b.hh, b.id
  917. """;
  918. var masterRows = await _db.Ado.SqlQueryAsync<ShippingNoteMasterRow>(masterSql, pars);
  919. var master = masterRows.FirstOrDefault();
  920. if (master == null)
  921. throw Oops.Oh($"发货单不存在:{shddh}");
  922. var list = await _db.Ado.SqlQueryAsync<ShippingNoteDetailRow>(detailSql, pars);
  923. if (list.Count == 0)
  924. throw Oops.Oh($"发货单无明细,无法打印:{shddh}");
  925. return new
  926. {
  927. id = master.id,
  928. sh_purchase_id = master.sh_purchase_id,
  929. gysmc = master.gysmc,
  930. gysdm = master.gysdm,
  931. sh_purchase_address = master.sh_purchase_address,
  932. sh_purchase_lxr = master.sh_purchase_lxr,
  933. sh_purchase_phone = master.sh_purchase_phone,
  934. client = master.client,
  935. delivery_Address = master.delivery_Address,
  936. expected_consignee = master.expected_consignee,
  937. consignee_phone = master.consignee_phone,
  938. estimated_delivery_date = master.estimated_delivery_date,
  939. po_billno = master.po_billno,
  940. shddh = master.shddh,
  941. jhshrq = master.jhshrq,
  942. tjrid = master.tjrid,
  943. tjrxm = master.tjrxm,
  944. tjrq = master.tjrq,
  945. scbq = master.scbq,
  946. wlsc = master.wlsc,
  947. yjdhrq = master.yjdhrq,
  948. wldh = master.wldh,
  949. sfpc = master.sfpc,
  950. pcsm = master.pcsm,
  951. chbg = master.chbg,
  952. list,
  953. items = list,
  954. details = list,
  955. table = list
  956. };
  957. }
  958. [DisplayName("打印送货单(预留)")]
  959. [HttpPost("supplier-shipment/print-shipping-note")]
  960. public Task<object> PrintShippingNote([FromBody] SupplierShipmentOperationInput input)
  961. => Task.FromResult<object>(new { message = $"请使用 GET supplier-shipment/shipping-note-data 与打印模板「送货单」" });
  962. [DisplayName("打印标签(预留)")]
  963. [HttpPost("supplier-shipment/print-label")]
  964. public Task<object> PrintLabel([FromBody] SupplierShipmentOperationInput input)
  965. => Task.FromResult<object>(new { message = $"发货单{input.Id}:功能预留,暂未启用" });
  966. [DisplayName("发货单标签数据(scm_shbq)")]
  967. [HttpGet("supplier-shipment/label-data")]
  968. public async Task<object> GetLabelData([FromQuery] string shddh)
  969. {
  970. if (string.IsNullOrWhiteSpace(shddh))
  971. throw Oops.Oh("缺少发货单编号(shddh)");
  972. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  973. var pars = new List<SugarParameter> { new("@shddh", shddh.Trim()), new("@TenantId", tenantId) };
  974. const string sql = """
  975. SELECT DISTINCT
  976. glid,
  977. sh_material_code AS wlbm,
  978. sh_material_name AS wlmc,
  979. CONCAT(IFNULL(sh_material_name, ''), IFNULL(sh_material_ggxh, '')) AS ggxh,
  980. sh_delivery_quantity AS zxsl,
  981. sh_material_dw AS dw,
  982. remarks,
  983. bzsl AS bz,
  984. order_type AS ddlx,
  985. po_billno AS ddh,
  986. shdh AS shdh,
  987. shdhh AS shdhh,
  988. scrq,
  989. scph,
  990. xh,
  991. gysmc,
  992. th,
  993. bbh,
  994. yt,
  995. ccrq,
  996. shpc
  997. FROM scm_shbq
  998. WHERE shdh = @shddh
  999. AND tenant_id = @TenantId
  1000. """;
  1001. var list = await _db.Ado.SqlQueryAsync<SupplierShipmentLabelRow>(sql, pars);
  1002. return new { list };
  1003. }
  1004. private async Task SaveDetailsAsync(long masterId, List<SupplierShipmentDetailInput> inputDetails)
  1005. {
  1006. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  1007. var dbDetails = await _detailRep.AsQueryable().Where(x => x.Glid == masterId.ToString() && x.TenantId == tenantId).ToListAsync();
  1008. var dbById = dbDetails.ToDictionary(x => x.Id);
  1009. var inputIds = new HashSet<long>(inputDetails.Where(d => d.Id is > 0).Select(d => d.Id!.Value));
  1010. for (var i = 0; i < inputDetails.Count; i++)
  1011. {
  1012. var d = inputDetails[i];
  1013. if (d.Id is > 0 && dbById.TryGetValue(d.Id.Value, out var existing))
  1014. {
  1015. existing.Hh = d.Hh ?? (i + 1);
  1016. existing.PoBill = d.PoBill;
  1017. existing.PoBillLine = d.PoBillLine;
  1018. existing.OrderType = d.OrderType;
  1019. existing.ShMaterialCode = d.ShMaterialCode;
  1020. existing.ShMaterialName = d.ShMaterialName;
  1021. existing.Th = d.Th;
  1022. existing.ShDeliveryQuantity = d.ShDeliveryQuantity;
  1023. existing.Bzsl = d.Bzsl;
  1024. existing.Bqsl = d.Bqsl;
  1025. existing.ShMaterialDw = d.ShMaterialDw;
  1026. existing.Scrq = d.Scrq;
  1027. existing.Scph = d.Scph;
  1028. existing.Remarks = d.Remarks;
  1029. existing.Djsl = d.Djsl;
  1030. existing.Jybb = d.Jybb;
  1031. existing.Jhdbh = d.Jhdbh;
  1032. await _detailRep.UpdateAsync(existing);
  1033. }
  1034. else
  1035. {
  1036. var detail = new ScmShdzb
  1037. {
  1038. Id = YitIdHelper.NextId(),
  1039. Glid = masterId.ToString(),
  1040. Hh = d.Hh ?? (i + 1),
  1041. PoBill = d.PoBill,
  1042. PoBillLine = d.PoBillLine,
  1043. OrderType = d.OrderType,
  1044. ShMaterialCode = d.ShMaterialCode,
  1045. ShMaterialName = d.ShMaterialName,
  1046. Th = d.Th,
  1047. ShDeliveryQuantity = d.ShDeliveryQuantity,
  1048. Bzsl = d.Bzsl,
  1049. Bqsl = d.Bqsl,
  1050. ShMaterialDw = d.ShMaterialDw,
  1051. Scrq = d.Scrq,
  1052. Scph = d.Scph,
  1053. Remarks = d.Remarks,
  1054. Djsl = d.Djsl,
  1055. Jybb = d.Jybb,
  1056. Jhdbh = d.Jhdbh
  1057. };
  1058. await _detailRep.InsertAsync(detail);
  1059. }
  1060. }
  1061. foreach (var old in dbDetails.Where(x => !inputIds.Contains(x.Id)))
  1062. {
  1063. await _detailRep.DeleteAsync(x => x.Id == old.Id);
  1064. }
  1065. }
  1066. private static string BuildListOrderBy(string? sortField, string? sortOrder)
  1067. {
  1068. var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
  1069. {
  1070. ["shddh"] = "t.Shddh",
  1071. ["poBill"] = "t.PoBill",
  1072. ["poUsage"] = "t.PoUsage",
  1073. ["jhshrq"] = "t.Jhshrq",
  1074. ["shMaterialCode"] = "t.ShMaterialCode",
  1075. ["shMaterialName"] = "t.ShMaterialName",
  1076. ["shDeliveryQuantity"] = "t.ShDeliveryQuantity",
  1077. ["sfpc"] = "t.Sfpc",
  1078. ["pcrksl"] = "t.Pcrksl",
  1079. ["shPurchaseName"] = "t.ShPurchaseName",
  1080. ["shpc"] = "t.Shpc",
  1081. ["scph"] = "t.Scph",
  1082. ["wldh"] = "t.Wldh",
  1083. ["dycs"] = "t.Dycs",
  1084. ["shzt"] = "t.Shzt",
  1085. ["th"] = "t.Th"
  1086. };
  1087. var field = map.TryGetValue(sortField ?? string.Empty, out var sqlField) ? sqlField : "t.mid";
  1088. var order = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  1089. return $" ORDER BY {field} {order} ";
  1090. }
  1091. private static string BuildListSql() => """
  1092. SELECT
  1093. m.mid AS Mid,
  1094. m.id AS Id,
  1095. m.sh_purchase_name AS ShPurchaseName,
  1096. m.sh_purchase_num AS ShPurchaseNum,
  1097. m.shddh AS Shddh,
  1098. m.jhshrq AS Jhshrq,
  1099. m.sfpc AS Sfpc,
  1100. m.wldh AS Wldh,
  1101. m.dycs AS Dycs,
  1102. m.shzt AS Shzt,
  1103. m.sh_material_code AS ShMaterialCode,
  1104. m.sh_material_name AS ShMaterialName,
  1105. m.sh_delivery_quantity AS ShDeliveryQuantity,
  1106. l.pcrksl AS Pcrksl,
  1107. m.po_bill AS PoBill,
  1108. po.`Usage` AS PoUsage,
  1109. m.shpc AS Shpc,
  1110. m.scph AS Scph,
  1111. m.th AS Th,
  1112. m.state AS State
  1113. FROM
  1114. (
  1115. SELECT
  1116. a.id mid,
  1117. b.id,
  1118. a.sh_purchase_id,
  1119. a.sh_purchase_name,
  1120. a.sh_purchase_num,
  1121. a.sh_purchase_address,
  1122. a.sh_purchase_lxr,
  1123. a.sh_purchase_phone,
  1124. a.client,
  1125. a.delivery_Address,
  1126. a.expected_consignee,
  1127. a.consignee_phone,
  1128. a.estimated_delivery_date,
  1129. a.po_billno,
  1130. a.shddh,
  1131. a.jhshrq,
  1132. a.tjrid,
  1133. a.tjrxm,
  1134. a.tjrq,
  1135. a.scbq,
  1136. a.chbg,
  1137. a.sfpc,
  1138. a.pcsm,
  1139. a.wlsc,
  1140. a.yjdhrq,
  1141. a.state,
  1142. IFNULL(a.shzt, '待收') shzt,
  1143. a.wldh,
  1144. a.dycs,
  1145. CONCAT(IFNULL(a.sh_purchase_num, ''), IFNULL(a.sh_purchase_name, '')) gys,
  1146. b.sh_material_code,
  1147. b.sh_material_name,
  1148. b.sh_material_ggxh,
  1149. b.hh,
  1150. b.po_bill,
  1151. c.shpc,
  1152. b.scph,
  1153. b.sh_delivery_quantity,
  1154. b.th
  1155. FROM scm_shd a
  1156. LEFT JOIN scm_shdzb b ON CAST(a.id AS CHAR) = b.glid
  1157. LEFT JOIN (SELECT DISTINCT glid, shpc FROM scm_shbq) c ON CAST(b.id AS CHAR) = c.glid
  1158. WHERE a.tenant_id = @TenantId
  1159. ) m
  1160. LEFT JOIN
  1161. (
  1162. SELECT
  1163. Delivery,
  1164. LotSerial pc,
  1165. SUM(ReceiptQty) zshl,
  1166. AVG(yssl) rksl,
  1167. SUM(QtyReturn) bhgsl,
  1168. SUM(QtyReturned) thsl
  1169. FROM vscm_cgshrk
  1170. GROUP BY Delivery, LotSerial
  1171. ) n ON m.shddh = n.Delivery AND m.shpc = n.pc
  1172. LEFT JOIN
  1173. (
  1174. SELECT lot_serial AS LotSerial, SUM(qty_change) pcrksl
  1175. FROM mdp_std_inv_trans
  1176. WHERE tenant_id = @TenantId
  1177. AND qty_change > 0
  1178. AND IFNULL(reason, '') LIKE '%收货'
  1179. GROUP BY lot_serial
  1180. ) l ON m.shpc = l.LotSerial
  1181. LEFT JOIN PurOrdMaster po ON m.po_bill = po.PurOrd
  1182. """;
  1183. private sealed class SupplierShipmentListRow
  1184. {
  1185. public long Mid { get; set; }
  1186. public long Id { get; set; }
  1187. public string? ShPurchaseName { get; set; }
  1188. public string? ShPurchaseNum { get; set; }
  1189. public string? Shddh { get; set; }
  1190. public string? Jhshrq { get; set; }
  1191. public int? Sfpc { get; set; }
  1192. public string? Wldh { get; set; }
  1193. public int? Dycs { get; set; }
  1194. public string? Shzt { get; set; }
  1195. public string? ShMaterialCode { get; set; }
  1196. public string? ShMaterialName { get; set; }
  1197. public decimal? ShDeliveryQuantity { get; set; }
  1198. public decimal? Pcrksl { get; set; }
  1199. public string? PoBill { get; set; }
  1200. public string? PoUsage { get; set; }
  1201. public string? Shpc { get; set; }
  1202. public string? Scph { get; set; }
  1203. public string? Th { get; set; }
  1204. public int? State { get; set; }
  1205. }
  1206. private sealed class SupplierShipmentDraftRow
  1207. {
  1208. public string? SourceId { get; set; }
  1209. public string? Gysdm { get; set; }
  1210. public string? Gysmc { get; set; }
  1211. public string? PoBill { get; set; }
  1212. public int? PoBillLine { get; set; }
  1213. public string? OrderType { get; set; }
  1214. public string? ShMaterialCode { get; set; }
  1215. public string? ShMaterialName { get; set; }
  1216. public string? Th { get; set; }
  1217. public decimal? ShDeliveryQuantity { get; set; }
  1218. public decimal? Bzsl { get; set; }
  1219. public decimal? Bqsl { get; set; }
  1220. public string? ShMaterialDw { get; set; }
  1221. public decimal? Djsl { get; set; }
  1222. public string? Jhdbh { get; set; }
  1223. public string? ShMaterialGgxh { get; set; }
  1224. }
  1225. private sealed class ShippingNoteMasterRow
  1226. {
  1227. public long id { get; set; }
  1228. public long? sh_purchase_id { get; set; }
  1229. public string? gysmc { get; set; }
  1230. public string? gysdm { get; set; }
  1231. public string? sh_purchase_address { get; set; }
  1232. public string? sh_purchase_lxr { get; set; }
  1233. public string? sh_purchase_phone { get; set; }
  1234. public long? client { get; set; }
  1235. public string? delivery_Address { get; set; }
  1236. public string? expected_consignee { get; set; }
  1237. public string? consignee_phone { get; set; }
  1238. public DateTime? estimated_delivery_date { get; set; }
  1239. public string? po_billno { get; set; }
  1240. public string? shddh { get; set; }
  1241. public string? jhshrq { get; set; }
  1242. public string? tjrid { get; set; }
  1243. public string? tjrxm { get; set; }
  1244. public string? tjrq { get; set; }
  1245. public int? scbq { get; set; }
  1246. public string? wlsc { get; set; }
  1247. public string? yjdhrq { get; set; }
  1248. public string? wldh { get; set; }
  1249. public int? sfpc { get; set; }
  1250. public string? pcsm { get; set; }
  1251. public string? chbg { get; set; }
  1252. }
  1253. private sealed class ShippingNoteDetailRow
  1254. {
  1255. public string? shddh { get; set; }
  1256. public int? xh { get; set; }
  1257. public string? ddbh { get; set; }
  1258. public string? ddlx { get; set; }
  1259. public string? lx1 { get; set; }
  1260. public string? wlbm { get; set; }
  1261. public string? wlmc { get; set; }
  1262. public string? ggxh { get; set; }
  1263. public decimal? shsl { get; set; }
  1264. public string? dw { get; set; }
  1265. public string? bz { get; set; }
  1266. public string? scrq { get; set; }
  1267. public string? scph { get; set; }
  1268. public string? th { get; set; }
  1269. }
  1270. private sealed class SupplierShipmentLabelRow
  1271. {
  1272. public string? Glid { get; set; }
  1273. public string? Wlbm { get; set; }
  1274. public string? Wlmc { get; set; }
  1275. public string? Ggxh { get; set; }
  1276. public decimal? Zxsl { get; set; }
  1277. public string? Dw { get; set; }
  1278. public string? Remarks { get; set; }
  1279. public decimal? Bz { get; set; }
  1280. public string? Ddlx { get; set; }
  1281. public string? Ddh { get; set; }
  1282. public string? Shdh { get; set; }
  1283. public int? Shdhh { get; set; }
  1284. public string? Scrq { get; set; }
  1285. public string? Scph { get; set; }
  1286. public string? Xh { get; set; }
  1287. public string? Gysmc { get; set; }
  1288. public string? Th { get; set; }
  1289. public string? Bbh { get; set; }
  1290. public string? Yt { get; set; }
  1291. public string? Ccrq { get; set; }
  1292. public string? Shpc { get; set; }
  1293. }
  1294. }