# 采购订单生成付款计划时追加制单人信息

1. 业务处理要求
    * 在`getPayPlanBillVOData`方法中将采购订单制单人(billmaker)设置给付款计划制单人上(billmaker)
  
2. 高级版2505版本源码如下
```java
package nc.pubitf.pu.m21p.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import nc.impl.scmpub.payterm.AbstractRecvPayPlan;
import nc.vo.bd.payment.IPaymentUtil;
import nc.vo.pu.m21.entity.OrderHeaderVO;
import nc.vo.pu.m21.entity.OrderItemVO;
import nc.vo.pu.m21.entity.OrderPaymentVO;
import nc.vo.pu.m21.entity.OrderVO;
import nc.vo.pu.m21.entity.PayPlanBillVO;
import nc.vo.pu.m21.entity.PayPlanItemVO;
import nc.vo.pu.m21.entity.PayPlanVO;
import nc.vo.pu.m21p.pub.RelationCalculateToolsFor21PUtils;
import nc.vo.pu.pub.constant.PUParaValue;
import nc.vo.pu.pub.util.PUSysParamUtil;
import nc.vo.pu.pub.util.PubSysParamUtil;
import nc.vo.pub.lang.UFBoolean;
import nc.vo.pub.lang.UFDate;
import nc.vo.pub.lang.UFDouble;
import nc.vo.pubapp.AppContext;
import nc.vo.pubapp.pattern.pub.MathTool;
import nc.vo.pubapp.scale.ScaleUtils;
import nc.vo.scmpub.payterm.pay.AbstractPayPlanVO;
import nc.vo.scmpub.res.billtype.POBillType;
import nc.vo.scmpub.res.para.NCPara;
import nc.vo.scmpub.util.BillBodySortUtils;
import nc.vo.scmpub.util.CollectionUtils;
import nc.vo.scmpub.util.StringUtil;
import nc.vo.scmpub.util.payterm.PayTermUtil;
import nccloud.commons.lang.ArrayUtils;

/** 
 * @description 采购订单审批生成采购订单付款计划工具类
 * @author yinl 
 * @date 2022年8月25日 下午1:46:04 
 * @version ncc1.0 
 */
public class PayPlanBillVODataUtil {

  /** 
   * 金额字段数组（下标要和orderBidToSub中对应的金额字段下标一直）
   * 0-税额-NTAX，1-本币金额-NMNY，2-无税金额-NORDERORIGMNY,
   * 3-本币无税金额-NORDERMNY,4-价税合计-NORIGTAXMNY,
   * 5-计税金额-NCALTAXMNY，6-计成本金额-NCALCOSTMNY,
   * 7-不可抵扣税额-NNOSUBTAX,8-全局本币无税金额-NGLOBALMNY,
   * 9-全局本币价税合计-NGLOBALTAXMNY,10-集团本币无税金额-NGROUPMNY,
   * 11-集团本币价税合计-NGROUPTAXMNY
   */
  private String[] mnyArrs = new String[] {
    PayPlanItemVO.NTAX, PayPlanItemVO.NMNY, PayPlanItemVO.NORDERORIGMNY,
    PayPlanItemVO.NORDERMNY, PayPlanItemVO.NORIGTAXMNY,
    PayPlanItemVO.NCALTAXMNY, PayPlanItemVO.NCALCOSTMNY,
    PayPlanItemVO.NNOSUBTAX, PayPlanItemVO.NGLOBALMNY,
    PayPlanItemVO.NGLOBALTAXMNY, PayPlanItemVO.NGROUPMNY,
    PayPlanItemVO.NGROUPTAXMNY
  };

  /**
   * Map<付款计划金额字段,订单表体金额字段> 
   * 用于找到付款计划表体和订单表体金额对应的关系，获取订单上的金额字段，计算尾差使用
   */
  private Map<String, String> planToOrderMnyKeys =
      new HashMap<String, String>();

  /**
   * 单价字段
   * 0-主本币无税单价，1-主无税单价，2- 主含税单价，3- 主本币含税单价
   * 
   * 用于恢复付款计划单价使用，付款计划表体的单价金额算法，以原币金额驱动，由于数量是计算过的，单价可能会变动，
   * 但是，生成付款计划的时候，单价是不变的才对，这里做个备份，计算完成之后，付款计划子表单价恢复成订单单价
   */
  public String[] priceArrs = new String[] {
    PayPlanItemVO.NORDERPRICE, PayPlanItemVO.NORDERORIGPRICE,
    PayPlanItemVO.NORDERORIGTAXPRICE, PayPlanItemVO.NORDERTAXPRICE
  };

  /**
   * Map<付款计划单价字段,订单单价字段> 
   * 用于恢复付款计划单价使用，付款计划表体的单价金额算法，以原币金额驱动，由于数量是计算过的，单价可能会变动，
   * 但是，生成付款计划的时候，单价是不变的才对，这里做个备份，计算完成之后，付款计划子表单价恢复成订单单价
   */
  private Map<String, String> planToOrderPriceKeys =
      new HashMap<String, String>();

  /** 原币金额字段集合，用于设置金额字段精度使用 */
  private List<String> oriMnyKeyList = new ArrayList<String>();

  /** 金额字段集合，用于设置金额字段精度使用 */
  private List<String> mnyKeyList = new ArrayList<String>();

  /** 金额字段集合，用于设置金额字段精度使用 */
  private List<String> glogalAndGroupMnyKeyList = new ArrayList<String>();

  public PayPlanBillVODataUtil() {
    // 原币金额字段
    oriMnyKeyList.add(PayPlanItemVO.NORDERORIGMNY);// 2-无税金额-NORDERORIGMNY
    oriMnyKeyList.add(PayPlanItemVO.NORIGTAXMNY);// 4-价税合计-NORIGTAXMNY

    // 本币金额字段
    mnyKeyList.add(PayPlanItemVO.NTAX);// 0-税额-NTAX
    mnyKeyList.add(PayPlanItemVO.NMNY);// 1-本币金额-NMNY
    mnyKeyList.add(PayPlanItemVO.NORDERMNY);// 3-本币无税金额-NORDERMNY
    mnyKeyList.add(PayPlanItemVO.NCALTAXMNY);// 5-计税金额-NCALTAXMNY
    mnyKeyList.add(PayPlanItemVO.NCALCOSTMNY);// 6-计成本金额-NCALCOSTMNY
    mnyKeyList.add(PayPlanItemVO.NNOSUBTAX);// 7-不可抵扣税额-NNOSUBTAX
    mnyKeyList.add(PayPlanItemVO.NGLOBALMNY);// 8-全局本币无税金额-NGLOBALMNY
    mnyKeyList.add(PayPlanItemVO.NGLOBALTAXMNY);// // 9-全局本币价税合计-NGLOBALTAXMNY
    mnyKeyList.add(PayPlanItemVO.NGROUPMNY);// // 10-集团本币无税金额-NGROUPMNY
    mnyKeyList.add(PayPlanItemVO.NGROUPTAXMNY);// 11-集团本币价税合计-NGROUPTAXMNY

    // 全局、集团本币字段
    glogalAndGroupMnyKeyList.add(PayPlanItemVO.NGLOBALMNY);// 8-全局本币无税金额-NGLOBALMNY
    glogalAndGroupMnyKeyList.add(PayPlanItemVO.NGLOBALTAXMNY);// 9-全局本币价税合计-NGLOBALTAXMNY
    glogalAndGroupMnyKeyList.add(PayPlanItemVO.NGROUPMNY);// 10-集团本币无税金额-NGROUPMNY
    glogalAndGroupMnyKeyList.add(PayPlanItemVO.NGROUPTAXMNY);// 11-集团本币价税合计-NGROUPTAXMNY

    // 设置集合Map<付款计划金额字段,订单表体金额字段>，用于计算尾差
    planToOrderMnyKeys.put(PayPlanItemVO.NTAX, OrderItemVO.NTAX);// 0-本币税额
    planToOrderMnyKeys.put(PayPlanItemVO.NMNY, OrderItemVO.NTAXMNY);// 1-本币金额
    planToOrderMnyKeys.put(PayPlanItemVO.NORDERORIGMNY, OrderItemVO.NORIGMNY);// 2-无税金额
    planToOrderMnyKeys.put(PayPlanItemVO.NORDERMNY, OrderItemVO.NMNY);// 3-无税金额
    planToOrderMnyKeys.put(PayPlanItemVO.NORIGTAXMNY, OrderItemVO.NORIGTAXMNY);// 4-价税合计
    planToOrderMnyKeys.put(PayPlanItemVO.NCALTAXMNY, OrderItemVO.NCALTAXMNY);// 5-计税金额
    planToOrderMnyKeys.put(PayPlanItemVO.NCALCOSTMNY, OrderItemVO.NCALCOSTMNY);// 6-记成本金额
    planToOrderMnyKeys.put(PayPlanItemVO.NNOSUBTAX, OrderItemVO.NNOSUBTAX);// 7-不可抵扣税额
    planToOrderMnyKeys.put(PayPlanItemVO.NGLOBALMNY, OrderItemVO.NGLOBALMNY);// 8-全局本币无税金额
    planToOrderMnyKeys.put(PayPlanItemVO.NGLOBALTAXMNY,
        OrderItemVO.NGLOBALTAXMNY);// 9-全局本币价税合计
    planToOrderMnyKeys.put(PayPlanItemVO.NGROUPMNY, OrderItemVO.NGROUPMNY);// 10-集团本币无税金额
    planToOrderMnyKeys.put(PayPlanItemVO.NGROUPTAXMNY,
        OrderItemVO.NGROUPTAXMNY);// 11-集团本币价税合计

    // Map<付款计划单价字段,订单单价字段>
    planToOrderPriceKeys.put(PayPlanItemVO.NORDERPRICE, OrderItemVO.NPRICE);// 0-主本币无税单价
    planToOrderPriceKeys.put(PayPlanItemVO.NORDERORIGPRICE,
        OrderItemVO.NORIGPRICE); // 1-主无税单价
    planToOrderPriceKeys.put(PayPlanItemVO.NORDERORIGTAXPRICE,
        OrderItemVO.NORIGTAXPRICE);// 2- 主含税单价
    planToOrderPriceKeys.put(PayPlanItemVO.NORDERTAXPRICE,
        OrderItemVO.NTAXPRICE);// 3- 主本币含税单价
  }

  /**
   * 里程碑采购添加，获取聚合的采购订单付款计划vo
   * @param vos 采购订单VO
   * @return 采购订单付款计划聚合VO
   *
   */
  public PayPlanBillVO[] getPayPlanBillVOData(OrderVO[] vos) {
    List<PayPlanBillVO> payPlanBillList = new ArrayList<PayPlanBillVO>();
    for (OrderVO vo : vos) {
      /** 1、获取订单的数据，用来组装采购订单付款计划 */
      OrderHeaderVO orderHVO = vo.getHVO(); // 采购订单表头
      OrderItemVO[] orderItemVOs = vo.getBVO(); // 采购订单表体
      OrderPaymentVO[] orderPaymentVOs = vo.getPaymentVO(); // 采购订单付款协议
      if (ArrayUtils.isEmpty(orderPaymentVOs)) {
        orderPaymentVOs = new OrderPaymentVO[] {
          new OrderPaymentVO()
        };
        // 结算方式
        orderPaymentVOs[0].setPk_balatype(orderHVO.getPk_balatype());
        // 设置默认账期号为1
        orderPaymentVOs[0].setShoworder(Integer.valueOf(1));
      }
      /** 2、通过采购订单，生成采购订单付款计划 */
      PayPlanBillVO[] payPlanBillVOs =
          this.splitByPtch(orderHVO, orderItemVOs, orderPaymentVOs);
      if (ArrayUtils.isEmpty(payPlanBillVOs)) {
        continue;
      }
      else {
        // 根据付款计划的付款方式（整单付款、明细付款）
        // 采购订单付款计划付款
        PUParaValue.po95 po95 = PUSysParamUtil.getPO95(orderHVO.getPk_org());
        for (PayPlanBillVO payPlanBillVO : payPlanBillVOs) {
          PayPlanVO hvo = payPlanBillVO.getHVO();
          // 采购订单付款计划->付款单 付款方式是否为整单付款
          if(po95.equals(PUParaValue.po95.BILLPAYMENT)){
            hvo.setBbillpay(UFBoolean.TRUE);
          }else if (po95.equals(PUParaValue.po95.DETAILPAYMENT)){
            hvo.setBbillpay(UFBoolean.FALSE);
          }
          payPlanBillList.add(payPlanBillVO);
        }
      }
    }

    return payPlanBillList.toArray(new PayPlanBillVO[payPlanBillList.size()]);
  }

  /**
   * 里程碑采购添加，获取聚合的采购订单付款计划vo
   * 1、先按照采购订单明细行，拆分付款协议，每一个物料行都有一套付款协议
   * 2、汇总采购订单付款计划子表信息，形成汇总数据
   * 3、组装聚合vo，并设置到list集合中
   * @param vos 采购订单VO
   * @return 采购订单付款计划聚合VO
   *
   */
  private PayPlanBillVO[] splitByPtch(OrderHeaderVO orderHVO,
      OrderItemVO[] orderItemVOs, OrderPaymentVO[] orderPaymentVOs) {
    if (CollectionUtils.isNullForAllTElements(orderItemVOs)) {
      return null;
    }
    /** 1、排序处理，以便后续计算尾差顺序是对的 */
    BillBodySortUtils.sort(orderItemVOs);
    BillBodySortUtils.sort(orderPaymentVOs, OrderPaymentVO.SHOWORDER);

    /** 2、生成付款计划子表数据，先按照采购订单明细行，拆分付款协议，每一个物料行都有一套付款协议，用以组装采购订单付款计划子表信息 */
    // 按照采购订单明细行拆分付款计划子表的集合，即每一个明细行都有一套付款计划
    Map<String, List<PayPlanItemVO>> payPlanBVOMap =
        new HashMap<String, List<PayPlanItemVO>>();
    // 付款时点主键结合，用于整理汇总付款计划表头数据使用
    List<String> pk_payments = new ArrayList<String>();

    // Map<订单表体主键，订单表体vo> - 用于计算尾差使用
    Map<String, OrderItemVO> orderBidToBvo = new HashMap<String, OrderItemVO>();

    // 生成付款计划子表信息，并整理一些后续需要的集合
    // payPlanBVOMap，付款计划子表信息
    // pk_payments,付款时点主键集合
    this.getPayPlanBVOs(orderHVO, orderItemVOs, orderPaymentVOs, payPlanBVOMap,
        pk_payments, orderBidToBvo);

    /** 3、获取付款计划组装好的主子表vo */
    PayPlanBillVO[] bills =
        this.getPayPlanVOs(orderHVO, payPlanBVOMap, pk_payments);
    // 价格金额算法，计算全局、集团本位币金额等字段

    /** 4、进行单价金额算法*/
    for (PayPlanBillVO bill : bills) {
      new RelationCalculateToolsFor21PUtils().calculate(bill,
          PayPlanItemVO.NORIGMNY);
    }

    /** 5、计算尾差，单价金额算法结束后，处理税额、无税金额、价税合计的尾差，并汇总表头的本币金额*/
    this.processLastDiff(bills, orderBidToBvo);

    /** 6、汇总表头本币金额 */
    this.sumHeadMny(bills);

    return bills;
  }

  /**
   * 获取账期到账日
   * @param dbegindate 起算日期
   * @param orderPaymentVO 付款协议
   * @param paymentday 账期天数
   * @return
   *
   */
  private UFDate getDenddate(UFDate dbegindate, OrderPaymentVO orderPaymentVO) {
    // 固定结账日
    Integer checkdata = orderPaymentVO.getCheckdata();
    Integer accountDate = orderPaymentVO.getAccountday();
    Integer effectmonth = orderPaymentVO.getEffectmonth();
    Integer effectaddmonth = orderPaymentVO.getEffectaddmonth();
    Integer iitermdays = orderPaymentVO.getPaymentday();
    return PayTermUtil.getEndDate(dbegindate, iitermdays, checkdata, accountDate, effectmonth, effectaddmonth);
  }

  /**
   * 生成付款计划子表信息
   * @param orderHVO 订单表头vo
   * @param orderItemVOs 订单明细表体vo
   * @param orderPaymentVOs 订单付款协议表体vo
   * @param payPlanBVOMap 生成的付款计划子表，Map<付款时点，List<付款计划子表>>
   * @param pk_payments 付款时点主键（付款计划表头是按照付款时点主键汇总的）
   * @param orderBidToSub Map<订单明细表体主键，[需要计算尾差的各字段数组]>
   *                        需要计算尾差的各字段数组的结构：
   *                        [0-税额，1-本币金额，2-无税金额，3-本币无税金额，
   *                         4-价税合计，5-计税金额 ，6-计成本金额，7-不可抵扣税额，
   *                         8-全局本币无税金额，9-全局本币价税合计，10-集团本币无税金额，11-集团本币价税合计]
   * @param orderBidToPrice Map<采购订单表体主键,[单价数组]>
   *                          单价数组结构：
   *                          [0-订单行主本币无税单价，1-付款计划订单行主无税单价，
   *                           2-订单行主含税单价，3-订单行主本币含税单价]
   *
   */
  public void getPayPlanBVOs(OrderHeaderVO orderHVO, OrderItemVO[] orderItemVOs,
      OrderPaymentVO[] orderPaymentVOs,
      Map<String, List<PayPlanItemVO>> payPlanBVOMap, List<String> pk_payments,
      Map<String, OrderItemVO> orderBidToBvo) {
    for (OrderItemVO orderItem : orderItemVOs) {
      // 获取订单表体主键
      String pk_order_b = orderItem.getPk_order_b();
      // NCC-365240 赠品行不生成付款计划子表 begin
      UFBoolean largess = orderItem.getBlargess();
      if(UFBoolean.TRUE.equals(largess)){
        continue;
      }
      // NCC-365240 赠品行不生成付款计划子表 end

      // 遍历采购订单付款协议，生成采购订单付款计划子表
      UFDouble accNum = UFDouble.ZERO_DBL;// 数量，用于最后一行价税合计倒挤
      UFDouble accOrimny = UFDouble.ZERO_DBL;// 金额和值，用于最后一行金额倒挤时使用
      UFDouble accNoTaxmny = UFDouble.ZERO_DBL;// 无税金额和值，用于最后一行无税金额倒挤使用

      // 设置需要计算尾差的map集合 begin
      orderBidToBvo.put(pk_order_b, orderItem);
      // 设置需要计算尾差的map集合 end

      // 创建精度处理工具
      String pk_group = AppContext.getInstance().getPkGroup();
      ScaleUtils scale = new ScaleUtils(pk_group);
      // 原币币种
      String corigcurrencyid = orderHVO.getCorigcurrencyid();

      for (int i = 0; i < orderPaymentVOs.length; i++) {
        PayPlanItemVO payPlanItem = new PayPlanItemVO();
        // 设置一些不需要计算的付款计划子表字段（非付款协议获取数据的字段）
        payPlanItem =
            PayPlanPubUtils.getPayPlanBVOs(orderHVO, orderItem, payPlanItem);
        // 采购订单付款协议主键
        String pk_payment = orderPaymentVOs[i].getPk_payment();
        if (!pk_payments.contains(pk_payment)) {
          pk_payments.add(pk_payment);
        }
        payPlanItem.setPk_paymentch(pk_payment);
        // 备注
        payPlanItem.setVmemo(orderItem.getVbmemo());
        // 账期号
        payPlanItem.setIaccounttermno(orderPaymentVOs[i].getShoworder());
        // 起算依据
        String effectdata = orderPaymentVOs[i].getPk_payperiod();
        payPlanItem.setFeffdatetype(effectdata);
        // 起算日期（原逻辑取采购订单的审批日期），需要根据起算日期延迟天数计算出真正的起算日期
        UFDate dbegindate = null;
        if (IPaymentUtil.PURCHASE_ORDER_APPROVE_DATE.equals(effectdata)) {
          UFBoolean breturn = orderHVO.getBreturn();
          if (UFBoolean.FALSE.equals(breturn)) {
            // 退货不需要更新起算日期
            dbegindate = orderHVO.getTaudittime();
          }
        }
        // 起效日期延迟天数
        Integer effectdateadddate = orderPaymentVOs[i].getEffectdateadddate();
        payPlanItem.setEffectdateadddate(effectdateadddate);
        // 根据起算日期延迟天数继续计算起算日期
        dbegindate = PayTermUtil.getEffectDate(dbegindate, effectdateadddate);
        payPlanItem.setDbegindate(dbegindate);
        // 账期天数
        Integer paymentday = orderPaymentVOs[i].getPaymentday();
        payPlanItem.setIitermdays(paymentday);
        // 账期到账日
        payPlanItem.setDenddate(this.getDenddate(dbegindate, orderPaymentVOs[i]));
        // 预付款
        UFBoolean prepayment = orderPaymentVOs[i].getPrepayment();
        if (prepayment == null) {
          prepayment = UFBoolean.FALSE;
        }
        payPlanItem.setBpreflag(prepayment);
        // 价税合计， 总价税合计（总付款金额）应该是表头的信息 (orderHVO.getNtotalorigmny());
        UFDouble norigtaxmny = orderItem.getNorigtaxmny();
        UFDouble norigmny = orderItem.getNorigmny();// 无税金额

        // 比率
        UFDouble rate = orderPaymentVOs[i].getAccrate();
        if (rate == null) {
          rate = new UFDouble(100);
        }
        payPlanItem.setNrate(rate);
        // 订单的价税合计（付款计划的原币金额）
        UFDouble mny = norigtaxmny.multiply(MathTool.nvl(rate)
            .div(AbstractRecvPayPlan.UF100, UFDouble.DEFAULT_POWER));
        mny = scale.adjustMnyScale(mny, corigcurrencyid);
        // 无税金额
        UFDouble noTaxMny = norigmny.multiply(MathTool.nvl(rate)
            .div(AbstractRecvPayPlan.UF100, UFDouble.DEFAULT_POWER));
        noTaxMny = scale.adjustMnyScale(noTaxMny, corigcurrencyid);

        if (i < orderPaymentVOs.length - 1) {
          // 计算金额
          payPlanItem.setNorigmny(mny);
          payPlanItem.setNorigtaxmny(mny);// 价税合计
          accOrimny = MathTool.add(accOrimny, mny);
          accNoTaxmny = MathTool.add(accNoTaxmny, noTaxMny);
        }
        else {
          // 最后一行倒挤
          payPlanItem.setNorigmny(MathTool.sub(norigtaxmny, accOrimny));
          payPlanItem.setNorigtaxmny(MathTool.sub(norigtaxmny, accOrimny));// 价税合计
          payPlanItem.setLastAccoutTerm(true);
          payPlanItem.setNorderorigmny(MathTool.sub(norigmny, accNoTaxmny));// 无税金额
        }
        // 序号
        payPlanItem.setCrowno(String.valueOf(10 * (i + 1)));
        // 质保金
        payPlanItem.setIsdeposit(orderPaymentVOs[i].getIsdeposit());
        // 结算方式
        payPlanItem.setPk_balatype(orderPaymentVOs[i].getPk_balatype());
        // 订单行主数量
        UFDouble orderItemNnum = orderItem.getNnum();
        // NCC-171729 主数量（本次确认主数量） 这里处理进度，和采购发票保持一致 begin
        UFDouble nnum = orderItemNnum.multiply(MathTool.nvl(rate)
            .div(AbstractRecvPayPlan.UF100, UFDouble.DEFAULT_POWER));
        UFDouble updateNum = UFDouble.ZERO_DBL;
        String cunitid = orderItem.getCunitid();
        if (i < orderPaymentVOs.length - 1) {
          updateNum = scale.adjustNumScale(nnum, cunitid);
          accNum = MathTool.add(accNum, updateNum);
        }
        else {
          // 求尾差
          updateNum = MathTool.sub(orderItemNnum, accNum);
        }
        payPlanItem.setNnum(updateNum);
        // NCC-171729 主数量（本次确认主数量） 这里处理进度，和采购发票保持一致 end
        // 价格金额算法
        if (payPlanBVOMap.containsKey(pk_payment)) {
          payPlanBVOMap.get(pk_payment).add(payPlanItem);
        }
        else {
          // 采购订单付款计划子表List集合
          List<PayPlanItemVO> payPlanBVOList = new ArrayList<PayPlanItemVO>();
          payPlanBVOList.add(payPlanItem);
          payPlanBVOMap.put(pk_payment, payPlanBVOList);
        }
      }
    }
  }

  /**
   * 获取付款计划主子表
   * @param orderHVO 订单表头数据
   * @param payPlanBVOMap 生成的付款计划子表，Map<付款时点，List<付款计划子表>>
   * @param pk_payments 付款时点主键（付款计划表头是按照付款时点主键汇总的）
   * @return
   */
  public PayPlanBillVO[] getPayPlanVOs(OrderHeaderVO orderHVO,
      Map<String, List<PayPlanItemVO>> payPlanBVOMap,
      List<String> pk_payments) {
    List<PayPlanBillVO> panPlanList = new ArrayList<PayPlanBillVO>();
    int hvoCrowno = 0;// 采购订单付款计划表头的序号基数，后面会乘以10
    for (int i = 0; i < pk_payments.size(); i++) {
      String pk_paymentList = pk_payments.get(i);
      PayPlanBillVO payPlan = new PayPlanBillVO();
      // 设置采购订单付款计划子表信息
      List<PayPlanItemVO> payPlanBVOs = payPlanBVOMap.get(pk_paymentList);
      PayPlanItemVO[] payPlanItems =
          payPlanBVOs.toArray(new PayPlanItemVO[payPlanBVOs.size()]);
      payPlan.setBVO(payPlanItems);
      // 设置采购订单付款计划表头信息（汇总信息）
      PayPlanVO payPlanHVO = this.getPayPlanHVO(pk_paymentList, orderHVO,
          payPlanItems, ++hvoCrowno);
      payPlan.setHVO(payPlanHVO);
      /** 4、组装聚合vo，并设置到list集合中，用以返回组装好的采购订单付款计划聚合vo数据 */
      // 将设置好的聚合VO设置到返回的list中
      panPlanList.add(payPlan);
    }
    PayPlanBillVO[] bills =
        panPlanList.toArray(new PayPlanBillVO[panPlanList.size()]);
    return bills;
  }

  /**
   * 组装采购订单付款计划表头VO
   * @param pk_payment 采购订单付款协议主键
   * @param orderHVO 采购订单表头数据
   * @param payPlanItems 采购订单付款计划表体数据
   * @param hvoCrowno 采购订单付款计划表头序号基数，乘以10作为采购订单付款计划表头的序号使用
   * @return
   *
   */
  public PayPlanVO getPayPlanHVO(String pk_payment, OrderHeaderVO orderHVO,
      PayPlanItemVO[] payPlanItems, int hvoCrowno) {
    BillBodySortUtils.sort(payPlanItems);// 排一下序
    // 存放已经设置过数值的字段，例如表头的应付财务组织，设置过之后，如果发现表体的应付组织不完全相同，则需要清空，后续不再设置
    Set<String> hadSetValue = new HashSet<String>();
    PayPlanVO payPlanHVO = new PayPlanVO();
    // 集团
    payPlanHVO.setPk_group(orderHVO.getPk_group());
    // 采购组织
    payPlanHVO.setPk_org(orderHVO.getPk_org());
    payPlanHVO.setPk_org_v(orderHVO.getPk_org_v());
    // 采购订单主键
    payPlanHVO.setPk_order(orderHVO.getPk_order());
    // 采购订单编码（单据号）
    payPlanHVO.setVordercode(orderHVO.getVbillcode());
    // 采购订单单据日期
    payPlanHVO.setVorderbilldate(orderHVO.getDbilldate());
    // 采购订单单据类型、交易类型、交易类型编码
    payPlanHVO.setVordertype(POBillType.Order.getCode());
    payPlanHVO.setVordertrantype(orderHVO.getCtrantypeid());
    payPlanHVO.setVordertrantypecode(orderHVO.getVtrantypecode());
    // 供应商
    payPlanHVO.setCvendorid(orderHVO.getPk_supplier());
    payPlanHVO.setCvendorvid(orderHVO.getPk_supplier_v());
    // 开票供应商
    payPlanHVO.setPk_invcsupllier(orderHVO.getPk_invcsupllier());
    payPlanHVO.setPk_invcsupllier_v(orderHVO.getPk_invcsupllier_v());
    // 散户
    payPlanHVO.setPk_freecust(orderHVO.getPk_freecust());
    // 质保金
    payPlanHVO.setIsdeposit(payPlanItems[0].getIsdeposit());
    // 账期号（付款计划子表的账期号都一样，都取自付款协议）
    payPlanHVO.setIaccounttermno(payPlanItems[0].getIaccounttermno());
    // 起算依据（付款计划子表的起算依据都一样，都取自付款协议）
    payPlanHVO.setFeffdatetype(payPlanItems[0].getFeffdatetype());
    // 账期天数（付款计划子表的账期天数都一样，都取自付款协议）
    payPlanHVO.setIitermdays(payPlanItems[0].getIitermdays());
    // 起效日期延迟天数（付款计划子表起效日期延迟天数都一样，都取自付款协议）
    payPlanHVO.setEffectdateadddate(payPlanItems[0].getEffectdateadddate());
    // 付款协议主键
    payPlanHVO.setPk_paytermch(pk_payment);
    // 付款协议参照
    String pk_payterm = orderHVO.getPk_payterm();
    payPlanHVO.setPk_payterm(pk_payterm);
    // 预付款（付款计划子表的预付款都一样，都取自付款协议）
    payPlanHVO.setBpreflag(payPlanItems[0].getBpreflag());
    // 比率(%)（付款计划子表的比率(%)都一样，都取自付款协议）
    payPlanHVO.setNrate(payPlanItems[0].getNrate());
    // 币种
    payPlanHVO.setCorigcurrencyid(orderHVO.getCorigcurrencyid());
    // 开户银行
    payPlanHVO.setPk_bankdoc(orderHVO.getPk_bankdoc());
    // 退货
    payPlanHVO.setBreturn(orderHVO.getBreturn());
    // 冻结
    payPlanHVO.setBfrozen(orderHVO.getBfrozen());
    // 业务流程
    payPlanHVO.setPk_busitype(orderHVO.getPk_busitype());
    // 整单扣税类别
    payPlanHVO.setFhtaxtypeflag(orderHVO.getFhtaxtypeflag());
    // 整单税率
    payPlanHVO.setNhtaxrate(orderHVO.getNhtaxrate());
    // 贸易术语
    payPlanHVO.setCtradewordid(orderHVO.getCtradewordid());
    // 结算方式 与05金盘一致，取表头
    if (payPlanItems[0].getPk_balatype() != null) {
      // 如果表体有，就取表体的，如果表体没有，就取表头的
      payPlanHVO.setPk_balatype(payPlanItems[0].getPk_balatype());
    }
    else {
      payPlanHVO.setPk_balatype(orderHVO.getPk_balatype());
    }
    // 总重量
    payPlanHVO.setNtotalweight(orderHVO.getNtotalweight());
    // 总体积
    payPlanHVO.setNtotalvolume(orderHVO.getNtotalvolume());
    // 总件数
    payPlanHVO.setNtotalpiece(orderHVO.getNtotalpiece());
    // 项目
    payPlanHVO.setPk_project(orderHVO.getPk_project());
    // 制单人
    payPlanHVO.setBillmaker(orderHVO.getBillmaker());
    // 制单日期
    payPlanHVO.setDmakedate(orderHVO.getDmakedate());
    // 采购员
    payPlanHVO.setCemployeeid(orderHVO.getCemployeeid());
    // 采购部门
    payPlanHVO.setPk_dept(orderHVO.getPk_dept());
    // 采购部门vid
    payPlanHVO.setPk_dept_v(orderHVO.getPk_dept_v());
    // 里程碑采购（付款计划子表的里程碑采购数都一样，都取自采购订单表头）
    payPlanHVO.setBismilepost(payPlanItems[0].getBismilepost());
    for (PayPlanItemVO payPlanItemVO : payPlanItems) {
      // 应付财务组织（如果表体行的应付财务组织相同再给表头赋值，否则不赋值）
      String pk_financeorg = payPlanHVO.getPk_financeorg();
      String pk_financeorgItem = payPlanItemVO.getPk_financeorg();
      String pk_financeorg_vItem = payPlanItemVO.getPk_financeorg_v();
      String ccurrencyid = payPlanItemVO.getCcurrencyid();// 本币币种
      if (pk_financeorg == null
          && !hadSetValue.contains(AbstractPayPlanVO.PK_FINANCEORG)) {
        hadSetValue.add(AbstractPayPlanVO.PK_FINANCEORG);
        payPlanHVO.setPk_financeorg(pk_financeorgItem);
        payPlanHVO.setPk_financeorg_v(pk_financeorg_vItem);
        payPlanHVO.setCcurrencyid(ccurrencyid);
      }
      else if (pk_financeorg != null
          && !pk_financeorg.equals(pk_financeorgItem)) {
        payPlanHVO.setPk_financeorg(null);
        payPlanHVO.setPk_financeorg_v(null);
        payPlanHVO.setCcurrencyid(null);
      }
      // 序号
      payPlanHVO.setCrowno(String.valueOf(10 * hvoCrowno));
      // 起算日期（取表体最新的起算日期），账期到账日（取表体最新的账期到账日即可，表体在组装的时候已经计算过了）
      UFDate dbegindate = payPlanHVO.getDbegindate();
      UFDate dbegindateItem = payPlanItemVO.getDbegindate();
      UFDate denddateItem = payPlanItemVO.getDenddate();
      if (dbegindate == null) {
        payPlanHVO.setDbegindate(dbegindateItem);
        payPlanHVO.setDenddate(denddateItem);
      }
      else {
        if (dbegindate.compareTo(dbegindateItem) < 0) {
          payPlanHVO.setDbegindate(dbegindateItem);
          payPlanHVO.setDenddate(denddateItem);
        }
      }
      // 金额
      UFDouble norigmny = payPlanHVO.getNorigmny();
      UFDouble norigmnyItem = payPlanItemVO.getNorigmny();
      payPlanHVO.setNorigmny(MathTool.add(norigmny, norigmnyItem));
      // 本币金额
      UFDouble nmny = payPlanHVO.getNmny();
      UFDouble nmnyItem = payPlanItemVO.getNmny();
      payPlanHVO.setNmny(MathTool.add(nmny, nmnyItem));
      // 折本汇率（付款计划子表的折本汇率如果一致，则给表头赋值，否则不赋值）
      this.setGatherPayPlanHVO(payPlanHVO, payPlanItemVO,
          AbstractPayPlanVO.NEXCHANGERATE, hadSetValue);
      // 组织汇率类型（付款计划子表的组织汇率类型如果一致，则给表头赋值，否则不赋值）
      this.setGatherPayPlanHVO(payPlanHVO, payPlanItemVO,
          AbstractPayPlanVO.CRATETYPE, hadSetValue);
      // 组织汇率类别（付款计划子表的组织汇率类别如果一致，则给表头赋值，否则不赋值）
      this.setGatherPayPlanHVO(payPlanHVO, payPlanItemVO,
          AbstractPayPlanVO.FRATECATEGORY, hadSetValue);
      // 组织汇率来源日期（付款计划子表的组织汇率来源日期如果一致，则给表头赋值，否则不赋值）
      this.setGatherPayPlanHVO(payPlanHVO, payPlanItemVO,
          AbstractPayPlanVO.DRATEDATE, hadSetValue);
      // 合同号（付款计划子表的合同号如果一致，则给表头赋值，否则不赋值）
      this.setGatherPayPlanHVO(payPlanHVO, payPlanItemVO,
          OrderItemVO.CCONTRACTID, hadSetValue);
    }
    // 总数量
    payPlanHVO.setNtotalastnum(orderHVO.getNtotalastnum());
    // 总价税合计
    payPlanHVO.setNtotalorigmny(orderHVO.getNtotalorigmny());
    // 审批时间
    payPlanHVO.setTaudittime(orderHVO.getTaudittime());
    // 审批人
    payPlanHVO.setApprover(orderHVO.getApprover());
    // 备注
    payPlanHVO.setVmemo(orderHVO.getVmemo());
    if (StringUtil.isSEmptyOrNull(payPlanHVO.getCcurrencyid())) {
      payPlanHVO.setNmny(null);
    }
    return payPlanHVO;
  }

  /**
   * 处理尾差
   * @param bills 付款计划主子表主键
   * @param orderBidToBvo Map<采购订单表体主键,采购订单表体>
   *
   */
  public void processLastDiff(PayPlanBillVO[] bills,
      Map<String, OrderItemVO> orderBidToBvo) {
    // 将相同物料付款计划子表放在一起
    Map<String, List<PayPlanItemVO>> mapItemVO =
        new HashMap<String, List<PayPlanItemVO>>();
    // Map<订单付款计划表头主键，订单付款计划表头vo>，用于设置付款计划表头的本币金额
    Map<String, PayPlanVO> pkToHvo = new HashMap<String, PayPlanVO>();
    // 将相同物料子表行放在一起
    for (int i = 0; i < bills.length; i++) {
      // 设置表头映射，用于设置表头的本币金额，本币金额单价金额算法只计算表体的，表头的金额是表体汇总的
      PayPlanVO hvo = bills[i].getHVO();
      String pk_order_payplan = hvo.getPk_order_payplan();
      pkToHvo.put(pk_order_payplan, hvo);
      // 设置表体的映射，计算表体尾差使用
      PayPlanItemVO[] itemVOs = bills[i].getBVO();
      for (int j = 0; j < itemVOs.length; j++) {
        PayPlanItemVO item = itemVOs[j];
        List<PayPlanItemVO> listItemVO = new ArrayList<PayPlanItemVO>();
        if (mapItemVO.containsKey(itemVOs[j].getPk_order_b())) {
          mapItemVO.get(itemVOs[j].getPk_order_b()).add(item);
        }
        else {
          listItemVO.add(item);
          mapItemVO.put(itemVOs[j].getPk_order_b(), listItemVO);
        }
      }
    }
    String[] pk_order_bs = mapItemVO.keySet().toArray(new String[0]);

    // 创建精度处理工具
    String pk_group = AppContext.getInstance().getPkGroup();
    ScaleUtils scale = new ScaleUtils(pk_group);
    // 处理尾差
    for (int i = 0; i < pk_order_bs.length; i++) {

      PayPlanItemVO[] itemvos =
          mapItemVO.get(pk_order_bs[i]).toArray(new PayPlanItemVO[0]);
      BillBodySortUtils.sort(itemvos);// 对付款计划进行排序

      UFDouble[] accs = new UFDouble[12];
      accs[0] = UFDouble.ZERO_DBL;// 0-税额
      accs[1] = UFDouble.ZERO_DBL;// 1-本币金额
      accs[2] = UFDouble.ZERO_DBL;// 2-无税金额
      accs[3] = UFDouble.ZERO_DBL;// 3-本币无税金额
      accs[4] = UFDouble.ZERO_DBL;// 4-价税合计
      accs[5] = UFDouble.ZERO_DBL;// 5-计税金额
      accs[6] = UFDouble.ZERO_DBL;// 6-计成本金额
      accs[7] = UFDouble.ZERO_DBL;// 7-不可抵扣税额
      accs[8] = UFDouble.ZERO_DBL;// 8-全局本币无税金额
      accs[9] = UFDouble.ZERO_DBL;// 9-全局本币价税合计
      accs[10] = UFDouble.ZERO_DBL;// 10-集团本币无税金额
      accs[11] = UFDouble.ZERO_DBL;// 11-集团本币价税合计

      for (int j = 0; j < itemvos.length; j++) {
        // 这里把订单的单价设置回付款计划自表上，因为生成付款计划的时候，单价不变才是对的
        String pk_order_b = itemvos[j].getPk_order_b();
        OrderItemVO orderItemVO = orderBidToBvo.get(pk_order_b);
        if (orderItemVO != null) {
          // 恢复单价
//          this.setPrice(itemvos[j], orderItemVO);
        }
        // 以下是求金额尾差逻辑，前面的行先求和，最后一行再求尾差
        if (j < itemvos.length - 1) {
          // 求和
          accs[0] = MathTool.add(accs[0], itemvos[j].getNtax());// 0-税额
          accs[1] = MathTool.add(accs[1], itemvos[j].getNmny());// 1-本币金额
          accs[2] = MathTool.add(accs[2], itemvos[j].getNorderorigmny());// 2-无税金额
          accs[3] = MathTool.add(accs[3], itemvos[j].getNordermny());// 3-无税本币金额
          accs[4] = MathTool.add(accs[4], itemvos[j].getNorigtaxmny());// 4-价税合计
          accs[5] = MathTool.add(accs[5], itemvos[j].getNcaltaxmny());// 5-计税金额
          accs[6] = MathTool.add(accs[6], itemvos[j].getNcalcostmny());// 6-计成本金额
          accs[7] = MathTool.add(accs[7], itemvos[j].getNnosubtax());// 7-不可抵扣税额
          accs[8] = MathTool.add(accs[8], itemvos[j].getNglobalmny());// 8-全局本币无税金额
          accs[9] = MathTool.add(accs[9], itemvos[j].getNglobaltaxmny());// 9-全局本币价税合计
          accs[10] = MathTool.add(accs[10], itemvos[j].getNgroupmny());// 10-集团本币无税金额
          accs[11] = MathTool.add(accs[11], itemvos[j].getNgrouptaxmny());// 11-集团本币价税合计
        }
        else {
          // 倒挤
          this.processLastDiffMnyAndScale(scale, orderBidToBvo, itemvos[j],
              accs);
        }
      }
    }
  }

  /**
   * 设置表头的本币金额，重构前是先计算了本币金额，然后汇总到订单表头，重构后，单价金额算法统一计算
   * 付款计划表头的本币金额是表体汇总的，因此这里重新汇总一下
   * 新增的数据没有主键，没有办法做集合处理，因此，这里统一处理
   * @param bills 单价金额算法之后的付款计划
   *
   */
  private void sumHeadMny(PayPlanBillVO[] bills) {
    for (PayPlanBillVO bill : bills) {
      UFDouble accHvoMny = UFDouble.ZERO_DBL;
      // PayPlanVO hvo = bill.getHVO();
      // UFDouble headMny = hvo.getNmny();
      PayPlanItemVO[] bvos = bill.getBVO();
      for (PayPlanItemVO bvo : bvos) {
        UFDouble bodyMny = bvo.getNmny();
        accHvoMny = MathTool.add(accHvoMny, bodyMny);
      }
      bill.getHVO().setNmny(accHvoMny);
    }
  }

  /**
   * 设置汇总信息，表体上的数据如果相同，则给表头设置，如果不同则不设置
   * @param payPlanHVO 付款计划表头信息
   * @param payPlanItem 付款计划表体信息
   * @param key 字段
   * @param hadSetValue 已经设置过的字段，如果这个集合里面有着字段，则这个字段的值为null时，不需要再设置
   *
   */
  public void setGatherPayPlanHVO(PayPlanVO payPlanHVO,
      PayPlanItemVO payPlanItem, String key, Set<String> hadSetValue) {
    Object headValue = payPlanHVO.getAttributeValue(key);
    Object bodyValue = payPlanItem.getAttributeValue(key);
    if (headValue == null && !hadSetValue.contains(key)) {
      hadSetValue.add(key);
      payPlanHVO.setAttributeValue(key, bodyValue);
    }
    else if (headValue != null && !headValue.equals(bodyValue)) {
      payPlanHVO.setAttributeValue(key, null);
    }
  }

  /**
   * 获取非null的UFDouble类型
   * @param number 数值
   * @return
   *
   */
  public UFDouble getUFDouleNotNull(UFDouble number) {
    return number == null ? UFDouble.ZERO_DBL : number;
  }

  /**
   * 设置金额的精度，总数-和值=尾差。
   * @param scale 精度工具类（调用处已创建对象）
   * @param orderBidToBvo Map<采购订单表体主键，采购订单表体>
   * @param item 付款计划表体
   * @param accs 和值数组（数值的下标对应的业务含义与mnyArrs一致）
   *
   */
  public void processLastDiffMnyAndScale(ScaleUtils scale,
      Map<String, OrderItemVO> orderBidToBvo, PayPlanItemVO item,
      UFDouble[] accs) {
    String corigcurrencyid = item.getCorigcurrencyid();// 原币币种
    String ccurrencyid = item.getCcurrencyid();// 本币币种
    String pk_order_b = item.getPk_order_b();
    // 总数数组，顺序和mnyArrs一致
    OrderItemVO orderItemVO = orderBidToBvo.get(pk_order_b);
    if (orderItemVO == null) {
      // 修订时删除了物料行，这个表体就找不到了
      return;
    }
    for (int i = 0; i < mnyArrs.length; i++) {
      String mnyArr = mnyArrs[i];// 对应的金额字段
      // 判断全局NC002是否启用，不启用不处理尾差
      if(PayPlanItemVO.NGLOBALMNY.equals(mnyArr) || PayPlanItemVO.NGLOBALTAXMNY.equals(mnyArr)){
        String nc002 = PubSysParamUtil.getNC002();
        if (NCPara.NC002_NOUSEGLOBALCURRTYPE.getName().equals(nc002)) {
            continue;
        }
      }
      // 判断集团NC001是否启用，不启用不处理尾差
      if(PayPlanItemVO.NGROUPMNY.equals(mnyArr) || PayPlanItemVO.NGROUPTAXMNY.equals(mnyArr)){
        String nc001 = PubSysParamUtil.getNC001(item.getPk_group());
        if (NCPara.NC001_NOUSEGROUPCURRTYPE.getName().equals(nc001)) {
          continue;
        }
      }
      UFDouble acc = accs[i];// 和值，求尾差使用的
      // 获取订单金额，mnyArr是付款计划子表的字段，需要找到对应订单的字段
      Object mnyObj =
          orderItemVO.getAttributeValue(planToOrderMnyKeys.get(mnyArr));
      UFDouble mny = mnyObj == null ? null : (UFDouble) mnyObj;
      // 尾差
      UFDouble lastDiffNumber = MathTool.sub(mny, acc);
      if (oriMnyKeyList.contains(mnyArr)) {
        // 处理原币金额精度
        item.setAttributeValue(mnyArr,
            scale.adjustMnyScale(lastDiffNumber, corigcurrencyid));
      }
      else if (mnyKeyList.contains(mnyArr)) {
        if (glogalAndGroupMnyKeyList.contains(mnyArr) && mny == null) {
          // 如果是全局、集团本币金额，没有金额就不需要处理了
          continue;
        }
        // 处理本币金额精度
        item.setAttributeValue(mnyArr,
            scale.adjustMnyScale(lastDiffNumber, ccurrencyid));
      }
    }
  }

  /**
   * 设置付款计划子表行上的单价，在付款计划子表行单价金额算法之后，需要把单价恢复成订单的
   * 
   * 设置过程：
   * 1、获取付款计划单价字段
   * 2、通过  planToOrderPriceKeys 集合，获取到订单上对应的单价字段
   * 3、获取订单单价，设置到付款计划子表上
   * 
   * @param item 单价金额算法之后的付款计划子表
   * @param orderItemVO 订单表体行数据
   *
   */
  public void setPrice(PayPlanItemVO item, OrderItemVO orderItemVO) {
    for (String priceKey : priceArrs) {
      String orderPriceKey = planToOrderPriceKeys.get(priceKey);
      Object price = orderItemVO.getAttributeValue(orderPriceKey);
      item.setAttributeValue(priceKey, price);
    }
  }
}
```
3. 检查当前工程下有没有对应的源码类：`nc.pubitf.pu.m21p.util.PayPlanBillVODataUtil`
   若存在源码，则在工程的源码中处理要求的业务
   若不存在源码，则先按2生成源码，再按要求处理业务

4. 要求
   * 不管工程中有没有源码，只需要在业务处理点处理业务，整体不做重构
   * 源码的编码格式为GBK，不可修改源码的编码格式


