refactor(so): 优化销售订单保存逻辑

- 添加销售订单变更检查逻辑,包括: - 销售订单总金额与实际收款金额的校验 - 行数量与金额的校验
  - 物料替换的校验
  - 删除行的校验
- 更新关联的下游单据,包括:
  - 流程生产订单
  - 收款单
  -销售发票
- 优化代码结构,提高可读性和可维护性
This commit is contained in:
mzr 2025-07-28 12:37:34 +08:00
parent 5898e36b89
commit 3046b5d6dc
1 changed files with 268 additions and 7 deletions

View File

@ -7,6 +7,7 @@ import nc.bs.dao.BaseDAO;
import nc.bs.dao.DAOException;
import nc.bs.framework.common.NCLocator;
import nc.bs.logging.Logger;
import nc.bs.trade.business.HYPubBO;
import nc.bs.trade.business.HYSuperDMO;
import nc.impl.pubapp.pattern.data.bill.BillQuery;
import nc.itf.scmpub.reference.uap.bd.customer.CustomerPubService;
@ -15,10 +16,12 @@ import nc.itf.so.m30.self.ISaleOrderMaintain;
import nc.itf.so.m30.self.ISaleOrderScriptMaintain;
import nc.itf.uap.IUAPQueryBS;
import nc.itf.uap.pf.IPFBusiAction;
import nc.jdbc.framework.SQLParameter;
import nc.jdbc.framework.processor.ColumnProcessor;
import nc.jdbc.framework.processor.MapProcessor;
import nc.pubimpl.so.m30.pub.SaleOrderSaveUtil;
import nc.pubitf.so.m30.api.ISaleOrderQueryAPI;
import nc.vo.arap.gathering.GatheringBillVO;
import nc.vo.bd.defdoc.DefdocVO;
import nc.vo.ml.NCLangRes4VoTransl;
import nc.vo.pub.BusinessException;
@ -30,6 +33,7 @@ import nc.vo.pub.lang.UFDouble;
import nc.vo.pubapp.AppContext;
import nc.vo.pubapp.calculator.HslParseUtil;
import nc.vo.pubapp.pattern.exception.ExceptionUtils;
import nc.vo.pubapp.pattern.pub.SqlBuilder;
import nc.vo.scmpub.check.billvalidate.BillVOsCheckRule;
import nc.vo.scmpub.fill.pricemny.INumPriceMnyCalculator;
import nc.vo.scmpub.res.billtype.SOBillType;
@ -38,6 +42,7 @@ import nc.vo.scmpub.util.StringUtil;
import nc.vo.so.m30.entity.SaleOrderBVO;
import nc.vo.so.m30.entity.SaleOrderHVO;
import nc.vo.so.m30.entity.SaleOrderVO;
import nc.vo.so.m32.entity.SaleInvoiceHVO;
import nc.vo.so.pub.SOConstant;
import nc.vo.so.pub.enumeration.BillStatus;
import nc.vo.so.pub.keyvalue.IKeyValue;
@ -417,7 +422,7 @@ public class APISaleOrderMaitainImpl implements IAPISaleOrderMaitain {
// 判断是否存在新增的子表
boolean hasNewStatus = Arrays.stream(bvos).anyMatch(bvo -> bvo.getStatus() == VOStatus.NEW);
// 新增子表或改订单类型的情况下不校验是否存在下游
if (!hasNewStatus && !isChangedTranType) {
/*if (!hasNewStatus && !isChangedTranType) {
String countSql = "SELECT count(1) FROM so_saleinvoice_b a"
+ " LEFT JOIN so_saleinvoice b ON a.csaleinvoiceid = b.csaleinvoiceid"
+ " WHERE b.fopposeflag = 0 AND nvl(b.dr, 0) = 0 and csrcid = '[csrcid]' ";
@ -428,17 +433,17 @@ public class APISaleOrderMaitainImpl implements IAPISaleOrderMaitain {
ExceptionUtils.wrappBusinessException("下游存在未红冲完成的销售发票");
return null;
}
}
}*/
}
fillcustomervidbyoid(combinBillVOs);
checkSaleOrderChange(combinBillVOs[0], originVos[0]);
// 保存
ISaleOrderScriptMaintain maintainsrv = NCLocator.getInstance().lookup(ISaleOrderScriptMaintain.class);
SaleOrderVO[] retvos = maintainsrv.saleOrderUpdate(combinBillVOs, null, originVos);
if (retvos != null) {
// 同步修改流程生产订单的国网行项目号国内采购订单号
updatePmoBill(retvos);
updateRelatedBill(retvos);
}
return retvos;
}
@ -1594,10 +1599,184 @@ public class APISaleOrderMaitainImpl implements IAPISaleOrderMaitain {
}
}
private void updatePmoBill(SaleOrderVO[] vos) {
// 同步修改流程生产订单的国网行项目号国内采购订单号
/**
* 检查销售订单数量金额字段的变化
*
* @param newVO
* @param oldVO
* @throws BusinessException
*/
private void checkSaleOrderChange(SaleOrderVO newVO, SaleOrderVO oldVO) throws BusinessException {
/**
* 1.实际收款金额 >0
* 销售订单行金额减少,校验 表头的销售订单总金额不得小于销售订单实际收款金额
* 2.累计开票主数量 & 累计确认应收金额 >0
* 修改行金额和行数量时,不得小于行累计确认应收金额和行累计开票主数量;
* 行数量金额减少时销售订单行金额不得小于行累计确认应收金额销售订单数量不得小于销售订单行累计开票主数量不允许删除已开票的物料行(判断累计开票主数量是否大于0);
* 3.实际收款和累计开票主数量累计确认应收金额 >0
* 变更行金额和行数量时销售订单行金额不得小于行累计确认应收金额销售订单行数量不得小于销售订单行累计开票主数量;
* 4.累计出库主数量 or 累计安排生产订单主数量 or 累计发货主数量 or 累计排产主数量 >0
* 不可替换物料,不可删除订单明细行(删除就是把子表VO的dr赋值1,0表示未删除);
* 行数量减少后的值不可小于 累计出库主数量,累计安排生产订单主数量,累计发货主数量,累计排产主数量
* 行金额不得小于行累计确认应收金额与收款金额的最小值
* 5.累计安排生产订单主数量 & 累计排产主数量 >0
* 行数量减少值数量减少后的值不可小于 累计排产主数量
*/
/**
* 字段解释:
* 行号 crowno
* 物料id cmaterialvid
* 行数量 nnum
* 行金额 norigtaxmny
* 销售订单总金额 ntotalorigmny
* 实际收款/销售订单实际收款金额 nreceivedmny
* 累计开票主数量 ntotalinvoicenum
* 累计确认应收金额 ntotalarmny
* 累计出库主数量 ntotaloutnum
* 累计安排生产订单主数量 narrangemonum
* 累计发货主数量 ntotalsendnum
* 累计排产主数量 vbdef12
* 实体的增删改 status VOStatus.NEW VOStatus.UPDATED
*/
if (newVO == null || oldVO == null) return;
SaleOrderHVO newHead = newVO.getParentVO();
SaleOrderHVO oldHead = oldVO.getParentVO();
SaleOrderBVO[] newBodies = newVO.getChildrenVO();
SaleOrderBVO[] oldBodies = oldVO.getChildrenVO();
// 1. 表头金额校验
UFDouble newNtotalorigmny = newHead.getNtotalorigmny();
UFDouble newNreceivedmny = newHead.getNreceivedmny();
if (newNreceivedmny != null && newNreceivedmny.doubleValue() > 0) {
if (newNtotalorigmny == null || newNtotalorigmny.doubleValue() < newNreceivedmny.doubleValue()) {
throw new BusinessException("销售订单总金额不得小于销售订单实际收款金额");
}
}
// 新旧表体映射
Map<String, SaleOrderBVO> oldBodyMap = new HashMap<>();
for (SaleOrderBVO oldBody : oldBodies) {
oldBodyMap.put(oldBody.getCsaleorderbid(), oldBody);
}
for (SaleOrderBVO newBody : newBodies) {
String bid = newBody.getCsaleorderbid();
SaleOrderBVO oldBody = oldBodyMap.get(bid);
if (oldBody == null) continue; // 新增行不校验
// 校验用到的字段
String newCmaterialvid = newBody.getCmaterialvid();
String oldCmaterialvid = oldBody.getCmaterialvid();
UFDouble newNnum = newBody.getNnum();
UFDouble oldNnum = oldBody.getNnum();
UFDouble newNorigtaxmny = newBody.getNorigtaxmny();
UFDouble oldNorigtaxmny = oldBody.getNorigtaxmny();
UFDouble ntotalinvoicenum = getUFDouble_NullAsOne(newBody.getNtotalinvoicenum());
UFDouble ntotalarmny = getUFDouble_NullAsOne(newBody.getNtotalarmny());
UFDouble ntotaloutnum = getUFDouble_NullAsOne(newBody.getNtotaloutnum());
UFDouble narrangemonum = getUFDouble_NullAsOne(newBody.getNarrangemonum());
UFDouble ntotalsendnum = getUFDouble_NullAsOne(newBody.getNtotalsendnum());
UFDouble vbdef12 = getUFDouble_NullAsOne(newBody.getVbdef12());
String crowno = newBody.getCrowno();
// 1. 修改行校验
if (VOStatus.UPDATED == newBody.getStatus()) {
// 1.1 累计开票主数量 & 累计确认应收金额 >0
if ((ntotalinvoicenum != null && ntotalinvoicenum.doubleValue() > 0) ||
(ntotalarmny != null && ntotalarmny.doubleValue() > 0)) {
if (ntotalinvoicenum != null && ntotalinvoicenum.doubleValue() > 0) {
if (newNnum == null || newNnum.doubleValue() < ntotalinvoicenum.doubleValue()) {
throw new BusinessException("行号:" + crowno + ", 销售订单行数量(" + newNnum + ")不得小于行累计开票主数量(" + ntotalinvoicenum + ")");
}
}
if (ntotalarmny != null && ntotalarmny.doubleValue() > 0) {
if (newNorigtaxmny == null || newNorigtaxmny.doubleValue() < ntotalarmny.doubleValue()) {
throw new BusinessException("行号:" + crowno + ", 销售订单行金额(" + newNorigtaxmny + ")不得小于行累计确认应收金额(" + ntotalarmny + ")");
}
}
}
// 1.2 累计出库/安排生产/发货/排产
boolean hasOutOrArrange = (ntotaloutnum != null && ntotaloutnum.doubleValue() > 0) ||
(narrangemonum != null && narrangemonum.doubleValue() > 0) ||
(ntotalsendnum != null && ntotalsendnum.doubleValue() > 0) ||
(vbdef12 != null && vbdef12.doubleValue() > 0);
if (hasOutOrArrange) {
if (newNnum != null && oldNnum != null && newNnum.doubleValue() < oldNnum.doubleValue()) {
if (ntotaloutnum != null && ntotaloutnum.doubleValue() > 0 && newNnum.doubleValue() < ntotaloutnum.doubleValue()) {
throw new BusinessException("行号:" + crowno + ", 行数量(" + newNnum + ")不可小于累计出库主数量(" + ntotaloutnum + ")");
}
if (narrangemonum != null && narrangemonum.doubleValue() > 0 && newNnum.doubleValue() < narrangemonum.doubleValue()) {
throw new BusinessException("行号:" + crowno + ", 行数量(" + newNnum + ")不可小于累计安排生产订单主数量(" + narrangemonum + ")");
}
if (ntotalsendnum != null && ntotalsendnum.doubleValue() > 0 && newNnum.doubleValue() < ntotalsendnum.doubleValue()) {
throw new BusinessException("行号:" + crowno + ", 行数量(" + newNnum + ")不可小于累计发货主数量(" + ntotalsendnum + ")");
}
if (vbdef12 != null && vbdef12.doubleValue() > 0 && newNnum.doubleValue() < vbdef12.doubleValue()) {
throw new BusinessException("行号:" + crowno + ", 行数量(" + newNnum + ")不可小于累计排产主数量(" + vbdef12 + ")");
}
}
// 金额校验
if (ntotalarmny != null && ntotalarmny.doubleValue() > 0) {
UFDouble minAmount = ntotalarmny;
if (newNreceivedmny != null && newNreceivedmny.doubleValue() > 0) {
minAmount = ntotalarmny.doubleValue() < newNreceivedmny.doubleValue() ? ntotalarmny : newNreceivedmny;
}
if (newNorigtaxmny == null || newNorigtaxmny.doubleValue() < minAmount.doubleValue()) {
throw new BusinessException("行号:" + crowno + ", 行金额(" + newNorigtaxmny + ")不得小于行累计确认应收金额与收款金额的最小值(" + minAmount + ")");
}
}
// 1.4 物料替换校验
if (StringUtils.isNotEmpty(oldCmaterialvid) && !newCmaterialvid.equals(oldCmaterialvid)) {
throw new BusinessException("行号:" + crowno + ",存在累计出库/安排生产订单/发货/排产主数量,请勿修改物料");
}
}
// 1.3 累计安排生产订单主数量 & 累计排产主数量 >0
if ((narrangemonum != null && narrangemonum.doubleValue() > 0) &&
(vbdef12 != null && vbdef12.doubleValue() > 0)) {
if (newNnum != null && oldNnum != null && newNnum.doubleValue() < oldNnum.doubleValue()) {
if (newNnum.doubleValue() < vbdef12.doubleValue()) {
throw new BusinessException("行号:" + crowno + ", 行数量减少后的值(" + newNnum + ")不可小于累计排产主数量(" + vbdef12 + ")");
}
}
}
}
// 2. 删除行校验
if (VOStatus.DELETED == newBody.getStatus()) {
if (ntotalinvoicenum != null && ntotalinvoicenum.doubleValue() > 0) {
throw new BusinessException("行号:" + crowno + ", 不允许删除已开票的物料行");
}
if ((ntotaloutnum != null && ntotaloutnum.doubleValue() > 0) ||
(narrangemonum != null && narrangemonum.doubleValue() > 0) ||
(ntotalsendnum != null && ntotalsendnum.doubleValue() > 0) ||
(vbdef12 != null && vbdef12.doubleValue() > 0)) {
throw new BusinessException("行号:" + crowno + ", 不可删除已有累计出库、安排生产、发货或排产的订单明细行");
}
}
// 3. 新增行校验
if (VOStatus.NEW == newBody.getStatus()) {
}
}
}
/**
* 更新关联的下游单据
*
* @param vos 销售订单集合
*/
private void updateRelatedBill(SaleOrderVO[] vos) throws BusinessException {
HYPubBO hypub = new HYPubBO();
for (SaleOrderVO vo : vos) {
SaleOrderHVO hvo = vo.getParentVO();
String csaleorderid = hvo.getCsaleorderid();// 销售订单ID
SaleOrderBVO[] bvos = vo.getChildrenVO();
// 同步修改流程生产订单的国网行项目号国内采购订单号
for (SaleOrderBVO bvo : bvos) {
String csaleorderbid = bvo.getCsaleorderbid();
// 源头单据明细IDvfirstbid
@ -1620,10 +1799,92 @@ public class APISaleOrderMaitainImpl implements IAPISaleOrderMaitain {
try {
getDao().executeUpdate(updateSql);
} catch (Exception e) {
ExceptionUtils.wrappBusinessException("so-updatePmoBill-exp: " + e.getMessage());
ExceptionUtils.wrappBusinessException("so-updateRelatedBill-exp: " + e.getMessage());
}
}
/**
* 收款单未生成凭证时,同步修改下游收款单的部门人员客户
* 首先根据源头单据id查询关联的收款单如果有则循环判断是否生成凭证未生成则更新字段值为销售订单同字段的值
* 收款单和凭证的关系查询
* 单据和凭证的关联 查询`fip_relation`表可知src_relationid 存的是关联单据的主键
*/
SqlBuilder strWhere = new SqlBuilder();
strWhere.append("dr = 0 and ");
strWhere.append("src_billid", csaleorderid);
GatheringBillVO[] payBillVOs = (GatheringBillVO[]) hypub.queryByCondition(GatheringBillVO.class, strWhere.toString());
String ccustomerid = hvo.getCcustomerid();
String ccustomervid = hvo.getCcustomervid();
String cemployeeid = hvo.getCemployeeid();
String cdeptid = hvo.getCdeptid();
String cdeptvid = hvo.getCdeptvid();
if (null != payBillVOs) {
for (GatheringBillVO payBillVO : payBillVOs) {
// 查询收款单是否已生成凭证
String countSql = "SELECT count(1) FROM fip_relation "
+ " WHERE dr = 0 and src_relationid = '[billId]' ";
countSql = countSql.replace("[billId]", payBillVO.getPk_gatherbill());
Integer num = (Integer) getDao().executeQuery(countSql, new ColumnProcessor());
if (num > 0) {
continue;
}
// 修改收款单
payBillVO.setPk_psndoc(cemployeeid);
payBillVO.setCustomer(ccustomerid);
payBillVO.setPk_deptid(cdeptid);
payBillVO.setPk_deptid_v(cdeptvid);
payBillVO.setStatus(VOStatus.UPDATED);
hypub.update(payBillVO);
// 修改收款单子表
String updateSql = "update ar_gatheritem set pk_psndoc=?,customer=?,pk_deptid=?,pk_deptid_v=? where pk_gatherbill=?";
SQLParameter parameter = new SQLParameter();
parameter.addParam(cemployeeid);
parameter.addParam(ccustomerid);
parameter.addParam(cdeptid);
parameter.addParam(cdeptvid);
int num1 = getDao().executeUpdate(updateSql);
}
}
/**
* 应收单未生效时,同步修改下游销售发票的部门(销售和生产)销售业务员客户开票客户
* 首先根据源头单据id查询关联的销售发票如果有则循环判断应收单是否生效未生效则更新字段值为销售订单同字段的值
*/
SqlBuilder strWhereInv = new SqlBuilder();
strWhereInv.append("dr = 0 and ");
strWhereInv.append("cfirstid", csaleorderid);
SaleInvoiceHVO[] invoiceHvos = (SaleInvoiceHVO[]) hypub.queryByCondition(SaleInvoiceHVO.class, strWhereInv.toString());
if (null != invoiceHvos) {
for (SaleInvoiceHVO invoiceHVO : invoiceHvos) {
// 查询应收单是否生效
String countSql = "SELECT count(1) FROM ar_recitem b "
+ " left join ar_recbill a on a.pk_recbill = b.pk_recbill "
+ " WHERE b.dr = 0 and a.effectstatus = 10 and b.src_billid = '[billId]' ";
countSql = countSql.replace("[billId]", csaleorderid);
Integer num = (Integer) getDao().executeQuery(countSql, new ColumnProcessor());
if (num > 0) {
continue;
}
invoiceHVO.setCinvoicecustid(ccustomerid);
invoiceHVO.setCinvoicecustvid(ccustomervid);
invoiceHVO.setStatus(VOStatus.UPDATED);
hypub.update(invoiceHVO);
// 修改销售发票子表
String updateSql = "update so_saleinvoice_b set " +
"cordercustid=?,cordercustvid=?,cdeptid=?,cdeptvid=?,cemployeeid=?, " +
"where csaleinvoiceid=?";
SQLParameter parameter = new SQLParameter();
parameter.addParam(ccustomerid);
parameter.addParam(ccustomerid);
parameter.addParam(cdeptid);
parameter.addParam(cdeptvid);
parameter.addParam(cemployeeid);
parameter.addParam(invoiceHVO.getPrimaryKey());
int num1 = getDao().executeUpdate(updateSql);
}
}
}
}
}