private Collection<I_PP_MRP> getPP_MRPs_ToUse() {
    final Map<Integer, I_PP_MRP> mrps = new LinkedHashMap<>();

    //
    // Get MRP records that were explicitelly set
    for (final I_PP_MRP mrp : _mrps) {
      mrps.put(mrp.getPP_MRP_ID(), mrp);
    }
    // If we have MRP records explicitelly assigned, there is no point checking the MRP Context
    // because developer specified them and the ones from MRP context could be not relevant
    if (!mrps.isEmpty()) {
      return mrps.values();
    }

    //
    // Get MRP records from context
    final IMRPContext mrpContext = getMRPContext();
    if (mrpContext != null) {
      final I_PP_MRP mrp = mrpContext.getPP_MRP();
      if (mrp != null) {
        mrps.put(mrp.getPP_MRP_ID(), mrp);
      }
    }

    return mrps.values();
  }
  private I_PP_Product_Planning getProductPlanning_ToUse() {
    final IMRPContext mrpContext = getMRPContext();
    if (mrpContext != null) {
      return mrpContext.getProductPlanning();
    }

    return null;
  }
  private final Properties getCtx() {
    final IMRPContext mrpContext = getMRPContext();
    if (mrpContext != null) {
      return mrpContext.getCtx();
    }

    return Env.getCtx();
  }
  private I_M_Product getM_Product_ToUse() {
    if (_product != null) {
      return _product;
    }

    final IMRPContext mrpContext = getMRPContext();
    if (mrpContext != null) {
      return mrpContext.getM_Product();
    }
    return null;
  }
  private I_AD_Org getAD_Org_ToUse() {
    if (_org != null) {
      return _org;
    }

    final IMRPContext mrpContext = getMRPContext();
    if (mrpContext != null) {
      return mrpContext.getAD_Org();
    }

    return null;
  }
  private I_M_Warehouse getM_Warehouse_ToUse() {
    if (_warehouse != null) {
      return _warehouse;
    }

    final IMRPContext mrpContext = getMRPContext();
    if (mrpContext != null) {
      return mrpContext.getM_Warehouse();
    }

    return null;
  }
  private I_S_Resource getPlant_ToUse() {
    if (_plant != null) {
      return _plant;
    }

    final IMRPContext mrpContext = getMRPContext();
    if (mrpContext != null) {
      return mrpContext.getPlant();
    }

    return null;
  }
  private int getAD_Client_ID_ToUse() {
    if (_adClientId != null) {
      return _adClientId;
    } else if (_mrpContext != null) {
      return _mrpContext.getAD_Client_ID();
    }

    return -1;
  }
  private int getM_Product_ID_ToUse() {
    if (_productId != null) {
      return _productId;
    } else if (_mrpContext != null) {
      return _mrpContext.getM_Product_ID();
    }

    return -1;
  }
  @Override
  public boolean applies(final IMRPContext mrpContext, final IMutable<String> notAppliesReason) {
    if (!mrpContext.isRequireDRP()) {
      notAppliesReason.setValue("DRP not enabled");
      return false;
    }

    // Check distribution network
    final I_PP_Product_Planning productPlanning = mrpContext.getProductPlanning();
    if (productPlanning.getDD_NetworkDistribution_ID() <= 0) {
      notAppliesReason.setValue(
          "No distribution network configured in product data planning: " + productPlanning);
      return false;
    }

    // If nothing else is preventing as, consider that we can do DRP
    return true;
  }
  private int getAD_Org_ID_ToUse() {
    if (_adOrgId != null) {
      return _adOrgId;
    } else if (_mrpContext != null) {
      final I_AD_Org org = _mrpContext.getAD_Org();
      if (org != null) {
        return org.getAD_Org_ID();
      }
    }

    return -1;
  }
  private int getM_Warehouse_ID_ToUse() {
    if (_warehouseId != null) {
      return _warehouseId;
    } else if (_mrpContext != null) {
      final I_M_Warehouse warehouse = _mrpContext.getM_Warehouse();
      if (warehouse != null) {
        return warehouse.getM_Warehouse_ID();
      }
    }

    return -1;
  }
  @Override
  public void cleanup(final IMRPContext mrpContext, final IMRPExecutor executor) {
    // If DRP module is not activated, then skip the cleanup
    if (!mrpContext.isRequireDRP()) {
      return;
    }

    final IQueryBL queryBL = Services.get(IQueryBL.class);

    //
    // Delete generated distribution orders
    // (i.e. Distribution Order with Draft Status)
    final ICompositeQueryFilter<I_DD_Order> filters =
        queryBL.createCompositeQueryFilter(I_DD_Order.class);
    filters.addEqualsFilter(I_DD_Order.COLUMNNAME_DocStatus, X_DD_Order.DOCSTATUS_Drafted);

    //
    // Only those which were generated by MRP
    filters.addEqualsFilter(I_DD_Order.COLUMN_MRP_Generated, true);
    // Only those which are allowed to be deleted by MRP cleanup
    filters.addEqualsFilter(I_DD_Order.COLUMN_MRP_AllowCleanup, true);

    //
    // Only for our AD_Client_ID
    filters.addEqualsFilter(I_DD_Order.COLUMNNAME_AD_Client_ID, mrpContext.getAD_Client_ID());
    //
    // Only for our AD_Org_ID
    filters.addEqualsFilter(I_DD_Order.COLUMNNAME_AD_Org_ID, mrpContext.getAD_Org().getAD_Org_ID());
    //
    // Only those DD Orders which are from our Plant or does not have a plant at all
    filters.addInArrayFilter(
        I_DD_Order.COLUMNNAME_PP_Plant_ID, null, mrpContext.getPlant().getS_Resource_ID());

    //
    // Only those which have a line with Destination Warehouse same as our warehouse
    final int targetWarehouseId = mrpContext.getM_Warehouse().getM_Warehouse_ID();
    filters.addFilter(
        Services.get(IDDOrderDAO.class)
            .getDDOrdersForTargetWarehouseQueryFilter(targetWarehouseId));

    //
    // If we are running in an constrained MRP Context, filter only those documents
    if (mrpContext.getEnforced_PP_MRP_Demand_ID() > 0) {
      final IQuery<I_PP_MRP> mrpQuery =
          createMRPQueryBuilderForCleanup(mrpContext, executor)
              .createQueryBuilder()
              .addEqualsFilter(I_PP_MRP.COLUMN_TypeMRP, X_PP_MRP.TYPEMRP_Supply)
              .create();

      filters.addInSubQueryFilter(
          I_DD_Order.COLUMN_DD_Order_ID, I_PP_MRP.COLUMN_DD_Order_ID, mrpQuery);
    }

    deletePO(mrpContext, executor, I_DD_Order.class, filters);
  }
  private int calculateDurationDays(
      final IMRPContext mrpContext, final I_DD_NetworkDistributionLine networkLine) {
    final I_PP_Product_Planning productPlanningData = mrpContext.getProductPlanning();

    //
    // Leadtime
    final int leadtimeDays = productPlanningData.getDeliveryTime_Promised().intValueExact();
    Check.assume(leadtimeDays >= 0, LiberoException.class, "leadtimeDays >= 0");

    //
    // Transfer time
    int transferTime = networkLine.getTransfertTime().intValueExact();
    if (transferTime <= 0) {
      transferTime = productPlanningData.getTransfertTime().intValueExact();
    }
    Check.assume(transferTime >= 0, LiberoException.class, "transferTime >= 0");

    final int durationTotalDays = leadtimeDays + transferTime;
    return durationTotalDays;
  }
  private Set<Integer> getPP_Plant_IDs_ToUse() {
    int ppPlantId = -1;
    if (_ppPlantId != null) {
      ppPlantId = _ppPlantId;
    } else if (_mrpContext != null) {
      final I_S_Resource plant = _mrpContext.getPlant();
      if (plant != null) {
        ppPlantId = plant.getS_Resource_ID();
      }
    }

    if (ppPlantId <= 0) {
      return Collections.emptySet();
    }

    final Set<Integer> plantIds = new HashSet<>();
    plantIds.add(ppPlantId);
    if (_acceptNoPlant) {
      plantIds.add(null);
    }

    return plantIds;
  }
  private I_DD_OrderLine createDD_OrderLine(
      final IMRPContext mrpContext,
      final I_DD_Order order,
      final I_DD_NetworkDistributionLine networkLine,
      final I_M_Locator locatorFrom,
      final I_M_Locator locatorTo,
      final BigDecimal qtyToMove,
      final Timestamp supplyDateFinishSchedule,
      final IMRPCreateSupplyRequest request) {
    final I_M_Product product = mrpContext.getM_Product();
    final int bpartnerId = request.getMRPDemandBPartnerId();
    final int orderLineSOId = request.getMRPDemandOrderLineSOId();

    //
    // Create DD Order Line
    final I_DD_OrderLine ddOrderline =
        InterfaceWrapperHelper.newInstance(I_DD_OrderLine.class, order);
    ddOrderline.setAD_Org_ID(order.getAD_Org_ID());
    ddOrderline.setDD_Order(order);
    ddOrderline.setC_BPartner_ID(bpartnerId);
    ddOrderline.setC_OrderLineSO_ID(orderLineSOId);

    //
    // Locator From/To
    ddOrderline.setM_Locator(locatorFrom);
    ddOrderline.setM_LocatorTo(locatorTo);

    //
    // Product, UOM, Qty
    // NOTE: we assume qtyToMove is in "mrpContext.getC_UOM()" which shall be the Product's stocking
    // UOM
    ddOrderline.setM_Product(product);
    ddOrderline.setC_UOM(mrpContext.getC_UOM());
    ddOrderline.setQtyEntered(qtyToMove);
    ddOrderline.setQtyOrdered(qtyToMove);
    ddOrderline.setTargetQty(qtyToMove);

    //
    // Dates
    ddOrderline.setDateOrdered(order.getDateOrdered());
    ddOrderline.setDatePromised(order.getDatePromised());

    //
    // Other flags
    ddOrderline.setIsInvoiced(false);
    ddOrderline.setDD_AllowPush(networkLine.isDD_AllowPush());
    ddOrderline.setIsKeepTargetPlant(networkLine.isKeepTargetPlant());

    //
    // Save DD Order Line
    InterfaceWrapperHelper.save(ddOrderline);

    //
    // Create DD_OrderLine_Alternatives
    // NOTE: demand MRP line is available only in lot-for-lot order policy
    // TODO: i think we shall get all MRP demands, retrieve their alternatives, aggregate them and
    // create DD_OrderLine_Alternatives
    final I_PP_MRP parentMRPDemand = request.getMRPDemandRecordOrNull();
    if (parentMRPDemand != null) {
      final List<I_PP_MRP_Alternative> mrpAlternatives =
          mrpDAO.retrieveMRPAlternativesQuery(parentMRPDemand).create().list();
      for (final I_PP_MRP_Alternative mrpAlternative : mrpAlternatives) {
        createDD_OrderLine_Alternative(mrpContext, ddOrderline, mrpAlternative);
      }
    }

    //
    // Set Correct Planning Dates
    final int durationDays = calculateDurationDays(mrpContext, networkLine);
    final Timestamp supplyDateStartSchedule =
        TimeUtil.addDays(supplyDateFinishSchedule, 0 - durationDays);
    final List<I_PP_MRP> mrpList = mrpDAO.retrieveMRPRecords(ddOrderline);
    for (final I_PP_MRP mrp : mrpList) {
      mrp.setDateStartSchedule(supplyDateStartSchedule);
      mrp.setDateFinishSchedule(supplyDateFinishSchedule);
      InterfaceWrapperHelper.save(mrp);
    }

    return ddOrderline;
  }
Exemplo n.º 17
0
  /** Check Scheduled Dates for given MRP Supply record and creates MRP notes if needed */
  private void checkMRPSupplyDates(final IMRPDemandToSupplyAllocation mrpDemandToSupplyAlloc) {
    //
    // Extract from parameters only what we need
    final IMRPContext mrpContext = getMRPContext();
    final Date mrpRunDate =
        mrpContext.getDate(); // date when MRP is executed; this is our reference date
    final I_PP_MRP mrpDemandRecord =
        mrpDemandToSupplyAlloc
            .getMRPDemand(); // NOTE: MRP Demand is needed only as a reference, no other
    // informations are/shall be used from there
    final I_PP_MRP mrpSupplyRecord = mrpDemandToSupplyAlloc.getMRPSupply();
    final BigDecimal mrpSupplyQty = mrpDemandToSupplyAlloc.getQtyAllocated();

    // In case is QOH allocation, consider it valid
    if (mrpBL.isQtyOnHandAnyReservation(mrpSupplyRecord)) {
      return;
    }

    //
    //
    final boolean mrpSupplyIsFirm = mrpBL.isReleased(mrpSupplyRecord);
    final boolean mrpSupplyQtyNotZero = mrpSupplyQty.signum() != 0;
    final Date mrpSupplyDateFinishSchedule =
        mrpSupplyRecord
            .getDateFinishSchedule(); // Date when the supply it's scheduled by MRP to be available
    // in our warehouse
    final Date mrpSupplyDatePromised =
        mrpSupplyRecord
            .getDatePromised(); // Date when supply is promised by "vendor" to be available in our
    // warehouse

    //
    // MRP-030 De-Expedite Action Notice
    // Indicates that a schedule supply order is due before it is needed and should be delayed,
    // or demand rescheduled to an earlier date.
    // aka: Push Out
    if (mrpSupplyIsFirm
        && mrpSupplyQtyNotZero
        && mrpSupplyDateFinishSchedule != null
        && mrpSupplyDateFinishSchedule.compareTo(demandDateStartSchedule)
            < 0 // supply is scheduled to arrive before it's needed
    ) {
      newSupplyMRPNote(mrpSupplyRecord, "MRP-030")
          .addParameter("DateSupply", mrpSupplyDateFinishSchedule)
          .addParameter("DateDemand", demandDateStartSchedule)
          .addParameter("PP_MRP_Demand_ID", mrpDemandRecord)
          .collect();
    }

    //
    // MRP-040 Expedite Action Notice
    // Indicates that a scheduled supply order is due after is needed and should be rescheduled to
    // an earlier date or demand rescheduled to a later date.
    // aka: Pull In
    if (mrpSupplyIsFirm
        && mrpSupplyQtyNotZero
        && mrpSupplyDateFinishSchedule != null
        && mrpSupplyDateFinishSchedule.compareTo(demandDateStartSchedule)
            > 0 // supply is scheduled to arrive after it's needed (that could be a serious problem)
    ) {
      newSupplyMRPNote(mrpSupplyRecord, "MRP-040")
          .addParameter("DateSupply", mrpSupplyDateFinishSchedule)
          .addParameter("DateDemand", demandDateStartSchedule)
          .addParameter("PP_MRP_Demand_ID", mrpDemandRecord)
          .collect();
    }

    //
    // MRP-060 Release Due For Action Notice in time
    // Indicate that a supply order should be released. if it is a draft order, it must also be
    // approved.
    // if(date release > today && date release + after floating)
    if (!mrpSupplyIsFirm
        && mrpSupplyQtyNotZero
        && mrpSupplyDatePromised.compareTo(mrpRunDate) >= 0) {
      newSupplyMRPNote(mrpSupplyRecord, MRPExecutor.MRP_ERROR_060_SupplyDueButNotReleased)
          .addParameter("SupplyDatePromised", mrpSupplyDatePromised)
          .addParameter("MRPDateRun", mrpRunDate)
          .collect();
    }

    //
    // MRP-070 Release Past Due For Action Notice overdue
    // Indicates that a supply order was not released when it was due, and should be either released
    // or expedited now, or the demand rescheduled for a later date.
    // if (date release < today && date erelese + before floating)
    if (!mrpSupplyIsFirm
        && mrpSupplyQtyNotZero
        && mrpSupplyDatePromised.compareTo(mrpRunDate) < 0) {
      newSupplyMRPNote(mrpSupplyRecord, MRPExecutor.MRP_ERROR_070_SupplyPastDueButNotReleased)
          .addParameter("SupplyDatePromised", mrpSupplyDatePromised)
          .addParameter("MRPDateRun", mrpRunDate)
          .collect();
    }

    //
    // MRP-110 Past Due Action Notice
    // Indicates that a schedule supply order receipt is past due.
    // i.e. it's a firm supply which was scheduled to be released in the past, but so far it's not
    // received yet.
    if (mrpSupplyIsFirm && mrpSupplyDatePromised.compareTo(mrpRunDate) < 0) {
      newSupplyMRPNote(mrpSupplyRecord, MRPExecutor.MRP_ERROR_110_SupplyPastDueButReleased)
          .addParameter("SupplyDatePromised", mrpSupplyDatePromised)
          .addParameter("MRPDateRun", mrpRunDate)
          .collect();
    }
  }
  @Override
  public void createSupply(final IMRPCreateSupplyRequest request) {
    final IMRPContext mrpContext = request.getMRPContext();
    final IMRPExecutor executor = request.getMRPExecutor();

    final Properties ctx = mrpContext.getCtx();
    final I_PP_Product_Planning productPlanningData = mrpContext.getProductPlanning();
    final I_AD_Org org = mrpContext.getAD_Org();
    final I_S_Resource plant = mrpContext.getPlant();
    final Timestamp supplyDateFinishSchedule = TimeUtil.asTimestamp(request.getDemandDate());

    // QtyToSupply: qty for which we need to produce the supply
    final BigDecimal qtyToSupply = request.getQtyToSupply();

    // TODO vpj-cd I need to create logic for DRP-040 Shipment Due Action Notice
    // Indicates that a shipment for a Order Distribution is due.
    // Action should be taken at the source warehouse to ensure that the order is received on time.

    // TODO vpj-cd I need to create logic for DRP-050 Shipment Pas Due Action Notice
    // Indicates that a shipment for a Order Distribution is past due. You should either delay the
    // orders created the requirement for the product
    // or expedite them when the product does arrive.

    if (productPlanningData.getDD_NetworkDistribution_ID() <= 0) {
      // Indicates that the Product Planning Data for this product does not specify a valid network
      // distribution.
      executor.newMRPNote(mrpContext, ERR_DRP_060_NoSourceOfSupply).collect();
      //
      return;
    }

    final I_DD_NetworkDistribution network = productPlanningData.getDD_NetworkDistribution();
    final List<I_DD_NetworkDistributionLine> networkLines =
        Services.get(IDistributionNetworkDAO.class)
            .retrieveNetworkLinesByTargetWarehouse(
                network, productPlanningData.getM_Warehouse_ID());
    if (networkLines.isEmpty()) {
      // No network lines were found for our target warehouse
      final I_M_Warehouse warehouseTo = productPlanningData.getM_Warehouse();
      executor
          .newMRPNote(mrpContext, ERR_DRP_060_NoSourceOfSupply)
          .setComment("@NotFound@ @DD_NetworkDistributionLine_ID@")
          .addParameter(
              I_DD_NetworkDistribution.COLUMNNAME_DD_NetworkDistribution_ID,
              network == null ? "?" : network.getName())
          .addParameter("M_Warehouse_Dest_ID", warehouseTo == null ? "?" : warehouseTo.getName())
          .collect();
      //
      return;
    }

    int M_Shipper_ID = -1;
    I_DD_Order order = null;

    BigDecimal qtyToSupplyRemaining = qtyToSupply;
    for (final I_DD_NetworkDistributionLine networkLine : networkLines) {
      //
      // Check: if we created DD Orders for all qty that needed to be supplied, stop here
      if (qtyToSupplyRemaining.signum() <= 0) {
        break;
      }

      // get supply source warehouse and locator
      final I_M_Warehouse warehouseFrom = networkLine.getM_WarehouseSource();
      final I_M_Locator locatorFrom =
          Services.get(IWarehouseBL.class).getDefaultLocator(warehouseFrom);

      // get supply target warehouse and locator
      final I_M_Warehouse warehouseTo = networkLine.getM_Warehouse();
      final I_M_Locator locatorTo = Services.get(IWarehouseBL.class).getDefaultLocator(warehouseTo);

      if (locatorFrom == null || locatorTo == null) {
        executor
            .newMRPNote(mrpContext, "DRP-001") // FIXME: DRP-001 error code does not exist
            .addParameter(
                I_DD_NetworkDistribution.COLUMNNAME_DD_NetworkDistribution_ID,
                network == null ? "?" : network.getName())
            .addParameter(
                I_DD_NetworkDistributionLine.COLUMNNAME_M_WarehouseSource_ID,
                warehouseFrom.getName())
            .addParameter(
                I_DD_NetworkDistributionLine.COLUMNNAME_M_Warehouse_ID, warehouseTo.getName())
            .setComment("No locators found for source or target warehouse")
            .collect();
        //
        continue;
      }

      //
      // Get the warehouse in transit
      final I_M_Warehouse warehouseInTrasit =
          retrieveInTransitWarehouse(ctx, warehouseFrom.getAD_Org_ID());
      if (warehouseInTrasit == null) {
        // DRP-010: Do not exist Transit Warehouse to this Organization
        executor
            .newMRPNote(mrpContext, ERR_DRP_010_InTransitWarehouseNotFound)
            .addParameter(I_AD_Org.COLUMNNAME_AD_Org_ID, org.getName())
            .collect();
        //
        continue;
      }

      //
      // DRP-030: Do not exist Shipper for Create Distribution Order
      if (networkLine.getM_Shipper_ID() <= 0) {
        executor
            .newMRPNote(mrpContext, "DRP-030")
            .addParameter(
                I_DD_NetworkDistribution.COLUMNNAME_DD_NetworkDistribution_ID,
                network == null ? "?" : network.getName())
            .addParameter(
                I_DD_NetworkDistributionLine.COLUMNNAME_DD_NetworkDistributionLine_ID, networkLine)
            .collect();
        //
        continue;
      }

      if (M_Shipper_ID != networkLine.getM_Shipper_ID()) {
        // Org Must be linked to BPartner
        final I_AD_Org locatorToOrg = locatorTo.getAD_Org();
        final IBPartnerOrgBL bpartnerOrgBL = Services.get(IBPartnerOrgBL.class);
        final I_C_BPartner orgBPartner = bpartnerOrgBL.retrieveLinkedBPartner(locatorToOrg);
        if (orgBPartner == null) {
          // DRP-020: Target Org has no BP linked to it
          executor
              .newMRPNote(mrpContext, "DRP-020")
              .addParameter(I_AD_Org.COLUMNNAME_AD_Org_ID, locatorToOrg.getName())
              .collect();
          //
          continue;
        }

        final I_C_BPartner_Location orgBPLocation =
            bpartnerOrgBL.retrieveOrgBPLocation(
                mrpContext.getCtx(), locatorToOrg.getAD_Org_ID(), ITrx.TRXNAME_None);

        //
        // Try found some DD_Order with Shipper , Business Partner and Doc Status = Draft
        // Consolidate the demand in a single order for each Shipper , Business Partner ,
        // DemandDateStartSchedule
        order =
            getDDOrderFromCache(
                org,
                plant,
                warehouseInTrasit,
                networkLine.getM_Shipper_ID(),
                orgBPartner.getC_BPartner_ID(),
                supplyDateFinishSchedule);
        if (order == null) {
          order = InterfaceWrapperHelper.newInstance(I_DD_Order.class, mrpContext);
          order.setMRP_Generated(true);
          order.setMRP_AllowCleanup(true);
          order.setAD_Org_ID(warehouseTo.getAD_Org_ID());
          order.setPP_Plant(plant);
          order.setC_BPartner(orgBPartner);
          order.setC_BPartner_Location(orgBPLocation);
          // order.setAD_User_ID(productPlanningData.getPlanner_ID()); // FIXME: improve
          // performances/cache and retrive Primary BP's User
          order.setSalesRep_ID(productPlanningData.getPlanner_ID());

          final int docTypeDO_ID =
              getC_DocType_ID(mrpContext, X_C_DocType.DOCBASETYPE_DistributionOrder);
          order.setC_DocType_ID(docTypeDO_ID);
          order.setM_Warehouse(warehouseInTrasit);
          order.setDocStatus(X_DD_Order.DOCSTATUS_Drafted);
          order.setDocAction(X_DD_Order.DOCACTION_Complete);
          order.setDateOrdered(mrpContext.getDateAsTimestamp());
          order.setDatePromised(supplyDateFinishSchedule);
          order.setM_Shipper_ID(networkLine.getM_Shipper_ID());
          order.setIsInDispute(false);
          order.setIsInTransit(false);

          InterfaceWrapperHelper.save(order);

          executor.addGeneratedSupplyDocument(order);
          addToCache(order);
        }
        M_Shipper_ID = networkLine.getM_Shipper_ID();
      }

      //
      // Crate DD order line
      final BigDecimal qtyToMove =
          calculateQtyToMove(qtyToSupplyRemaining, networkLine.getPercent());
      createDD_OrderLine(
          mrpContext,
          order,
          networkLine,
          locatorFrom,
          locatorTo,
          qtyToMove,
          supplyDateFinishSchedule,
          request);

      qtyToSupplyRemaining = qtyToSupplyRemaining.subtract(qtyToMove);
    }

    //
    // Check: remaining qtyToSupply shall be ZERO
    if (qtyToSupplyRemaining.signum() != 0) {
      // TODO: introduce DRP-XXX notice
      throw new LiberoException(
          "Cannot create DD Order for required Qty To Supply."
              + "\nQtyToSupply: "
              + qtyToSupply
              + "\nQtyToSupply (remaining): "
              + qtyToSupplyRemaining
              + "\n@DD_NetworkDistribution_ID@: "
              + network
              + "\n@DD_NetworkDistributionLine_ID@: "
              + networkLines
              + "\nMRPContext: "
              + mrpContext);
    }
  }