@Override
  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  public Boolean remove(Long id) throws IabakoActionForbiddenException {

    saleDAO.deleteAttachedObjects(id);

    Sale sale = saleDAO.findById(id);
    if (sale == null) {
      log.warn("No sale found with id :" + id);
      return false;
    }

    stockRollBack(sale);

    Client client = sale.getClient();

    boolean result = super.remove(id);

    if (client != null) {
      clientService.calculateStatus(client);
    }

    trackingService.addTrackingToUserSession(TrackingType.saleDelete, sale);

    return result;
  }
  private void addPaymentsInstallments(
      Sale sale, List<Payment> payments, List<Installment> installments) {
    if (installments != null) {
      for (Installment i : installments) {
        i.setDate(GenericTools.getDateFromString(i.getDateString()));
        i.setAmount(ServerTools.round(i.getAmount()));
      }
      if (sale.getClient() != null) {
        // Client must be enabled if he has future or pending installments
        sale.getClient().setDisabled(false);
      }
    }

    if (payments != null) {
      for (Payment p : payments) {
        p.setDate(GenericTools.getDateFromString(p.getDateString()));
        p.setScheduleDate(GenericTools.getDateFromString(p.getScheduleDateString()));
        p.setAmount(ServerTools.round(p.getAmount()));
        p.setPaymentMethod(sale.getPaymentMethod());
      }
    }

    sale.setInstallments(installments);
    sale.setPayments(payments);
  }
 private void setAttributesFromDB(Sale sale, Sale saleFromDB) {
   sale.setStatus(saleFromDB.getStatus());
   sale.setRecalculatedTotal(saleFromDB.getRecalculatedTotal());
   sale.setTotalBeforeTax(saleFromDB.getTotalBeforeTax());
   sale.setTotalAfterTax(saleFromDB.getTotalAfterTax());
   sale.setTotalNoTax(saleFromDB.getTotalNoTax());
 }
 private void addClientTag(Sale sale) {
   if (sale.getClient() != null && sale.getClient().getBusinessTag() != null) {
     BusinessTagSale businessTagSale = new BusinessTagSale();
     businessTagSale.setBusinessTag(sale.getClient().getBusinessTag());
     businessTagSale.setClient(sale.getClient());
     businessTagSale.setSale(sale);
     sale.getBusinessTagList().add(businessTagSale);
   }
 }
 @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
 public Sale calculateStatusAndSave(Sale sale, boolean isDraft) {
   sale.calculateStatus(isDraft);
   sale = (Sale) super.save(sale);
   if (!isDraft) {
     clientService.calculateStatus(sale.getClient());
   }
   return sale;
 }
 @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
 public List<Installment> getCustomInstallmentsDefinition(Sale sale) {
   if (sale == null || sale.getId() == null) {
     return null;
   }
   return saleDAO.getCustomInstallmentsDefinition(sale);
 }
 @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
 public List<Payment> getPayments(Sale sale) {
   if (sale == null || sale.getId() == null) {
     return null;
   }
   return saleDAO.getPayments(sale);
 }
 private Sale checkIfQuote(Sale sale) {
   if (sale.getSaleQuoteId() != null) {
     Sale saleQuote = saleDAO.findById(Long.parseLong(sale.getSaleQuoteId()));
     if (saleQuote != null && saleQuote.isQuote()) {
       saleQuote.setSaleQuoteId(
           sale.getStatus() == FinancialStatusSale._0_draft
               ? "_draft_" + sale.getId()
               : sale.getId() + "");
       saleQuote.setStatus(FinancialStatusSale._2_quote_transformed);
     } else {
       sale.setSaleQuoteId(null);
     }
     saleDAO.merge(saleQuote);
     return saleQuote;
   }
   return null;
 }
 private void stockRollBack(Sale sale) {
   for (SaleProductService saleProductService : sale.getProductsServices()) {
     if (saleProductService.getProduct() != null) {
       Product product = saleProductService.getProduct();
       product.setQuantity(product.getQuantity() + saleProductService.getQuantity());
       productDAO.merge(product);
     }
   }
 }
  @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);
  }
  public void addProductsServicesToSale(
      boolean isDraft, Sale saleFromDB, List<SaleProductService> productsServices)
      throws IabakoStockException {

    Enterprise enterprise = getEnterpriseFromSessionUser();
    String productsServicesAsString = "";

    Map<Product, Double> stockReductionMap = new HashMap<Product, Double>();

    // 1. Create productService as String (for table results)
    saleFromDB.setProductsServicesAsString(null);

    saleDAO.cleanProductService(saleFromDB);

    for (SaleProductService productService : productsServices) {

      String quantityString =
          productService.getQuantity() == 0d
                  || productService.getQuantity()
                          / new Double(productService.getQuantity()).intValue()
                      == 1
              ? new Double(productService.getQuantity()).intValue() + ""
              : ServerTools.formatAmount(
                  saleFromDB.getEnterprise().getLanguage(), productService.getQuantity());

      if (productService.getProduct() != null) {

        if (!productService.getProduct().isStockDisabled()) {
          Double quantity = stockReductionMap.get(productService.getProduct());
          stockReductionMap.put(
              productService.getProduct(),
              quantity == null
                  ? productService.getQuantity()
                  : quantity + productService.getQuantity());
        }

        quantityString +=
            productService.getProduct().getPriceUnit() == PriceUnit.unit
                ? ""
                : messages.getLabel(productService.getProduct().getPriceUnit().getLabelKey());

        productsServicesAsString +=
            productService.getProduct().getName() + " (" + quantityString + ")\n";
      } else {
        productsServicesAsString +=
            productService.getService().getName() + " (" + quantityString + ")\n";
      }
    }
    if (!GenericTools.isEmpty(productsServicesAsString)) {
      productsServicesAsString =
          productsServicesAsString.substring(0, productsServicesAsString.lastIndexOf("\n"));
      productsServicesAsString =
          productsServicesAsString.length() > 1000
              ? productsServicesAsString.substring(0, 1000)
              : productsServicesAsString;
      saleFromDB.setProductsServicesAsString(productsServicesAsString);
      saleFromDB.setProductsServices(productsServices);
    }

    // 2. Reset Product/Service Enterprise (and do not change Enterprise!)
    boolean enterpriseHasChildren = enterprise.getAllRelatedEnterprises().size() > 1;
    Product prod;
    Service service;
    for (SaleProductService saleProductService : saleFromDB.getProductsServices()) {
      if ((prod = saleProductService.getProduct()) != null
          && saleProductService.getProduct().getEnterprise() == null) {
        saleProductService
            .getProduct()
            .setEnterprise(
                enterpriseHasChildren
                    ? productDAO.findById(prod.getId()).getEnterprise()
                    : enterprise);

      } else if ((service = saleProductService.getService()) != null
          && saleProductService.getService().getEnterprise() == null) {
        saleProductService
            .getService()
            .setEnterprise(
                enterpriseHasChildren
                    ? serviceDAO.findById(service.getId()).getEnterprise()
                    : enterprise);
      }
    }

    // 3. Stock reduction
    if (isDraft) {
      return;
    }

    if (stockReductionMap.keySet().isEmpty()) {
      return;
    }

    ProductStockModification productStockModification =
        productDAO.persistProductStockModification(false);

    String errorMessage = "";
    for (Product product : stockReductionMap.keySet()) {
      if (product.getQuantity() < stockReductionMap.get(product)) {
        String unit = messages.getLabel(product.getPriceUnit().getLabelKey());
        unit = GenericTools.isEmpty(unit) ? " " + messages.getLabel("stock_unit") : unit;

        String stringQuantity =
            product.getQuantity() == 0d
                    || product.getQuantity() / new Double(product.getQuantity()).intValue() == 1
                ? new Double(product.getQuantity()).intValue() + ""
                : ServerTools.formatAmount(
                    saleFromDB.getEnterprise().getLanguage(), product.getQuantity());

        errorMessage +=
            "\n"
                + messages.getLabel(
                    "stock_sale_not_enough_text", product.getName(), stringQuantity, unit);
      }

      product.setQuantity(
          ServerTools.round(product.getQuantity() - stockReductionMap.get(product)));
      productDAO.merge(product);

      productDAO.trackStockModification(
          product,
          product.getQuantity(),
          stockReductionMap.get(product) * -1,
          productStockModification);
    }
    if (!GenericTools.isEmpty(errorMessage)) {
      errorMessage += messages.getLabel("stock_sale_not_enough_resolve_text");
      throw new IabakoStockException("stock_sale_not_enough_title", errorMessage, true);
    }

    // 4. Tag management
    for (SaleProductService saleProductService : saleFromDB.getProductsServices()) {
      if ((prod = saleProductService.getProduct()) != null
          && saleProductService.getProduct().getBusinessTag() != null) {
        BusinessTagSale businessTagSale = new BusinessTagSale();
        businessTagSale.setBusinessTag(prod.getBusinessTag());
        businessTagSale.setProduct(prod);
        businessTagSale.setSale(saleFromDB);
        saleFromDB.getBusinessTagList().add(businessTagSale);

      } else if ((service = saleProductService.getService()) != null
          && saleProductService.getService().getBusinessTag() != null) {
        BusinessTagSale businessTagSale = new BusinessTagSale();
        businessTagSale.setBusinessTag(service.getBusinessTag());
        businessTagSale.setService(service);
        businessTagSale.setSale(saleFromDB);
        saleFromDB.getBusinessTagList().add(businessTagSale);
      }
    }
  }
  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;
  }
  /**
   * This method is used by SaleModificationForm
   *
   * @param sale
   * @return
   * @throws IabakoActionForbiddenException
   */
  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
  public Sale save(Sale sale)
      throws IabakoActionForbiddenException, IabakoUniqueConstraintException {

    Enterprise enterprise = getEnterpriseFromSessionUser();
    if (sale.getId() != null && enterprise.getAllRelatedEnterprises().size() > 1) {
      // Do not change Enterprise!
      sale.setEnterprise(saleDAO.findById(sale.getId()).getEnterprise());
    } else {
      sale.setEnterprise(enterprise);
    }

    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);
    }

    Sale saleFromDB = saleDAO.findById(sale.getId());
    setAttributesFromDB(sale, saleFromDB);

    if (!enterprise.getPaymentRequestConfig().isEnabled()
        || (sale.getClient() != null && sale.getClient().isPaymentRequestDisabled())) {
      // If paymentRequest disable, then do not change initial value (it doesn't come from client
      // world)
      sale.setPaymentRequestDisabled(saleFromDB.isPaymentRequestDisabled());
    }

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

    trackingService.addTrackingToUserSession(TrackingType.saleModify, sale);

    return rawSave(sale);
  }