@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
 public void deleteInstallmentsByAmount(Sale sale, Double amount) {
   List<Installment> installments = getInstallments(sale);
   Double balance = amount;
   List<Installment> toDelete = new ArrayList<Installment>();
   Installment toMerge = null;
   for (Installment i : installments) {
     balance = ServerTools.round(balance - i.getAmount());
     if (balance >= 0) {
       toDelete.add(i);
     } else {
       toMerge = i;
       toMerge.setAmount(balance * -1);
       break;
     }
   }
   for (Installment i : toDelete) {
     installmentService.remove(i, true);
   }
   if (toMerge != null) {
     installmentService.save(toMerge, true);
   }
 }
  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);
      }
    }
  }
  /**
   * Method to ALWAYS USED IN SALE MAKE (PERSIST AND DRAFT MODE) We can't pass by cascading persist
   * of SaleProductServices, Payments and Installments because GWT does not support (serialize)
   * PersistentBag and Proxy/Lazy hibernate objects
   *
   * @param sale Sale object from GWT client (navigator/JS world)
   * @param payments List of Payment from GWT client (navigator/JS world)
   * @param installments List of Installments from GWT client (navigator/JS world)
   */
  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  public synchronized Sale save(
      boolean isDraft,
      Sale sale,
      List<SaleProductService> productsServices,
      List<Expense> expenses,
      List<Payment> payments,
      List<Installment> installments)
      throws IabakoActionForbiddenException, IabakoPackageForbiddenException,
          IabakoUniqueConstraintException, IabakoStockException {

    Enterprise enterprise = getEnterpriseFromSessionUser();

    if (enterprise != null && enterprise.isDemo()) {
      throw new IabakoActionForbiddenException(
          "action_forbidden_for_demo_title", "action_forbidden_for_demo_text", true);
    }

    if (!getDAO().isUniqueNumber(sale)) {
      throw new IabakoUniqueConstraintException(
          messages.getLabel("validation_sale_unique_constraint_error_title"),
          messages.getLabel("validation_sale_unique_constraint_error_text", sale.getNumber()),
          true,
          true);
    }

    if (sale.getEnterprise() == null) {
      sale.setEnterprise(enterprise);
    }

    if (sale.getClient() != null && sale.getClient().getEnterprise() == null) {
      sale.getClient().setEnterprise(enterprise);
    }

    sale.setDate(GenericTools.getDateFromString(sale.getDateString()));

    addProductsServicesToSale(isDraft, sale, productsServices);
    addCustomInstallmentsDefinition(sale, installments);

    sale.setExpenses(expenses);
    // This is because expenses is @GWTTransient and It should not be used too much (so it helps to
    // avoid unnecessary server calls).
    sale.setSaleWithExpenses(!expenses.isEmpty());

    if (!isDraft) {
      addClientTag(sale);
      addPaymentsInstallments(sale, payments, installments);
      sale.setRecalculatedTotal(
          ServerTools.round(sale.calculateAmountFromPaymentsAndInstalments()));
      sale.setTaxPercent(sale.getPonderedTaxPercent());
    } else {
      sale.setTaxPercent(sale.getPonderedTaxPercent());
      sale.setRecalculatedTotal(sale.calculateAmountFromProductsServices());
    }
    sale.setTotalBeforeTax(
        sale.getTaxPercent() > 0
            ? ServerTools.round(sale.getRecalculatedTotal() / (1 + sale.getTaxPercent() / 100))
            : null);
    sale.setTotalAfterTax(sale.getTaxPercent() > 0 ? sale.getRecalculatedTotal() : null);
    sale.setTotalNoTax(sale.getTaxPercent() > 0 ? null : sale.getRecalculatedTotal());

    boolean isCreation = sale.getId() == null;
    sale = calculateStatusAndSave(sale, isDraft);
    setBusinessInfo(sale);

    for (Expense e : expenses) {
      // Sale.expenses is a detached relationship. It must be merge manually (not by cascade
      // hibernate function)
      e.setSale(sale);
      e.setPaymentDate(GenericTools.getDateFromString(e.getPaymentDateString()));
      e.setEnterprise(enterprise);
      expenseService.save(e);
    }

    Sale quote = checkIfQuote(sale);

    if (isCreation && quote != null) {
      trackingService.addTrackingToUserSession(TrackingType.quoteTransformed, quote);

    } else if (isDraft) {
      trackingService.addTrackingToUserSession(TrackingType.saleDraft, sale);

    } else {
      trackingService.addTrackingToUserSession(TrackingType.saleNew, sale);
      userService.addInvoiceReceiptToEnterpriseBalance(sale, null);
    }

    return sale;
  }