@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  public Sale stopUndefinedInstallmentsCreation(Sale sale) throws IabakoActionForbiddenException {
    InstallmentsDefinition iDef = sale.getInstallmentsDefinition();
    Date lastUndefinedInstallment =
        iDef.addNToDate(
            new ServerTools(),
            iDef.getNextUndefinedInstallment(),
            iDef.getInstallmentsPeriodicalFrequency(),
            iDef.getInstallmentsPeriodicalFrequencyN() * -1);

    sale.getInstallmentsDefinition().setNextUndefinedInstallment(null);
    sale.getInstallmentsDefinition().setStopUndefinedInstallments(new Date());
    if (lastUndefinedInstallment.compareTo(iDef.getInstallmentsPeriodicalStartDate()) >= 0) {
      sale.getInstallmentsDefinition().setLastUndefinedInstallment(lastUndefinedInstallment);
    }

    try {
      return save(sale);
    } catch (IabakoUniqueConstraintException e) {
      // Should never happen
      log.error(e.getMessage(), e);
    }
    return sale;
  }
  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  public Sale duplicate(Sale sale)
      throws IabakoUniqueConstraintException, IabakoPackageForbiddenException,
          IabakoActionForbiddenException, IabakoStockException {
    // get ProdSer from DB
    sale.setProductsServices(saleDAO.getProductServiceList(sale));

    if (sale.getInstallmentsDefinition() != null
        && sale.getInstallmentsDefinition().getInstallmentsFrequency()
            == InstallmentsFrequency.custom
        && GenericTools.isEmpty(sale.getInstallmentsDefinition().getInstallments())) {
      // initialize empty relation coming from GWT world before clone() call
      sale.getInstallmentsDefinition()
          .setInstallments(saleDAO.getCustomInstallmentsDefinition(sale));
    }
    Sale newSale = sale.customClone();

    newSale.setDate(new Date());
    newSale.setDateString(GenericTools.formatDateToString(newSale.getDate()));

    newSale.setClient(sale.getClient());
    if (sale.getStatus() != FinancialStatusSale._0_draft) {
      newSale.setSaleStep(SaleStep.confirmation);
    }

    newSale.getPayments().clear();
    newSale.getInstallments().clear();

    List<Installment> installments =
        newSale.getInstallmentsDefinition() != null
            ? newSale.getInstallmentsDefinition().getInstallments()
            : null;

    return save(
        true, newSale, newSale.getProductsServices(), newSale.getExpenses(), null, installments);
  }
  private void addCustomInstallmentsDefinition(Sale sale, List<Installment> installments) {
    if (sale.getInstallmentsDefinition() == null
        || sale.getPaymentType() == PaymentType.onePayment
        || sale.getInstallmentsDefinition().getInstallmentsType() == null
        || sale.getInstallmentsDefinition().getInstallmentsFrequency() == null
        || sale.getInstallmentsDefinition().getInstallmentsFrequency()
                == InstallmentsFrequency.custom
            && GenericTools.isEmpty(installments)) {
      sale.setPaymentType(PaymentType.onePayment);
      sale.setInstallmentsDefinition(null);

    } else {
      InstallmentsDefinition installDef = sale.getInstallmentsDefinition();

      sale.setPaymentMethod(null);

      if (installDef.getInstallmentsFrequency() == InstallmentsFrequency.periodical) {
        installDef.setInstallmentsPeriodicalStartDate(
            GenericTools.getDateFromString(installDef.getInstallmentsPeriodicalStartDateString()));

      } else {
        // Clear periodical values
        installDef.setInstallmentsPeriodicalStartDate(null);
        installDef.setInstallmentsPeriodicalPrice(null);
      }

      if (sale.getInstallmentsDefinition().getInstallmentsFrequency()
          == InstallmentsFrequency.custom) {

        List<Installment> customInstallmentDefList = new ArrayList<Installment>();

        for (Installment installment : installments) {
          Installment customInstallmentDef = new Installment();
          customInstallmentDef.setDate(GenericTools.getDateFromString(installment.getDateString()));
          // In drafts, amount can be null (but in database)
          customInstallmentDef.setAmount(
              installment.getAmount() == null ? 0 : installment.getAmount());
          customInstallmentDef.setInstallmentsDefinition(sale.getInstallmentsDefinition());
          customInstallmentDefList.add(customInstallmentDef);
        }

        sale.getInstallmentsDefinition().setInstallments(customInstallmentDefList);

      } else if (sale.getInstallmentsDefinition().getInstallmentsType()
          == InstallmentsType.undefined) {

        // Clear defined value
        installDef.setInstallmentsNumber(0);

        Date lastInstallmentDate =
            installments.isEmpty()
                ? sale.getInstallmentsDefinition().getInstallmentsPeriodicalStartDate()
                : installments.get(installments.size() - 1).getDate();

        Date nextUndefinedInstallment;
        if (installments.isEmpty()) {
          nextUndefinedInstallment = lastInstallmentDate;

        } else {
          nextUndefinedInstallment =
              sale.getInstallmentsDefinition()
                  .addNToDate(
                      new ServerTools(),
                      lastInstallmentDate,
                      sale.getInstallmentsDefinition().getInstallmentsPeriodicalFrequency(),
                      sale.getInstallmentsDefinition().getInstallmentsPeriodicalFrequencyN());
        }
        sale.getInstallmentsDefinition().setNextUndefinedInstallment(nextUndefinedInstallment);
      }
    }
  }