Merge branch 'develop' into filter-uncategorized-bank-transactions

This commit is contained in:
Ahmed Bouhuolia
2024-08-22 00:12:37 +02:00
320 changed files with 3212 additions and 1420 deletions

View File

@@ -249,6 +249,7 @@ export default class Ledger implements ILedger {
transactionId: entry.referenceId,
transactionType: entry.referenceType,
transactionSubType: entry.transactionType,
transactionNumber: entry.transactionNumber,
referenceNumber: entry.referenceNumber,
@@ -262,6 +263,8 @@ export default class Ledger implements ILedger {
taxRateId: entry.taxRateId,
taxRate: entry.taxRate,
note: entry.note,
};
}

View File

@@ -43,8 +43,8 @@ export class AccountsApplication {
/**
* Creates a new account.
* @param {number} tenantId
* @param {IAccountCreateDTO} accountDTO
* @param {number} tenantId
* @param {IAccountCreateDTO} accountDTO
* @returns {Promise<IAccount>}
*/
public createAccount = (
@@ -108,8 +108,8 @@ export class AccountsApplication {
/**
* Retrieves the account details.
* @param {number} tenantId
* @param {number} accountId
* @param {number} tenantId
* @param {number} accountId
* @returns {Promise<IAccount>}
*/
public getAccount = (tenantId: number, accountId: number) => {

View File

@@ -73,6 +73,7 @@ export class DeleteAccount {
.throwIfNotFound()
.queryAndThrowIfHasRelations({
type: ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS,
excludeRelations: ['uncategorizedTransactions', 'plaidItem']
});
// Authorize before delete account.
await this.authorize(tenantId, accountId, oldAccount);

View File

@@ -1,10 +1,10 @@
import { Inject, Service } from 'typedi';
import { isEmpty } from 'lodash';
import {
IPaymentReceiveCreatedPayload,
IPaymentReceiveCreatingPayload,
IPaymentReceiveDeletingPayload,
IPaymentReceiveEditedPayload,
IPaymentReceivedCreatedPayload,
IPaymentReceivedCreatingPayload,
IPaymentReceivedDeletingPayload,
IPaymentReceivedEditedPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { LinkAttachment } from '../LinkAttachment';
@@ -50,13 +50,13 @@ export class AttachmentsOnPaymentsReceived {
/**
* Validates the attachment keys on creating payment.
* @param {IPaymentReceiveCreatingPayload}
* @param {IPaymentReceivedCreatingPayload}
* @returns {Promise<void>}
*/
private async validateAttachmentsOnPaymentCreate({
paymentReceiveDTO,
tenantId,
}: IPaymentReceiveCreatingPayload): Promise<void> {
}: IPaymentReceivedCreatingPayload): Promise<void> {
if (isEmpty(paymentReceiveDTO.attachments)) {
return;
}
@@ -67,7 +67,7 @@ export class AttachmentsOnPaymentsReceived {
/**
* Handles linking the attachments of the created payment.
* @param {IPaymentReceiveCreatedPayload}
* @param {IPaymentReceivedCreatedPayload}
* @returns {Promise<void>}
*/
private async handleAttachmentsOnPaymentCreated({
@@ -75,7 +75,7 @@ export class AttachmentsOnPaymentsReceived {
paymentReceiveDTO,
paymentReceive,
trx,
}: IPaymentReceiveCreatedPayload): Promise<void> {
}: IPaymentReceivedCreatedPayload): Promise<void> {
if (isEmpty(paymentReceiveDTO.attachments)) return;
const keys = paymentReceiveDTO.attachments?.map(
@@ -92,14 +92,14 @@ export class AttachmentsOnPaymentsReceived {
/**
* Handles unlinking all the unpresented keys of the edited payment.
* @param {IPaymentReceiveEditedPayload}
* @param {IPaymentReceivedEditedPayload}
*/
private async handleUnlinkUnpresentedKeysOnPaymentEdited({
tenantId,
paymentReceiveDTO,
oldPaymentReceive,
trx,
}: IPaymentReceiveEditedPayload) {
}: IPaymentReceivedEditedPayload) {
const keys = paymentReceiveDTO.attachments?.map(
(attachment) => attachment.key
);
@@ -114,7 +114,7 @@ export class AttachmentsOnPaymentsReceived {
/**
* Handles linking all the presented keys of the edited payment.
* @param {IPaymentReceiveEditedPayload}
* @param {IPaymentReceivedEditedPayload}
* @returns {Promise<void>}
*/
private async handleLinkPresentedKeysOnPaymentEdited({
@@ -122,7 +122,7 @@ export class AttachmentsOnPaymentsReceived {
paymentReceiveDTO,
oldPaymentReceive,
trx,
}: IPaymentReceiveEditedPayload) {
}: IPaymentReceivedEditedPayload) {
if (isEmpty(paymentReceiveDTO.attachments)) return;
const keys = paymentReceiveDTO.attachments?.map(
@@ -146,7 +146,7 @@ export class AttachmentsOnPaymentsReceived {
tenantId,
oldPaymentReceive,
trx,
}: IPaymentReceiveDeletingPayload) {
}: IPaymentReceivedDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'PaymentReceive',

View File

@@ -52,6 +52,9 @@ export class GetBankAccountSummary {
q.withGraphJoined('matchedBankTransactions');
q.whereNull('matchedBankTransactions.id');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
@@ -65,16 +68,32 @@ export class GetBankAccountSummary {
q.withGraphJoined('recognizedTransaction');
q.whereNotNull('recognizedTransaction.id');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Retrieves excluded transactions count.
const excludedTransactionsCount =
await UncategorizedCashflowTransaction.query().onBuild((q) => {
q.where('accountId', bankAccountId);
q.modify('excluded');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Retrieves the pending transactions count.
const pendingTransactionsCount =
await UncategorizedCashflowTransaction.query().onBuild((q) => {
q.where('accountId', bankAccountId);
q.modify('pending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
@@ -83,14 +102,15 @@ export class GetBankAccountSummary {
const totalUncategorizedTransactions =
uncategorizedTranasctionsCount?.total || 0;
const totalRecognizedTransactions = recognizedTransactionsCount?.total || 0;
const totalExcludedTransactions = excludedTransactionsCount?.total || 0;
const totalPendingTransactions = pendingTransactionsCount?.total || 0;
return {
name: bankAccount.name,
totalUncategorizedTransactions,
totalRecognizedTransactions,
totalExcludedTransactions,
totalPendingTransactions,
};
}
}

View File

@@ -0,0 +1,78 @@
import { Inject, Service } from 'typedi';
import { initialize } from 'objection';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import events from '@/subscribers/events';
import { IAccountEventDeletePayload } from '@/interfaces';
import { DeleteBankRulesService } from '../../Rules/DeleteBankRules';
import { RevertRecognizedTransactions } from '../../RegonizeTranasctions/RevertRecognizedTransactions';
@Service()
export class DeleteUncategorizedTransactionsOnAccountDeleting {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private deleteBankRules: DeleteBankRulesService;
@Inject()
private revertRecognizedTransactins: RevertRecognizedTransactions;
/**
* Constructor method.
*/
public attach(bus) {
bus.subscribe(
events.accounts.onDelete,
this.handleDeleteBankRulesOnAccountDeleting.bind(this)
);
}
/**
* Handles revert the recognized transactions and delete all the bank rules
* associated to the deleted bank account.
* @param {IAccountEventDeletePayload}
*/
private async handleDeleteBankRulesOnAccountDeleting({
tenantId,
oldAccount,
trx,
}: IAccountEventDeletePayload) {
const knex = this.tenancy.knex(tenantId);
const {
BankRule,
UncategorizedCashflowTransaction,
MatchedBankTransaction,
RecognizedBankTransaction,
} = this.tenancy.models(tenantId);
const foundAssociatedRules = await BankRule.query(trx).where(
'applyIfAccountId',
oldAccount.id
);
const foundAssociatedRulesIds = foundAssociatedRules.map((rule) => rule.id);
await initialize(knex, [
UncategorizedCashflowTransaction,
RecognizedBankTransaction,
MatchedBankTransaction,
]);
// Revert the recognized transactions of the given bank rules.
await this.revertRecognizedTransactins.revertRecognizedTransactions(
tenantId,
foundAssociatedRulesIds,
null,
trx
);
// Delete the associated uncategorized transactions.
await UncategorizedCashflowTransaction.query(trx)
.where('accountId', oldAccount.id)
.delete();
// Delete the given bank rules.
await this.deleteBankRules.deleteBankRules(
tenantId,
foundAssociatedRulesIds,
trx
);
}
}

View File

@@ -51,6 +51,7 @@ export class DisconnectPlaidItemOnAccountDeleted {
.findOne('plaidItemId', oldAccount.plaidItemId)
.delete();
// Remove Plaid item once the transaction resolve.
if (oldPlaidItem) {
const plaidInstance = PlaidClientWrapper.getClient();

View File

@@ -64,7 +64,7 @@ export class GetMatchedTransactions {
.whereIn('id', uncategorizedTransactionIds)
.throwIfNotFound();
const totalPending = Math.abs(sumBy(uncategorizedTransactions, 'amount'));
const totalPending = sumBy(uncategorizedTransactions, 'amount');
const filtered = filter.transactionType
? this.registered.filter((item) => item.type === filter.transactionType)

View File

@@ -1,18 +1,25 @@
import { Inject, Service } from 'typedi';
import { initialize } from 'objection';
import { Knex } from 'knex';
import { first } from 'lodash';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import {
GetMatchedTransactionsFilter,
IMatchTransactionDTO,
MatchedTransactionPOJO,
} from './types';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { CreateBillPayment } from '@/services/Purchases/BillPayments/CreateBillPayment';
import { IBillPaymentDTO } from '@/interfaces';
@Service()
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
@Inject()
private tenancy: HasTenancyService;
private transformer: TransformerInjectable;
@Inject()
private transformer: TransformerInjectable;
private createPaymentMadeService: CreateBillPayment;
/**
* Retrieves the matched transactions.
@@ -71,4 +78,62 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
new GetMatchedTransactionBillsTransformer()
);
}
/**
* Creates the common matched transaction.
* @param {number} tenantId
* @param {Array<number>} uncategorizedTransactionIds
* @param {IMatchTransactionDTO} matchTransactionDTO
* @param {Knex.Transaction} trx
*/
public async createMatchedTransaction(
tenantId: number,
uncategorizedTransactionIds: Array<number>,
matchTransactionDTO: IMatchTransactionDTO,
trx?: Knex.Transaction
): Promise<void> {
await super.createMatchedTransaction(
tenantId,
uncategorizedTransactionIds,
matchTransactionDTO,
trx
);
const { Bill, UncategorizedCashflowTransaction, MatchedBankTransaction } =
this.tenancy.models(tenantId);
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
const uncategorizedTransaction =
await UncategorizedCashflowTransaction.query(trx)
.findById(uncategorizedTransactionId)
.throwIfNotFound();
const bill = await Bill.query(trx)
.findById(matchTransactionDTO.referenceId)
.throwIfNotFound();
const createPaymentMadeDTO: IBillPaymentDTO = {
vendorId: bill.vendorId,
paymentAccountId: uncategorizedTransaction.accountId,
paymentDate: uncategorizedTransaction.date,
exchangeRate: 1,
entries: [
{
paymentAmount: bill.dueAmount,
billId: bill.id,
},
],
branchId: bill.branchId,
};
// Create a new bill payment associated to the matched bill.
const billPayment = await this.createPaymentMadeService.createBillPayment(
tenantId,
createPaymentMadeDTO,
trx
);
// Link the create bill payment with matched transaction.
await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
referenceType: 'BillPayment',
referenceId: billPayment.id,
}, trx);
}
}

View File

@@ -1,22 +1,26 @@
import { Inject, Service } from 'typedi';
import { initialize } from 'objection';
import { Knex } from 'knex';
import { first } from 'lodash';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
import {
GetMatchedTransactionsFilter,
IMatchTransactionDTO,
MatchedTransactionPOJO,
MatchedTransactionsPOJO,
} from './types';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { CreatePaymentReceived } from '@/services/Sales/PaymentReceived/CreatePaymentReceived';
import { IPaymentReceivedCreateDTO } from '@/interfaces';
@Service()
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
@Inject()
protected tenancy: HasTenancyService;
protected transformer: TransformerInjectable;
@Inject()
protected transformer: TransformerInjectable;
protected createPaymentReceivedService: CreatePaymentReceived;
/**
* Retrieves the matched transactions.
@@ -78,4 +82,64 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
new GetMatchedTransactionInvoicesTransformer()
);
}
/**
* Creates the common matched transaction.
* @param {number} tenantId
* @param {Array<number>} uncategorizedTransactionIds
* @param {IMatchTransactionDTO} matchTransactionDTO
* @param {Knex.Transaction} trx
*/
public async createMatchedTransaction(
tenantId: number,
uncategorizedTransactionIds: Array<number>,
matchTransactionDTO: IMatchTransactionDTO,
trx?: Knex.Transaction
) {
await super.createMatchedTransaction(
tenantId,
uncategorizedTransactionIds,
matchTransactionDTO,
trx
);
const { SaleInvoice, UncategorizedCashflowTransaction, MatchedBankTransaction } =
this.tenancy.models(tenantId);
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
const uncategorizedTransaction =
await UncategorizedCashflowTransaction.query(trx)
.findById(uncategorizedTransactionId)
.throwIfNotFound();
const invoice = await SaleInvoice.query(trx)
.findById(matchTransactionDTO.referenceId)
.throwIfNotFound();
const createPaymentReceivedDTO: IPaymentReceivedCreateDTO = {
customerId: invoice.customerId,
paymentDate: uncategorizedTransaction.date,
amount: invoice.dueAmount,
depositAccountId: uncategorizedTransaction.accountId,
entries: [
{
index: 1,
invoiceId: invoice.id,
paymentAmount: invoice.dueAmount,
},
],
branchId: invoice.branchId,
};
// Create a payment received associated to the matched invoice.
const paymentReceived = await this.createPaymentReceivedService.createPaymentReceived(
tenantId,
createPaymentReceivedDTO,
{},
trx
);
// Link the create payment received with matched invoice transaction.
await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
referenceType: 'PaymentReceive',
referenceId: paymentReceived.id,
}, trx)
}
}

View File

@@ -1,7 +1,7 @@
import { Inject, Service } from 'typedi';
import {
IBillPaymentEventDeletedPayload,
IPaymentReceiveDeletedPayload,
IPaymentReceivedDeletedPayload,
} from '@/interfaces';
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
import events from '@/subscribers/events';
@@ -23,7 +23,7 @@ export class ValidateMatchingOnPaymentMadeDelete {
/**
* Validates the payment made transaction whether matched with bank transaction on deleting.
* @param {IPaymentReceiveDeletedPayload}
* @param {IPaymentReceivedDeletedPayload}
*/
public async validateMatchingOnPaymentMadeDeleting({
tenantId,

View File

@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi';
import { IPaymentReceiveDeletedPayload } from '@/interfaces';
import { IPaymentReceivedDeletedPayload } from '@/interfaces';
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
import events from '@/subscribers/events';
@@ -20,13 +20,13 @@ export class ValidateMatchingOnPaymentReceivedDelete {
/**
* Validates the payment received transaction whether matched with bank transaction on deleting.
* @param {IPaymentReceiveDeletedPayload}
* @param {IPaymentReceivedDeletedPayload}
*/
public async validateMatchingOnPaymentReceivedDeleting({
tenantId,
oldPaymentReceive,
trx,
}: IPaymentReceiveDeletedPayload) {
}: IPaymentReceivedDeletedPayload) {
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
tenantId,
'PaymentReceive',

View File

@@ -25,6 +25,7 @@ import { Knex } from 'knex';
import uniqid from 'uniqid';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
import { RemovePendingUncategorizedTransaction } from '@/services/Cashflow/RemovePendingUncategorizedTransaction';
const CONCURRENCY_ASYNC = 10;
@@ -40,7 +41,7 @@ export class PlaidSyncDb {
private cashflowApp: CashflowApplication;
@Inject()
private deleteCashflowTransactionService: DeleteCashflowTransaction;
private removePendingTransaction: RemovePendingUncategorizedTransaction;
@Inject()
private eventPublisher: EventPublisher;
@@ -185,21 +186,22 @@ export class PlaidSyncDb {
plaidTransactionsIds: string[],
trx?: Knex.Transaction
) {
const { CashflowTransaction } = this.tenancy.models(tenantId);
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
const cashflowTransactions = await CashflowTransaction.query(trx).whereIn(
'plaidTransactionId',
plaidTransactionsIds
);
const cashflowTransactionsIds = cashflowTransactions.map(
const uncategorizedTransactions =
await UncategorizedCashflowTransaction.query(trx).whereIn(
'plaidTransactionId',
plaidTransactionsIds
);
const uncategorizedTransactionsIds = uncategorizedTransactions.map(
(trans) => trans.id
);
await bluebird.map(
cashflowTransactionsIds,
(transactionId: number) =>
this.deleteCashflowTransactionService.deleteCashflowTransaction(
uncategorizedTransactionsIds,
(uncategorizedTransactionId: number) =>
this.removePendingTransaction.removePendingTransaction(
tenantId,
transactionId,
uncategorizedTransactionId,
trx
),
{ concurrency: CONCURRENCY_ASYNC }

View File

@@ -1,10 +1,10 @@
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
import { PlaidSyncDb } from './PlaidSyncDB';
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
import UnitOfWork from '@/services/UnitOfWork';
import { Knex } from 'knex';
@Service()
export class PlaidUpdateTransactions {
@@ -19,9 +19,9 @@ export class PlaidUpdateTransactions {
/**
* Handles sync the Plaid item to Bigcaptial under UOW.
* @param {number} tenantId
* @param {number} plaidItemId
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
* @param {number} tenantId - Tenant id.
* @param {number} plaidItemId - Plaid item id.
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
*/
public async updateTransactions(tenantId: number, plaidItemId: string) {
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {
@@ -73,6 +73,12 @@ export class PlaidUpdateTransactions {
item,
trx
);
// Sync removed transactions.
await this.plaidSync.syncRemoveTransactions(
tenantId,
removed?.map((r) => r.transaction_id),
trx
);
// Sync bank account transactions.
await this.plaidSync.syncAccountsTransactions(
tenantId,

View File

@@ -3,11 +3,11 @@ import {
Item as PlaidItem,
Institution as PlaidInstitution,
AccountBase as PlaidAccount,
TransactionBase as PlaidTransactionBase,
} from 'plaid';
import {
CreateUncategorizedTransactionDTO,
IAccountCreateDTO,
PlaidTransaction,
} from '@/interfaces';
/**
@@ -48,7 +48,7 @@ export const transformPlaidAccountToCreateAccount = R.curry(
export const transformPlaidTrxsToCashflowCreate = R.curry(
(
cashflowAccountId: number,
plaidTranasction: PlaidTransaction
plaidTranasction: PlaidTransactionBase
): CreateUncategorizedTransactionDTO => {
return {
date: plaidTranasction.date,
@@ -64,6 +64,8 @@ export const transformPlaidTrxsToCashflowCreate = R.curry(
accountId: cashflowAccountId,
referenceNo: plaidTranasction.payment_meta?.reference_number,
plaidTransactionId: plaidTranasction.transaction_id,
pending: plaidTranasction.pending,
pendingPlaidTransactionId: plaidTranasction.pending_transaction_id,
};
}
);

View File

@@ -33,17 +33,29 @@ const matchNumberCondition = (
transaction: UncategorizedCashflowTransaction,
condition: IBankRuleCondition
) => {
const conditionValue = parseFloat(condition.value);
const transactionAmount =
condition.field === 'amount'
? Math.abs(transaction[condition.field])
: (transaction[condition.field] as unknown as number);
switch (condition.comparator) {
case BankRuleConditionComparator.Equals:
return transaction[condition.field] === condition.value;
case BankRuleConditionComparator.Contains:
return transaction[condition.field]
?.toString()
.includes(condition.value.toString());
case BankRuleConditionComparator.NotContain:
return !transaction[condition.field]
?.toString()
.includes(condition.value.toString());
case BankRuleConditionComparator.Equal:
return transactionAmount === conditionValue;
case BankRuleConditionComparator.BiggerOrEqual:
return transactionAmount >= conditionValue;
case BankRuleConditionComparator.Bigger:
return transactionAmount > conditionValue;
case BankRuleConditionComparator.Smaller:
return transactionAmount < conditionValue;
case BankRuleConditionComparator.SmallerOrEqual:
return transactionAmount <= conditionValue;
default:
return false;
}
@@ -53,18 +65,19 @@ const matchTextCondition = (
transaction: UncategorizedCashflowTransaction,
condition: IBankRuleCondition
): boolean => {
const transactionValue = transaction[condition.field] as string;
switch (condition.comparator) {
case BankRuleConditionComparator.Equals:
return transaction[condition.field] === condition.value;
case BankRuleConditionComparator.Equal:
return transactionValue === condition.value;
case BankRuleConditionComparator.Contains:
const fieldValue = lowerCase(transaction[condition.field]);
const fieldValue = lowerCase(transactionValue);
const conditionValue = lowerCase(condition.value);
return fieldValue.includes(conditionValue);
case BankRuleConditionComparator.NotContain:
return !transaction[condition.field]?.includes(
condition.value.toString()
);
return !transactionValue?.includes(condition.value.toString());
default:
return false;
}
@@ -101,8 +114,8 @@ const determineFieldType = (field: string): string => {
case 'amount':
return 'number';
case 'description':
return 'text';
case 'payee':
default:
return 'unknown';
return 'text';
}
};
};

View File

@@ -1,4 +1,5 @@
import { Inject, Service } from 'typedi';
import { isEqual, omit } from 'lodash';
import events from '@/subscribers/events';
import {
IBankRuleEventCreatedPayload,
@@ -55,10 +56,22 @@ export class TriggerRecognizedTransactions {
private async recognizedTransactionsOnRuleEdited({
tenantId,
editRuleDTO,
oldBankRule,
bankRule,
ruleId,
}: IBankRuleEventEditedPayload) {
const payload = { tenantId, ruleId };
// Cannot continue if the new and old bank rule values are the same,
// after excluding `createdAt` and `updatedAt` dates.
if (
isEqual(
omit(bankRule, ['createdAt', 'updatedAt']),
omit(oldBankRule, ['createdAt', 'updatedAt'])
)
) {
return;
}
await this.agenda.now(
'rerecognize-uncategorized-transactions-job',
payload

View File

@@ -26,31 +26,39 @@ export class DeleteBankRuleSerivce {
* @param {number} ruleId
* @returns {Promise<void>}
*/
public async deleteBankRule(tenantId: number, ruleId: number): Promise<void> {
public async deleteBankRule(
tenantId: number,
ruleId: number,
trx?: Knex.Transaction
): Promise<void> {
const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId);
const oldBankRule = await BankRule.query()
.findById(ruleId)
.throwIfNotFound();
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onBankRuleDeleting` event.
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
tenantId,
oldBankRule,
ruleId,
trx,
} as IBankRuleEventDeletingPayload);
return this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Triggers `onBankRuleDeleting` event.
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
tenantId,
oldBankRule,
ruleId,
trx,
} as IBankRuleEventDeletingPayload);
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete();
await BankRule.query(trx).findById(ruleId).delete();
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete()
await BankRule.query(trx).findById(ruleId).delete();
// Triggers `onBankRuleDeleted` event.
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
tenantId,
ruleId,
trx,
} as IBankRuleEventDeletedPayload);
});
// Triggers `onBankRuleDeleted` event.
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
tenantId,
ruleId,
trx,
} as IBankRuleEventDeletedPayload);
},
trx
);
}
}

View File

@@ -0,0 +1,34 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import PromisePool from '@supercharge/promise-pool';
import { castArray, uniq } from 'lodash';
import { DeleteBankRuleSerivce } from './DeleteBankRule';
@Service()
export class DeleteBankRulesService {
@Inject()
private deleteBankRuleService: DeleteBankRuleSerivce;
/**
* Delete bank rules.
* @param {number} tenantId
* @param {number | Array<number>} bankRuleId
*/
async deleteBankRules(
tenantId: number,
bankRuleId: number | Array<number>,
trx?: Knex.Transaction
) {
const bankRulesIds = uniq(castArray(bankRuleId));
const results = await PromisePool.withConcurrency(1)
.for(bankRulesIds)
.process(async (bankRuleId: number) => {
await this.deleteBankRuleService.deleteBankRule(
tenantId,
bankRuleId,
trx
);
});
}
}

View File

@@ -47,6 +47,7 @@ export class EditBankRuleService {
const oldBankRule = await BankRule.query()
.findById(ruleId)
.withGraphFetched('conditions')
.throwIfNotFound();
const tranformDTO = this.transformDTO(editRuleDTO);
@@ -64,15 +65,15 @@ export class EditBankRuleService {
} as IBankRuleEventEditingPayload);
// Updates the given bank rule.
await BankRule.query(trx).upsertGraphAndFetch({
const bankRule = await BankRule.query(trx).upsertGraphAndFetch({
...tranformDTO,
id: ruleId,
});
// Triggers `onBankRuleEdited` event.
await this.eventPublisher.emitAsync(events.bankRules.onEdited, {
tenantId,
oldBankRule,
bankRule,
ruleId,
editRuleDTO,
trx,

View File

@@ -1,15 +1,20 @@
import { Knex } from 'knex';
export enum BankRuleConditionField {
Amount = 'Amount',
Description = 'Description',
Payee = 'Payee',
Amount = 'amount',
Description = 'description',
Payee = 'payee',
}
export enum BankRuleConditionComparator {
Contains = 'contains',
Equals = 'equals',
Equal = 'equal',
NotContain = 'not_contain',
Bigger = 'bigger',
BiggerOrEqual = 'bigger_or_equal',
Smaller = 'smaller',
SmallerOrEqual = 'smaller_or_equal',
}
export interface IBankRuleCondition {
@@ -56,7 +61,15 @@ export enum BankRuleAssignCategory {
export interface IBankRuleConditionDTO {
id?: number;
field: string;
comparator: string;
comparator:
| 'contains'
| 'equals'
| 'not_contains'
| 'equal'
| 'bigger'
| 'bigger_or_equal'
| 'smaller'
| 'smaller_or_equal';
value: number;
}
@@ -99,6 +112,8 @@ export interface IBankRuleEventEditingPayload {
export interface IBankRuleEventEditedPayload {
tenantId: number;
ruleId: number;
oldBankRule: IBankRule;
bankRule: IBankRule;
editRuleDTO: IEditBankRuleDTO;
trx?: Knex.Transaction;
}

View File

@@ -1,8 +1,8 @@
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import {
IPaymentReceiveCreatingPayload,
IPaymentReceiveEditingPayload,
IPaymentReceivedCreatingPayload,
IPaymentReceivedEditingPayload,
} from '@/interfaces';
import { ValidateBranchExistance } from '../../Integrations/ValidateBranchExistance';
@@ -28,12 +28,12 @@ export class PaymentReceiveBranchValidateSubscriber {
/**
* Validate branch existance on estimate creating.
* @param {IPaymentReceiveCreatingPayload} payload
* @param {IPaymentReceivedCreatingPayload} payload
*/
private validateBranchExistanceOnPaymentCreating = async ({
tenantId,
paymentReceiveDTO,
}: IPaymentReceiveCreatingPayload) => {
}: IPaymentReceivedCreatingPayload) => {
await this.validateBranchExistance.validateTransactionBranchWhenActive(
tenantId,
paymentReceiveDTO.branchId
@@ -42,12 +42,12 @@ export class PaymentReceiveBranchValidateSubscriber {
/**
* Validate branch existance once estimate editing.
* @param {IPaymentReceiveEditingPayload} payload
* @param {IPaymentReceivedEditingPayload} payload
*/
private validateBranchExistanceOnPaymentEditing = async ({
paymentReceiveDTO,
tenantId,
}: IPaymentReceiveEditingPayload) => {
}: IPaymentReceivedEditingPayload) => {
await this.validateBranchExistance.validateTransactionBranchWhenActive(
tenantId,
paymentReceiveDTO.branchId

View File

@@ -8,7 +8,12 @@ export class CashflowAccountTransformer extends Transformer {
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return ['formattedAmount'];
return [
'formattedAmount',
'lastFeedsUpdatedAt',
'lastFeedsUpdatedAtFormatted',
'lastFeedsUpdatedFromNow',
];
};
/**
@@ -29,7 +34,7 @@ export class CashflowAccountTransformer extends Transformer {
/**
* Retrieve formatted account amount.
* @param {IAccount} invoice
* @param {IAccount} invoice
* @returns {string}
*/
protected formattedAmount = (account: IAccount): string => {
@@ -37,4 +42,22 @@ export class CashflowAccountTransformer extends Transformer {
currencyCode: account.currencyCode,
});
};
/**
* Retrieves the last feeds update at formatted date.
* @param {IAccount} account
* @returns {string}
*/
protected lastFeedsUpdatedAtFormatted(account: IAccount): string {
return this.formatDate(account.lastFeedsUpdatedAt);
}
/**
* Retrieves the last feeds updated from now.
* @param {IAccount} account
* @returns {string}
*/
protected lastFeedsUpdatedFromNow(account: IAccount): string {
return this.formatDateFromNow(account.lastFeedsUpdatedAt);
}
}

View File

@@ -21,6 +21,7 @@ import GetCashflowAccountsService from './GetCashflowAccountsService';
import { GetCashflowTransactionService } from './GetCashflowTransactionsService';
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
import { GetRecognizedTransactionService } from './GetRecognizedTransaction';
import { UncategorizeCashflowTransactionsBulk } from './UncategorizeCashflowTransactionsBulk';
@Service()
export class CashflowApplication {
@@ -39,6 +40,9 @@ export class CashflowApplication {
@Inject()
private uncategorizeTransactionService: UncategorizeCashflowTransaction;
@Inject()
private uncategorizeTransasctionsService: UncategorizeCashflowTransactionsBulk;
@Inject()
private categorizeTransactionService: CategorizeCashflowTransaction;
@@ -155,6 +159,22 @@ export class CashflowApplication {
);
}
/**
* Uncategorize the given transactions in bulk.
* @param {number} tenantId
* @param {number | Array<number>} transactionId
* @returns
*/
public uncategorizeTransactions(
tenantId: number,
transactionId: number | Array<number>
) {
return this.uncategorizeTransasctionsService.uncategorizeBulk(
tenantId,
transactionId
);
}
/**
* Categorize the given cashflow transaction.
* @param {number} tenantId
@@ -241,9 +261,9 @@ export class CashflowApplication {
/**
* Retrieves the recognized transaction of the given uncategorized transaction.
* @param {number} tenantId
* @param {number} uncategorizedTransactionId
* @returns
* @param {number} tenantId
* @param {number} uncategorizedTransactionId
* @returns
*/
public getRecognizedTransaction(
tenantId: number,

View File

@@ -0,0 +1,53 @@
import { Inject } from 'typedi';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import HasTenancyService from '../Tenancy/TenancyService';
import { GetPendingBankAccountTransactionTransformer } from './GetPendingBankAccountTransactionTransformer';
export class GetPendingBankAccountTransactions {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieves the given bank accounts pending transaction.
* @param {number} tenantId - Tenant id.
* @param {GetPendingTransactionsQuery} filter - Pending transactions query.
*/
async getPendingTransactions(
tenantId: number,
filter?: GetPendingTransactionsQuery
) {
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
const _filter = {
page: 1,
pageSize: 20,
...filter,
};
const { results, pagination } =
await UncategorizedCashflowTransaction.query()
.onBuild((q) => {
q.modify('pending');
if (_filter?.accountId) {
q.where('accountId', _filter.accountId);
}
})
.pagination(_filter.page - 1, _filter.pageSize);
const data = await this.transformer.transform(
tenantId,
results,
new GetPendingBankAccountTransactionTransformer()
);
return { data, pagination };
}
}
interface GetPendingTransactionsQuery {
page?: number;
pageSize?: number;
accountId?: number;
}

View File

@@ -0,0 +1,73 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from '@/utils';
export class GetPendingBankAccountTransactionTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return [
'formattedAmount',
'formattedDate',
'formattedDepositAmount',
'formattedWithdrawalAmount',
];
};
/**
* Exclude all attributes.
* @returns {Array<string>}
*/
public excludeAttributes = (): string[] => {
return [];
};
/**
* Formattes the transaction date.
* @param transaction
* @returns {string}
*/
public formattedDate(transaction) {
return this.formatDate(transaction.date);
}
/**
* Formatted amount.
* @param transaction
* @returns {string}
*/
public formattedAmount(transaction) {
return formatNumber(transaction.amount, {
currencyCode: transaction.currencyCode,
});
}
/**
* Formatted deposit amount.
* @param transaction
* @returns {string}
*/
protected formattedDepositAmount(transaction) {
if (transaction.isDepositTransaction) {
return formatNumber(transaction.deposit, {
currencyCode: transaction.currencyCode,
});
}
return '';
}
/**
* Formatted withdrawal amount.
* @param transaction
* @returns {string}
*/
protected formattedWithdrawalAmount(transaction) {
if (transaction.isWithdrawalTransaction) {
return formatNumber(transaction.withdrawal, {
currencyCode: transaction.currencyCode,
});
}
return '';
}
}

View File

@@ -34,8 +34,13 @@ export class GetRecognizedTransactionsService {
q.withGraphFetched('recognizedTransaction.assignAccount');
q.withGraphFetched('recognizedTransaction.bankRule');
q.whereNotNull('recognizedTransactionId');
// Exclude the excluded transactions.
q.modify('notExcluded');
// Exclude the pending transactions.
q.modify('notPending');
if (_query.accountId) {
q.where('accountId', _query.accountId);
}
@@ -51,6 +56,9 @@ export class GetRecognizedTransactionsService {
if (_query.maxAmount) {
q.modify('maxAmount', _query.maxAmount);
}
if (_query.accountId) {
q.where('accountId', _query.accountId);
}
})
.pagination(_query.page - 1, _query.pageSize);

View File

@@ -51,7 +51,9 @@ export class GetUncategorizedTransactions {
.onBuild((q) => {
q.where('accountId', accountId);
q.where('categorized', false);
q.modify('notExcluded');
q.modify('notPending');
q.withGraphFetched('account');
q.withGraphFetched('recognizedTransaction.assignAccount');

View File

@@ -0,0 +1,72 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import HasTenancyService from '../Tenancy/TenancyService';
import UnitOfWork from '../UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './constants';
import {
IPendingTransactionRemovedEventPayload,
IPendingTransactionRemovingEventPayload,
} from '@/interfaces';
@Service()
export class RemovePendingUncategorizedTransaction {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
/**
* REmoves the pending uncategorized transaction.
* @param {number} tenantId -
* @param {number} uncategorizedTransactionId -
* @param {Knex.Transaction} trx -
* @returns {Promise<void>}
*/
public async removePendingTransaction(
tenantId: number,
uncategorizedTransactionId: number,
trx?: Knex.Transaction
): Promise<void> {
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
const pendingTransaction = await UncategorizedCashflowTransaction.query(trx)
.findById(uncategorizedTransactionId)
.throwIfNotFound();
if (!pendingTransaction.isPending) {
throw new ServiceError(ERRORS.TRANSACTION_NOT_PENDING);
}
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
await this.eventPublisher.emitAsync(
events.bankTransactions.onPendingRemoving,
{
tenantId,
uncategorizedTransactionId,
pendingTransaction,
trx,
} as IPendingTransactionRemovingEventPayload
);
// Removes the pending uncategorized transaction.
await UncategorizedCashflowTransaction.query(trx)
.findById(uncategorizedTransactionId)
.delete();
await this.eventPublisher.emitAsync(
events.bankTransactions.onPendingRemoved,
{
tenantId,
uncategorizedTransactionId,
pendingTransaction,
trx,
} as IPendingTransactionRemovedEventPayload
);
});
}
}

View File

@@ -0,0 +1,37 @@
import PromisePool from '@supercharge/promise-pool';
import { castArray } from 'lodash';
import { Service, Inject } from 'typedi';
import HasTenancyService from '../Tenancy/TenancyService';
import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransaction';
@Service()
export class UncategorizeCashflowTransactionsBulk {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uncategorizeTransaction: UncategorizeCashflowTransaction;
/**
* Uncategorize the given bank transactions in bulk.
* @param {number} tenantId
* @param {number} uncategorizedTransactionId
*/
public async uncategorizeBulk(
tenantId: number,
uncategorizedTransactionId: number | Array<number>
) {
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
const result = await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
.for(uncategorizedTransactionIds)
.process(async (_uncategorizedTransactionId: number, index, pool) => {
await this.uncategorizeTransaction.uncategorize(
tenantId,
_uncategorizedTransactionId
);
});
}
}
const MIGRATION_CONCURRENCY = 1;

View File

@@ -15,10 +15,10 @@ export const ERRORS = {
'UNCATEGORIZED_TRANSACTION_TYPE_INVALID',
CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED:
'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED',
CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION: 'CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION',
TRANSACTION_NOT_CATEGORIZED: 'TRANSACTION_NOT_CATEGORIZED'
CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION:
'CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION',
TRANSACTION_NOT_CATEGORIZED: 'TRANSACTION_NOT_CATEGORIZED',
TRANSACTION_NOT_PENDING: 'TRANSACTION_NOT_PENDING',
};
export enum CASHFLOW_DIRECTION {

View File

@@ -1,11 +1,11 @@
import { Inject, Service } from 'typedi';
import PromisePool from '@supercharge/promise-pool';
import events from '@/subscribers/events';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import {
ICashflowTransactionCategorizedPayload,
ICashflowTransactionUncategorizedPayload,
} from '@/interfaces';
import PromisePool from '@supercharge/promise-pool';
@Service()
export class DecrementUncategorizedTransactionOnCategorize {
@@ -36,13 +36,17 @@ export class DecrementUncategorizedTransactionOnCategorize {
public async decrementUnCategorizedTransactionsOnCategorized({
tenantId,
uncategorizedTransactions,
trx
trx,
}: ICashflowTransactionCategorizedPayload) {
const { Account } = this.tenancy.models(tenantId);
await PromisePool.withConcurrency(1)
.for(uncategorizedTransactions)
.process(async (uncategorizedTransaction) => {
// Cannot continue if the transaction is still pending.
if (uncategorizedTransaction.isPending) {
return;
}
await Account.query(trx)
.findById(uncategorizedTransaction.accountId)
.decrement('uncategorizedTransactions', 1);
@@ -56,13 +60,17 @@ export class DecrementUncategorizedTransactionOnCategorize {
public async incrementUnCategorizedTransactionsOnUncategorized({
tenantId,
uncategorizedTransactions,
trx
trx,
}: ICashflowTransactionUncategorizedPayload) {
const { Account } = this.tenancy.models(tenantId);
await PromisePool.withConcurrency(1)
.for(uncategorizedTransactions)
.process(async (uncategorizedTransaction) => {
// Cannot continue if the transaction is still pending.
if (uncategorizedTransaction.isPending) {
return;
}
await Account.query(trx)
.findById(uncategorizedTransaction.accountId)
.increment('uncategorizedTransactions', 1);
@@ -82,6 +90,9 @@ export class DecrementUncategorizedTransactionOnCategorize {
if (!uncategorizedTransaction.accountId) return;
// Cannot continue if the transaction is still pending.
if (uncategorizedTransaction.isPending) return;
await Account.query(trx)
.findById(uncategorizedTransaction.accountId)
.increment('uncategorizedTransactions', 1);

View File

@@ -9,7 +9,7 @@ import {
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import { PaymentReceiveValidators } from '../Sales/PaymentReceives/PaymentReceiveValidators';
import { PaymentReceivedValidators } from '../Sales/PaymentReceived/PaymentReceivedValidators';
import BaseCreditNotes from './CreditNotes';
import {
IApplyCreditToInvoicesDTO,
@@ -26,7 +26,7 @@ export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
private tenancy: HasTenancyService;
@Inject()
private paymentReceiveValidators: PaymentReceiveValidators;
private paymentReceiveValidators: PaymentReceivedValidators;
@Inject()
private uow: UnitOfWork;

View File

@@ -16,6 +16,7 @@ export class ExpenseTransfromer extends Transformer {
'formattedAllocatedCostAmount',
'formattedDate',
'formattedCreatedAt',
'formattedPublishedAt',
'categories',
'attachments',
];
@@ -91,4 +92,13 @@ export class ExpenseTransfromer extends Transformer {
protected attachments = (expense: IExpense) => {
return this.item(expense.attachments, new AttachmentTransformer());
};
/**
* Retrieve formatted published at date.
* @param {IExpense} expense
* @returns {string}
*/
protected formattedPublishedAt = (expense: IExpense): string => {
return this.formatDate(expense.publishedAt);
}
}

View File

@@ -9,12 +9,13 @@ import { SaleInvoicesExportable } from '../Sales/Invoices/SaleInvoicesExportable
import { SaleEstimatesExportable } from '../Sales/Estimates/SaleEstimatesExportable';
import { SaleReceiptsExportable } from '../Sales/Receipts/SaleReceiptsExportable';
import { BillsExportable } from '../Purchases/Bills/BillsExportable';
import { PaymentsReceivedExportable } from '../Sales/PaymentReceives/PaymentsReceivedExportable';
import { PaymentsReceivedExportable } from '../Sales/PaymentReceived/PaymentsReceivedExportable';
import { BillPaymentExportable } from '../Purchases/BillPayments/BillPaymentExportable';
import { ManualJournalsExportable } from '../ManualJournals/ManualJournalExportable';
import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable';
import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable';
import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable';
import { TaxRatesExportable } from '../TaxRates/TaxRatesExportable';
@Service()
export class ExportableResources {
@@ -46,6 +47,7 @@ export class ExportableResources {
{ resource: 'ManualJournal', exportable: ManualJournalsExportable },
{ resource: 'CreditNote', exportable: CreditNotesExportable },
{ resource: 'VendorCredit', exportable: VendorCreditsExportable },
{ resource: 'TaxRate', exportable: TaxRatesExportable },
];
/**

View File

@@ -15,6 +15,7 @@ import { FinancialSheetStructure } from '../FinancialSheetStructure';
import { flatToNestedArray } from '@/utils';
import Ledger from '@/services/Accounting/Ledger';
import { calculateRunningBalance } from './_utils';
import { getTransactionTypeLabel } from '@/utils/transactions-types';
/**
* General ledger sheet.
@@ -90,11 +91,13 @@ export default class GeneralLedgerSheet extends R.compose(
date: entry.date,
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
transactionNumber: entry.transactionNumber,
referenceType: entry.referenceType,
referenceId: entry.referenceId,
referenceTypeFormatted: this.i18n.__(entry.referenceTypeFormatted),
referenceType: entry.transactionType,
referenceId: entry.transactionId,
transactionNumber: entry.transactionNumber,
transactionTypeFormatted: this.i18n.__(
getTransactionTypeLabel(entry.transactionType, entry.transactionSubType)
),
contactName: get(contact, 'displayName'),
contactType: get(contact, 'contactService'),

View File

@@ -67,7 +67,7 @@ export class GeneralLedgerTable extends R.compose(
return [
{ key: 'date', accessor: 'dateFormatted' },
{ key: 'account_name', accessor: 'account.name' },
{ key: 'reference_type', accessor: 'referenceTypeFormatted' },
{ key: 'reference_type', accessor: 'transactionTypeFormatted' },
{ key: 'reference_number', accessor: 'transactionNumber' },
{ key: 'description', accessor: 'note' },
{ key: 'credit', accessor: 'formattedCredit' },

View File

@@ -13,9 +13,10 @@ import { SaleInvoicesImportable } from '../Sales/Invoices/SaleInvoicesImportable
import { SaleEstimatesImportable } from '../Sales/Estimates/SaleEstimatesImportable';
import { BillPaymentsImportable } from '../Purchases/BillPayments/BillPaymentsImportable';
import { VendorCreditsImportable } from '../Purchases/VendorCredits/VendorCreditsImportable';
import { PaymentReceivesImportable } from '../Sales/PaymentReceives/PaymentReceivesImportable';
import { PaymentsReceivedImportable } from '../Sales/PaymentReceived/PaymentsReceivedImportable';
import { CreditNotesImportable } from '../CreditNotes/CreditNotesImportable';
import { SaleReceiptsImportable } from '../Sales/Receipts/SaleReceiptsImportable';
import { TaxRatesImportable } from '../TaxRates/TaxRatesImportable';
@Service()
export class ImportableResources {
@@ -44,10 +45,11 @@ export class ImportableResources {
{ resource: 'SaleInvoice', importable: SaleInvoicesImportable },
{ resource: 'SaleEstimate', importable: SaleEstimatesImportable },
{ resource: 'BillPayment', importable: BillPaymentsImportable },
{ resource: 'PaymentReceive', importable: PaymentReceivesImportable },
{ resource: 'PaymentReceive', importable: PaymentsReceivedImportable },
{ resource: 'VendorCredit', importable: VendorCreditsImportable },
{ resource: 'CreditNote', importable: CreditNotesImportable },
{ resource: 'SaleReceipt', importable: SaleReceiptsImportable }
{ resource: 'SaleReceipt', importable: SaleReceiptsImportable },
{ resource: 'TaxRate', importable: TaxRatesImportable },
];
public get registry() {

View File

@@ -43,12 +43,22 @@ export class CreateItem {
itemDTO.sellAccountId
);
}
// Validate the income account id existance if the item is sellable.
this.validators.validateIncomeAccountExistance(
itemDTO.sellable,
itemDTO.sellAccountId
);
if (itemDTO.costAccountId) {
await this.validators.validateItemCostAccountExistance(
tenantId,
itemDTO.costAccountId
);
}
// Validate the cost account id existance if the item is purchasable.
this.validators.validateCostAccountExistance(
itemDTO.purchasable,
itemDTO.costAccountId
);
if (itemDTO.inventoryAccountId) {
await this.validators.validateItemInventoryAccountExistance(
tenantId,

View File

@@ -55,6 +55,11 @@ export class EditItem {
itemDTO.categoryId
);
}
// Validate the income account id existance if the item is sellable.
this.validators.validateIncomeAccountExistance(
itemDTO.sellable,
itemDTO.sellAccountId
);
// Validate the sell account existance on the storage.
if (itemDTO.sellAccountId) {
await this.validators.validateItemSellAccountExistance(
@@ -62,6 +67,11 @@ export class EditItem {
itemDTO.sellAccountId
);
}
// Validate the cost account id existance if the item is purchasable.
this.validators.validateCostAccountExistance(
itemDTO.purchasable,
itemDTO.costAccountId
);
// Validate the cost account existance on the storage.
if (itemDTO.costAccountId) {
await this.validators.validateItemCostAccountExistance(

View File

@@ -85,6 +85,42 @@ export class ItemsValidators {
}
}
/**
* Validates income account existance.
* @param {number|null} sellable - Detarmines if the item sellable.
* @param {number|null} incomeAccountId - Income account id.
* @throws {ServiceError(ERRORS.INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM)}
*/
public validateIncomeAccountExistance(
sellable?: boolean,
incomeAccountId?: number
) {
if (sellable && !incomeAccountId) {
throw new ServiceError(
ERRORS.INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM,
'Income account is require with sellable item.'
);
}
}
/**
* Validates the cost account existance.
* @param {boolean|null} purchasable - Detarmines if the item purchasble.
* @param {number|null} costAccountId - Cost account id.
* @throws {ServiceError(ERRORS.COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM)}
*/
public validateCostAccountExistance(
purchasable: boolean,
costAccountId?: number
) {
if (purchasable && !costAccountId) {
throw new ServiceError(
ERRORS.COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM,
'The cost account is required with purchasable item.'
);
}
}
/**
* Validate item inventory account existance and type.
* @param {number} tenantId

View File

@@ -26,6 +26,11 @@ export const ERRORS = {
PURCHASE_TAX_RATE_NOT_FOUND: 'PURCHASE_TAX_RATE_NOT_FOUND',
SELL_TAX_RATE_NOT_FOUND: 'SELL_TAX_RATE_NOT_FOUND',
INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM:
'INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM',
COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM:
'COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM',
};
export const DEFAULT_VIEW_COLUMNS = [];

View File

@@ -6,7 +6,7 @@ import SaleNotifyBySms from '../SaleNotifyBySms';
import SmsNotificationsSettingsService from '@/services/Settings/SmsNotificationsSettings';
import {
ICustomer,
IPaymentReceiveSmsDetails,
IPaymentReceivedSmsDetails,
ISaleEstimate,
SMS_NOTIFICATION_KEY,
} from '@/interfaces';
@@ -173,12 +173,12 @@ export class SaleEstimateNotifyBySms {
* Retrieve the SMS details of the given payment receive transaction.
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {Promise<IPaymentReceiveSmsDetails>}
* @returns {Promise<IPaymentReceivedSmsDetails>}
*/
public smsDetails = async (
tenantId: number,
saleEstimateId: number
): Promise<IPaymentReceiveSmsDetails> => {
): Promise<IPaymentReceivedSmsDetails> => {
const { SaleEstimate } = this.tenancy.models(tenantId);
// Retrieve the sale invoice or throw not found service error.

View File

@@ -3,7 +3,7 @@ import { CreateSaleEstimate } from './CreateSaleEstimate';
import {
IFilterMeta,
IPaginationMeta,
IPaymentReceiveSmsDetails,
IPaymentReceivedSmsDetails,
ISaleEstimate,
ISaleEstimateDTO,
ISalesEstimatesFilter,
@@ -191,12 +191,12 @@ export class SaleEstimatesApplication {
* Retrieve the SMS details of the given payment receive transaction.
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {Promise<IPaymentReceiveSmsDetails>}
* @returns {Promise<IPaymentReceivedSmsDetails>}
*/
public getSaleEstimateSmsDetails = (
tenantId: number,
saleEstimateId: number
): Promise<IPaymentReceiveSmsDetails> => {
): Promise<IPaymentReceivedSmsDetails> => {
return this.saleEstimateNotifyBySmsService.smsDetails(
tenantId,
saleEstimateId

View File

@@ -2,7 +2,7 @@ import { Knex } from 'knex';
import async from 'async';
import { Inject, Service } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { PaymentReceiveGLEntries } from '../PaymentReceives/PaymentReceiveGLEntries';
import { PaymentReceivedGLEntries } from '../PaymentReceived/PaymentReceivedGLEntries';
@Service()
export class InvoicePaymentsGLEntriesRewrite {
@@ -10,7 +10,7 @@ export class InvoicePaymentsGLEntriesRewrite {
public tenancy: HasTenancyService;
@Inject()
public paymentGLEntries: PaymentReceiveGLEntries;
public paymentGLEntries: PaymentReceivedGLEntries;
/**
* Rewrites the payment GL entries task.

View File

@@ -2,23 +2,23 @@ import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import {
ICustomer,
IPaymentReceiveCreateDTO,
IPaymentReceiveCreatedPayload,
IPaymentReceiveCreatingPayload,
IPaymentReceivedCreateDTO,
IPaymentReceivedCreatedPayload,
IPaymentReceivedCreatingPayload,
ISystemUser,
} from '@/interfaces';
import { PaymentReceiveValidators } from './PaymentReceiveValidators';
import { PaymentReceivedValidators } from './PaymentReceivedValidators';
import events from '@/subscribers/events';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import { PaymentReceiveDTOTransformer } from './PaymentReceiveDTOTransformer';
import { PaymentReceiveDTOTransformer } from './PaymentReceivedDTOTransformer';
import { TenantMetadata } from '@/system/models';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
@Service()
export class CreatePaymentReceive {
export class CreatePaymentReceived {
@Inject()
private validators: PaymentReceiveValidators;
private validators: PaymentReceivedValidators;
@Inject()
private eventPublisher: EventPublisher;
@@ -37,11 +37,11 @@ export class CreatePaymentReceive {
* with associated invoices payment and journal transactions.
* @async
* @param {number} tenantId - Tenant id.
* @param {IPaymentReceive} paymentReceive
* @param {IPaymentReceived} paymentReceive
*/
public async createPaymentReceive(
public async createPaymentReceived(
tenantId: number,
paymentReceiveDTO: IPaymentReceiveCreateDTO,
paymentReceiveDTO: IPaymentReceivedCreateDTO,
authorizedUser: ISystemUser,
trx?: Knex.Transaction
) {
@@ -97,7 +97,7 @@ export class CreatePaymentReceive {
trx,
paymentReceiveDTO,
tenantId,
} as IPaymentReceiveCreatingPayload);
} as IPaymentReceivedCreatingPayload);
// Inserts the payment receive transaction.
const paymentReceive = await PaymentReceive.query(
@@ -113,7 +113,7 @@ export class CreatePaymentReceive {
paymentReceiveDTO,
authorizedUser,
trx,
} as IPaymentReceiveCreatedPayload);
} as IPaymentReceivedCreatedPayload);
return paymentReceive;
},
@@ -125,13 +125,13 @@ export class CreatePaymentReceive {
* Transform the create payment receive DTO.
* @param {number} tenantId
* @param {ICustomer} customer
* @param {IPaymentReceiveCreateDTO} paymentReceiveDTO
* @param {IPaymentReceivedCreateDTO} paymentReceiveDTO
* @returns
*/
private transformCreateDTOToModel = async (
tenantId: number,
customer: ICustomer,
paymentReceiveDTO: IPaymentReceiveCreateDTO
paymentReceiveDTO: IPaymentReceivedCreateDTO
) => {
return this.transformer.transformPaymentReceiveDTOToModel(
tenantId,

View File

@@ -1,8 +1,8 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import {
IPaymentReceiveDeletedPayload,
IPaymentReceiveDeletingPayload,
IPaymentReceivedDeletedPayload,
IPaymentReceivedDeletingPayload,
ISystemUser,
} from '@/interfaces';
import UnitOfWork from '@/services/UnitOfWork';
@@ -11,7 +11,7 @@ import events from '@/subscribers/events';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
@Service()
export class DeletePaymentReceive {
export class DeletePaymentReceived {
@Inject()
private eventPublisher: EventPublisher;
@@ -33,7 +33,7 @@ export class DeletePaymentReceive {
* @async
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId - Payment receive id.
* @param {IPaymentReceive} paymentReceive - Payment receive object.
* @param {IPaymentReceived} paymentReceive - Payment receive object.
*/
public async deletePaymentReceive(
tenantId: number,
@@ -56,7 +56,7 @@ export class DeletePaymentReceive {
tenantId,
oldPaymentReceive,
trx,
} as IPaymentReceiveDeletingPayload);
} as IPaymentReceivedDeletingPayload);
// Deletes the payment receive associated entries.
await PaymentReceiveEntry.query(trx)
@@ -73,7 +73,7 @@ export class DeletePaymentReceive {
oldPaymentReceive,
authorizedUser,
trx,
} as IPaymentReceiveDeletedPayload);
} as IPaymentReceivedDeletedPayload);
});
}
}

View File

@@ -2,14 +2,14 @@ import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import {
ICustomer,
IPaymentReceive,
IPaymentReceiveEditDTO,
IPaymentReceiveEditedPayload,
IPaymentReceiveEditingPayload,
IPaymentReceived,
IPaymentReceivedEditDTO,
IPaymentReceivedEditedPayload,
IPaymentReceivedEditingPayload,
ISystemUser,
} from '@/interfaces';
import { PaymentReceiveDTOTransformer } from './PaymentReceiveDTOTransformer';
import { PaymentReceiveValidators } from './PaymentReceiveValidators';
import { PaymentReceiveDTOTransformer } from './PaymentReceivedDTOTransformer';
import { PaymentReceivedValidators } from './PaymentReceivedValidators';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
import UnitOfWork from '@/services/UnitOfWork';
@@ -17,12 +17,12 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
import { TenantMetadata } from '@/system/models';
@Service()
export class EditPaymentReceive {
export class EditPaymentReceived {
@Inject()
private transformer: PaymentReceiveDTOTransformer;
@Inject()
private validators: PaymentReceiveValidators;
private validators: PaymentReceivedValidators;
@Inject()
private eventPublisher: EventPublisher;
@@ -46,12 +46,12 @@ export class EditPaymentReceive {
* @async
* @param {number} tenantId -
* @param {Integer} paymentReceiveId -
* @param {IPaymentReceive} paymentReceive -
* @param {IPaymentReceived} paymentReceive -
*/
public async editPaymentReceive(
tenantId: number,
paymentReceiveId: number,
paymentReceiveDTO: IPaymentReceiveEditDTO,
paymentReceiveDTO: IPaymentReceivedEditDTO,
authorizedUser: ISystemUser
) {
const { PaymentReceive, Contact } = this.tenancy.models(tenantId);
@@ -131,7 +131,7 @@ export class EditPaymentReceive {
tenantId,
oldPaymentReceive,
paymentReceiveDTO,
} as IPaymentReceiveEditingPayload);
} as IPaymentReceivedEditingPayload);
// Update the payment receive transaction.
const paymentReceive = await PaymentReceive.query(
@@ -149,7 +149,7 @@ export class EditPaymentReceive {
paymentReceiveDTO,
authorizedUser,
trx,
} as IPaymentReceiveEditedPayload);
} as IPaymentReceivedEditedPayload);
return paymentReceive;
});
@@ -159,15 +159,15 @@ export class EditPaymentReceive {
* Transform the edit payment receive DTO.
* @param {number} tenantId
* @param {ICustomer} customer
* @param {IPaymentReceiveEditDTO} paymentReceiveDTO
* @param {IPaymentReceive} oldPaymentReceive
* @param {IPaymentReceivedEditDTO} paymentReceiveDTO
* @param {IPaymentReceived} oldPaymentReceive
* @returns
*/
private transformEditDTOToModel = async (
tenantId: number,
customer: ICustomer,
paymentReceiveDTO: IPaymentReceiveEditDTO,
oldPaymentReceive: IPaymentReceive
paymentReceiveDTO: IPaymentReceivedEditDTO,
oldPaymentReceive: IPaymentReceived
) => {
return this.transformer.transformPaymentReceiveDTOToModel(
tenantId,

View File

@@ -1,13 +1,13 @@
import { ServiceError } from '@/exceptions';
import { IPaymentReceive } from '@/interfaces';
import { IPaymentReceived } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import { ERRORS } from './constants';
import { PaymentReceiveTransfromer } from './PaymentReceiveTransformer';
import { PaymentReceiveTransfromer } from './PaymentReceivedTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export class GetPaymentReceive {
export class GetPaymentReceived {
@Inject()
private tenancy: HasTenancyService;
@@ -18,12 +18,12 @@ export class GetPaymentReceive {
* Retrieve payment receive details.
* @param {number} tenantId - Tenant id.
* @param {number} paymentReceiveId - Payment receive id.
* @return {Promise<IPaymentReceive>}
* @return {Promise<IPaymentReceived>}
*/
public async getPaymentReceive(
tenantId: number,
paymentReceiveId: number
): Promise<IPaymentReceive> {
): Promise<IPaymentReceived> {
const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentReceive = await PaymentReceive.query()

View File

@@ -1,14 +1,14 @@
import { Inject, Service } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { PaymentReceiveValidators } from './PaymentReceiveValidators';
import { PaymentReceivedValidators } from './PaymentReceivedValidators';
@Service()
export class GetPaymentReceiveInvoices {
export class GetPaymentReceivedInvoices {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private validators: PaymentReceiveValidators;
private validators: PaymentReceivedValidators;
/**
* Retrieve sale invoices that assocaited to the given payment receive.

View File

@@ -1,10 +1,10 @@
import { Inject, Service } from 'typedi';
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
import { GetPaymentReceive } from './GetPaymentReceive';
import { GetPaymentReceived } from './GetPaymentReceived';
@Service()
export default class GetPaymentReceivePdf {
export default class GetPaymentReceivedPdf {
@Inject()
private chromiumlyTenancy: ChromiumlyTenancy;
@@ -12,12 +12,12 @@ export default class GetPaymentReceivePdf {
private templateInjectable: TemplateInjectable;
@Inject()
private getPaymentService: GetPaymentReceive;
private getPaymentService: GetPaymentReceived;
/**
* Retrieve sale invoice pdf content.
* @param {number} tenantId -
* @param {IPaymentReceive} paymentReceive -
* @param {IPaymentReceived} paymentReceive -
* @returns {Promise<Buffer>}
*/
async getPaymentReceivePdf(

View File

@@ -3,10 +3,10 @@ import * as R from 'ramda';
import {
IFilterMeta,
IPaginationMeta,
IPaymentReceive,
IPaymentReceivesFilter,
IPaymentReceived,
IPaymentsReceivedFilter,
} from '@/interfaces';
import { PaymentReceiveTransfromer } from './PaymentReceiveTransformer';
import { PaymentReceiveTransfromer } from './PaymentReceivedTransformer';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
@@ -25,13 +25,13 @@ export class GetPaymentReceives {
/**
* Retrieve payment receives paginated and filterable list.
* @param {number} tenantId
* @param {IPaymentReceivesFilter} paymentReceivesFilter
* @param {IPaymentsReceivedFilter} paymentReceivesFilter
*/
public async getPaymentReceives(
tenantId: number,
filterDTO: IPaymentReceivesFilter
filterDTO: IPaymentsReceivedFilter
): Promise<{
paymentReceives: IPaymentReceive[];
paymentReceives: IPaymentReceived[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {

View File

@@ -1,44 +1,44 @@
import {
IFilterMeta,
IPaginationMeta,
IPaymentReceive,
IPaymentReceiveCreateDTO,
IPaymentReceiveEditDTO,
IPaymentReceiveSmsDetails,
IPaymentReceivesFilter,
IPaymentReceived,
IPaymentReceivedCreateDTO,
IPaymentReceivedEditDTO,
IPaymentReceivedSmsDetails,
IPaymentsReceivedFilter,
ISystemUser,
PaymentReceiveMailOptsDTO,
} from '@/interfaces';
import { Inject, Service } from 'typedi';
import { CreatePaymentReceive } from './CreatePaymentReceive';
import { EditPaymentReceive } from './EditPaymentReceive';
import { DeletePaymentReceive } from './DeletePaymentReceive';
import { GetPaymentReceives } from './GetPaymentReceives';
import { GetPaymentReceive } from './GetPaymentReceive';
import { GetPaymentReceiveInvoices } from './GetPaymentReceiveInvoices';
import { PaymentReceiveNotifyBySms } from './PaymentReceiveSmsNotify';
import GetPaymentReceivePdf from './GetPaymentReeceivePdf';
import { SendPaymentReceiveMailNotification } from './PaymentReceiveMailNotification';
import { CreatePaymentReceived } from './CreatePaymentReceived';
import { EditPaymentReceived } from './EditPaymentReceived';
import { DeletePaymentReceived } from './DeletePaymentReceived';
import { GetPaymentReceives } from './GetPaymentsReceived';
import { GetPaymentReceived } from './GetPaymentReceived';
import { GetPaymentReceivedInvoices } from './GetPaymentReceivedInvoices';
import { PaymentReceiveNotifyBySms } from './PaymentReceivedSmsNotify';
import GetPaymentReceivedPdf from './GetPaymentReceivedPdf';
import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification';
@Service()
export class PaymentReceivesApplication {
@Inject()
private createPaymentReceiveService: CreatePaymentReceive;
private createPaymentReceivedService: CreatePaymentReceived;
@Inject()
private editPaymentReceiveService: EditPaymentReceive;
private editPaymentReceivedService: EditPaymentReceived;
@Inject()
private deletePaymentReceiveService: DeletePaymentReceive;
private deletePaymentReceivedService: DeletePaymentReceived;
@Inject()
private getPaymentReceivesService: GetPaymentReceives;
private getPaymentsReceivedService: GetPaymentReceives;
@Inject()
private getPaymentReceiveService: GetPaymentReceive;
private getPaymentReceivedService: GetPaymentReceived;
@Inject()
private getPaymentReceiveInvoicesService: GetPaymentReceiveInvoices;
private getPaymentReceiveInvoicesService: GetPaymentReceivedInvoices;
@Inject()
private paymentSmsNotify: PaymentReceiveNotifyBySms;
@@ -47,21 +47,21 @@ export class PaymentReceivesApplication {
private paymentMailNotify: SendPaymentReceiveMailNotification;
@Inject()
private getPaymentReceivePdfService: GetPaymentReceivePdf;
private getPaymentReceivePdfService: GetPaymentReceivedPdf;
/**
* Creates a new payment receive.
* @param {number} tenantId
* @param {IPaymentReceiveCreateDTO} paymentReceiveDTO
* @param {IPaymentReceivedCreateDTO} paymentReceiveDTO
* @param {ISystemUser} authorizedUser
* @returns
*/
public createPaymentReceive(
public createPaymentReceived(
tenantId: number,
paymentReceiveDTO: IPaymentReceiveCreateDTO,
paymentReceiveDTO: IPaymentReceivedCreateDTO,
authorizedUser: ISystemUser
) {
return this.createPaymentReceiveService.createPaymentReceive(
return this.createPaymentReceivedService.createPaymentReceived(
tenantId,
paymentReceiveDTO,
authorizedUser
@@ -72,17 +72,17 @@ export class PaymentReceivesApplication {
* Edit details the given payment receive with associated entries.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {IPaymentReceiveEditDTO} paymentReceiveDTO
* @param {IPaymentReceivedEditDTO} paymentReceiveDTO
* @param {ISystemUser} authorizedUser
* @returns
*/
public editPaymentReceive(
tenantId: number,
paymentReceiveId: number,
paymentReceiveDTO: IPaymentReceiveEditDTO,
paymentReceiveDTO: IPaymentReceivedEditDTO,
authorizedUser: ISystemUser
) {
return this.editPaymentReceiveService.editPaymentReceive(
return this.editPaymentReceivedService.editPaymentReceive(
tenantId,
paymentReceiveId,
paymentReceiveDTO,
@@ -102,7 +102,7 @@ export class PaymentReceivesApplication {
paymentReceiveId: number,
authorizedUser: ISystemUser
) {
return this.deletePaymentReceiveService.deletePaymentReceive(
return this.deletePaymentReceivedService.deletePaymentReceive(
tenantId,
paymentReceiveId,
authorizedUser
@@ -112,18 +112,18 @@ export class PaymentReceivesApplication {
/**
* Retrieve payment receives paginated and filterable.
* @param {number} tenantId
* @param {IPaymentReceivesFilter} filterDTO
* @param {IPaymentsReceivedFilter} filterDTO
* @returns
*/
public async getPaymentReceives(
tenantId: number,
filterDTO: IPaymentReceivesFilter
filterDTO: IPaymentsReceivedFilter
): Promise<{
paymentReceives: IPaymentReceive[];
paymentReceives: IPaymentReceived[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
return this.getPaymentReceivesService.getPaymentReceives(
return this.getPaymentsReceivedService.getPaymentReceives(
tenantId,
filterDTO
);
@@ -133,13 +133,13 @@ export class PaymentReceivesApplication {
* Retrieves the given payment receive.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @returns {Promise<IPaymentReceive>}
* @returns {Promise<IPaymentReceived>}
*/
public async getPaymentReceive(
tenantId: number,
paymentReceiveId: number
): Promise<IPaymentReceive> {
return this.getPaymentReceiveService.getPaymentReceive(
): Promise<IPaymentReceived> {
return this.getPaymentReceivedService.getPaymentReceive(
tenantId,
paymentReceiveId
);
@@ -175,7 +175,7 @@ export class PaymentReceivesApplication {
public getPaymentSmsDetails = async (
tenantId: number,
paymentReceiveId: number
): Promise<IPaymentReceiveSmsDetails> => {
): Promise<IPaymentReceivedSmsDetails> => {
return this.paymentSmsNotify.smsDetails(tenantId, paymentReceiveId);
};

View File

@@ -3,22 +3,22 @@ import { Inject, Service } from 'typedi';
import { omit, sumBy } from 'lodash';
import {
ICustomer,
IPaymentReceive,
IPaymentReceiveCreateDTO,
IPaymentReceiveEditDTO,
IPaymentReceived,
IPaymentReceivedCreateDTO,
IPaymentReceivedEditDTO,
} from '@/interfaces';
import { PaymentReceiveValidators } from './PaymentReceiveValidators';
import { PaymentReceiveIncrement } from './PaymentReceiveIncrement';
import { PaymentReceivedValidators } from './PaymentReceivedValidators';
import { PaymentReceivedIncrement } from './PaymentReceivedIncrement';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
import { formatDateFields } from '@/utils';
@Service()
export class PaymentReceiveDTOTransformer {
@Inject()
private validators: PaymentReceiveValidators;
private validators: PaymentReceivedValidators;
@Inject()
private increments: PaymentReceiveIncrement;
private increments: PaymentReceivedIncrement;
@Inject()
private branchDTOTransform: BranchTransactionDTOTransform;
@@ -26,16 +26,16 @@ export class PaymentReceiveDTOTransformer {
/**
* Transformes the create payment receive DTO to model object.
* @param {number} tenantId
* @param {IPaymentReceiveCreateDTO|IPaymentReceiveEditDTO} paymentReceiveDTO - Payment receive DTO.
* @param {IPaymentReceive} oldPaymentReceive -
* @return {IPaymentReceive}
* @param {IPaymentReceivedCreateDTO|IPaymentReceivedEditDTO} paymentReceiveDTO - Payment receive DTO.
* @param {IPaymentReceived} oldPaymentReceive -
* @return {IPaymentReceived}
*/
public async transformPaymentReceiveDTOToModel(
tenantId: number,
customer: ICustomer,
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
oldPaymentReceive?: IPaymentReceive
): Promise<IPaymentReceive> {
paymentReceiveDTO: IPaymentReceivedCreateDTO | IPaymentReceivedEditDTO,
oldPaymentReceive?: IPaymentReceived
): Promise<IPaymentReceived> {
const amount =
paymentReceiveDTO.amount ??
sumBy(paymentReceiveDTO.entries, 'paymentAmount');
@@ -65,7 +65,7 @@ export class PaymentReceiveDTOTransformer {
})),
};
return R.compose(
this.branchDTOTransform.transformDTO<IPaymentReceive>(tenantId)
this.branchDTOTransform.transformDTO<IPaymentReceived>(tenantId)
)(initialDTO);
}
}

View File

@@ -2,7 +2,7 @@ import { Transformer } from '@/lib/Transformer/Transformer';
import { SaleInvoiceTransformer } from '../Invoices/SaleInvoiceTransformer';
import { formatNumber } from '@/utils';
export class PaymentReceiveEntryTransfromer extends Transformer {
export class PaymentReceivedEntryTransfromer extends Transformer {
/**
* Include these attributes to payment receive entry object.
* @returns {Array}

View File

@@ -4,7 +4,7 @@ import { Knex } from 'knex';
import Ledger from '@/services/Accounting/Ledger';
import TenancyService from '@/services/Tenancy/TenancyService';
import {
IPaymentReceive,
IPaymentReceived,
ILedgerEntry,
AccountNormal,
IPaymentReceiveGLCommonEntry,
@@ -13,7 +13,7 @@ import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import { TenantMetadata } from '@/system/models';
@Service()
export class PaymentReceiveGLEntries {
export class PaymentReceivedGLEntries {
@Inject()
private tenancy: TenancyService;
@@ -93,14 +93,14 @@ export class PaymentReceiveGLEntries {
/**
* Retrieves the payment receive general ledger.
* @param {number} tenantId -
* @param {IPaymentReceive} paymentReceive -
* @param {IPaymentReceived} paymentReceive -
* @param {string} baseCurrencyCode -
* @param {Knex.Transaction} trx -
* @returns {Ledger}
*/
public getPaymentReceiveGLedger = async (
tenantId: number,
paymentReceive: IPaymentReceive,
paymentReceive: IPaymentReceived,
baseCurrencyCode: string,
trx?: Knex.Transaction
): Promise<Ledger> => {
@@ -132,7 +132,7 @@ export class PaymentReceiveGLEntries {
* @returns {number}
*/
private getPaymentExGainOrLoss = (
paymentReceive: IPaymentReceive
paymentReceive: IPaymentReceived
): number => {
return sumBy(paymentReceive.entries, (entry) => {
const paymentLocalAmount =
@@ -145,11 +145,11 @@ export class PaymentReceiveGLEntries {
/**
* Retrieves the common entry of payment receive.
* @param {IPaymentReceive} paymentReceive
* @param {IPaymentReceived} paymentReceive
* @returns {}
*/
private getPaymentReceiveCommonEntry = (
paymentReceive: IPaymentReceive
paymentReceive: IPaymentReceived
): IPaymentReceiveGLCommonEntry => {
return {
debit: 0,
@@ -174,14 +174,14 @@ export class PaymentReceiveGLEntries {
/**
* Retrieves the payment exchange gain/loss entry.
* @param {IPaymentReceive} paymentReceive -
* @param {IPaymentReceived} paymentReceive -
* @param {number} ARAccountId -
* @param {number} exchangeGainOrLossAccountId -
* @param {string} baseCurrencyCode -
* @returns {ILedgerEntry[]}
*/
private getPaymentExchangeGainLossEntry = (
paymentReceive: IPaymentReceive,
paymentReceive: IPaymentReceived,
ARAccountId: number,
exchangeGainOrLossAccountId: number,
baseCurrencyCode: string
@@ -219,11 +219,11 @@ export class PaymentReceiveGLEntries {
/**
* Retrieves the payment deposit GL entry.
* @param {IPaymentReceive} paymentReceive
* @param {IPaymentReceived} paymentReceive
* @returns {ILedgerEntry}
*/
private getPaymentDepositGLEntry = (
paymentReceive: IPaymentReceive
paymentReceive: IPaymentReceived
): ILedgerEntry => {
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
@@ -238,12 +238,12 @@ export class PaymentReceiveGLEntries {
/**
* Retrieves the payment receivable entry.
* @param {IPaymentReceive} paymentReceive
* @param {IPaymentReceived} paymentReceive
* @param {number} ARAccountId
* @returns {ILedgerEntry}
*/
private getPaymentReceivableEntry = (
paymentReceive: IPaymentReceive,
paymentReceive: IPaymentReceived,
ARAccountId: number
): ILedgerEntry => {
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
@@ -267,14 +267,14 @@ export class PaymentReceiveGLEntries {
* - Payment account [current asset] -> Credit
*
* @param {number} tenantId
* @param {IPaymentReceive} paymentRecieve - Payment receive model.
* @param {IPaymentReceived} paymentRecieve - Payment receive model.
* @param {number} ARAccountId - A/R account id.
* @param {number} exGainOrLossAccountId - Exchange gain/loss account id.
* @param {string} baseCurrency - Base currency code.
* @returns {Promise<ILedgerEntry>}
*/
public getPaymentReceiveGLEntries = (
paymentReceive: IPaymentReceive,
paymentReceive: IPaymentReceived,
ARAccountId: number,
exGainOrLossAccountId: number,
baseCurrency: string

View File

@@ -2,7 +2,7 @@ import { Inject, Service } from 'typedi';
import AutoIncrementOrdersService from '../AutoIncrementOrdersService';
@Service()
export class PaymentReceiveIncrement {
export class PaymentReceivedIncrement {
@Inject()
private autoIncrementOrdersService: AutoIncrementOrdersService;

View File

@@ -1,11 +1,11 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { IPaymentReceiveEntryDTO } from '@/interfaces';
import { IPaymentReceivedEntryDTO } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { entriesAmountDiff } from '@/utils';
@Service()
export class PaymentReceiveInvoiceSync {
export class PaymentReceivedInvoiceSync {
@Inject()
private tenancy: HasTenancyService;
@@ -19,8 +19,8 @@ export class PaymentReceiveInvoiceSync {
*/
public async saveChangeInvoicePaymentAmount(
tenantId: number,
newPaymentReceiveEntries: IPaymentReceiveEntryDTO[],
oldPaymentReceiveEntries?: IPaymentReceiveEntryDTO[],
newPaymentReceiveEntries: IPaymentReceivedEntryDTO[],
oldPaymentReceiveEntries?: IPaymentReceivedEntryDTO[],
trx?: Knex.Transaction
): Promise<void> {
const { SaleInvoice } = this.tenancy.models(tenantId);

View File

@@ -11,7 +11,7 @@ import {
DEFAULT_PAYMENT_MAIL_CONTENT,
DEFAULT_PAYMENT_MAIL_SUBJECT,
} from './constants';
import { GetPaymentReceive } from './GetPaymentReceive';
import { GetPaymentReceived } from './GetPaymentReceived';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
@@ -23,7 +23,7 @@ export class SendPaymentReceiveMailNotification {
private tenancy: HasTenancyService;
@Inject()
private getPaymentService: GetPaymentReceive;
private getPaymentService: GetPaymentReceived;
@Inject()
private contactMailNotification: ContactMailNotification;

View File

@@ -1,8 +1,8 @@
import Container, { Service } from 'typedi';
import { SendPaymentReceiveMailNotification } from './PaymentReceiveMailNotification';
import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification';
@Service()
export class PaymentReceiveMailNotificationJob {
export class PaymentReceivedMailNotificationJob {
/**
* Constructor method.
*/

View File

@@ -2,17 +2,17 @@ import { Service, Inject } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import events from '@/subscribers/events';
import {
IPaymentReceiveSmsDetails,
IPaymentReceivedSmsDetails,
SMS_NOTIFICATION_KEY,
IPaymentReceive,
IPaymentReceiveEntry,
IPaymentReceived,
IPaymentReceivedEntry,
} from '@/interfaces';
import SmsNotificationsSettingsService from '@/services/Settings/SmsNotificationsSettings';
import { formatNumber, formatSmsMessage } from 'utils';
import { TenantMetadata } from '@/system/models';
import SaleNotifyBySms from '../SaleNotifyBySms';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { PaymentReceiveValidators } from './PaymentReceiveValidators';
import { PaymentReceivedValidators } from './PaymentReceivedValidators';
@Service()
export class PaymentReceiveNotifyBySms {
@@ -29,7 +29,7 @@ export class PaymentReceiveNotifyBySms {
private saleSmsNotification: SaleNotifyBySms;
@Inject()
private validators: PaymentReceiveValidators;
private validators: PaymentReceivedValidators;
/**
* Notify customer via sms about payment receive details.
@@ -71,12 +71,12 @@ export class PaymentReceiveNotifyBySms {
/**
* Sends the payment details sms notification of the given customer.
* @param {number} tenantId
* @param {IPaymentReceive} paymentReceive
* @param {IPaymentReceived} paymentReceive
* @param {ICustomer} customer
*/
private sendSmsNotification = async (
tenantId: number,
paymentReceive: IPaymentReceive
paymentReceive: IPaymentReceived
) => {
const smsClient = this.tenancy.smsClient(tenantId);
const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
@@ -116,12 +116,12 @@ export class PaymentReceiveNotifyBySms {
/**
* Formates the payment receive details sms message.
* @param {number} tenantId -
* @param {IPaymentReceive} payment -
* @param {IPaymentReceived} payment -
* @param {ICustomer} customer -
*/
private formattedPaymentDetailsMessage = (
tenantId: number,
payment: IPaymentReceive,
payment: IPaymentReceived,
tenantMetadata: TenantMetadata
) => {
const notification = this.smsNotificationsSettings.getSmsNotificationMeta(
@@ -138,14 +138,14 @@ export class PaymentReceiveNotifyBySms {
/**
* Formattes the payment details sms notification messafge.
* @param {string} smsMessage
* @param {IPaymentReceive} payment
* @param {IPaymentReceived} payment
* @param {ICustomer} customer
* @param {TenantMetadata} tenantMetadata
* @returns {string}
*/
private formatPaymentDetailsMessage = (
smsMessage: string,
payment: IPaymentReceive,
payment: IPaymentReceived,
tenantMetadata: any
): string => {
const invoiceNumbers = this.stringifyPaymentInvoicesNumber(payment);
@@ -167,12 +167,12 @@ export class PaymentReceiveNotifyBySms {
/**
* Stringify payment receive invoices to numbers as string.
* @param {IPaymentReceive} payment
* @param {IPaymentReceived} payment
* @returns {string}
*/
private stringifyPaymentInvoicesNumber(payment: IPaymentReceive) {
private stringifyPaymentInvoicesNumber(payment: IPaymentReceived) {
const invoicesNumberes = payment.entries.map(
(entry: IPaymentReceiveEntry) => entry.invoice.invoiceNo
(entry: IPaymentReceivedEntry) => entry.invoice.invoiceNo
);
return invoicesNumberes.join(', ');
}
@@ -185,7 +185,7 @@ export class PaymentReceiveNotifyBySms {
public smsDetails = async (
tenantId: number,
paymentReceiveid: number
): Promise<IPaymentReceiveSmsDetails> => {
): Promise<IPaymentReceivedSmsDetails> => {
const { PaymentReceive } = this.tenancy.models(tenantId);
// Retrieve the payment receive or throw not found service error.

View File

@@ -1,8 +1,7 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from '@/subscribers/events';
import SaleReceiptNotifyBySms from '@/services/Sales/SaleReceiptNotifyBySms';
import PaymentReceiveNotifyBySms from './PaymentReceiveSmsNotify';
import { PaymentReceiveNotifyBySms } from './PaymentReceivedSmsNotify';
@EventSubscriber()
export default class SendSmsNotificationPaymentReceive {

View File

@@ -1,7 +1,7 @@
import { IPaymentReceive, IPaymentReceiveEntry } from '@/interfaces';
import { IPaymentReceived, IPaymentReceivedEntry } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { PaymentReceiveEntryTransfromer } from './PaymentReceiveEntryTransformer';
import { PaymentReceivedEntryTransfromer } from './PaymentReceivedEntryTransformer';
export class PaymentReceiveTransfromer extends Transformer {
/**
@@ -24,25 +24,25 @@ export class PaymentReceiveTransfromer extends Transformer {
* @param {ISaleInvoice} invoice
* @returns {String}
*/
protected formattedPaymentDate = (payment: IPaymentReceive): string => {
protected formattedPaymentDate = (payment: IPaymentReceived): string => {
return this.formatDate(payment.paymentDate);
};
/**
* Retrieves the formatted created at date.
* @param {IPaymentReceive} payment
* @param {IPaymentReceived} payment
* @returns {string}
*/
protected formattedCreatedAt = (payment: IPaymentReceive): string => {
protected formattedCreatedAt = (payment: IPaymentReceived): string => {
return this.formatDate(payment.createdAt);
};
/**
* Retrieve the formatted payment subtotal.
* @param {IPaymentReceive} payment
* @param {IPaymentReceived} payment
* @returns {string}
*/
protected subtotalFormatted = (payment: IPaymentReceive): string => {
protected subtotalFormatted = (payment: IPaymentReceived): string => {
return formatNumber(payment.amount, {
currencyCode: payment.currencyCode,
money: false,
@@ -54,25 +54,25 @@ export class PaymentReceiveTransfromer extends Transformer {
* @param {ISaleInvoice} invoice
* @returns {string}
*/
protected formattedAmount = (payment: IPaymentReceive): string => {
protected formattedAmount = (payment: IPaymentReceived): string => {
return formatNumber(payment.amount, { currencyCode: payment.currencyCode });
};
/**
* Retrieve the formatted exchange rate.
* @param {IPaymentReceive} payment
* @param {IPaymentReceived} payment
* @returns {string}
*/
protected formattedExchangeRate = (payment: IPaymentReceive): string => {
protected formattedExchangeRate = (payment: IPaymentReceived): string => {
return formatNumber(payment.exchangeRate, { money: false });
};
/**
* Retrieves the payment entries.
* @param {IPaymentReceive} payment
* @returns {IPaymentReceiveEntry[]}
* @param {IPaymentReceived} payment
* @returns {IPaymentReceivedEntry[]}
*/
protected entries = (payment: IPaymentReceive): IPaymentReceiveEntry[] => {
return this.item(payment.entries, new PaymentReceiveEntryTransfromer());
protected entries = (payment: IPaymentReceived): IPaymentReceivedEntry[] => {
return this.item(payment.entries, new PaymentReceivedEntryTransfromer());
};
}

View File

@@ -2,10 +2,10 @@ import { Inject, Service } from 'typedi';
import { difference, sumBy } from 'lodash';
import {
IAccount,
IPaymentReceive,
IPaymentReceiveEditDTO,
IPaymentReceiveEntry,
IPaymentReceiveEntryDTO,
IPaymentReceived,
IPaymentReceivedEditDTO,
IPaymentReceivedEntry,
IPaymentReceivedEntryDTO,
ISaleInvoice,
} from '@/interfaces';
import { ServiceError } from '@/exceptions';
@@ -15,7 +15,7 @@ import { ACCOUNT_TYPE } from '@/data/AccountTypes';
import { PaymentReceive } from '@/models';
@Service()
export class PaymentReceiveValidators {
export class PaymentReceivedValidators {
@Inject()
private tenancy: HasTenancyService;
@@ -58,7 +58,7 @@ export class PaymentReceiveValidators {
* Validates the invoices IDs existance.
* @param {number} tenantId -
* @param {number} customerId -
* @param {IPaymentReceiveEntryDTO[]} paymentReceiveEntries -
* @param {IPaymentReceivedEntryDTO[]} paymentReceiveEntries -
*/
public async validateInvoicesIDsExistance(
tenantId: number,
@@ -100,12 +100,12 @@ export class PaymentReceiveValidators {
*/
public async validateInvoicesPaymentsAmount(
tenantId: number,
paymentReceiveEntries: IPaymentReceiveEntryDTO[],
oldPaymentEntries: IPaymentReceiveEntry[] = []
paymentReceiveEntries: IPaymentReceivedEntryDTO[],
oldPaymentEntries: IPaymentReceivedEntry[] = []
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const invoicesIds = paymentReceiveEntries.map(
(e: IPaymentReceiveEntryDTO) => e.invoiceId
(e: IPaymentReceivedEntryDTO) => e.invoiceId
);
const storedInvoices = await SaleInvoice.query().whereIn('id', invoicesIds);
@@ -124,7 +124,7 @@ export class PaymentReceiveValidators {
const hasWrongPaymentAmount: any[] = [];
paymentReceiveEntries.forEach(
(entry: IPaymentReceiveEntryDTO, index: number) => {
(entry: IPaymentReceivedEntryDTO, index: number) => {
const entryInvoice = storedInvoicesMap.get(entry.invoiceId);
const { dueAmount } = entryInvoice;
@@ -140,9 +140,9 @@ export class PaymentReceiveValidators {
/**
* Validate the payment receive number require.
* @param {IPaymentReceive} paymentReceiveObj
* @param {IPaymentReceived} paymentReceiveObj
*/
public validatePaymentReceiveNoRequire(paymentReceiveObj: IPaymentReceive) {
public validatePaymentReceiveNoRequire(paymentReceiveObj: IPaymentReceived) {
if (!paymentReceiveObj.paymentReceiveNo) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NO_IS_REQUIRED);
}
@@ -152,12 +152,12 @@ export class PaymentReceiveValidators {
* Validate the payment receive entries IDs existance.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {IPaymentReceiveEntryDTO[]} paymentReceiveEntries
* @param {IPaymentReceivedEntryDTO[]} paymentReceiveEntries
*/
public async validateEntriesIdsExistance(
tenantId: number,
paymentReceiveId: number,
paymentReceiveEntries: IPaymentReceiveEntryDTO[]
paymentReceiveEntries: IPaymentReceivedEntryDTO[]
) {
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
@@ -189,12 +189,12 @@ export class PaymentReceiveValidators {
/**
* Validate the payment customer whether modified.
* @param {IPaymentReceiveEditDTO} paymentReceiveDTO
* @param {IPaymentReceive} oldPaymentReceive
* @param {IPaymentReceivedEditDTO} paymentReceiveDTO
* @param {IPaymentReceived} oldPaymentReceive
*/
public validateCustomerNotModified(
paymentReceiveDTO: IPaymentReceiveEditDTO,
oldPaymentReceive: IPaymentReceive
paymentReceiveDTO: IPaymentReceivedEditDTO,
oldPaymentReceive: IPaymentReceived
) {
if (paymentReceiveDTO.customerId !== oldPaymentReceive.customerId) {
throw new ServiceError(ERRORS.PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE);
@@ -230,7 +230,7 @@ export class PaymentReceiveValidators {
async getPaymentReceiveOrThrowError(
tenantId: number,
paymentReceiveId: number
): Promise<IPaymentReceive> {
): Promise<IPaymentReceived> {
const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentReceive = await PaymentReceive.query()
.withGraphFetched('entries')

View File

@@ -1,7 +1,7 @@
import { Inject, Service } from 'typedi';
import { IAccountsStructureType, IPaymentReceivesFilter } from '@/interfaces';
import { IAccountsStructureType, IPaymentsReceivedFilter } from '@/interfaces';
import { Exportable } from '@/services/Export/Exportable';
import { PaymentReceivesApplication } from './PaymentReceivesApplication';
import { PaymentReceivesApplication } from './PaymentReceivedApplication';
@Service()
export class PaymentsReceivedExportable extends Exportable {
@@ -11,17 +11,17 @@ export class PaymentsReceivedExportable extends Exportable {
/**
* Retrieves the accounts data to exportable sheet.
* @param {number} tenantId
* @param {IPaymentReceivesFilter} query -
* @param {IPaymentsReceivedFilter} query -
* @returns
*/
public exportable(tenantId: number, query: IPaymentReceivesFilter) {
public exportable(tenantId: number, query: IPaymentsReceivedFilter) {
const parsedQuery = {
sortOrder: 'desc',
columnSortBy: 'created_at',
inactiveMode: false,
...query,
structure: IAccountsStructureType.Flat,
} as IPaymentReceivesFilter;
} as IPaymentsReceivedFilter;
return this.paymentReceivedApp
.getPaymentReceives(tenantId, parsedQuery)

View File

@@ -1,14 +1,14 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { IPaymentReceiveCreateDTO } from '@/interfaces';
import { IPaymentReceivedCreateDTO } from '@/interfaces';
import { Importable } from '@/services/Import/Importable';
import { CreatePaymentReceive } from './CreatePaymentReceive';
import { CreatePaymentReceived } from './CreatePaymentReceived';
import { PaymentsReceiveSampleData } from './constants';
@Service()
export class PaymentReceivesImportable extends Importable {
export class PaymentsReceivedImportable extends Importable {
@Inject()
private createPaymentReceiveService: CreatePaymentReceive;
private createPaymentReceiveService: CreatePaymentReceived;
/**
* Importing to account service.
@@ -18,10 +18,10 @@ export class PaymentReceivesImportable extends Importable {
*/
public importable(
tenantId: number,
createPaymentDTO: IPaymentReceiveCreateDTO,
createPaymentDTO: IPaymentReceivedCreateDTO,
trx?: Knex.Transaction
) {
return this.createPaymentReceiveService.createPaymentReceive(
return this.createPaymentReceiveService.createPaymentReceived(
tenantId,
createPaymentDTO,
{},

View File

@@ -3,7 +3,7 @@ import { omit } from 'lodash';
import {
ISaleInvoice,
IPaymentReceivePageEntry,
IPaymentReceive,
IPaymentReceived,
ISystemUser,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
@@ -14,12 +14,9 @@ import { ERRORS } from './constants';
* Payment receives edit/new pages service.
*/
@Service()
export default class PaymentReceivesPages {
export default class PaymentsReceivedPages {
@Inject()
tenancy: TenancyService;
@Inject('logger')
logger: any;
private tenancy: TenancyService;
/**
* Retrive page invoices entries from the given sale invoices models.
@@ -68,7 +65,7 @@ export default class PaymentReceivesPages {
tenantId: number,
paymentReceiveId: number
): Promise<{
paymentReceive: Omit<IPaymentReceive, 'entries'>;
paymentReceive: Omit<IPaymentReceived, 'entries'>;
entries: IPaymentReceivePageEntry[];
}> {
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);

View File

@@ -1,9 +1,10 @@
import { ServiceError } from '@/exceptions';
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import { difference } from 'lodash';
import { ServiceError } from '@/exceptions';
import HasTenancyService from '../Tenancy/TenancyService';
import { IItemEntryDTO, ITaxRate } from '@/interfaces';
import { ERRORS } from './constants';
import { difference } from 'lodash';
@Service()
export class CommandTaxRatesValidators {
@@ -44,11 +45,16 @@ export class CommandTaxRatesValidators {
* Validates the tax code uniquiness.
* @param {number} tenantId
* @param {string} taxCode
* @param {Knex.Transaction} trx -
*/
public async validateTaxCodeUnique(tenantId: number, taxCode: string) {
public async validateTaxCodeUnique(
tenantId: number,
taxCode: string,
trx?: Knex.Transaction
) {
const { TaxRate } = this.tenancy.models(tenantId);
const foundTaxCode = await TaxRate.query().findOne({ code: taxCode });
const foundTaxCode = await TaxRate.query(trx).findOne({ code: taxCode });
if (foundTaxCode) {
throw new ServiceError(ERRORS.TAX_CODE_NOT_UNIQUE);

View File

@@ -1,3 +1,4 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import {
ICreateTaxRateDTO,
@@ -7,7 +8,6 @@ import {
import UnitOfWork from '../UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '../Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidators';
@@ -32,36 +32,41 @@ export class CreateTaxRate {
*/
public async createTaxRate(
tenantId: number,
createTaxRateDTO: ICreateTaxRateDTO
createTaxRateDTO: ICreateTaxRateDTO,
trx?: Knex.Transaction
) {
const { TaxRate } = this.tenancy.models(tenantId);
// Validates the tax code uniquiness.
await this.validators.validateTaxCodeUnique(
tenantId,
createTaxRateDTO.code
createTaxRateDTO.code,
trx
);
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onTaxRateCreating` event.
await this.eventPublisher.emitAsync(events.taxRates.onCreating, {
createTaxRateDTO,
tenantId,
trx,
} as ITaxRateCreatingPayload);
return this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Triggers `onTaxRateCreating` event.
await this.eventPublisher.emitAsync(events.taxRates.onCreating, {
createTaxRateDTO,
tenantId,
trx,
} as ITaxRateCreatingPayload);
const taxRate = await TaxRate.query(trx).insertAndFetch({
...createTaxRateDTO,
});
const taxRate = await TaxRate.query(trx).insertAndFetch({
...createTaxRateDTO,
});
// Triggers `onTaxRateCreated` event.
await this.eventPublisher.emitAsync(events.taxRates.onCreated, {
createTaxRateDTO,
taxRate,
tenantId,
trx,
} as ITaxRateCreatedPayload);
// Triggers `onTaxRateCreated` event.
await this.eventPublisher.emitAsync(events.taxRates.onCreated, {
createTaxRateDTO,
taxRate,
tenantId,
trx,
} as ITaxRateCreatedPayload);
return taxRate;
});
return taxRate;
},
trx
);
}
}

View File

@@ -0,0 +1,18 @@
import { Inject, Service } from 'typedi';
import { Exportable } from '../Export/Exportable';
import { TaxRatesApplication } from './TaxRatesApplication';
@Service()
export class TaxRatesExportable extends Exportable {
@Inject()
private taxRatesApplication: TaxRatesApplication;
/**
* Retrieves the accounts data to exportable sheet.
* @param {number} tenantId
* @returns
*/
public exportable(tenantId: number) {
return this.taxRatesApplication.getTaxRates(tenantId);
}
}

View File

@@ -0,0 +1,18 @@
export const TaxRatesSampleData = [
{
'Tax Name': 'Value Added Tax',
Code: 'VAT-STD',
Rate: '20',
Description: 'Standard VAT rate applied to most goods and services.',
'Is Non Recoverable': 'F',
Active: 'T',
},
{
'Tax Name': 'Luxury Goods Tax',
Code: 'TAX-LUXURY',
Rate: '25',
Description: 'Tax imposed on the sale of luxury items.',
'Is Non Recoverable': 'T',
Active: 'T',
},
];

View File

@@ -0,0 +1,46 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { ICreateTaxRateDTO } from '@/interfaces';
import { CreateTaxRate } from './CreateTaxRate';
import { Importable } from '../Import/Importable';
import { TaxRatesSampleData } from './TaxRatesImportable.SampleData';
@Service()
export class TaxRatesImportable extends Importable {
@Inject()
private createTaxRateService: CreateTaxRate;
/**
* Importing to tax rate creating service.
* @param {number} tenantId -
* @param {ICreateTaxRateDTO} ICreateTaxRateDTO -
* @param {Knex.Transaction} trx -
* @returns
*/
public importable(
tenantId: number,
createAccountDTO: ICreateTaxRateDTO,
trx?: Knex.Transaction
) {
return this.createTaxRateService.createTaxRate(
tenantId,
createAccountDTO,
trx
);
}
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
return TaxRatesSampleData;
}
}

View File

@@ -18,8 +18,4 @@ export default class HasSystemService implements SystemService {
cache() {
return this.container('cache');
}
dbManager() {
return this.container('dbManager');
}
}

View File

@@ -1,18 +1,16 @@
import { Container } from 'typedi';
import Knex from 'knex';
import { Knex, knex } from 'knex';
import { knexSnakeCaseMappers } from 'objection';
import { tenantKnexConfig, tenantSeedConfig } from '@/config/knexConfig';
import config from '@/config';
import { ITenant, ITenantDBManager } from '@/interfaces';
import SystemService from '@/services/Tenancy/SystemService';
import { TenantDBAlreadyExists } from '@/exceptions';
import { sanitizeDatabaseName } from '@/utils/sanitizers';
export default class TenantDBManager implements ITenantDBManager {
static knexCache: { [key: string]: Knex } = {};
// System database manager.
dbManager: any;
// System knex instance.
sysKnex: Knex;
@@ -23,7 +21,6 @@ export default class TenantDBManager implements ITenantDBManager {
constructor() {
const systemService = Container.get(SystemService);
this.dbManager = systemService.dbManager();
this.sysKnex = systemService.knex();
}
@@ -32,7 +29,9 @@ export default class TenantDBManager implements ITenantDBManager {
* @return {string}
*/
private getDatabaseName(tenant: ITenant) {
return `${config.tenant.db_name_prefix}${tenant.organizationId}`;
return sanitizeDatabaseName(
`${config.tenant.db_name_prefix}${tenant.organizationId}`
);
}
/**
@@ -59,7 +58,9 @@ export default class TenantDBManager implements ITenantDBManager {
await this.throwErrorIfTenantDBExists(tenant);
const databaseName = this.getDatabaseName(tenant);
await this.dbManager.createDb(databaseName);
await this.sysKnex.raw(
`CREATE DATABASE ${databaseName} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci`
);
}
/**
@@ -72,7 +73,6 @@ export default class TenantDBManager implements ITenantDBManager {
if (!isExists) {
return;
}
await this.dropDatabase(tenant);
}
@@ -83,7 +83,7 @@ export default class TenantDBManager implements ITenantDBManager {
public async dropDatabase(tenant: ITenant) {
const databaseName = this.getDatabaseName(tenant);
await this.dbManager.dropDb(databaseName);
await this.sysKnex.raw(`DROP DATABASE IF EXISTS ${databaseName}`);
}
/**
@@ -118,7 +118,7 @@ export default class TenantDBManager implements ITenantDBManager {
let knexInstance = TenantDBManager.knexCache[key];
if (!knexInstance) {
knexInstance = Knex({
knexInstance = knex({
...tenantKnexConfig(tenant),
...knexSnakeCaseMappers({ upperCase: true }),
});

View File

@@ -5,9 +5,9 @@ import {
ISaleInvoiceCreatingPaylaod,
ISaleReceiptDeletingPayload,
ICreditNoteDeletingPayload,
IPaymentReceiveCreatingPayload,
IPaymentReceivedCreatingPayload,
IRefundCreditNoteDeletingPayload,
IPaymentReceiveDeletingPayload,
IPaymentReceivedDeletingPayload,
ISaleEstimateDeletingPayload,
ISaleEstimateCreatingPayload,
ISaleEstimateEditingPayload,
@@ -17,7 +17,7 @@ import {
ISaleInvoiceWrittenOffCancelPayload,
ICreditNoteEditingPayload,
ISaleReceiptEditingPayload,
IPaymentReceiveEditingPayload,
IPaymentReceivedEditingPayload,
ISaleReceiptEventClosingPayload,
ICreditNoteCreatingPayload,
} from '@/interfaces';
@@ -454,13 +454,13 @@ export default class SalesTransactionLockingGuardSubscriber {
/**
* Transaction locking guard on payment receive editing.
* @param {IPaymentReceiveEditingPayload}
* @param {IPaymentReceivedEditingPayload}
*/
private transactionLockingGuardOnPaymentEditing = async ({
tenantId,
oldPaymentReceive,
paymentReceiveDTO,
}: IPaymentReceiveEditingPayload) => {
}: IPaymentReceivedEditingPayload) => {
// Validate the old payment date.
await this.salesLockingGuard.transactionLockingGuard(
tenantId,
@@ -475,12 +475,12 @@ export default class SalesTransactionLockingGuardSubscriber {
/**
* Transaction locking guard on payment creating.
* @param {IPaymentReceiveCreatingPayload}
* @param {IPaymentReceivedCreatingPayload}
*/
private transactionLockingGuardOnPaymentCreating = async ({
tenantId,
paymentReceiveDTO,
}: IPaymentReceiveCreatingPayload) => {
}: IPaymentReceivedCreatingPayload) => {
await this.salesLockingGuard.transactionLockingGuard(
tenantId,
paymentReceiveDTO.paymentDate
@@ -489,12 +489,12 @@ export default class SalesTransactionLockingGuardSubscriber {
/**
* Transaction locking guard on payment deleting.
* @param {IPaymentReceiveDeletingPayload} payload -
* @param {IPaymentReceivedDeletingPayload} payload -
*/
private transactionLockingGuardPaymentDeleting = async ({
oldPaymentReceive,
tenantId,
}: IPaymentReceiveDeletingPayload) => {
}: IPaymentReceivedDeletingPayload) => {
await this.salesLockingGuard.transactionLockingGuard(
tenantId,
oldPaymentReceive.paymentDate