SupplierShipmentService.cs 55 KB

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