feat: Enhance Supply Chain Module with Analytic Move Line Features

- Added support for analytic move lines in InvoiceServiceSupplychainImpl.
- Implemented methods to check for missing analytic move lines in PurchaseOrderController and PurchaseRequestController.
- Introduced budget distribution line generation in PurchaseOrderController.
- Updated SaleOrderController to generate analytic move lines.
- Enhanced StockMoveController with budget distribution line generation.
- Modified StockMoveLineController to include analytic account and axis handling.
- Updated domain models to include references to analytic accounts and axes in Partner, StockLocation, and StockMoveLine.
- Created unit tests for StockMoveLineServiceSupplychainImpl to ensure proper functionality of new features.
- Added MockQuery class for testing purposes.
This commit is contained in:
BACHIR SOULDI
2026-02-17 15:13:17 +01:00
parent 9eb959f07a
commit 6881c439b2
74 changed files with 2975 additions and 930 deletions

View File

@@ -22,8 +22,10 @@ import com.axelor.apps.account.db.AccountManagement;
import com.axelor.apps.account.db.AnalyticDistributionTemplate;
import com.axelor.apps.account.db.FiscalPosition;
import com.axelor.apps.account.db.FixedAssetCategory;
import com.axelor.apps.account.db.repo.AccountManagementRepository;
import com.axelor.apps.account.exception.IExceptionMessage;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.FamilleProduit;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.base.service.tax.AccountManagementServiceImpl;
import com.axelor.apps.base.service.tax.FiscalPositionService;
@@ -31,8 +33,11 @@ import com.axelor.apps.base.service.tax.TaxService;
import com.axelor.exception.AxelorException;
import com.axelor.exception.db.repo.TraceBackRepository;
import com.axelor.i18n.I18n;
import com.axelor.inject.Beans;
import com.axelor.meta.CallMethod;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import java.lang.invoke.MethodHandles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -222,4 +227,55 @@ public class AccountManagementServiceAccountImpl extends AccountManagementServic
return fixedAssetCategory;
}
@Transactional(rollbackOn = {Exception.class})
public void setAccountManagementForProduct(Product product,Company company) {
FamilleProduit familleProduit = product.getFamilleProduit();
FamilleProduit sousFamilleProduit = product.getSousFamilleProduit();
if (product == null || familleProduit == null || company == null) {
return;
}
if (product.getAccountManagementList() == null || product.getAccountManagementList().isEmpty()) {
AccountManagement accountManagement = new AccountManagement();
accountManagement.setCompany(company);
accountManagement.setProduct(product);
// Use sousFamilleProduit accounts if they exist, otherwise fallback to familleProduit
accountManagement.setPurchaseAccount(
(sousFamilleProduit != null && sousFamilleProduit.getPurchaseAccount() != null) ?
sousFamilleProduit.getPurchaseAccount() :
familleProduit.getPurchaseAccount()
);
accountManagement.setStockAccount(
(sousFamilleProduit != null && sousFamilleProduit.getStockAccount() != null) ?
sousFamilleProduit.getStockAccount() :
familleProduit.getStockAccount()
);
accountManagement.setConsumptionAccount(
(sousFamilleProduit != null && sousFamilleProduit.getConsumptionAccount() != null) ?
sousFamilleProduit.getConsumptionAccount() :
familleProduit.getConsumptionAccount()
);
accountManagement.setSaleAccount(
(sousFamilleProduit != null && sousFamilleProduit.getSaleAccount() != null) ?
sousFamilleProduit.getSaleAccount() :
familleProduit.getSaleAccount()
);
accountManagement.setPurchFixedAssetsAccount(
(sousFamilleProduit != null && sousFamilleProduit.getPurchFixedAssetsAccount() != null) ?
sousFamilleProduit.getPurchFixedAssetsAccount() :
familleProduit.getPurchFixedAssetsAccount()
);
accountManagement.setTypeSelect(1); // Product type
Beans.get(AccountManagementRepository.class).save(accountManagement);
}
}
}

View File

@@ -20,6 +20,8 @@ package com.axelor.apps.account.service.invoice;
import com.axelor.apps.account.db.Account;
import com.axelor.apps.account.db.AccountConfig;
import com.axelor.apps.account.db.AccountingSituation;
import com.axelor.apps.account.db.AnalyticJournal;
import com.axelor.apps.account.db.AnalyticMoveLine;
import com.axelor.apps.account.db.Invoice;
import com.axelor.apps.account.db.InvoiceLine;
import com.axelor.apps.account.db.InvoicePayment;
@@ -36,6 +38,7 @@ import com.axelor.apps.account.db.repo.MoveRepository;
import com.axelor.apps.account.db.repo.PaymentModeRepository;
import com.axelor.apps.account.exception.IExceptionMessage;
import com.axelor.apps.account.service.AccountingSituationService;
import com.axelor.apps.account.service.AnalyticMoveLineService;
import com.axelor.apps.account.service.app.AppAccountService;
import com.axelor.apps.account.service.config.AccountConfigService;
import com.axelor.apps.account.service.invoice.factory.CancelFactory;
@@ -46,6 +49,7 @@ import com.axelor.apps.account.service.invoice.generator.invoice.RefundInvoice;
import com.axelor.apps.account.service.invoice.print.InvoicePrintService;
import com.axelor.apps.account.service.payment.invoice.payment.InvoicePaymentToolService;
import com.axelor.apps.base.db.Alarm;
import com.axelor.apps.base.db.AppBudget;
import com.axelor.apps.base.db.BankDetails;
import com.axelor.apps.base.db.CancelReason;
import com.axelor.apps.base.db.Company;
@@ -53,6 +57,7 @@ import com.axelor.apps.base.db.Currency;
import com.axelor.apps.base.db.Partner;
import com.axelor.apps.base.db.PriceList;
import com.axelor.apps.base.db.Sequence;
import com.axelor.apps.base.db.repo.AppBudgetRepository;
import com.axelor.apps.base.db.repo.BankDetailsRepository;
import com.axelor.apps.base.db.repo.PriceListRepository;
import com.axelor.apps.base.service.PartnerService;
@@ -102,6 +107,7 @@ public class InvoiceServiceImpl extends InvoiceRepository implements InvoiceServ
protected PartnerService partnerService;
protected InvoiceLineService invoiceLineService;
protected AccountConfigService accountConfigService;
protected AnalyticMoveLineService analyticMoveLineService;
@Inject
public InvoiceServiceImpl(
@@ -113,7 +119,8 @@ public class InvoiceServiceImpl extends InvoiceRepository implements InvoiceServ
AppAccountService appAccountService,
PartnerService partnerService,
InvoiceLineService invoiceLineService,
AccountConfigService accountConfigService) {
AccountConfigService accountConfigService,
AnalyticMoveLineService analyticMoveLineService) {
this.validateFactory = validateFactory;
this.ventilateFactory = ventilateFactory;
@@ -124,6 +131,7 @@ public class InvoiceServiceImpl extends InvoiceRepository implements InvoiceServ
this.partnerService = partnerService;
this.invoiceLineService = invoiceLineService;
this.accountConfigService = accountConfigService;
this.analyticMoveLineService = analyticMoveLineService;
}
// WKF
@@ -445,7 +453,10 @@ public class InvoiceServiceImpl extends InvoiceRepository implements InvoiceServ
public void setSequence(Invoice invoice) throws AxelorException {
if (invoice.getId() != null && Strings.isNullOrEmpty(invoice.getInvoiceId())) {
invoice.setInvoiceId(Beans.get(SequenceService.class).getSequenceNumber(getSequence(invoice), Beans.get(AppBaseService.class).getTodayDate()));
invoice.setInvoiceId(
Beans.get(SequenceService.class)
.getSequenceNumber(
getSequence(invoice), Beans.get(AppBaseService.class).getTodayDate()));
}
}
@@ -461,15 +472,17 @@ public class InvoiceServiceImpl extends InvoiceRepository implements InvoiceServ
return accountConfigService.getSuppRefSequence(accountConfig);
case InvoiceRepository.OPERATION_TYPE_CLIENT_SALE:
if(invoice.getOperationSubTypeSelect() == InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_DISCOUNT){
return accountConfigService.getFinancialCustInvSequence(accountConfig);
}else{
return accountConfigService.getCustInvSequence(accountConfig);
}
if (invoice.getOperationSubTypeSelect()
== InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_DISCOUNT) {
return accountConfigService.getFinancialCustInvSequence(accountConfig);
} else {
return accountConfigService.getCustInvSequence(accountConfig);
}
case InvoiceRepository.OPERATION_TYPE_CLIENT_REFUND:
if(invoice.getOperationSubTypeSelect() == InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_REFUNDS){
if (invoice.getOperationSubTypeSelect()
== InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_REFUNDS) {
return accountConfigService.getFinancialCustRefSequence(accountConfig);
}else{
} else {
return accountConfigService.getCustRefSequence(accountConfig);
}
@@ -482,7 +495,6 @@ public class InvoiceServiceImpl extends InvoiceRepository implements InvoiceServ
}
}
public Invoice mergeInvoiceProcess(
List<Invoice> invoiceList,
Company company,
@@ -1016,4 +1028,55 @@ public class InvoiceServiceImpl extends InvoiceRepository implements InvoiceServ
.getResultList()
.size();
}
public List<String> checkInconsistentAnalyticDistribution(Invoice invoice)
throws AxelorException {
List<InvoiceLine> invoiceLines = invoice.getInvoiceLineList();
List<String> products = new ArrayList<>();
for (InvoiceLine invoiceLine : invoiceLines) {
BigDecimal sumPourcentagePerLine =
invoiceLine
.getAnalyticMoveLineList()
.stream()
.map(AnalyticMoveLine::getPercentage)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (sumPourcentagePerLine.compareTo(BigDecimal.valueOf(100.0)) > 0) {
products.add(invoiceLine.getProductName());
break;
}
}
return products;
}
public Boolean isMissingAnalyticMoveLine(Invoice invoice) {
Boolean isMissing = false;
for (InvoiceLine invoiceLine : invoice.getInvoiceLineList()) {
if (invoiceLine.getAnalyticMoveLineList().isEmpty()) {
isMissing = true;
break;
}
}
return isMissing;
}
@Transactional
public void generateAnalyticMoveLines(Invoice invoice) {
List<InvoiceLine> invoiceLines = invoice.getInvoiceLineList();
for (InvoiceLine invoiceLine : invoiceLines) {
AnalyticMoveLine aml = new AnalyticMoveLine();
AppBudget AppBudget = Beans.get(AppBudgetRepository.class).all().fetchOne();
AnalyticJournal deAnalyticJournal = AppBudget.getDefaultAnalyticJournal();
if (invoice.getAnalyticJournal() != null) {
aml.setAnalyticJournal(invoice.getAnalyticJournal());
} else {
aml.setAnalyticJournal(deAnalyticJournal);
}
aml.setAnalyticAccount(invoice.getAnalyticAccount());
aml.setAnalyticAxis(invoice.getAnalyticAxis());
aml.setPercentage(BigDecimal.valueOf(100));
analyticMoveLineService.updateAnalyticMoveLine(
aml, invoiceLine.getCompanyExTaxTotal(), invoice.getInvoiceDate());
invoiceLine.addAnalyticMoveLineListItem(aml);
}
}
}

View File

@@ -319,15 +319,17 @@ public class VentilateState extends WorkflowInvoice {
return accountConfigService.getSuppRefSequence(accountConfig);
case InvoiceRepository.OPERATION_TYPE_CLIENT_SALE:
if(invoice.getOperationSubTypeSelect() == InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_DISCOUNT){
return accountConfigService.getFinancialCustInvSequence(accountConfig);
}else{
return accountConfigService.getCustInvSequence(accountConfig);
}
if (invoice.getOperationSubTypeSelect()
== InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_DISCOUNT) {
return accountConfigService.getFinancialCustInvSequence(accountConfig);
} else {
return accountConfigService.getCustInvSequence(accountConfig);
}
case InvoiceRepository.OPERATION_TYPE_CLIENT_REFUND:
if(invoice.getOperationSubTypeSelect() == InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_REFUNDS){
if (invoice.getOperationSubTypeSelect()
== InvoiceRepository.OPERATION_SUB_TYPE_FINANCIAL_REFUNDS) {
return accountConfigService.getFinancialCustRefSequence(accountConfig);
}else{
} else {
return accountConfigService.getCustRefSequence(accountConfig);
}

View File

@@ -38,14 +38,13 @@ import com.axelor.apps.account.exception.IExceptionMessage;
import com.axelor.apps.account.service.AccountManagementAccountService;
import com.axelor.apps.account.service.AnalyticMoveLineService;
import com.axelor.apps.account.service.FiscalPositionAccountService;
import com.axelor.apps.account.service.ReconcileServiceImpl;
import com.axelor.apps.account.service.TaxAccountService;
import com.axelor.apps.account.service.TaxPaymentMoveLineService;
import com.axelor.apps.account.service.app.AppAccountService;
import com.axelor.apps.account.service.config.AccountConfigService;
import com.axelor.apps.account.service.invoice.InvoiceToolService;
import com.axelor.apps.account.service.payment.PaymentService;
import com.axelor.apps.account.service.payment.PaymentServiceImpl;
import com.axelor.apps.account.util.MoveLineKey;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.Currency;
import com.axelor.apps.base.db.Partner;
@@ -486,28 +485,31 @@ public class MoveLineService {
moveLineId++,
origin,
invoiceLine.getProductName());
if(invoiceLine.getAnalyticDistributionTemplate() != null)
if (invoiceLine.getAnalyticDistributionTemplate() != null)
moveLine.setAnalyticDistributionTemplate(invoiceLine.getAnalyticDistributionTemplate());
if (invoiceLine.getAnalyticMoveLineList() != null
&& !invoiceLine.getAnalyticMoveLineList().isEmpty()) {
for (AnalyticMoveLine invoiceAnalyticMoveLine : invoiceLine.getAnalyticMoveLineList()) {
System.out.println("-----------------invoiceAnalyticMoveLine.getAccount().getName()----------------------------");
System.out.println(invoiceAnalyticMoveLine.getAccount());
AnalyticMoveLine analyticMoveLine =
analyticMoveLineRepository.copy(invoiceAnalyticMoveLine, false);
analyticMoveLine.setTypeSelect(AnalyticMoveLineRepository.STATUS_REAL_ACCOUNTING);
analyticMoveLine.setInvoiceLine(null);
analyticMoveLine.setAccount(moveLine.getAccount());
analyticMoveLine.setAccountType(moveLine.getAccount().getAccountType());
analyticMoveLineService.updateAnalyticMoveLine(
analyticMoveLine,
moveLine.getDebit().add(moveLine.getCredit()),
moveLine.getDate());
moveLine.addAnalyticMoveLineListItem(analyticMoveLine);
}
} else {
// if (invoiceLine.getAnalyticMoveLineList() != null
// && !invoiceLine.getAnalyticMoveLineList().isEmpty()) {
// for (AnalyticMoveLine invoiceAnalyticMoveLine : invoiceLine.getAnalyticMoveLineList()) {
// System.out.println(
// "-----------------invoiceAnalyticMoveLine.getAccount().getName()----------------------------");
// System.out.println(invoiceAnalyticMoveLine.getId());
// System.out.println(invoiceAnalyticMoveLine.toString());
// System.out.println( "-----------------invoiceAnalyticMoveLine.getAccount().getName()----------------------------");
// AnalyticMoveLine analyticMoveLine =
// analyticMoveLineRepository.copy(invoiceAnalyticMoveLine, false);
// analyticMoveLine.setTypeSelect(AnalyticMoveLineRepository.STATUS_REAL_ACCOUNTING);
// analyticMoveLine.setInvoiceLine(null);
// analyticMoveLine.setAccount(moveLine.getAccount());
// analyticMoveLine.setAccountType(moveLine.getAccount().getAccountType());
// analyticMoveLineService.updateAnalyticMoveLine(
// analyticMoveLine,
// moveLine.getDebit().add(moveLine.getCredit()),
// moveLine.getDate());
// moveLine.addAnalyticMoveLineListItem(analyticMoveLine);
// }
// } else {
generateAnalyticMoveLines(moveLine);
}
// }
TaxLine taxLine = invoiceLine.getTaxLine();
if (taxLine != null) {
@@ -655,65 +657,60 @@ public class MoveLineService {
}
public MoveLine findConsolidateMoveLine(
Map<List<Object>, MoveLine> map, MoveLine moveLine, List<Object> keys) {
if (map != null && !map.isEmpty()) {
Map<MoveLineKey, MoveLine> map, MoveLine moveLine, MoveLineKey keys) {
Map<List<Object>, MoveLine> copyMap = new HashMap<List<Object>, MoveLine>(map);
while (!copyMap.isEmpty()) {
if (map == null || map.isEmpty()) {
return null;
}
if (map.containsKey(keys)) {
// Quick lookup by composite key (account, taxLine, template)
MoveLine existing = map.get(keys);
if (existing == null) {
return null;
}
MoveLine moveLineIt = map.get(keys);
int count = 0;
if (moveLineIt.getAnalyticMoveLineList() == null
&& moveLine.getAnalyticMoveLineList() == null) {
return moveLineIt;
} else if (moveLineIt.getAnalyticMoveLineList() == null
|| moveLine.getAnalyticMoveLineList() == null) {
break;
}
List<AnalyticMoveLine> list1 = moveLineIt.getAnalyticMoveLineList();
List<AnalyticMoveLine> list2 = moveLine.getAnalyticMoveLineList();
List<AnalyticMoveLine> copyList = new ArrayList<AnalyticMoveLine>(list1);
if (list1.size() == list2.size()) {
for (AnalyticMoveLine analyticDistributionLine : list2) {
for (AnalyticMoveLine analyticDistributionLineIt : copyList) {
if (analyticDistributionLine
.getAnalyticAxis()
.equals(analyticDistributionLineIt.getAnalyticAxis())
&& analyticDistributionLine
.getAnalyticAccount()
.equals(analyticDistributionLineIt.getAnalyticAccount())
&& analyticDistributionLine
.getAccount()
.equals(analyticDistributionLineIt.getAccount())
&& analyticDistributionLine
.getPercentage()
.equals(analyticDistributionLineIt.getPercentage())
&& ((analyticDistributionLine.getAnalyticJournal() == null
&& analyticDistributionLineIt.getAnalyticJournal() == null)
|| analyticDistributionLine
.getAnalyticJournal()
.equals(analyticDistributionLineIt.getAnalyticJournal()))) {
copyList.remove(analyticDistributionLineIt);
count++;
break;
}
}
}
if (count == list1.size()) {
return moveLineIt;
}
}
} else {
return null;
}
}
// Fast exit if both have no analytic lines
List<AnalyticMoveLine> list1 = existing.getAnalyticMoveLineList();
List<AnalyticMoveLine> list2 = moveLine.getAnalyticMoveLineList();
if ((list1 == null || list1.isEmpty()) && (list2 == null || list2.isEmpty())) {
return existing;
}
// If one has analytics but not the other → not same
if ((list1 == null || list1.isEmpty()) ^ (list2 == null || list2.isEmpty())) {
return null;
}
// ⚙️ Build lightweight hash sets for quick equality check
Set<String> set1 = new HashSet<>(list1.size());
Set<String> set2 = new HashSet<>(list2.size());
for (AnalyticMoveLine a1 : list1) {
set1.add(buildAnalyticKey(a1));
}
for (AnalyticMoveLine a2 : list2) {
set2.add(buildAnalyticKey(a2));
}
// Compare as sets — same content, order doesnt matter
if (set1.equals(set2)) {
return existing;
}
return null;
}
/** Build a lightweight key for an AnalyticMoveLine to avoid deep equals. */
private static String buildAnalyticKey(AnalyticMoveLine a) {
Long axisId = a.getAnalyticAxis() != null ? a.getAnalyticAxis().getId() : null;
Long accId = a.getAnalyticAccount() != null ? a.getAnalyticAccount().getId() : null;
Long glId = a.getAccount() != null ? a.getAccount().getId() : null;
BigDecimal pct = a.getPercentage() != null ? a.getPercentage() : BigDecimal.ZERO;
Long journalId = a.getAnalyticJournal() != null ? a.getAnalyticJournal().getId() : null;
return axisId + ":" + accId + ":" + glId + ":" + pct + ":" + journalId;
}
/**
* Consolider des lignes d'écritures par compte comptable.
*
@@ -721,16 +718,12 @@ public class MoveLineService {
*/
public List<MoveLine> consolidateMoveLines(List<MoveLine> moveLines) {
Map<List<Object>, MoveLine> map = new HashMap<List<Object>, MoveLine>();
Map<MoveLineKey, MoveLine> map = new HashMap<>();
MoveLine consolidateMoveLine = null;
for (MoveLine moveLine : moveLines) {
List<Object> keys = new ArrayList<Object>();
keys.add(moveLine.getAccount());
keys.add(moveLine.getTaxLine());
keys.add(moveLine.getAnalyticDistributionTemplate());
MoveLineKey keys = new MoveLineKey(moveLine);
consolidateMoveLine = this.findConsolidateMoveLine(map, moveLine, keys);
if (consolidateMoveLine != null) {
@@ -1046,7 +1039,7 @@ public class MoveLineService {
JPA.clear();
}
}
// }
// else{
// throw new AxelorException(

View File

@@ -572,7 +572,7 @@ public class PaymentVoucherConfirmService {
paymentDate,
paymentMode,
MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC);
move.setPaymentVoucher(paymentVoucher);
List<? extends PayVoucherElementToPay> payVoucherElementToPayList =
@@ -638,8 +638,8 @@ public class PaymentVoucherConfirmService {
if (isDebitToPay) {
reconcileService.balanceCredit(moveLine);
}
if(paymentVoucher.getPartner().getId() == 2040L){
this.updateCashRegisterTheoricalSolde(paymentVoucher, isDebitToPay,paidAmount);
if (paymentVoucher.getPartner().getId() == 2040L) {
this.updateCashRegisterTheoricalSolde(paymentVoucher, isDebitToPay, paidAmount);
}
// Beans.get(PaymentVoucherLoadService.class).updateVoucher(paymentVoucher);
}
@@ -1025,10 +1025,8 @@ public class PaymentVoucherConfirmService {
*/
@Transactional(rollbackOn = {Exception.class})
public void confirmCashPayment(PaymentVoucher paymentVoucher) throws AxelorException {
if(paymentVoucher.getGeneratedMoveList().size() > 0){
throw new AxelorException(
TraceBackRepository.CATEGORY_INCONSISTENCY,
"Already ventilated.");
if (paymentVoucher.getGeneratedMoveList().size() > 0) {
throw new AxelorException(TraceBackRepository.CATEGORY_INCONSISTENCY, "Already ventilated.");
}
PaymentMode paymentMode = paymentVoucher.getPaymentMode();
Company company = paymentVoucher.getCompany();
@@ -1038,8 +1036,7 @@ public class PaymentVoucherConfirmService {
// LocalDate paymentDate = paymentVoucher.getPaymentDate();
LocalDate paymentDate = paymentVoucher.getPaymentEmissionDate();
Account paymentModeAccount =
paymentVoucher.getCashAccountConfig().getAccount();
Account paymentModeAccount = paymentVoucher.getCashAccountConfig().getAccount();
Move move =
moveService
@@ -1095,11 +1092,13 @@ public class PaymentVoucherConfirmService {
move.getMoveLineList().add(moveLine2);
String label = paymentVoucher.getLabel() != null ? paymentVoucher.getLabel() : "";
String fullName = (paymentVoucher.getRecipientPartner() != null && paymentVoucher.getRecipientPartner().getFullName() != null)
? paymentVoucher.getRecipientPartner().getFullName()
: "";
String fullName =
(paymentVoucher.getRecipientPartner() != null
&& paymentVoucher.getRecipientPartner().getFullName() != null)
? paymentVoucher.getRecipientPartner().getFullName()
: "";
String description = label +" "+ fullName;
String description = label + " " + fullName;
moveLine.setDescription(description);
moveLine2.setDescription(description);
@@ -1107,7 +1106,7 @@ public class PaymentVoucherConfirmService {
moveService.getMoveValidateService().validate(move);
paymentVoucher.setGeneratedMove(move);
paymentVoucher.addGeneratedMoveListItem(move);
// confirmed is ventilation equivalent
paymentVoucher.setConfirmedByUser(AuthUtils.getUser());
paymentVoucher.setConfirmationDate(LocalDate.now());
@@ -1120,8 +1119,10 @@ public class PaymentVoucherConfirmService {
}
@Transactional(rollbackOn = {Exception.class})
public void updateCashRegisterTheoricalSolde(PaymentVoucher paymentVoucher,Boolean isDebitCredit,BigDecimal amount) {
System.out.println("updateCashRegisterTheoricalSolde : : : " + paymentVoucher.getCashAccountConfig());
public void updateCashRegisterTheoricalSolde(
PaymentVoucher paymentVoucher, Boolean isDebitCredit, BigDecimal amount) {
System.out.println(
"updateCashRegisterTheoricalSolde : : : " + paymentVoucher.getCashAccountConfig());
if (paymentVoucher.getCashAccountConfig() != null || paymentVoucher.getPartner() != null) {
CashRegisterRepository casRegisterRepository = Beans.get(CashRegisterRepository.class);
CashRegister cashRegister = casRegisterRepository.all().order("-createdOn").fetchOne();
@@ -1148,66 +1149,64 @@ public class PaymentVoucherConfirmService {
throw new AxelorException(
TraceBackRepository.CATEGORY_INCONSISTENCY,
"Cash account is not set for the payment voucher.");
}
CashRegisterRepository casRegisterRepository = Beans.get(CashRegisterRepository.class);
CashRegister cashRegister = casRegisterRepository.all().order("-createdOn").fetchOne();
BigDecimal solde = cashRegister.getTheoricalSolde();
if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
solde = solde.subtract(paymentVoucher.getPaidAmount());
}else{
solde = solde.add(paymentVoucher.getPaidAmount());
}
}
CashRegisterRepository casRegisterRepository = Beans.get(CashRegisterRepository.class);
CashRegister cashRegister = casRegisterRepository.all().order("-createdOn").fetchOne();
paymentVoucher.setInitialTheoricalSolde(cashRegister.getTheoricalSolde());
paymentVoucher.setTheoricalSolde(solde);
paymentVoucher.setCashStatusSelect(PaymentVoucherRepository.STATUS_WAITING_FOR_DEPOSIT_SLIP);
paymentVoucher.setApprovalDate(LocalDate.now());
paymentVoucher.setApprovedByUser(AuthUtils.getUser());
Boolean isDebitToPay = false;
BigDecimal solde = cashRegister.getTheoricalSolde();
if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
solde = solde.subtract(paymentVoucher.getPaidAmount());
} else {
solde = solde.add(paymentVoucher.getPaidAmount());
}
paymentVoucher.setInitialTheoricalSolde(cashRegister.getTheoricalSolde());
paymentVoucher.setTheoricalSolde(solde);
paymentVoucher.setCashStatusSelect(PaymentVoucherRepository.STATUS_WAITING_FOR_DEPOSIT_SLIP);
paymentVoucher.setApprovalDate(LocalDate.now());
paymentVoucher.setApprovedByUser(AuthUtils.getUser());
Boolean isDebitToPay = false;
if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
isDebitToPay = true;
}
updateCashRegisterTheoricalSolde(paymentVoucher, isDebitToPay,paymentVoucher.getPaidAmount());
updateCashRegisterTheoricalSolde(paymentVoucher, isDebitToPay, paymentVoucher.getPaidAmount());
}
@Transactional(rollbackOn = {Exception.class})
public void cancelCashPaymentVoucher(PaymentVoucher paymentVoucher) throws AxelorException {
if (paymentVoucher.getCashAccountConfig() == null) {
throw new AxelorException(
TraceBackRepository.CATEGORY_INCONSISTENCY,
"Cash account config is not set for the payment voucher.");
}
if (paymentVoucher.getCashAccountConfig().getAccount() == null) {
throw new AxelorException(
TraceBackRepository.CATEGORY_INCONSISTENCY,
"Cash account is not set for the payment voucher.");
}
CashRegisterRepository casRegisterRepository = Beans.get(CashRegisterRepository.class);
CashRegister cashRegister = casRegisterRepository.all().order("-createdOn").fetchOne();
BigDecimal solde = cashRegister.getTheoricalSolde();
if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 2) {
solde = solde.subtract(paymentVoucher.getPaidAmount());
}else{
solde = solde.add(paymentVoucher.getPaidAmount());
}
if (paymentVoucher.getCashAccountConfig() == null) {
throw new AxelorException(
TraceBackRepository.CATEGORY_INCONSISTENCY,
"Cash account config is not set for the payment voucher.");
}
if (paymentVoucher.getCashAccountConfig().getAccount() == null) {
throw new AxelorException(
TraceBackRepository.CATEGORY_INCONSISTENCY,
"Cash account is not set for the payment voucher.");
}
CashRegisterRepository casRegisterRepository = Beans.get(CashRegisterRepository.class);
CashRegister cashRegister = casRegisterRepository.all().order("-createdOn").fetchOne();
paymentVoucher.setInitialTheoricalSolde(cashRegister.getTheoricalSolde());
paymentVoucher.setTheoricalSolde(solde);
paymentVoucher.setCashStatusSelect(4);
Boolean isDebitToPay = false;
BigDecimal solde = cashRegister.getTheoricalSolde();
if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 2) {
solde = solde.subtract(paymentVoucher.getPaidAmount());
} else {
solde = solde.add(paymentVoucher.getPaidAmount());
}
if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
isDebitToPay = true;
}
// Beans.get(MoveServiceImpl.class).
updateCashRegisterTheoricalSolde(paymentVoucher, !isDebitToPay,paymentVoucher.getPaidAmount());
for (Move move : paymentVoucher.getGeneratedMoveList()) {
Beans.get(MoveCancelService.class).cancel(move);
}
paymentVoucher.setInitialTheoricalSolde(cashRegister.getTheoricalSolde());
paymentVoucher.setTheoricalSolde(solde);
paymentVoucher.setCashStatusSelect(4);
Boolean isDebitToPay = false;
if (Integer.parseInt(paymentVoucher.getDebitCreditSelect()) == 1) {
isDebitToPay = true;
}
// Beans.get(MoveServiceImpl.class).
updateCashRegisterTheoricalSolde(paymentVoucher, !isDebitToPay, paymentVoucher.getPaidAmount());
for (Move move : paymentVoucher.getGeneratedMoveList()) {
Beans.get(MoveCancelService.class).cancel(move);
}
}
}

View File

@@ -28,6 +28,7 @@ import com.axelor.apps.base.service.administration.SequenceService;
import com.axelor.exception.AxelorException;
import com.axelor.exception.db.repo.TraceBackRepository;
import com.axelor.i18n.I18n;
import com.axelor.inject.Beans;
import com.google.common.base.Strings;
import com.google.inject.Inject;
@@ -58,9 +59,18 @@ public class PaymentVoucherSequenceService {
Company company = paymentVoucher.getCompany();
// Sequence seq = Beans.get(SequenceRepository.class).find(new Long("126"));
return sequenceService.getSequenceNumber(
String sequence = "";
if (paymentVoucher.getOperationTypeSelect() == 9) {
sequence = sequenceService
.getSequenceNumber("CashSequence", paymentVoucher.getCompany());
} else {
sequence = sequenceService.getSequenceNumber(
paymentModeService.getPaymentModeSequence(
paymentMode, company, paymentVoucher.getCompanyBankDetails()));
}
return sequence;
// return sequenceService.getSequenceNumber(seq);
}

View File

@@ -0,0 +1,34 @@
package com.axelor.apps.account.util;
import com.axelor.apps.account.db.MoveLine;
import java.util.Objects;
public class MoveLineKey {
private final Long accountId;
private final Long taxLineId;
private final Long analyticTemplateId;
public MoveLineKey(MoveLine line) {
this.accountId = line.getAccount() != null ? line.getAccount().getId() : null;
this.taxLineId = line.getTaxLine() != null ? line.getTaxLine().getId() : null;
this.analyticTemplateId =
line.getAnalyticDistributionTemplate() != null
? line.getAnalyticDistributionTemplate().getId()
: null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MoveLineKey)) return false;
MoveLineKey that = (MoveLineKey) o;
return Objects.equals(accountId, that.accountId)
&& Objects.equals(taxLineId, that.taxLineId)
&& Objects.equals(analyticTemplateId, that.analyticTemplateId);
}
@Override
public int hashCode() {
return Objects.hash(accountId, taxLineId, analyticTemplateId);
}
}

View File

@@ -0,0 +1,21 @@
package com.axelor.apps.account.web;
import com.axelor.apps.account.service.AccountManagementAccountService;
import com.axelor.apps.account.service.AccountManagementServiceAccountImpl;
import com.axelor.apps.base.db.Company;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.base.db.repo.ProductRepository;
import com.axelor.auth.AuthUtils;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.axelor.rpc.Context;
public class AccountManagementController {
public void searchMoveLine(ActionRequest request, ActionResponse response) {
Product product = request.getContext().asType(Product.class);
product = Beans.get(ProductRepository.class).find(product.getId());
Company company = AuthUtils.getUser().getActiveCompany();
Beans.get(AccountManagementServiceAccountImpl.class).setAccountManagementForProduct(product, company);
}
}

View File

@@ -16,7 +16,6 @@ import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.google.common.base.Strings;
import com.google.inject.Inject;
import java.lang.invoke.MethodHandles;
import java.math.BigDecimal;
import java.util.List;
@@ -27,8 +26,7 @@ import org.slf4j.LoggerFactory;
public class CashInventoryController {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Inject
private final ConvertNumberToFrenchWordsService convertService;
@Inject private final ConvertNumberToFrenchWordsService convertService;
@Inject
public CashInventoryController(ConvertNumberToFrenchWordsService convertService) {
@@ -38,7 +36,7 @@ public class CashInventoryController {
/**
* Prints the cash inventory report.
*
* @param request The action request containing the context.
* @param request The action request containing the context.
* @param response The action response to set the view with the report link.
* @throws AxelorException If an error occurs during report generation.
*/
@@ -52,12 +50,13 @@ public class CashInventoryController {
name += " " + cashInventory.getRef();
}
String fileLink = ReportFactory.createReport(
String.format(IReport.CASH_INVENTORY, cashInventory.getOperationTypeSelect()),
name + "-${date}")
.addParam("cashInventoryId", cashInventory.getId())
.generate()
.getFileLink();
String fileLink =
ReportFactory.createReport(
String.format(IReport.CASH_INVENTORY, cashInventory.getOperationTypeSelect()),
name + "-${date}")
.addParam("cashInventoryId", cashInventory.getId())
.generate()
.getFileLink();
logger.debug("Printing " + name);
@@ -67,7 +66,7 @@ public class CashInventoryController {
/**
* Computes the theoretical solde in words for the cash inventory.
*
* @param request The action request containing the context.
* @param request The action request containing the context.
* @param response The action response to set the theoretical solde in words.
*/
public void convertTheoricalSoldeInWords(ActionRequest request, ActionResponse response) {
@@ -85,7 +84,7 @@ public class CashInventoryController {
/**
* Initializes the theoretical solde in words for the cash inventory.
*
* @param request The action request containing the context.
* @param request The action request containing the context.
* @param response The action response to set the theoretical solde value.
*/
public void initThetoricalSolde(ActionRequest request, ActionResponse response) {
@@ -96,14 +95,16 @@ public class CashInventoryController {
/**
* Computes the totals for the cash inventory.
*
* @param request The action request containing the context.
* @param request The action request containing the context.
* @param response The action response to set the computed values.
*/
public void computeToTals(ActionRequest request, ActionResponse response) {
CashInventory cashInventory = request.getContext().asType(CashInventory.class);
if(cashInventory.getCashInventoryLines() != null && cashInventory.getCashInventoryLines().size() != 0){
Map<String, BigDecimal> map = Beans.get(CashInventoryService.class).computeToTals(cashInventory);
if (cashInventory.getCashInventoryLines() != null
&& cashInventory.getCashInventoryLines().size() != 0) {
Map<String, BigDecimal> map =
Beans.get(CashInventoryService.class).computeToTals(cashInventory);
response.setValues(map);
}
}
@@ -111,16 +112,18 @@ public class CashInventoryController {
/**
* Retrieves the last cash data and calculates the theoretical solde.
*
* @param request The action request containing the context.
* @param request The action request containing the context.
* @param response The action response to set the theoretical solde value.
*/
public void getLastCashData(ActionRequest request, ActionResponse response) {
CashInventory lastCashInventory = Beans.get(CashInventoryRepository.class).all().order("-id").fetchOne();
CashInventory lastCashInventory =
Beans.get(CashInventoryRepository.class).all().order("-id").fetchOne();
List<PaymentVoucher> cashVouchers = Beans.get(PaymentVoucherRepository.class)
.all()
.filter("self.operationTypeSelect = 9 and self.cashStatusSelect = 3")
.fetch();
List<PaymentVoucher> cashVouchers =
Beans.get(PaymentVoucherRepository.class)
.all()
.filter("self.operationTypeSelect = 9 and self.cashStatusSelect = 3")
.fetch();
BigDecimal totalSolde = lastCashInventory.getPhysicalSolde();
@@ -142,19 +145,22 @@ public class CashInventoryController {
}
// generate sequence for cash inventory
public void generateSequence(ActionRequest request, ActionResponse response) throws AxelorException {
public void generateSequence(ActionRequest request, ActionResponse response)
throws AxelorException {
CashInventory cashInventory = request.getContext().asType(CashInventory.class);
if (cashInventory.getRef() == null || cashInventory.getRef().isEmpty()) {
String sequence = Beans.get(CashInventoryService.class).getCashInventorySequence(cashInventory);
String sequence =
Beans.get(CashInventoryService.class).getCashInventorySequence(cashInventory);
response.setValue("ref", sequence);
}
}
public void approveCashInventory(ActionRequest request, ActionResponse response) throws AxelorException{
CashInventory cashInventoryContext = request.getContext().asType(CashInventory.class);
CashInventory cashInventory = Beans.get(CashInventoryRepository.class).find(cashInventoryContext.getId());
Beans.get(CashInventoryService.class).approveCashInventory(cashInventory);
response.setReload(true);
public void approveCashInventory(ActionRequest request, ActionResponse response)
throws AxelorException {
CashInventory cashInventoryContext = request.getContext().asType(CashInventory.class);
CashInventory cashInventory =
Beans.get(CashInventoryRepository.class).find(cashInventoryContext.getId());
Beans.get(CashInventoryService.class).approveCashInventory(cashInventory);
response.setReload(true);
}
}

View File

@@ -27,11 +27,11 @@ import com.axelor.apps.account.db.PaymentMode;
import com.axelor.apps.account.db.PaymentVoucher;
import com.axelor.apps.account.db.repo.InvoiceRepository;
import com.axelor.apps.account.exception.IExceptionMessage;
import com.axelor.apps.account.report.IReport;
import com.axelor.apps.account.service.AccountingSituationService;
import com.axelor.apps.account.service.IrrecoverableService;
import com.axelor.apps.account.service.app.AppAccountService;
import com.axelor.apps.account.service.invoice.InvoiceService;
import com.axelor.apps.account.service.invoice.InvoiceServiceImpl;
import com.axelor.apps.account.service.invoice.InvoiceToolService;
import com.axelor.apps.account.service.invoice.print.InvoicePrintService;
import com.axelor.apps.account.service.payment.invoice.payment.InvoicePaymentCreateService;
@@ -1014,11 +1014,11 @@ public class InvoiceController {
System.out.println(paymentVoucher.getOperationTypeSelect() == 9);
System.out.println(paymentVoucher.getDebitCreditSelect() == "1");
if(paymentVoucher.getOperationTypeSelect() == 9){
if(paymentVoucher.getDebitCreditSelect().equals("1")){
reportType = "CashPaymentRequestDebit.rptdesign";
}else if(paymentVoucher.getDebitCreditSelect().equals("2")){
reportType = "CashPaymentRequestCredit.rptdesign";
if (paymentVoucher.getOperationTypeSelect() == 9) {
if (paymentVoucher.getDebitCreditSelect().equals("1")) {
reportType = "CashPaymentRequestDebit.rptdesign";
} else if (paymentVoucher.getDebitCreditSelect().equals("2")) {
reportType = "CashPaymentRequestCredit.rptdesign";
}
}
@@ -1100,4 +1100,40 @@ public class InvoiceController {
}
}
}
public void isMissingAnalyticMoveLine(ActionRequest request, ActionResponse response)
throws AxelorException {
Invoice invoice = request.getContext().asType(Invoice.class);
Invoice inv = Beans.get(InvoiceRepository.class).find(invoice.getId());
Boolean isMissingAnalyticMoveLine =
Beans.get(InvoiceServiceImpl.class).isMissingAnalyticMoveLine(inv);
if (isMissingAnalyticMoveLine) {
throw new AxelorException(
invoice,
TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
"The analytic account is missing on one of the lines.");
}
}
public void generateAnalyticMoveLines(ActionRequest request, ActionResponse response) {
Invoice invoice = request.getContext().asType(Invoice.class);
Invoice inv = Beans.get(InvoiceRepository.class).find(invoice.getId());
Beans.get(InvoiceServiceImpl.class).generateAnalyticMoveLines(inv);
response.setAlert("The analytic move lines have been generated.");
}
public void checkInconsistentAnalyticDistribution(ActionRequest request, ActionResponse response)
throws AxelorException {
Context context = request.getContext();
Invoice invoice = context.asType(Invoice.class);
invoice = Beans.get(InvoiceRepository.class).find(invoice.getId());
List<String> products =
Beans.get(InvoiceServiceImpl.class).checkInconsistentAnalyticDistribution(invoice);
if (products.isEmpty()) {
return;
}
response.setAlert(
"The analytic distribution of at least one invoice line is inconsistent with the total amount of the line. Please check the analytic distribution of each invoice line."
+ String.join(", ", products));
}
}

View File

@@ -338,11 +338,11 @@ public class PaymentVoucherController {
System.out.println(paymentVoucher.getOperationTypeSelect() == 9);
System.out.println(paymentVoucher.getDebitCreditSelect() == "1");
if(paymentVoucher.getOperationTypeSelect() == 9){
if(paymentVoucher.getDebitCreditSelect() == "1"){
reportType = "CashPaymentRequestDebit.rptdesign";
}else if(paymentVoucher.getDebitCreditSelect() == "2"){
reportType = "CashPaymentRequestCredit.rptdesign";
if (paymentVoucher.getOperationTypeSelect() == 9) {
if (paymentVoucher.getDebitCreditSelect() == "1") {
reportType = "CashPaymentRequestDebit.rptdesign";
} else if (paymentVoucher.getDebitCreditSelect() == "2") {
reportType = "CashPaymentRequestCredit.rptdesign";
}
}
@@ -495,8 +495,8 @@ public class PaymentVoucherController {
.map());
}
public void validateCashPaymentVoucher(
ActionRequest request, ActionResponse response) throws AxelorException {
public void validateCashPaymentVoucher(ActionRequest request, ActionResponse response)
throws AxelorException {
PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());
@@ -508,9 +508,8 @@ public class PaymentVoucherController {
}
}
public void cancelCashPaymentVoucher(
ActionRequest request, ActionResponse response) throws AxelorException {
public void cancelCashPaymentVoucher(ActionRequest request, ActionResponse response)
throws AxelorException {
PaymentVoucher paymentVoucher = request.getContext().asType(PaymentVoucher.class);
paymentVoucher = Beans.get(PaymentVoucherRepository.class).find(paymentVoucher.getId());
@@ -521,4 +520,4 @@ public class PaymentVoucherController {
TraceBackService.trace(response, e);
}
}
}
}

View File

@@ -61,6 +61,8 @@
]]>
</string>
<boolean name="isActive" title="Is active" default="true"/>
<unique-constraint columns="code,name,analyticAxis,parent" />
</entity>

View File

@@ -32,6 +32,8 @@
public static final int STATUS_FORECAST_ORDER = 1;
public static final int STATUS_FORECAST_INVOICE = 2;
public static final int STATUS_REAL_ACCOUNTING = 3;
public static final int STATUS_PURCHASE_REQUEST = 4;
public static final int STATUS_STOCK_MOVE = 5;
]]></extra-code>

View File

@@ -10,10 +10,15 @@
<boolean name="manageMultiBudget" title="Manage multi budgets on lines"/>
<boolean name="budgetRequiredOnPO" title="Budget required on purchase order lines"/>
<many-to-one ref="com.axelor.apps.account.db.AnalyticJournal" name="defaultAnalyticJournal" />
<many-to-one ref="com.axelor.apps.account.db.AnalyticJournal" name="defaultPurchaseAnalyticJournal" />
<many-to-one ref="com.axelor.apps.account.db.AnalyticJournal" name="defaultSaleAnalyticJournal" />
<track>
<field name="checkAvailableBudget" on="UPDATE"/>
<field name="manageMultiBudget" on="UPDATE"/>
<field name="budgetRequiredOnPO" on="UPDATE"/>
<field name="defaultAnalyticJournal" on="UPDATE"/>
</track>
</entity>
</domain-models>

View File

@@ -20,9 +20,12 @@
<decimal name="amountForGeneration" title="Amount for each line"/>
<boolean name="checkAvailableBudget" title="Check available budget"/>
<many-to-one name="analyticAccount" ref="com.axelor.apps.account.db.AnalyticAccount" title="Analytic account"/>
<string name="accountAccountCode" title="Account code"/>
<boolean name="isGrouped" title="Is grouped"/>
<boolean name="isActive" title="Is active"/>
<finder-method name="findByAnalyticAccount" using="analyticAccount"/>
<finder-method name="findByAnalyticAccountAndAccountCode" using="analyticAccount,accountAccountCode,isActive"/>
<extra-code><![CDATA[

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain-models xmlns="http://axelor.com/xml/ns/domain-models"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://axelor.com/xml/ns/domain-models http://axelor.com/xml/ns/domain-models/domain-models_5.2.xsd">
<module name="base" package="com.axelor.apps.base.db"/>
<entity name="FamilleProduit">
<many-to-one name="purchaseAccount" ref="com.axelor.apps.account.db.Account" title="Purchase account"/>
<many-to-one name="saleAccount" ref="com.axelor.apps.account.db.Account" title="Sale account"/>
<many-to-one name="stockAccount" ref="com.axelor.apps.account.db.Account" title="Stock account"/>
<many-to-one name="consumptionAccount" ref="com.axelor.apps.account.db.Account" title="Consumption account"/>
<many-to-one name="purchFixedAssetsAccount" ref="com.axelor.apps.account.db.Account" title="Account of purchase fixed assets"/>
<many-to-one name="purchaseTax" ref="com.axelor.apps.account.db.Tax" title="Purchase Tax"/>
<many-to-one name="saleTax" ref="com.axelor.apps.account.db.Tax" title="Sale Tax"/>
<many-to-one name="tax" ref="com.axelor.apps.account.db.Tax" title="Tax"/>
<track>
<field name="purchaseAccount"/>
<field name="saleAccount"/>
<field name="stockAccount"/>
<field name="consumptionAccount"/>
<field name="purchFixedAssetsAccount"/>
<field name="purchaseTax"/>
<field name="saleTax"/>
<field name="tax"/>
</track>
</entity>
</domain-models>

View File

@@ -155,6 +155,10 @@
<many-to-one name="deliveryPartner" ref="com.axelor.apps.base.db.Partner" title="Delivery Supplier"/>
<many-to-one name="analyticJournal" ref="com.axelor.apps.account.db.AnalyticJournal" title="Analytic journal"/>
<many-to-one name="analyticAccount" ref="com.axelor.apps.account.db.AnalyticAccount" title="Analytic account"/>
<many-to-one name="analyticAxis" ref="com.axelor.apps.account.db.AnalyticAxis" title="Analytic axis" />
<unique-constraint columns="invoiceId,company"/>
<extra-code><![CDATA[

View File

@@ -1,80 +1,76 @@
package com.axelor.apps.account.web;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
import com.axelor.apps.account.db.CashInventory;
import com.axelor.apps.base.service.ConvertNumberToFrenchWordsService;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import static org.mockito.Mockito.*;
import org.mockito.ArgumentCaptor;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
public class CashInventoryControllerTest {
private CashInventoryController controller;
private ActionRequest request;
private ActionResponse response;
private CashInventory cashInventory;
private ConvertNumberToFrenchWordsService convertService;
private CashInventoryController controller;
private ActionRequest request;
private ActionResponse response;
private CashInventory cashInventory;
private ConvertNumberToFrenchWordsService convertService;
@Before
public void setUp() {
request = mock(ActionRequest.class);
response = mock(ActionResponse.class);
cashInventory = mock(CashInventory.class);
convertService = mock(ConvertNumberToFrenchWordsService.class);
@Before
public void setUp() {
request = mock(ActionRequest.class);
response = mock(ActionResponse.class);
cashInventory = mock(CashInventory.class);
convertService = mock(ConvertNumberToFrenchWordsService.class);
controller = new CashInventoryController(convertService); // ✅ No Guice needed
controller = new CashInventoryController(convertService); // ✅ No Guice needed
// Mock request.getContext().asType(CashInventory.class)
com.axelor.rpc.Context context = mock(com.axelor.rpc.Context.class);
when(request.getContext()).thenReturn(context);
when(context.asType(CashInventory.class)).thenReturn(cashInventory);
}
// Mock request.getContext().asType(CashInventory.class)
com.axelor.rpc.Context context = mock(com.axelor.rpc.Context.class);
when(request.getContext()).thenReturn(context);
when(context.asType(CashInventory.class)).thenReturn(cashInventory);
}
@Test
public void testConvert_withDecimalSolde() {
// Given
when(cashInventory.getTheoricalSolde()).thenReturn(new BigDecimal("1234.56"));
when(convertService.convert(1234L)).thenReturn("mille deux cent trente-quatre");
when(convertService.convert(56L)).thenReturn("cinquante-six");
@Test
public void testConvert_withDecimalSolde() {
// Given
when(cashInventory.getTheoricalSolde()).thenReturn(new BigDecimal("1234.56"));
when(convertService.convert(1234L)).thenReturn("mille deux cent trente-quatre");
when(convertService.convert(56L)).thenReturn("cinquante-six");
}
}
@Test
public void testConvert_withIntegerSolde() {
// Given
when(cashInventory.getTheoricalSolde()).thenReturn(new BigDecimal("1000.00"));
when(convertService.convert(1000L)).thenReturn("mille");
when(convertService.convert(0L)).thenReturn("zéro");
@Test
public void testConvert_withIntegerSolde() {
// Given
when(cashInventory.getTheoricalSolde()).thenReturn(new BigDecimal("1000.00"));
when(convertService.convert(1000L)).thenReturn("mille");
when(convertService.convert(0L)).thenReturn("zéro");
// Then
ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
verify(response).setValue(keyCaptor.capture(), valueCaptor.capture());
assertEquals("theoricalSoldeInWords", keyCaptor.getValue());
assertEquals("mille dinars algériens et zéro Cts", valueCaptor.getValue());
}
// @Test
// public void testConvert_withNoDecimalPart() {
// // Given
// when(cashInventory.getTheoricalSolde()).thenReturn(new BigDecimal("42"));
// when(convertService.convert(42L)).thenReturn("quarante-deux");
// when(convertService.convert(0L)).thenReturn("zéro");
// Then
ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
verify(response).setValue(keyCaptor.capture(), valueCaptor.capture());
assertEquals("theoricalSoldeInWords", keyCaptor.getValue());
assertEquals("mille dinars algériens et zéro Cts", valueCaptor.getValue());
}
// @Test
// public void testConvert_withNoDecimalPart() {
// // Given
// when(cashInventory.getTheoricalSolde()).thenReturn(new BigDecimal("42"));
// when(convertService.convert(42L)).thenReturn("quarante-deux");
// when(convertService.convert(0L)).thenReturn("zéro");
// // Then
// ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
// ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
// verify(response).setValue(keyCaptor.capture(), valueCaptor.capture());
// assertEquals("theoricalSoldeInWords", keyCaptor.getValue());
// assertEquals("quarante-deux dinars algériens et zéro Cts",
// valueCaptor.getValue());
// }
}
// // Then
// ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
// ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
// verify(response).setValue(keyCaptor.capture(), valueCaptor.capture());
// assertEquals("theoricalSoldeInWords", keyCaptor.getValue());
// assertEquals("quarante-deux dinars algériens et zéro Cts",
// valueCaptor.getValue());
// }
}