ContractReviewService.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. namespace Admin.NET.Plugin.AiDOP.Order;
  2. /// <summary>
  3. /// 合同评审服务 📋
  4. /// 路由前缀:/api/Order/contract/...
  5. /// 审批:由审批流插件(BizType=CONTRACT_REVIEW)+ 前端 ApprovalPanel 发起与办理;本服务仅保留列表/详情/保存/删除。
  6. /// </summary>
  7. [ApiDescriptionSettings(Order = 250, Description = "合同评审")]
  8. [Route("api/Order")]
  9. [AllowAnonymous]
  10. [NonUnify]
  11. public class ContractReviewService : IDynamicApiController, ITransient
  12. {
  13. private readonly ISqlSugarClient _db;
  14. private readonly SqlSugarRepository<ContractReview> _reviewRep;
  15. private readonly SqlSugarRepository<ContractReviewFlow> _flowRep;
  16. private readonly UserManager _userManager;
  17. public ContractReviewService(
  18. ISqlSugarClient db,
  19. SqlSugarRepository<ContractReview> reviewRep,
  20. SqlSugarRepository<ContractReviewFlow> flowRep,
  21. UserManager userManager)
  22. {
  23. _db = db;
  24. _reviewRep = reviewRep;
  25. _flowRep = flowRep;
  26. _userManager = userManager;
  27. }
  28. // ══════════════════════════════════════════════════════════════
  29. // 列表 GET /api/Order/contract/list
  30. // ══════════════════════════════════════════════════════════════
  31. /// <summary>获取合同评审分页列表 📋</summary>
  32. [DisplayName("获取合同评审列表")]
  33. [HttpGet("contract/list")]
  34. public async Task<object> GetContractReviewList([FromQuery] ContractReviewListInput input)
  35. {
  36. var tenantId = _userManager.TenantId;
  37. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  38. var conditions = new List<string> { "tenant_id = @TenantId" };
  39. if (!string.IsNullOrWhiteSpace(input.BillNo))
  40. {
  41. conditions.Add("BillNo LIKE @BillNo");
  42. pars.Add(new SugarParameter("@BillNo", $"%{input.BillNo.Trim()}%"));
  43. }
  44. if (!string.IsNullOrWhiteSpace(input.Title))
  45. {
  46. conditions.Add("Title LIKE @Title");
  47. pars.Add(new SugarParameter("@Title", $"%{input.Title.Trim()}%"));
  48. }
  49. if (!string.IsNullOrWhiteSpace(input.CustomerName))
  50. {
  51. conditions.Add("CustomerName LIKE @CustomerName");
  52. pars.Add(new SugarParameter("@CustomerName", $"%{input.CustomerName.Trim()}%"));
  53. }
  54. if (!string.IsNullOrWhiteSpace(input.FlowStatus))
  55. {
  56. conditions.Add("FlowStatus = @FlowStatus");
  57. pars.Add(new SugarParameter("@FlowStatus", input.FlowStatus.Trim()));
  58. }
  59. var whereClause = "WHERE " + string.Join(" AND ", conditions);
  60. var baseSql = $"SELECT * FROM ado_contract_review {whereClause}";
  61. var offset = (input.Page - 1) * input.PageSize;
  62. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(*) FROM ({baseSql}) AS t", pars);
  63. var list = await _db.Ado.SqlQueryAsync<ContractReviewListRow>(
  64. $"SELECT * FROM ({baseSql}) AS t ORDER BY t.RecID DESC LIMIT {input.PageSize} OFFSET {offset}",
  65. pars);
  66. return new { total, page = input.Page, pageSize = input.PageSize, list };
  67. }
  68. // ══════════════════════════════════════════════════════════════
  69. // 详情 GET /api/Order/contract/{id}
  70. // ══════════════════════════════════════════════════════════════
  71. /// <summary>获取合同评审详情(含流程节点)📋</summary>
  72. [DisplayName("获取合同评审详情")]
  73. [HttpGet("contract/{id}")]
  74. public async Task<object> GetContractReviewDetail(int id)
  75. {
  76. var tenantId = _userManager.TenantId;
  77. var review = await _reviewRep.GetFirstAsync(u => u.RecID == id && u.TenantId == tenantId)
  78. ?? throw Oops.Oh("合同评审记录不存在");
  79. var flows = await _flowRep.GetListAsync(u => u.ReviewRecID == id && u.TenantId == tenantId);
  80. return new
  81. {
  82. recID = review.RecID,
  83. billNo = review.BillNo,
  84. title = review.Title,
  85. customerName = review.CustomerName,
  86. customerNo = review.CustomerNo,
  87. salesCompany = review.SalesCompany,
  88. salesArea = review.SalesArea,
  89. projectCode = review.ProjectCode,
  90. crmNo = review.CrmNo,
  91. responsibleAccount = review.ResponsibleAccount,
  92. responsibleName = review.ResponsibleName,
  93. projectStartDate = review.ProjectStartDate?.ToString("yyyy-MM-dd"),
  94. currentStage = review.CurrentStage ?? 0,
  95. currentDept = review.CurrentDept,
  96. projectStatus = review.ProjectStatus,
  97. winRate = review.WinRate,
  98. expectedOrderMonth = review.ExpectedOrderMonth?.ToString("yyyy-MM"),
  99. expectedDeliveryDate = review.ExpectedDeliveryDate?.ToString("yyyy-MM-dd"),
  100. projectRequirement = review.ProjectRequirement,
  101. remark = review.Remark,
  102. flowStatus = review.FlowStatus ?? "draft",
  103. createUser = review.CreateUser,
  104. createTime = review.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  105. updateTime = review.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  106. flows = flows.OrderBy(f => f.StageNo).ThenBy(f => f.Seq).Select(f => new
  107. {
  108. recID = f.RecID,
  109. reviewRecID = f.ReviewRecID,
  110. reviewBillNo = f.ReviewBillNo,
  111. stageNo = f.StageNo,
  112. stageName = f.StageName,
  113. department = f.Department,
  114. deptNo = f.DeptNo,
  115. seq = f.Seq,
  116. reviewerAccount = f.ReviewerAccount,
  117. reviewerName = f.ReviewerName,
  118. opinion = f.Opinion,
  119. startTime = f.StartTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  120. completeTime = f.CompleteTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  121. actualDays = f.ActualDays,
  122. nodeStatus = f.NodeStatus ?? "pending",
  123. })
  124. };
  125. }
  126. // ══════════════════════════════════════════════════════════════
  127. // 保存(新增/编辑)POST /api/Order/contract/save
  128. // ══════════════════════════════════════════════════════════════
  129. /// <summary>保存合同评审(草稿新增或编辑)📋</summary>
  130. [DisplayName("保存合同评审")]
  131. [ApiDescriptionSettings(Name = "SaveContractReview"), HttpPost("contract/save")]
  132. public async Task<object> SaveContractReview([FromBody] ContractReviewSaveInput input)
  133. {
  134. var now = DateTime.Now;
  135. var user = _userManager.Account ?? "system";
  136. if (input.RecID is null or 0)
  137. {
  138. // ── 新增:参照 SysJobService.AddJobDetail ──
  139. var tenantId = _userManager.TenantId;
  140. var month = now.ToString("yyyyMM");
  141. var maxSeq = await _db.Ado.GetIntAsync(
  142. $"SELECT IFNULL(MAX(CAST(SUBSTRING(BillNo, 9) AS UNSIGNED)), 0) FROM ado_contract_review WHERE BillNo LIKE 'CR{month}%' AND tenant_id = @TenantId",
  143. new SugarParameter("@TenantId", tenantId));
  144. var billNo = $"CR{month}{(maxSeq + 1):D4}";
  145. var entity = new ContractReview
  146. {
  147. BillNo = billNo,
  148. Title = input.Title,
  149. CustomerName = input.CustomerName,
  150. CustomerNo = input.CustomerNo,
  151. SalesCompany = input.SalesCompany,
  152. SalesArea = input.SalesArea,
  153. ProjectCode = input.ProjectCode,
  154. CrmNo = input.CrmNo,
  155. ResponsibleAccount = input.ResponsibleAccount,
  156. ResponsibleName = input.ResponsibleName,
  157. ProjectStartDate = string.IsNullOrWhiteSpace(input.ProjectStartDate) ? null : DateTime.Parse(input.ProjectStartDate),
  158. ProjectStatus = input.ProjectStatus,
  159. WinRate = input.WinRate,
  160. ExpectedOrderMonth = string.IsNullOrWhiteSpace(input.ExpectedOrderMonth) ? null : DateTime.Parse(input.ExpectedOrderMonth + "-01"),
  161. ExpectedDeliveryDate = string.IsNullOrWhiteSpace(input.ExpectedDeliveryDate) ? null : DateTime.Parse(input.ExpectedDeliveryDate),
  162. ProjectRequirement = input.ProjectRequirement,
  163. Remark = input.Remark,
  164. FlowStatus = "draft",
  165. CurrentStage = 0,
  166. CreateUser = user,
  167. CreateTime = now,
  168. IsActive = 1,
  169. TenantId = tenantId,
  170. };
  171. await _reviewRep.InsertAsync(entity);
  172. // 取刚插入行的 RecID
  173. var newId = await _db.Ado.GetIntAsync(
  174. "SELECT MAX(RecID) FROM ado_contract_review WHERE BillNo = @BillNo",
  175. new SugarParameter("@BillNo", billNo));
  176. return new { id = newId, billNo, message = "新增成功" };
  177. }
  178. else
  179. {
  180. // ── 编辑:参照 SysJobService.UpdateJobDetail ──
  181. var tenantId = _userManager.TenantId;
  182. var entity = await _reviewRep.GetFirstAsync(u => u.RecID == input.RecID.Value && u.TenantId == tenantId)
  183. ?? throw Oops.Oh("合同评审记录不存在");
  184. if (entity.FlowStatus == "reviewing")
  185. throw Oops.Oh("审批中的合同评审不允许编辑,如需修改请先驳回");
  186. entity.Title = input.Title;
  187. entity.CustomerName = input.CustomerName;
  188. entity.CustomerNo = input.CustomerNo;
  189. entity.SalesCompany = input.SalesCompany;
  190. entity.SalesArea = input.SalesArea;
  191. entity.ProjectCode = input.ProjectCode;
  192. entity.CrmNo = input.CrmNo;
  193. entity.ResponsibleAccount = input.ResponsibleAccount;
  194. entity.ResponsibleName = input.ResponsibleName;
  195. entity.ProjectStartDate = string.IsNullOrWhiteSpace(input.ProjectStartDate) ? null : DateTime.Parse(input.ProjectStartDate);
  196. entity.ProjectStatus = input.ProjectStatus;
  197. entity.WinRate = input.WinRate;
  198. entity.ExpectedOrderMonth = string.IsNullOrWhiteSpace(input.ExpectedOrderMonth) ? null : DateTime.Parse(input.ExpectedOrderMonth + "-01");
  199. entity.ExpectedDeliveryDate = string.IsNullOrWhiteSpace(input.ExpectedDeliveryDate) ? null : DateTime.Parse(input.ExpectedDeliveryDate);
  200. entity.ProjectRequirement = input.ProjectRequirement;
  201. entity.Remark = input.Remark;
  202. entity.UpdateUser = user;
  203. entity.UpdateTime = now;
  204. await _reviewRep.UpdateAsync(entity);
  205. return new { id = entity.RecID, billNo = entity.BillNo, message = "编辑成功" };
  206. }
  207. }
  208. // ══════════════════════════════════════════════════════════════
  209. // 删除 POST /api/Order/contract/delete
  210. // ══════════════════════════════════════════════════════════════
  211. /// <summary>删除合同评审(审批中不允许删除)📋</summary>
  212. [DisplayName("删除合同评审")]
  213. [ApiDescriptionSettings(Name = "DeleteContractReview"), HttpPost("contract/delete")]
  214. public async Task<object> DeleteContractReview([FromBody] ContractReviewDeleteInput input)
  215. {
  216. // 参照 SysJobService.DeleteJobDetail:先查再删,同时删关联数据
  217. var tenantId = _userManager.TenantId;
  218. var entity = await _reviewRep.GetFirstAsync(u => u.RecID == input.RecID && u.TenantId == tenantId)
  219. ?? throw Oops.Oh("合同评审记录不存在");
  220. if (entity.FlowStatus == "reviewing")
  221. throw Oops.Oh("审批中的合同评审不允许删除");
  222. await _flowRep.DeleteAsync(u => u.ReviewRecID == input.RecID);
  223. await _reviewRep.DeleteAsync(u => u.RecID == input.RecID);
  224. return new { message = "删除成功" };
  225. }
  226. // ──────────────── 内部查询结果映射类 ────────────────
  227. private sealed class ContractReviewListRow
  228. {
  229. public int RecID { get; set; }
  230. public string? BillNo { get; set; }
  231. public string? Title { get; set; }
  232. public string? CustomerName { get; set; }
  233. public string? SalesCompany { get; set; }
  234. public int? CurrentStage { get; set; }
  235. public string? CurrentDept { get; set; }
  236. public string? FlowStatus { get; set; }
  237. public string? ResponsibleAccount { get; set; }
  238. public string? ResponsibleName { get; set; }
  239. public DateTime? CreateTime { get; set; }
  240. public DateTime? UpdateTime { get; set; }
  241. }
  242. }