T8BaseInboundMdpSyncService.NeutralProjection.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. using SqlSugar;
  2. namespace Admin.NET.Plugin.AiDOP.DataPlatform;
  3. /// <summary>
  4. /// 把 T8 形态标准层投影到中立标准层。厂商枚举只允许出现在本文件(与主服务同属 T8 适配器)。
  5. /// 指标 SQL 只读中立层。
  6. /// </summary>
  7. public sealed partial class T8BaseInboundMdpSyncService
  8. {
  9. private const string NeutralBalanceConfigId = "t8_v5";
  10. public async Task<string> ProjectNeutralAsync(
  11. long tenantId, string batchId, DateTime now, CancellationToken cancellationToken = default)
  12. {
  13. cancellationToken.ThrowIfCancellationRequested();
  14. var ready = await _db.Ado.GetIntAsync(
  15. """
  16. SELECT COUNT(*) FROM information_schema.COLUMNS
  17. WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='mdp_std_inv_trans' AND COLUMN_NAME='src_trans_type_raw'
  18. """);
  19. if (ready == 0)
  20. return "中立层未就绪,已跳过投影";
  21. var ps = new[]
  22. {
  23. new SugarParameter("@tid", tenantId),
  24. new SugarParameter("@batch", batchId),
  25. new SugarParameter("@now", now)
  26. };
  27. var steps = new (string Name, string Sql)[]
  28. {
  29. ("inv_trans", ProjectInvTransSql),
  30. ("api_inv_trans", ProjectApiInvTransSql),
  31. ("api_inv_trans_unmapped", ProjectApiInvTransUnmappedSql),
  32. ("work_order_schedule", ProjectWorkOrderScheduleSql),
  33. ("work_order_line", ProjectWorkOrderLineSql),
  34. ("work_order_bom", ProjectWorkOrderBomSql),
  35. ("employee", ProjectEmployeeSql),
  36. ("fqc", ProjectFqcSql),
  37. ("report_start", ProjectReportStartSql)
  38. };
  39. var failed = new List<string>();
  40. foreach (var step in steps)
  41. {
  42. cancellationToken.ThrowIfCancellationRequested();
  43. try
  44. {
  45. await _db.Ado.ExecuteCommandAsync(step.Sql, ps);
  46. }
  47. catch (Exception ex) when (ex is not OperationCanceledException)
  48. {
  49. failed.Add($"{step.Name}: {ex.Message}");
  50. }
  51. }
  52. return failed.Count == 0 ? "OK" : string.Join(" | ", failed);
  53. }
  54. /// <summary>
  55. /// 表值函数结果落入月度库存金额表。T8 未配置或调用失败时返回 0,不抛出,避免挡住推送现场。
  56. /// </summary>
  57. public async Task<int> TryMaterializeInventoryBalanceAsync(
  58. long tenantId, long factoryId, string domainCode, string periodYm, string batchId, DateTime now)
  59. {
  60. const string sqlTvf = @"
  61. select ckcode as ckcode, ckname as ckname,
  62. code as code, cname as cname,
  63. pcode as pcode, pname as pname,
  64. je3 as je3, je2 as je2
  65. from dbo.Rep_总账_存货_V3(@domainCode, N'普通', N'正常', @startYm, @endYm)";
  66. List<T8InventoryBalanceRow> rows;
  67. try
  68. {
  69. var t8 = _db.AsTenant().GetConnectionScope(NeutralBalanceConfigId);
  70. t8.Ado.CommandTimeOut = 60;
  71. rows = await t8.Ado.SqlQueryAsync<T8InventoryBalanceRow>(sqlTvf, new[]
  72. {
  73. new SugarParameter("@domainCode", domainCode),
  74. new SugarParameter("@startYm", periodYm),
  75. new SugarParameter("@endYm", periodYm)
  76. });
  77. }
  78. catch (Exception ex)
  79. {
  80. Console.WriteLine($"[NeutralBalance] TVF skipped: {ex.Message}");
  81. return 0;
  82. }
  83. var affected = 0;
  84. foreach (var r in rows)
  85. {
  86. var category = r.pcode ?? "";
  87. var warehouse = r.ckcode ?? "";
  88. var item = r.code ?? "";
  89. affected += await _db.Ado.ExecuteCommandAsync(
  90. """
  91. INSERT INTO mdp_std_inventory_balance_monthly
  92. (tenant_id, factory_id, source_system, domain, period_ym,
  93. category_code, category_name, warehouse_code, warehouse_name, item_code,
  94. avg_balance_amount, issue_cost_amount, source_biz_key, sync_batch_id, sync_time)
  95. VALUES
  96. (@tid, @factoryId, 'T8', @domain, @period,
  97. @category, @categoryName, @warehouse, @warehouseName, @item,
  98. @avgAmt, @issueAmt, @bizKey, @batch, @now)
  99. ON DUPLICATE KEY UPDATE
  100. category_name=VALUES(category_name), warehouse_name=VALUES(warehouse_name),
  101. avg_balance_amount=VALUES(avg_balance_amount),
  102. issue_cost_amount=VALUES(issue_cost_amount),
  103. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time)
  104. """,
  105. new SugarParameter("@tid", tenantId),
  106. new SugarParameter("@factoryId", factoryId),
  107. new SugarParameter("@domain", domainCode),
  108. new SugarParameter("@period", periodYm),
  109. new SugarParameter("@category", category),
  110. new SugarParameter("@categoryName", r.pname),
  111. new SugarParameter("@warehouse", warehouse),
  112. new SugarParameter("@warehouseName", r.ckname),
  113. new SugarParameter("@item", item),
  114. new SugarParameter("@avgAmt", r.je3 ?? 0m),
  115. new SugarParameter("@issueAmt", r.je2 ?? 0m),
  116. new SugarParameter("@bizKey", $"{domainCode}:{periodYm}:{category}:{warehouse}:{item}"),
  117. new SugarParameter("@batch", batchId),
  118. new SugarParameter("@now", now));
  119. }
  120. return affected;
  121. }
  122. private const string BizDocCase = """
  123. CASE h.lbs
  124. WHEN '采购入库' THEN 'PUR_RECEIPT'
  125. WHEN '生产领料' THEN 'PROD_ISSUE'
  126. WHEN '生产入库' THEN 'PROD_RECEIPT'
  127. WHEN '销售出库' THEN 'SALES_SHIP'
  128. WHEN '销售退货' THEN 'SALES_RETURN'
  129. ELSE 'OTHER' END
  130. """;
  131. private static readonly string ProjectInvTransSql = $@"
  132. INSERT INTO mdp_std_inv_trans
  133. (tenant_id, source_system, domain, src_rec_id, item_num, qty_change, doc_qty,
  134. trans_time, eff_date, approved_time, approved_flag, void_flag, summary_flag,
  135. biz_doc_type, src_biz_doc_type_raw, trans_type, ref_task_no,
  136. line_closed_flag, line_closed_time,
  137. history_from, as_of, sync_batch_id, dimension1, dimension2)
  138. SELECT
  139. l.tenant_id, 'T8', IFNULL(h.ztid,''), CAST(l.src_id AS CHAR),
  140. l.code, IFNULL(l.slzx,0), l.sl,
  141. l.addtime, h.date0, IFNULL(h.shtime, l.addtime),
  142. IFNULL(h.shyn,1), IFNULL(h.zfyn,0), IFNULL(h.hzyn,0),
  143. {BizDocCase}, h.lbs,
  144. CASE h.lbs
  145. WHEN '采购入库' THEN 'MAT_RECEIPT'
  146. WHEN '生产领料' THEN 'MAT_ISSUE'
  147. WHEN '生产入库' THEN 'FG_PROD_RECEIPT'
  148. WHEN '销售出库' THEN 'FG_SHIP'
  149. ELSE NULL END,
  150. l.lynoid,
  151. IFNULL(l.gdyn,0), l.gdtime,
  152. IFNULL(IFNULL(h.shtime, l.addtime), @now), @now, @batch, '', ''
  153. FROM mdp_std_t8_kc_tz_list l
  154. INNER JOIN mdp_std_t8_kc_tz_head h ON h.src_id=l.idid AND h.tenant_id=l.tenant_id
  155. WHERE l.tenant_id=@tid
  156. ON DUPLICATE KEY UPDATE
  157. item_num=VALUES(item_num), qty_change=VALUES(qty_change), doc_qty=VALUES(doc_qty),
  158. trans_time=VALUES(trans_time), eff_date=VALUES(eff_date), approved_time=VALUES(approved_time),
  159. approved_flag=VALUES(approved_flag), void_flag=VALUES(void_flag), summary_flag=VALUES(summary_flag),
  160. biz_doc_type=VALUES(biz_doc_type), src_biz_doc_type_raw=VALUES(src_biz_doc_type_raw),
  161. trans_type=VALUES(trans_type), ref_task_no=VALUES(ref_task_no),
  162. line_closed_flag=VALUES(line_closed_flag), line_closed_time=VALUES(line_closed_time),
  163. sync_batch_id=VALUES(sync_batch_id), as_of=VALUES(as_of)";
  164. private static readonly string ProjectApiInvTransSql = BuildProjectApiInvTransSql();
  165. private static string BuildProjectApiInvTransSql()
  166. {
  167. const string biz = "NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.BizDocType')),'null')";
  168. const string trans = "NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.TransType')),'null')";
  169. const string src = "COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.SrcTransType')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.BizDocType')),'null'))";
  170. return $@"
  171. INSERT INTO mdp_std_inv_trans
  172. (tenant_id, source_system, written_by, domain, src_rec_id, item_num, qty_change, location,
  173. trans_time, eff_date, approved_time, approved_flag, void_flag, summary_flag,
  174. biz_doc_type, src_biz_doc_type_raw, trans_type, src_trans_type_raw, ref_task_no,
  175. history_from, as_of, sync_batch_id, dimension1, dimension2)
  176. SELECT
  177. s.tenant_id,
  178. IFNULL(NULLIF(s.source_system,''),''),
  179. 'API_INBOUND',
  180. IFNULL(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.Domain')),'null'),''),
  181. s.source_row_id,
  182. COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.ItemCode')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.code')),'null')),
  183. CAST(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.Qty')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.sl')),'null')) AS DECIMAL(18,6)),
  184. NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.Location')),'null'),
  185. STR_TO_DATE(SUBSTRING_INDEX(REPLACE(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.TransTime')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.addtime')),'null')),'T',' '),'.',1),'%Y-%m-%d %H:%i:%s'),
  186. STR_TO_DATE(SUBSTRING_INDEX(REPLACE(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.DocDate')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.date0')),'null')),'T',' '),'.',1),'%Y-%m-%d %H:%i:%s'),
  187. IFNULL(
  188. STR_TO_DATE(SUBSTRING_INDEX(REPLACE(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.ApprovedTime')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.shtime')),'null')),'T',' '),'.',1),'%Y-%m-%d %H:%i:%s'),
  189. STR_TO_DATE(SUBSTRING_INDEX(REPLACE(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.TransTime')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.addtime')),'null')),'T',' '),'.',1),'%Y-%m-%d %H:%i:%s')),
  190. IFNULL(CAST(COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.ApprovedFlag')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.shyn')),'null')) AS SIGNED), 1),
  191. IFNULL(CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.VoidFlag')),'null') AS SIGNED), 0),
  192. IFNULL(CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.SummaryFlag')),'null') AS SIGNED), 0),
  193. {NeutralTransTypeCodes.KnownBizDocCase(biz)},
  194. {biz},
  195. {NeutralTransTypeCodes.ApiTransTypeCase(trans, biz)},
  196. {src},
  197. COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.RefTaskNo')),'null'), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.rwnoid')),'null')),
  198. IFNULL(
  199. STR_TO_DATE(SUBSTRING_INDEX(REPLACE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.ApprovedTime')),'null'),'T',' '),'.',1),'%Y-%m-%d %H:%i:%s'),
  200. @now),
  201. @now, @batch, '', ''
  202. FROM mdp_stg_t8_kc_tz_list s
  203. WHERE s.tenant_id=@tid
  204. AND s.source_row_id IS NOT NULL AND s.source_row_id<>''
  205. ON DUPLICATE KEY UPDATE
  206. item_num=VALUES(item_num), qty_change=VALUES(qty_change), location=VALUES(location),
  207. approved_time=VALUES(approved_time), approved_flag=VALUES(approved_flag),
  208. void_flag=VALUES(void_flag), summary_flag=VALUES(summary_flag),
  209. biz_doc_type=VALUES(biz_doc_type), trans_type=VALUES(trans_type),
  210. src_trans_type_raw=VALUES(src_trans_type_raw),
  211. ref_task_no=VALUES(ref_task_no), sync_batch_id=VALUES(sync_batch_id)";
  212. }
  213. private const string ProjectApiInvTransUnmappedSql = @"
  214. INSERT INTO mdp_std_inv_trans_unmapped
  215. (tenant_id, source_system, src_rec_id, src_trans_type_raw, unmapped_reason, location, row_count, first_seen, last_seen)
  216. SELECT tenant_id, source_system, src_rec_id, src_trans_type_raw, 'UNKNOWN_CODE', IFNULL(location,''), 1, NOW(), NOW()
  217. FROM mdp_std_inv_trans
  218. WHERE tenant_id=@tid AND sync_batch_id=@batch
  219. AND trans_type IS NULL AND src_trans_type_raw IS NOT NULL
  220. ON DUPLICATE KEY UPDATE last_seen=NOW(), row_count=row_count+1, unmapped_reason=VALUES(unmapped_reason), location=VALUES(location)";
  221. private const string ProjectWorkOrderScheduleSql = @"
  222. INSERT INTO mdp_std_work_order_schedule
  223. (tenant_id, factory_id, source_system, work_order, doc_type, approved_flag, void_flag,
  224. source_biz_key, sync_batch_id, sync_time)
  225. SELECT
  226. h.tenant_id, 1, 'T8', h.noid,
  227. CASE h.lbs WHEN '生产任务' THEN 'PROD_TASK' WHEN '销售订单' THEN 'SALES_ORDER' ELSE 'OTHER' END,
  228. IFNULL(h.shyn,1), IFNULL(h.zf,0),
  229. CONCAT(IFNULL(h.ztid,''), ':', h.src_id),
  230. @batch, @now
  231. FROM mdp_std_t8_kc_dd_head h
  232. WHERE h.tenant_id=@tid AND h.noid IS NOT NULL AND h.noid<>''
  233. AND NOT EXISTS (
  234. SELECT 1 FROM mdp_std_work_order_schedule w
  235. WHERE w.tenant_id=h.tenant_id AND w.work_order=h.noid AND w.source_system<>'T8'
  236. AND w.doc_type = CASE h.lbs WHEN '销售订单' THEN 'SALES_ORDER' ELSE 'PROD_TASK' END)
  237. ON DUPLICATE KEY UPDATE
  238. approved_flag=VALUES(approved_flag), void_flag=VALUES(void_flag),
  239. source_biz_key=VALUES(source_biz_key), sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time)";
  240. private const string ProjectWorkOrderLineSql = @"
  241. INSERT INTO mdp_std_work_order_line
  242. (tenant_id, factory_id, source_system, domain, doc_type, src_doc_type_raw,
  243. order_no, task_no, item_code, qty_planned, qty_completed, plan_finish_date, release_time,
  244. closed_flag, closed_time, approved_flag, void_flag, source_row_id, source_biz_key, sync_batch_id, sync_time)
  245. SELECT
  246. l.tenant_id, 1, 'T8', h.ztid,
  247. CASE h.lbs WHEN '生产任务' THEN 'PROD_TASK' WHEN '销售订单' THEN 'SALES_ORDER' ELSE 'OTHER' END,
  248. h.lbs, h.noid, l.rwnoid, l.code, l.sl, l.slzx, l.jhdate, l.addtime,
  249. IFNULL(l.gdyn,0), l.gdtime, IFNULL(h.shyn,1), IFNULL(h.zf,0),
  250. CAST(l.src_id AS CHAR), CONCAT(IFNULL(h.ztid,''), ':', l.src_id), @batch, @now
  251. FROM mdp_std_t8_kc_dd_list l
  252. INNER JOIN mdp_std_t8_kc_dd_head h ON h.src_id=l.idid AND h.tenant_id=l.tenant_id
  253. WHERE l.tenant_id=@tid AND h.noid IS NOT NULL AND h.noid<>''
  254. ON DUPLICATE KEY UPDATE
  255. doc_type=VALUES(doc_type), task_no=VALUES(task_no), item_code=VALUES(item_code),
  256. qty_planned=VALUES(qty_planned), qty_completed=VALUES(qty_completed),
  257. plan_finish_date=VALUES(plan_finish_date), release_time=VALUES(release_time),
  258. closed_flag=VALUES(closed_flag), closed_time=VALUES(closed_time),
  259. approved_flag=VALUES(approved_flag), void_flag=VALUES(void_flag),
  260. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time)";
  261. private const string ProjectWorkOrderBomSql = @"
  262. INSERT INTO mdp_std_work_order_bom
  263. (tenant_id, factory_id, source_system, domain, order_no, item_code, qty_required,
  264. source_row_id, source_biz_key, sync_batch_id, sync_time)
  265. SELECT
  266. l.tenant_id, 1, 'T8', h.ztid, h.noid, l.code, IFNULL(l.sl, 0),
  267. CAST(l.src_id AS CHAR), CONCAT(IFNULL(h.ztid,''), ':', l.src_id), @batch, @now
  268. FROM mdp_std_t8_kc_dd_list_cllist l
  269. INNER JOIN mdp_std_t8_kc_dd_head h ON h.src_id=l.idid AND h.tenant_id=l.tenant_id
  270. WHERE l.tenant_id=@tid AND h.noid IS NOT NULL AND h.noid<>''
  271. ON DUPLICATE KEY UPDATE
  272. order_no=VALUES(order_no), item_code=VALUES(item_code), qty_required=VALUES(qty_required),
  273. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time)";
  274. private const string ProjectEmployeeSql = @"
  275. INSERT INTO mdp_std_employee
  276. (tenant_id, factory_id, source_system, domain, employee_no, position_code, src_position_raw,
  277. employment_status, src_employment_status_raw, source_row_id, source_biz_key, sync_batch_id, sync_time)
  278. SELECT
  279. p.tenant_id, 1, 'T8', IFNULL(p.ztid,''), CAST(p.src_id AS CHAR),
  280. CASE p.gw WHEN '计划' THEN 'PLANNER' WHEN '仓管' THEN 'WAREHOUSE' WHEN '生产' THEN 'PRODUCTION' ELSE 'OTHER' END,
  281. p.gw,
  282. CASE WHEN p.zzzt='在职' THEN 'ACTIVE' WHEN p.zzzt LIKE '%离职%' THEN 'LEFT' ELSE 'INACTIVE' END,
  283. p.zzzt,
  284. CAST(p.src_id AS CHAR), CONCAT(IFNULL(p.ztid,''), ':', p.src_id), @batch, @now
  285. FROM mdp_std_t8_sys_pelist p
  286. WHERE p.tenant_id=@tid AND p.src_id IS NOT NULL
  287. ON DUPLICATE KEY UPDATE
  288. position_code=VALUES(position_code), src_position_raw=VALUES(src_position_raw),
  289. employment_status=VALUES(employment_status), src_employment_status_raw=VALUES(src_employment_status_raw),
  290. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time)";
  291. private const string ProjectFqcSql = @"
  292. INSERT INTO mdp_std_fqc_task
  293. (tenant_id, source_system, domain, apply_time, apply_flag, sales_order_line,
  294. source_row_id, source_biz_key, sync_batch_id, sync_time)
  295. SELECT
  296. z.tenant_id, 'T8', z.ztid, z.shdate, IFNULL(z.zjyn,0), CAST(z.lyid AS CHAR),
  297. CAST(z.src_id AS CHAR), CONCAT(IFNULL(z.ztid,''), ':', z.src_id), @batch, @now
  298. FROM mdp_std_t8_kc_zj_list z
  299. WHERE z.tenant_id=@tid AND z.src_id IS NOT NULL
  300. ON DUPLICATE KEY UPDATE
  301. domain=VALUES(domain), apply_time=VALUES(apply_time), apply_flag=VALUES(apply_flag),
  302. sales_order_line=VALUES(sales_order_line),
  303. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time)";
  304. private const string ProjectReportStartSql = @"
  305. INSERT INTO mdp_std_s6_report
  306. (tenant_id, source_system, work_order_no, start_work_date, domain,
  307. source_row_id, source_biz_key, sync_batch_id, sync_time)
  308. SELECT
  309. r.tenant_id, 'T8', r.noid, r.kgdate, r.ztid,
  310. CAST(r.src_id AS CHAR), CONCAT(IFNULL(r.ztid,''), ':', r.src_id), @batch, @now
  311. FROM mdp_std_t8_cj_bg_head_rep r
  312. WHERE r.tenant_id=@tid AND r.noid IS NOT NULL AND r.noid<>'' AND r.src_id IS NOT NULL
  313. ON DUPLICATE KEY UPDATE
  314. start_work_date=VALUES(start_work_date), domain=VALUES(domain),
  315. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time)";
  316. }
  317. internal sealed class T8InventoryBalanceRow
  318. {
  319. public string? ckcode { get; set; }
  320. public string? ckname { get; set; }
  321. public string? code { get; set; }
  322. public string? cname { get; set; }
  323. public string? pcode { get; set; }
  324. public string? pname { get; set; }
  325. public decimal? je3 { get; set; }
  326. public decimal? je2 { get; set; }
  327. }