示例#1
0
  public boolean sameOrderLineUOM() {
    if (getC_OrderLine_ID() <= 0) return false;

    MOrderLine oLine = new MOrderLine(getCtx(), getC_OrderLine_ID(), get_TrxName());

    if (oLine.getC_UOM_ID() != getC_UOM_ID()) return false;

    // inout has orderline and both has the same UOM
    return true;
  }
示例#2
0
  public static int[] getAllOrderlineIDs(Properties ctx, int orderId, String trxName) {
    String whereClause =
        ""
            + " C_ORDER_ID ="
            + orderId
            + " AND AD_CLIENT_ID ="
            + Env.getAD_Client_ID(ctx)
            + " AND ISACTIVE = 'Y'";

    return MOrderLine.getAllIDs(MOrderLine.Table_Name, whereClause, trxName);
  }
示例#3
0
  /** Create PO's */
  private void createPO() {
    int noOrders = 0;
    String info = "";
    //
    MOrder order = null;
    MWarehouse wh = null;
    X_T_Replenish[] replenishs = getReplenish("M_WarehouseSource_ID IS NULL");
    for (int i = 0; i < replenishs.length; i++) {
      X_T_Replenish replenish = replenishs[i];
      if (wh == null || wh.getM_Warehouse_ID() != replenish.getM_Warehouse_ID())
        wh = MWarehouse.get(getCtx(), replenish.getM_Warehouse_ID());
      //
      if (order == null
          || order.getC_BPartner_ID() != replenish.getC_BPartner_ID()
          || order.getM_Warehouse_ID() != replenish.getM_Warehouse_ID()) {
        order = new MOrder(getCtx(), 0, get_TrxName());
        order.setIsSOTrx(false);
        order.setC_DocTypeTarget_ID(p_C_DocType_ID);

        final MBPartner bp = new MBPartner(getCtx(), replenish.getC_BPartner_ID(), get_TrxName());
        Services.get(IOrderBL.class).setBPartner(order, bp);

        order.setSalesRep_ID(getAD_User_ID());
        order.setDescription(Msg.getMsg(getCtx(), "Replenishment"));
        //	Set Org/WH
        order.setAD_Org_ID(wh.getAD_Org_ID());
        order.setM_Warehouse_ID(wh.getM_Warehouse_ID());
        if (!order.save()) return;
        log.debug(order.toString());
        noOrders++;
        info += " - " + order.getDocumentNo();
      }
      MOrderLine line = new MOrderLine(order);
      line.setM_Product_ID(replenish.getM_Product_ID());
      line.setQty(replenish.getQtyToOrder());
      line.setPrice();
      line.save();
    }
    m_info = "#" + noOrders + info;
    log.info(m_info);
  } //	createPO
示例#4
0
  /**
   * Create Order from Basket
   *
   * @param wu web User
   * @param wb web basket
   * @return true if created & processed
   */
  private boolean createOrder(WebUser wu, WebBasket wb) {
    m_order = new MOrder(m_ctx, 0, null);
    log.fine(
        "AD_Client_ID="
            + m_order.getAD_Client_ID()
            + ",AD_Org_ID="
            + m_order.getAD_Org_ID()
            + " - "
            + m_order);
    //
    m_order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Prepay);
    m_order.setPaymentRule(MOrder.PAYMENTRULE_CreditCard);
    m_order.setDeliveryRule(MOrder.DELIVERYRULE_AfterReceipt);
    m_order.setInvoiceRule(MOrder.INVOICERULE_Immediate);
    m_order.setIsSelfService(true);
    if (wb.getM_PriceList_ID() > 0) m_order.setM_PriceList_ID(wb.getM_PriceList_ID());
    if (wb.getSalesRep_ID() != 0) m_order.setSalesRep_ID(wb.getSalesRep_ID());

    //	BPartner
    m_order.setC_BPartner_ID(wu.getC_BPartner_ID());
    m_order.setC_BPartner_Location_ID(wu.getC_BPartner_Location_ID());
    m_order.setAD_User_ID(wu.getAD_User_ID());
    //
    m_order.setSendEMail(true);
    m_order.setDocAction(MOrder.DOCACTION_Prepare);
    m_order.saveEx();
    log.fine("ID=" + m_order.getC_Order_ID() + ", DocNo=" + m_order.getDocumentNo());

    ArrayList<WebBasketLine> lines = wb.getLines();
    for (int i = 0; i < lines.size(); i++) {
      WebBasketLine wbl = lines.get(i);
      MOrderLine ol = new MOrderLine(m_order);
      ol.setM_Product_ID(wbl.getM_Product_ID(), true);
      ol.setQty(wbl.getQuantity());
      ol.setPrice();
      ol.setPrice(wbl.getPrice());
      ol.setTax();
      ol.saveEx();
    } //	for all lines
    boolean ok = m_order.processIt(MOrder.DOCACTION_Prepare);
    m_order.saveEx();

    //	Web User = Customer
    if (!wu.isCustomer()) {
      //	log.info("-------------------------------------- " + wu.isCustomer());
      wu.setIsCustomer(true);
      wu.save();
      //	log.info("-------------------------------------- " + wu.isCustomer());
    }
    BigDecimal amt = m_order.getGrandTotal();
    log.info("Amt=" + amt);
    return ok;
  } //	createOrder
  /**
   * Process. Create purchase order(s) for the resonse(s) and lines marked as Selected Winner using
   * the selected Purchase Quantity (in RfQ Line Quantity) . If a Response is marked as Selected
   * Winner, all lines are created (and Selected Winner of other responses ignored). If there is no
   * response marked as Selected Winner, the lines are used.
   *
   * @return message
   */
  protected String doIt() throws Exception {
    MRfQ rfq = new MRfQ(getCtx(), p_C_RfQ_ID, get_TrxName());
    if (rfq.get_ID() == 0) throw new IllegalArgumentException("No RfQ found");
    log.info(rfq.toString());

    //	Complete
    MRfQResponse[] responses = rfq.getResponses(true, true);
    log.config("#Responses=" + responses.length);
    if (responses.length == 0)
      throw new IllegalArgumentException("No completed RfQ Responses found");

    //	Winner for entire RfQ
    for (int i = 0; i < responses.length; i++) {
      MRfQResponse response = responses[i];
      if (!response.isSelectedWinner()) continue;
      //
      MBPartner bp = new MBPartner(getCtx(), response.getC_BPartner_ID(), get_TrxName());
      log.config("Winner=" + bp);
      MOrder order = new MOrder(getCtx(), 0, get_TrxName());
      order.setIsSOTrx(false);
      if (p_C_DocType_ID != 0) order.setC_DocTypeTarget_ID(p_C_DocType_ID);
      else order.setC_DocTypeTarget_ID();
      order.setBPartner(bp);
      order.setC_BPartner_Location_ID(response.getC_BPartner_Location_ID());
      order.setSalesRep_ID(rfq.getSalesRep_ID());
      if (response.getDateWorkComplete() != null)
        order.setDatePromised(response.getDateWorkComplete());
      else if (rfq.getDateWorkComplete() != null) order.setDatePromised(rfq.getDateWorkComplete());
      order.saveEx();
      //
      MRfQResponseLine[] lines = response.getLines(false);
      for (int j = 0; j < lines.length; j++) {
        //	Respones Line
        MRfQResponseLine line = lines[j];
        if (!line.isActive()) continue;
        MRfQResponseLineQty[] qtys = line.getQtys(false);
        //	Response Line Qty
        for (int k = 0; k < qtys.length; k++) {
          MRfQResponseLineQty qty = qtys[k];
          //	Create PO Lline for all Purchase Line Qtys
          if (qty.getRfQLineQty().isActive() && qty.getRfQLineQty().isPurchaseQty()) {
            MOrderLine ol = new MOrderLine(order);
            ol.setM_Product_ID(
                line.getRfQLine().getM_Product_ID(), qty.getRfQLineQty().getC_UOM_ID());
            ol.setDescription(line.getDescription());
            ol.setQty(qty.getRfQLineQty().getQty());
            BigDecimal price = qty.getNetAmt();
            ol.setPrice();
            ol.setPrice(price);
            ol.saveEx();
          }
        }
      }
      response.setC_Order_ID(order.getC_Order_ID());
      response.saveEx();
      return order.getDocumentNo();
    }

    //	Selected Winner on Line Level
    int noOrders = 0;
    for (int i = 0; i < responses.length; i++) {
      MRfQResponse response = responses[i];
      MBPartner bp = null;
      MOrder order = null;
      //	For all Response Lines
      MRfQResponseLine[] lines = response.getLines(false);
      for (int j = 0; j < lines.length; j++) {
        MRfQResponseLine line = lines[j];
        if (!line.isActive() || !line.isSelectedWinner()) continue;
        //	New/different BP
        if (bp == null || bp.getC_BPartner_ID() != response.getC_BPartner_ID()) {
          bp = new MBPartner(getCtx(), response.getC_BPartner_ID(), get_TrxName());
          order = null;
        }
        log.config("Line=" + line + ", Winner=" + bp);
        //	New Order
        if (order == null) {
          order = new MOrder(getCtx(), 0, get_TrxName());
          order.setIsSOTrx(false);
          order.setC_DocTypeTarget_ID();
          order.setBPartner(bp);
          order.setC_BPartner_Location_ID(response.getC_BPartner_Location_ID());
          order.setSalesRep_ID(rfq.getSalesRep_ID());
          order.saveEx();
          noOrders++;
          addLog(0, null, null, order.getDocumentNo());
        }
        //	For all Qtys
        MRfQResponseLineQty[] qtys = line.getQtys(false);
        for (int k = 0; k < qtys.length; k++) {
          MRfQResponseLineQty qty = qtys[k];
          if (qty.getRfQLineQty().isActive() && qty.getRfQLineQty().isPurchaseQty()) {
            MOrderLine ol = new MOrderLine(order);
            ol.setM_Product_ID(
                line.getRfQLine().getM_Product_ID(), qty.getRfQLineQty().getC_UOM_ID());
            ol.setDescription(line.getDescription());
            ol.setQty(qty.getRfQLineQty().getQty());
            BigDecimal price = qty.getNetAmt();
            ol.setPrice();
            ol.setPrice(price);
            ol.saveEx();
          }
        } //	for all Qtys
      } //	for all Response Lines
      if (order != null) {
        response.setC_Order_ID(order.getC_Order_ID());
        response.saveEx();
      }
    }

    return "#" + noOrders;
  } //	doIt
  /**
   * Creates a counter document for an order. The counter document is also processed, if there is a
   * {@link I_C_DocTypeCounter} with a <code>DocAction</code> configured.
   *
   * <p>This implementation partially uses legacy code. I didn't yet get to refactor/remove/replace
   * it all.
   */
  @Override
  public DocAction createCounterDocument(final DocAction document) {
    final I_C_Order order = InterfaceWrapperHelper.create(document, I_C_Order.class);
    final MOrder orderPO = LegacyAdapters.convertToPO(order);

    final I_C_DocType counterDocType = retrieveCounterDocTypeOrNull(document);

    final I_AD_Org counterOrg = retrieveCounterOrgOrNull(document);

    final de.metas.adempiere.model.I_C_Order counterOrder =
        InterfaceWrapperHelper.newInstance(
            de.metas.adempiere.model.I_C_Order.class, document.getCtx());
    final MOrder counterOrderPO = (MOrder) LegacyAdapters.convertToPO(counterOrder);

    counterOrder.setAD_Org(counterOrg); // 09700

    //
    counterOrder.setC_DocTypeTarget(counterDocType);
    counterOrder.setIsSOTrx(counterDocType.isSOTrx());

    // the new order needs to figure out the pricing by itself
    counterOrder.setM_PricingSystem(null);
    counterOrder.setM_PriceList(null);

    counterOrder.setDateOrdered(order.getDateOrdered());
    counterOrder.setDateAcct(order.getDateAcct());
    counterOrder.setDatePromised(order.getDatePromised());

    counterOrder.setRef_Order_ID(order.getC_Order_ID());

    final I_C_BPartner counterBP = retrieveCounterPartnerOrNull(document);
    counterOrderPO.setBPartner(counterBP);

    final I_M_Warehouse counterWarehouse =
        Services.get(IWarehouseAdvisor.class).evaluateOrderWarehouse(counterOrder);
    counterOrder.setM_Warehouse(counterWarehouse);

    // References (should not be required)
    counterOrder.setSalesRep_ID(order.getSalesRep_ID());
    InterfaceWrapperHelper.save(counterOrder);

    // copy the order lines
    final boolean counter = true;
    final boolean copyASI = true;
    counterOrderPO.copyLinesFrom(orderPO, counter, copyASI);

    // Update copied lines
    final boolean requery = true;
    final MOrderLine[] counterLines = counterOrderPO.getLines(requery, null);
    for (int i = 0; i < counterLines.length; i++) {
      final MOrderLine counterLine = counterLines[i];
      counterLine.setOrder(counterOrderPO); // copies header values (BP, etc.)
      counterLine.setPrice();
      counterLine.setTax();
      InterfaceWrapperHelper.save(counterLine);
    }
    logger.debug(counterOrder.toString());

    // Document Action
    final MDocTypeCounter counterDT =
        MDocTypeCounter.getCounterDocType(document.getCtx(), order.getC_DocType_ID());
    if (counterDT != null) {
      if (counterDT.getDocAction() != null) {
        counterOrder.setDocAction(counterDT.getDocAction());
        Services.get(IDocActionBL.class)
            .processEx(
                counterOrder,
                counterDT.getDocAction(),
                null); // not expecting a particular docStatus (e.g. for prepay orders, it might be
                       // "waiting to payment")
      }
    }
    return counterOrderPO;
  }
  private void checkInvoicingDataEntry(
      final I_C_Order order, final I_C_Flatrate_DataEntry dataEntry, final I_C_Flatrate_Term term) {
    assertThat(dataEntry.isSimulation(), equalTo(term.isSimulation()));
    assertThat(term.getC_Flatrate_Term_ID(), equalTo(dataEntry.getC_Flatrate_Term_ID()));

    final TestConfig testConfig = helper.getConfig();
    final boolean paramIsSimulation =
        testConfig.getCustomParamBool(FlatFeeScenario.PARAM_BOOL_IS_SIMULATION);

    final IFlatrateDAO flatrateDB = Services.get(IFlatrateDAO.class);

    BigDecimal sum = BigDecimal.ZERO;

    final boolean dataEntryCompleted =
        dataEntry.getDocStatus().equals(X_C_Flatrate_DataEntry.DOCSTATUS_Completed);

    final List<I_C_Invoice_Clearing_Alloc> clearingAllocs =
        flatrateDB.retrieveClearingAllocs(dataEntry);

    if (paramIsSimulation) {
      assertThat(
          "Expected *no* C_Invoice_Clearing_Allocs for " + dataEntry,
          clearingAllocs.size(),
          equalTo(0));
    } else {
      // note that we expect the C_Invoice_Clearing_Allocs no matter whether 'dataEntry' has already
      // been
      // completed
      assertThat(
          "Expected C_Invoice_Clearing_Allocs for " + dataEntry,
          clearingAllocs.size(),
          greaterThan(0));
    }

    for (final MOrderLine ol :
        driver.getHelper().mkOrderHelper().getOrderPO(order).getLines(true, null)) {
      for (final I_C_Invoice_Clearing_Alloc ica : clearingAllocs) {
        assertThat(ica.getC_Invoice_Cand_ToClear_ID(), greaterThan(0));

        assertThat(ica.getC_Flatrate_Term_ID(), equalTo(term.getC_Flatrate_Term_ID()));
        assertThat(
            ica.getC_Flatrate_DataEntry_ID(), equalTo(dataEntry.getC_Flatrate_DataEntry_ID()));

        final I_C_Invoice_Candidate icToClear = ica.getC_Invoice_Cand_ToClear();

        assertThat(
            icToClear + " has wrong C_OrderLine_ID",
            ol.getC_OrderLine_ID(),
            equalTo(icToClear.getC_OrderLine_ID()));

        assertThat(
            "Sales test driver should have called the update process",
            icToClear.isToRecompute(),
            is(false));
        assertThat(icToClear.isToClear(), is(true));

        if (dataEntryCompleted) {
          assertThat(ica.getC_Invoice_Candidate_ID(), greaterThan(0));
          assertThat(
              ica.getC_Invoice_Candidate_ID(), equalTo(dataEntry.getC_Invoice_Candidate_ID()));

          final I_C_Invoice_Candidate newCand = ica.getC_Invoice_Candidate();
          assertThat(
              "Sales test driver should have called the update process",
              newCand.isToRecompute(),
              is(false));

          assertThat(newCand.isToClear(), is(false));
          assertThat(newCand.isProcessed(), is(false));
          assertThat(icToClear + " has been marked a processed", icToClear.isProcessed(), is(true));
          assertThat(icToClear.getQtyInvoiced(), not(comparesEqualTo(BigDecimal.ZERO)));
          assertThat(icToClear.getQtyToInvoice(), comparesEqualTo(BigDecimal.ZERO));

          sum = sum.add(icToClear.getQtyInvoiced());
        } else {
          assertThat(ica.getC_Invoice_Candidate_ID(), equalTo(0));

          assertThat(icToClear + " has wrong 'Processed'", icToClear.isProcessed(), is(false));
          assertThat(
              icToClear + " has wrong 'QtyInvoiced'",
              icToClear.getQtyInvoiced(),
              comparesEqualTo(BigDecimal.ZERO));
          assertThat(
              icToClear + " has wrong 'QtyToInvoice'",
              icToClear.getQtyToInvoice(),
              not(comparesEqualTo(BigDecimal.ZERO)));

          sum = sum.add(icToClear.getQtyToInvoice());
        }
      }
    }

    if (!paramIsSimulation) {
      assertThat(
          "Expecting test config param 'PARAM_ACTUAL_QTY' to match C_FlatRate_DataEntry.ActualQty of "
              + dataEntry,
          testConfig.getCustomParamBD(PARAM_BD_ACTUAL_QTY),
          comparesEqualTo(dataEntry.getActualQty()));
      assertThat(
          "Expecting sum of invoice candidates to match C_FlatRate_DataEntry.ActualQty of "
              + dataEntry,
          sum,
          comparesEqualTo(dataEntry.getActualQty()));
    }
  }
示例#8
0
  public static MOrderLine createOrderLine(
      Properties ctx,
      MOrder order,
      int productId,
      BigDecimal qty,
      BigDecimal discount,
      BigDecimal lineNet)
      throws OperationException {
    if (qty == null) {
      qty = Env.ONE;
    }

    MOrderLine orderLine = new MOrderLine(order);

    MProduct product = new MProduct(ctx, productId, order.get_TrxName());
    MTax tax =
        TaxManager.getTaxFromCategory(ctx, product.getC_TaxCategory_ID(), order.get_TrxName());
    orderLine.setC_Tax_ID(tax.get_ID());
    orderLine.setC_UOM_ID(product.getC_UOM_ID());
    orderLine.setC_BPartner_ID(order.getC_BPartner_ID());
    // Must set Product Id before quantity Ordered.
    orderLine.setM_Product_ID(productId);
    orderLine.setQty(qty);
    orderLine.setPrice();

    // Bug fix, set price to 2dp
    MPriceList orderPriceList = MPriceList.get(ctx, order.getM_PriceList_ID(), order.get_TrxName());

    if (!orderPriceList.isTaxIncluded()) {
      BigDecimal taxAmt = tax.calculateTax(lineNet, true, 12);
      BigDecimal lineNetWithoutTax = lineNet.subtract(taxAmt);
      orderLine.setLineNetAmt(lineNetWithoutTax);

      BigDecimal unitPriceWithoutTax =
          lineNetWithoutTax.divide(qty, 12, BigDecimal.ROUND_HALF_DOWN);

      orderLine.setPriceEntered(unitPriceWithoutTax.setScale(2, BigDecimal.ROUND_HALF_UP));
      orderLine.setPriceActual(unitPriceWithoutTax.setScale(2, BigDecimal.ROUND_HALF_UP));
    } else {
      BigDecimal unitPrice = lineNet.divide(qty, 12, BigDecimal.ROUND_HALF_DOWN);
      orderLine.setLineNetAmt(lineNet.setScale(2, BigDecimal.ROUND_HALF_UP));
      orderLine.setPriceEntered(unitPrice.setScale(2, BigDecimal.ROUND_HALF_UP));
      orderLine.setPriceActual(unitPrice.setScale(2, BigDecimal.ROUND_HALF_UP));
    }

    if (discount.doubleValue() != 0.0) {
      orderLine.setDiscount();
    }

    PoManager.save(orderLine);

    return orderLine;
  }
示例#9
0
 /**
  * Set Order Line. Does not set Quantity!
  *
  * @param oLine order line
  * @param M_Locator_ID locator
  * @param Qty used only to find suitable locator
  */
 public void setOrderLine(MOrderLine oLine, int M_Locator_ID, BigDecimal Qty) {
   setC_OrderLine_ID(oLine.getC_OrderLine_ID());
   setLine(oLine.getLine());
   setC_UOM_ID(oLine.getC_UOM_ID());
   MProduct product = oLine.getProduct();
   if (product == null) {
     set_ValueNoCheck("M_Product_ID", null);
     set_ValueNoCheck("M_AttributeSetInstance_ID", null);
     set_ValueNoCheck("M_Locator_ID", null);
   } else {
     setM_Product_ID(oLine.getM_Product_ID());
     setM_AttributeSetInstance_ID(oLine.getM_AttributeSetInstance_ID());
     //
     if (product.isItem()) {
       if (M_Locator_ID == 0) setM_Locator_ID(Qty); // 	requires warehouse, product, asi
       else setM_Locator_ID(M_Locator_ID);
     } else set_ValueNoCheck("M_Locator_ID", null);
   }
   setC_Charge_ID(oLine.getC_Charge_ID());
   setDescription(oLine.getDescription());
   setIsDescription(oLine.isDescription());
   //
   setC_Project_ID(oLine.getC_Project_ID());
   setC_ProjectPhase_ID(oLine.getC_ProjectPhase_ID());
   setC_ProjectTask_ID(oLine.getC_ProjectTask_ID());
   setC_Activity_ID(oLine.getC_Activity_ID());
   setC_Campaign_ID(oLine.getC_Campaign_ID());
   setAD_OrgTrx_ID(oLine.getAD_OrgTrx_ID());
   setUser1_ID(oLine.getUser1_ID());
   setUser2_ID(oLine.getUser2_ID());
 } //	setOrderLine
  /**
   * Updated all inoutLines' PostageFreeStatus according to the respective bPartner's postage free
   * amount.
   *
   * @param ignorePostageFreeAmt if true, the lines qty is not set to {@link BigDecimal#ZERO}, even
   *     if we are below the customer's postage free amount. However, the status is still set.
   */
  @Override
  public void updatePostageFreeStatus(final boolean ignorePostageFreeAmt) {
    for (final I_M_InOut shipmentCandidate : getCandidates()) {
      final I_C_BPartner bPartner =
          InterfaceWrapperHelper.create(shipmentCandidate.getC_BPartner(), I_C_BPartner.class);

      final BigDecimal postageFree = (BigDecimal) bPartner.getPostageFreeAmt();

      BigDecimal shipmentValue = BigDecimal.ZERO;

      // if ignorePostageFreeAmount is false and a postage free amount
      // is set, we need to check if the value of this shipment is
      // enough to make shipping profitable
      boolean sufficiantValue = postageFree == null;

      if (!sufficiantValue) {
        for (final I_M_InOutLine inOutLine : getLines(shipmentCandidate)) {

          // access orderLineCache instead of loading the line
          // from DB
          final MOrderLine orderLinePO = this.orderLineCache.get(inOutLine.getC_OrderLine_ID());
          final BigDecimal lineValue =
              orderLinePO.getPriceActual().multiply(inOutLine.getQtyEntered());

          shipmentValue = shipmentValue.add(lineValue);

          if (shipmentValue.compareTo(postageFree) >= 0) {
            sufficiantValue = true;
            break;
          }
        }
      }
      for (final I_M_InOutLine inOutLine : getLines(shipmentCandidate)) {
        final PostageFreeStatus status;

        if (sufficiantValue) {
          status = PostageFreeStatus.OK;
        } else {
          if (!ignorePostageFreeAmt) {
            inOutLine.setQtyEntered(BigDecimal.ZERO);
            inOutLine.setMovementQty(BigDecimal.ZERO);
          }

          status = PostageFreeStatus.BELOW_POSTAGEFREE_AMT;

          if (logger.isInfoEnabled()) {
            logger.info(
                "Shipment "
                    + shipmentCandidate.getDocumentNo()
                    + " has an insufficient value of "
                    + shipmentValue.toPlainString()
                    + " (minimum value is "
                    + postageFree.toPlainString()
                    + ")");
          }
        }

        final MInOutLine inOutLinePO = getPO(inOutLine);
        line2PostageFreeStatus.put(inOutLinePO, status);
      }
    }
  }