【NC】NC6系列金额计算处理逻辑剖析

对于ERP系统来说,管控的是整个企业的业务和财务数据,实际生产和运行过程中,除了尾差外,我们不允许出现任何形式的的数据运算错误,所以也就对系统的计算提出了很大的挑战。

单方面的加减乘除,对于任何系统来说都很轻易完成,加上精度计算,也不太费力,但是现在如果有一个业务场景:一条业务数据,包含无税金额(原币),含税金额,数量,税率,税额,集团本币(无税,含税,税额),组织本币(无税,含税,税额)汇率,折扣比例,单品折扣等等…
现在我要告诉你,这些业务数据中,如果有一个数据发生变动,所有数据都要重新计算发生变动,当然也有可能是部分变动。

我们讲一下用友NC6系列的一般业务处理逻辑:

一般来说我们把一个事件切分成三个阶段:前、中、后。

对于代码处理来理解就是: 编辑前事件、编辑和编辑后事件

我们首先想到这个问题的解决方案是:这个问题简单,比如说你要修改数量,我在数量的编辑后事件里边计算逻辑,然后重新计算金额、税额等数据。

这个方案怎么样?可行吗?

这个解决方案可以解决实际的计算处理问题,但如果说可行与否,举个栗子,古代的马车也能把人送到目的地,现代的汽车也能把人送到目的地,方案可行,但绝对不是较优,更谈不上最优。如果有2-3个字段需要联动,这样的处理一点问题没有,但是现在20-30个字段需要联动,难道要写20-30个编辑后事件吗?当然不可能。

我们首先可以先做一个约定或者约束,比如说数量就叫做num,金额叫nmny,而且可以通过映射类来做你的命名和我标准命名的映射,计算一般是需要两个数据,JAVA面向对象编程,我们可以把这两个数据称为数值对象和参数对象,参数对象里面主要是指一些参数条件,是否含税优先、是否折扣优先等等

public void calculate(int row, String editKey) {
    // 如果编辑字段不会影响到数量单价金额,不进行计算
    if (!CalculatorUtil.getInstance().getNeedCalKey().contains(editKey)) {
      return;
    }
    // 1.创建数据映射实例 获得数据项之间的映射关系
    IRelationForItems item = new RelationItemForCal();
    // 单品折扣字段设为“发票折扣”
    item.setNitemdiscountrateKey(SaleInvoiceBVO.NINVOICEDISRATE);

    // 2.创建数据集实例 初始化数据关系计算用的数据集
    IDataSetForCal data = null;
    Calculator tool = null;
    ScaleUtils scale = UIScaleUtils.getScaleUtils();
    CardKeyValue keyValue = new CardKeyValue(this.cardpanel);
    // 创建参数实例,在计算的时候用来获得参数条件:是否含税优先等
    Condition cond = SOCalConditionRule.getCondition();
    // 设置调单价方式调折扣
    String trantypecode =
        keyValue.getHeadStringValue(SaleInvoiceHVO.VTRANTYPECODE);
    String pk_group = AppContext.getInstance().getPkGroup();
    boolean ischgprice =
        CalculatorUtil.getInstance().getChgPriceOrDiscount(pk_group,
            trantypecode);
    cond.setIsChgPriceOrDiscount(false);

    SOBuysellTriaRule buysellrule = new SOBuysellTriaRule(keyValue);
    cond.setInternational(buysellrule.isHeadBuysellFlagOut());
    // 3.计算每行数据
    UnitChangeRateRule rule = new UnitChangeRateRule(keyValue);

    // 设置业务单位是否固定换算率
    cond.setIsFixNchangerate(rule.isAstFixedRate(row));
    // 设置报价单位是否固定换算率
    cond.setIsFixNqtunitrate(rule.isQtFixedRate(row));

    data = new SaleInvoiceCarDataSet(this.cardpanel, row, item);
    tool = new Calculator(data, scale);
    // 两个参数 cond 为计算时的参数条件
    tool.calculate(cond, editKey);

    // 数量单价金额编辑后重建前金额处理
    // 冲减前金额 = 价税合计 + 费用冲抵金额
    UFDouble taxmny =
        keyValue.getBodyUFDoubleValue(row, SaleInvoiceBVO.NORIGTAXMNY);

    UFDouble origarsubmny =
        keyValue.getBodyUFDoubleValue(row, SaleInvoiceBVO.NORIGSUBMNY);
    UFDouble nbfsubmny = MathTool.add(taxmny, origarsubmny);
    keyValue.setBodyValue(row, "nbforigsubmny", nbfsubmny);
    }

我们可以看一下具体的处理操作
我们在编辑字段时,当然一次只能编辑一次,所以我们肯定是针对某个字段进行联动字段的计算处理

  1. 首先我们获取这个字段与之相关的字段:例如,改动数量的话,金额等字段一定会变动,那么金额字段就是数量字段的相关字段
  2. 针对某个字段来说,我们可以指定计算策略,比如我在计算金额的时候,我要判断一下当前组织,再确定其他字段是不是要进行改动,那么这种情况,不属于标准的计算逻辑,但是我们可以自己编写策略来实现计算逻辑
  3. 指定几个特殊的处理操作,如果改动的是集团金额,我们使用集团金额的处理类,如果改动的是全局金额,我们使用全局金额的处理类
  4. 美元相关字段的处理,避免重复计算
  5. 计算完成后提交数据结果集
public void calculate(Condition cond, String key) {
this.wrapCondtion(cond, key);
IRelationForItems lItem = this.data.getRelationForItem();
// cond.setUnitPriorType(Condition.MAIN_PRIOR);
IStrategyForCal strategy = this.map.get(key);
if ((strategy != null || ((StringUtils.equalsIgnoreCase(key, lItem.getNexchangerateKey()))
|| (StringUtils.equalsIgnoreCase(key, lItem.getNglobalexchgrateKey())) || (StringUtils
  .equalsIgnoreCase(key, lItem.getNgroupexchgrateKey())))) && !cond.isCalLocalPior()) {
  if (!StringUtils.equalsIgnoreCase(key, lItem.getNexchangerateKey()) && strategy != null) {
// cond.setIsChgPriceOrDiscount(cond.getIsChgPriceOrDiscount()&&cond.is)
strategy.calculate(cond);
  }
  this.setNoPermitModifyColumn(key);
  if (StringUtils.equalsIgnoreCase(key, lItem.getNglobalexchgrateKey())) {

GlobalMoneyCalculator cal = new GlobalMoneyCalculator(this.data, this.scale);
cal.calculate(cond);
this.data.commit();
return;
  }
  if (StringUtils.equalsIgnoreCase(key, lItem.getNgroupexchgrateKey())) {
GroupMoneyCalculator cal = new GroupMoneyCalculator(this.data, this.scale);
cal.calculate(cond);
this.data.commit();
return;
  }
  this.calculateLocalGlobalGroup(cond);
} else {
  strategy = this.localmap.get(key);
  if (strategy != null && !StringUtils.equalsIgnoreCase(key, lItem.getNexchangerateKey())) {
// if (!key.equals(lItem.getNexchangerateKey())) {
strategy.calculate(cond);
// }

  }

  // 币种相同并且(有策略或者这本汇率变)
  if (strategy != null || StringUtils.equalsIgnoreCase(key, lItem.getNexchangerateKey())) {
boolean isSame =
(this.data.getCorigcurrencyid() != null)
&& (this.data.getCcurrencyid() != null)
&& StringUtils.equalsIgnoreCase(this.data.getCorigcurrencyid(),
this.data.getCcurrencyid());

if (isSame
|| (cond.isCalOrigCurr()
&& !StringUtils.equalsIgnoreCase(key, lItem.getNdeductibletaxKey())
&& !StringUtils.equalsIgnoreCase(key, lItem.getNdeductibleTaxrateKey())
&& !StringUtils.equalsIgnoreCase(key, lItem.getNcaltaxmnyKey()) && !StringUtils
  .equalsIgnoreCase(key, lItem.getNtaxKey()))) {
  this.setNoPermitModifyColumn(key);
  this.calculateOrigGlobalGroup(cond);
}
  }

}

if ((this.data.getRelationForItem() instanceof IRelationDollarForItems)
&& (this.data.getDataset() instanceof IDataSetDollarForCal)) {

  // 如果key是美元相关字段,则不再重复进行美元计算
  IRelationDollarForItems dollarItem = (IRelationDollarForItems) this.data.getRelationForItem();
  boolean isDollarKey =
  (StringUtils.equalsIgnoreCase(key, dollarItem.getNusdmnyKey()))
  || (StringUtils.equalsIgnoreCase(key, dollarItem.getNqtusdpriceKey()))
  || (StringUtils.equalsIgnoreCase(key, dollarItem.getNusdpriceKey()));
  if (!isDollarKey) {
this.calculateDollar(cond);
  }
}
// DollarAndPriceFormula daf = new
// DollarAndPriceFormula(this.data,this.scale);
// daf.calculate(cond);
// CheckIsResultRight check = new CheckIsResultRight(data);
// check.checkResult();
// 没有问题,将数据写入到真正的数据源中
this.data.commit();
  }

实际上我们在具体计算处理中,还是主要使用策略的模式进行计算,我们匹配了20多种策略方式来进行计算处理,这样能针对高效完成数据计算处理,防止重复计算或者没有计算,一般来说我们的所有业务数据的计算处理都应该是幂等的

我们简单分析一下一个计算策略,在计算策略中我们要考虑的一般有,是否含税优先,调单价还是调折扣,是否进行本辅币计算

public void calculate(Condition cond) {
    if (this.isSameCurrencyId()) {
      this.data.commit();
      this.cloneValueFromOrigValue();
      // // 记税金额
      // this.calculateNCalTaxMny();
      // // 主单位本币税额
      // this.calculateNTax();

      // 需求变更2012-3-23
      if (this.data.getCondition().getIsTaxOrNet()) {
        // 无税金额 = 本币无税金额(见本币金额的算法)
        new LocalTaxMnyCalculator(this.data, this.scale).calculate(cond);
        this.data.setAttributeValue(this.item.getNorigmnyKey(), this.data.getNmny());
      } else {
        // 价税合计 = 本币价税合计(见本币金额的算法)
        new LocalMnyCalculator(this.data, this.scale).calculate(cond);
        this.data.setAttributeValue(this.item.getNorigtaxmnyKey(), this.data.getNtaxmny());
      }

      // 本币不可抵扣税额
      // this.calculateNDeductibleTax();
      // 记成本金额
      // this.calculateNCostmny();
      return;
    }
    if (cond.getIsTaxOrNet()) {
      this.calculateNtaxMny();
      // this.calculateLocalPriceFromOrigPrize(this.data.getNtaxmny(), cond);
      new LocalTaxMnyCalculator(this.data, this.scale).calculate(cond);
    } else {
      this.calculateNmy();
      // this.calculateLocalPriceFromOrigPrize(this.data.getNmny(), cond);
      new LocalMnyCalculator(this.data, this.scale).calculate(cond);
    }
    }

我们不难发现在实际处理计算逻辑中,我们还是需要根据不同的cond进行不同的计算类的处理

以下代码实现
主单位本币税额=主单位本币无税金额*税率

    private void calculateNTax() {
        String moneyKey = this.item.getNmnyKey();
        String taxKey = this.item.getNtaxKey();
        String taxRateKey = this.item.getNtaxrateKey();
        String ftaxtypeflag = this.item.getFtaxtypeflagKey();
        String curridKey = this.item.getCcurrencyidKey();
        Object currid = this.data.getAttributeValue(curridKey);
        boolean flag = this.data.hasItemKey(moneyKey)
                && this.data.hasItemKey(taxRateKey)
                && this.data.hasItemKey(taxRateKey) && (currid != null);
        if (!flag) {
            return;
        }
        TaxTaxRateMoneyFormula formula = new TaxTaxRateMoneyFormula(this.data,
                this.scale, moneyKey, taxKey, taxRateKey, ftaxtypeflag,
                curridKey);
        formula.calculateTax();
    }

再进行税额等的计算处理,以及计算后的赋值操作,对于税额的数据计算处理执行完毕

这就是整体计算逻辑的一个很小的分支计算

    public void calculateTax() {

        UFDouble money = (UFDouble) this.data.getAttributeValue(this.moneyKey);
        // UFDouble taxRate = this.getTaxRate();
        String curr = (String) this.data.getAttributeValue(this.currid);
        UFDouble rate = (UFDouble) this.data.getAttributeValue(this.taxRateKey);
        UFDouble value = null;
        if (!this.data.getCondition().isInternational()) {
            value = this.calTax(money, rate);
        } else {
            rate = CalculatorUtil.div(rate, new UFDouble(100));
            UFDouble calMoney = (UFDouble) this.data
                    .getAttributeValue(this.data.getRelationForItem()
                            .getNcaltaxmnyKey());
            value = CalculatorUtil.multiply(calMoney, rate);
        }
        value = this.scale.adjustMnyScale(value, curr);
        this.data.setAttributeValue(this.taxKey, value);

    }

针对不同情况的针对性处理,获取相关联字段的联动,避免重复计算更新data赋值一套完整的处理流程就处理完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值