feat: advanced payments

This commit is contained in:
Ahmed Bouhuolia
2024-07-22 20:40:15 +02:00
parent fe214b1b2d
commit 8cd3a6c48d
17 changed files with 548 additions and 162 deletions

View File

@@ -151,6 +151,8 @@ export default class PaymentReceivesController extends BaseController {
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('payment_date').exists(),
check('amount').exists().isNumeric().toFloat(),
check('reference_no').optional(),
check('deposit_account_id').exists().isNumeric().toInt(),
check('payment_receive_no').optional({ nullable: true }).trim().escape(),
@@ -158,15 +160,20 @@ export default class PaymentReceivesController extends BaseController {
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').isArray({ min: 1 }),
check('entries').isArray(),
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.invoice_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
check('entries.*.amount_applied').exists().isNumeric().toFloat(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
check('unearned_revenue_account_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
];
}

View File

@@ -0,0 +1,19 @@
exports.up = function (knex) {
return knex.schema.table('payment_receives', (table) => {
table.decimal('unapplied_amount', 13, 3).defaultTo(0);
table.decimal('used_amount', 13, 3).defaultTo(0);
table
.integer('unearned_revenue_account_id')
.unsigned()
.references('id')
.inTable('accounts');
});
};
exports.down = function (knex) {
return knex.schema.table('payment_receives', (table) => {
table.dropColumn('unapplied_amount');
table.dropColumn('used_amount');
table.dropColumn('unearned_revenue_account_id');
});
};

View File

@@ -323,4 +323,24 @@ export default [
index: 1,
predefined: 0,
},
{
name: 'Unearned Revenue',
slug: 'unearned-revenue',
account_type: 'other-income',
parent_account_id: null,
code: '50005',
active: true,
index: 1,
predefined: true,
},
{
name: 'Prepaid Expenses',
slug: 'prepaid-expenses',
account_type: 'prepaid-expenses',
parent_account_id: null,
code: '',
active: true,
index: 1,
predefined: true,
}
];

View File

@@ -25,6 +25,7 @@ export interface IPaymentReceive {
updatedAt: Date;
localAmount?: number;
branchId?: number;
unearnedRevenueAccountId?: number;
}
export interface IPaymentReceiveCreateDTO {
customerId: number;
@@ -39,6 +40,8 @@ export interface IPaymentReceiveCreateDTO {
branchId?: number;
attachments?: AttachmentLinkDTO[];
unearnedRevenueAccountId?: number;
}
export interface IPaymentReceiveEditDTO {
@@ -69,7 +72,7 @@ export interface IPaymentReceiveEntryDTO {
index: number;
paymentReceiveId: number;
invoiceId: number;
paymentAmount: number;
amountApplied: number;
}
export interface IPaymentReceivesFilter extends IDynamicListFilterDTO {

View File

@@ -113,6 +113,8 @@ import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/
import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch';
import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude';
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
import { AutoApplyUnearnedRevenueOnInvoiceCreated } from '@/services/Sales/PaymentReceives/events/AutoApplyUnearnedRevenueOnInvoiceCreated';
import { AutoApplyPrepardExpensesOnBillCreated } from '@/services/Purchases/Bills/events/AutoApplyPrepardExpensesOnBillCreated';
export default () => {
return new EventPublisher();
@@ -274,5 +276,9 @@ export const susbcribers = () => {
// Plaid
RecognizeSyncedBankTranasctions,
// Advanced Payments
AutoApplyUnearnedRevenueOnInvoiceCreated,
AutoApplyPrepardExpensesOnBillCreated
];
};

View File

@@ -0,0 +1,73 @@
import { Knex } from 'knex';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import PromisePool from '@supercharge/promise-pool';
import { Inject, Service } from 'typedi';
@Service()
export class AutoApplyPrepardExpenses {
@Inject()
private tenancy: HasTenancyService;
/**
* Auto apply prepard expenses to the given bill.
* @param {number} tenantId
* @param {number} billId
* @returns {Promise<void>}
*/
async autoApplyPrepardExpensesToBill(
tenantId: number,
billId: number,
trx?: Knex.Transaction
): Promise<void> {
const { PaymentMade, Bill } = this.tenancy.models(tenantId);
const unappliedPayments = await PaymentMade.query(trx).where(
'unappliedAmount',
'>',
0
);
const bill = Bill.query(trx).findById(billId).throwIfNotFound();
await PromisePool.withConcurrency(1)
.for(unappliedPayments)
.process(async (unappliedPayment: any) => {
const appliedAmount = 1;
await this.applyBillToPaymentMade(
tenantId,
unappliedPayment.id,
bill.id,
appliedAmount,
trx
);
});
// Increase the paid amount of the purchase invoice.
await Bill.changePaymentAmount(billId, 0, trx);
}
/**
* Apply the given bill to payment made transaction.
* @param {number} tenantId
* @param {number} billPaymentId
* @param {number} billId
* @param {number} appliedAmount
* @param {Knex.Transaction} trx
*/
public applyBillToPaymentMade = async (
tenantId: number,
billPaymentId: number,
billId: number,
appliedAmount: number,
trx?: Knex.Transaction
) => {
const { BillPaymentEntry, BillPayment } = this.tenancy.models(tenantId);
await BillPaymentEntry.query(trx).insert({
billPaymentId,
billId,
paymentAmount: appliedAmount,
});
await BillPayment.query().increment('usedAmount', appliedAmount);
};
}

View File

@@ -0,0 +1,36 @@
import { Inject, Service } from 'typedi';
import { AutoApplyPrepardExpenses } from '../AutoApplyPrepardExpenses';
import events from '@/subscribers/events';
import { IBillCreatedPayload } from '@/interfaces';
@Service()
export class AutoApplyPrepardExpensesOnBillCreated {
@Inject()
private autoApplyPrepardExpenses: AutoApplyPrepardExpenses;
/**
* Constructor method.
*/
public attach(bus) {
bus.subscribe(
events.saleInvoice.onCreated,
this.handleAutoApplyPrepardExpensesOnBillCreated.bind(this)
);
}
/**
* Handles the auto apply prepard expenses on bill created.
* @param {IBillCreatedPayload} payload -
*/
private async handleAutoApplyPrepardExpensesOnBillCreated({
tenantId,
billId,
trx,
}: IBillCreatedPayload) {
await this.autoApplyPrepardExpenses.autoApplyPrepardExpensesToBill(
tenantId,
billId,
trx
);
}
}

View File

@@ -0,0 +1,75 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import PromisePool from '@supercharge/promise-pool';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class AutoApplyUnearnedRevenue {
@Inject()
private tenancy: HasTenancyService;
/**
* Auto apply invoice to advanced payment received transactions.
* @param {number} tenantId
* @param {number} invoiceId
*/
public async autoApplyUnearnedRevenueToInvoice(
tenantId: number,
invoiceId: number,
trx?: Knex.Transaction
) {
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
const unappliedPayments = await PaymentReceive.query(trx).where(
'unappliedAmount',
'>',
0
);
const invoice = await SaleInvoice.query(trx)
.findById(invoiceId)
.throwIfNotFound();
let unappliedAmount = invoice.balance;
await PromisePool.withConcurrency(1)
.for(unappliedPayments)
.process(async (unappliedPayment: any) => {
const appliedAmount = unappliedAmount;
await this.applyInvoiceToPaymentReceived(
tenantId,
unappliedPayment.id,
invoice.id,
appliedAmount,
trx
);
});
// Increase the paid amount of the sale invoice.
await SaleInvoice.changePaymentAmount(invoiceId, unappliedAmount, trx);
}
/**
* Apply the given invoice to payment received transaction.
* @param {number} tenantId
* @param {number} paymentReceivedId
* @param {number} invoiceId
* @param {number} appliedAmount
* @param {Knex.Transaction} trx
*/
public applyInvoiceToPaymentReceived = async (
tenantId: number,
paymentReceiveId: number,
invoiceId: number,
appliedAmount: number,
trx?: Knex.Transaction
) => {
const { PaymentReceiveEntry, PaymentReceive } =
this.tenancy.models(tenantId);
await PaymentReceiveEntry.query(trx).insert({
paymentReceiveId,
invoiceId,
paymentAmount: appliedAmount,
});
await PaymentReceive.query(trx).increment('usedAmount', appliedAmount);
};
}

View File

@@ -78,10 +78,10 @@ export class CreatePaymentReceive {
paymentReceiveDTO.entries
);
// Validate invoice payment amount.
await this.validators.validateInvoicesPaymentsAmount(
tenantId,
paymentReceiveDTO.entries
);
// await this.validators.validateInvoicesPaymentsAmount(
// tenantId,
// paymentReceiveDTO.entries
// );
// Validates the payment account currency code.
this.validators.validatePaymentAccountCurrency(
depositAccount.currencyCode,

View File

@@ -36,7 +36,8 @@ export class PaymentReceiveDTOTransformer {
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
oldPaymentReceive?: IPaymentReceive
): Promise<IPaymentReceive> {
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
const appliedAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
const unappliedAmount = paymentReceiveDTO.amount - appliedAmount;
// Retreive the next invoice number.
const autoNextNumber =
@@ -54,7 +55,7 @@ export class PaymentReceiveDTOTransformer {
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
'paymentDate',
]),
amount: paymentAmount,
unappliedAmount,
currencyCode: customer.currencyCode,
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
exchangeRate: paymentReceiveDTO.exchangeRate || 1,

View File

@@ -1,19 +1,14 @@
import { Service, Inject } from 'typedi';
import { sumBy } from 'lodash';
import { Knex } from 'knex';
import Ledger from '@/services/Accounting/Ledger';
import TenancyService from '@/services/Tenancy/TenancyService';
import {
IPaymentReceive,
ILedgerEntry,
AccountNormal,
IPaymentReceiveGLCommonEntry,
} from '@/interfaces';
import { IPaymentReceive, ILedgerEntry, AccountNormal } from '@/interfaces';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import { TenantMetadata } from '@/system/models';
import { PaymentReceivedGLCommon } from './PaymentReceivedGLCommon';
@Service()
export class PaymentReceiveGLEntries {
export class PaymentReceiveGLEntries extends PaymentReceivedGLCommon {
@Inject()
private tenancy: TenancyService;
@@ -22,9 +17,9 @@ export class PaymentReceiveGLEntries {
/**
* Writes payment GL entries to the storage.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {Knex.Transaction} trx
* @param {number} tenantId - Tenant id.
* @param {number} paymentReceiveId - Payment received id.
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public writePaymentGLEntries = async (
@@ -34,14 +29,19 @@ export class PaymentReceiveGLEntries {
): Promise<void> => {
const { PaymentReceive } = this.tenancy.models(tenantId);
// Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
// Retrieves the payment receive with associated entries.
const paymentReceive = await PaymentReceive.query(trx)
.findById(paymentReceiveId)
.withGraphFetched('entries.invoice');
// Cannot continue if the received payment is unearned revenue type,
// that type of transactions have different type of GL entries.
if (paymentReceive.unearnedRevenueAccountId) {
return;
}
// Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
// Retrives the payment receive ledger.
const ledger = await this.getPaymentReceiveGLedger(
tenantId,
@@ -53,25 +53,6 @@ export class PaymentReceiveGLEntries {
await this.ledgerStorage.commit(tenantId, ledger, trx);
};
/**
* Reverts the given payment receive GL entries.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {Knex.Transaction} trx
*/
public revertPaymentGLEntries = async (
tenantId: number,
paymentReceiveId: number,
trx?: Knex.Transaction
) => {
await this.ledgerStorage.deleteByReference(
tenantId,
paymentReceiveId,
'PaymentReceive',
trx
);
};
/**
* Rewrites the given payment receive GL entries.
* @param {number} tenantId
@@ -92,10 +73,10 @@ export class PaymentReceiveGLEntries {
/**
* Retrieves the payment receive general ledger.
* @param {number} tenantId -
* @param {IPaymentReceive} paymentReceive -
* @param {string} baseCurrencyCode -
* @param {Knex.Transaction} trx -
* @param {number} tenantId -
* @param {IPaymentReceive} paymentReceive -
* @param {string} baseCurrencyCode -
* @param {Knex.Transaction} trx -
* @returns {Ledger}
*/
public getPaymentReceiveGLedger = async (
@@ -126,100 +107,9 @@ export class PaymentReceiveGLEntries {
return new Ledger(ledgerEntries);
};
/**
* Calculates the payment total exchange gain/loss.
* @param {IBillPayment} paymentReceive - Payment receive with entries.
* @returns {number}
*/
private getPaymentExGainOrLoss = (
paymentReceive: IPaymentReceive
): number => {
return sumBy(paymentReceive.entries, (entry) => {
const paymentLocalAmount =
entry.paymentAmount * paymentReceive.exchangeRate;
const invoicePayment = entry.paymentAmount * entry.invoice.exchangeRate;
return paymentLocalAmount - invoicePayment;
});
};
/**
* Retrieves the common entry of payment receive.
* @param {IPaymentReceive} paymentReceive
* @returns {}
*/
private getPaymentReceiveCommonEntry = (
paymentReceive: IPaymentReceive
): IPaymentReceiveGLCommonEntry => {
return {
debit: 0,
credit: 0,
currencyCode: paymentReceive.currencyCode,
exchangeRate: paymentReceive.exchangeRate,
transactionId: paymentReceive.id,
transactionType: 'PaymentReceive',
transactionNumber: paymentReceive.paymentReceiveNo,
referenceNumber: paymentReceive.referenceNo,
date: paymentReceive.paymentDate,
userId: paymentReceive.userId,
createdAt: paymentReceive.createdAt,
branchId: paymentReceive.branchId,
};
};
/**
* Retrieves the payment exchange gain/loss entry.
* @param {IPaymentReceive} paymentReceive -
* @param {number} ARAccountId -
* @param {number} exchangeGainOrLossAccountId -
* @param {string} baseCurrencyCode -
* @returns {ILedgerEntry[]}
*/
private getPaymentExchangeGainLossEntry = (
paymentReceive: IPaymentReceive,
ARAccountId: number,
exchangeGainOrLossAccountId: number,
baseCurrencyCode: string
): ILedgerEntry[] => {
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
const gainOrLoss = this.getPaymentExGainOrLoss(paymentReceive);
const absGainOrLoss = Math.abs(gainOrLoss);
return gainOrLoss
? [
{
...commonJournal,
currencyCode: baseCurrencyCode,
exchangeRate: 1,
debit: gainOrLoss > 0 ? absGainOrLoss : 0,
credit: gainOrLoss < 0 ? absGainOrLoss : 0,
accountId: ARAccountId,
contactId: paymentReceive.customerId,
index: 3,
accountNormal: AccountNormal.CREDIT,
},
{
...commonJournal,
currencyCode: baseCurrencyCode,
exchangeRate: 1,
credit: gainOrLoss > 0 ? absGainOrLoss : 0,
debit: gainOrLoss < 0 ? absGainOrLoss : 0,
accountId: exchangeGainOrLossAccountId,
index: 3,
accountNormal: AccountNormal.DEBIT,
},
]
: [];
};
/**
* Retrieves the payment deposit GL entry.
* @param {IPaymentReceive} paymentReceive
* @param {IPaymentReceive} paymentReceive
* @returns {ILedgerEntry}
*/
private getPaymentDepositGLEntry = (
@@ -238,8 +128,8 @@ export class PaymentReceiveGLEntries {
/**
* Retrieves the payment receivable entry.
* @param {IPaymentReceive} paymentReceive
* @param {number} ARAccountId
* @param {IPaymentReceive} paymentReceive
* @param {number} ARAccountId
* @returns {ILedgerEntry}
*/
private getPaymentReceivableEntry = (
@@ -262,15 +152,15 @@ export class PaymentReceiveGLEntries {
* Records payment receive journal transactions.
*
* Invoice payment journals.
* --------
* - Account receivable -> Debit
* - Payment account [current asset] -> Credit
* ------------
* - Account Receivable -> Debit
* - Payment Account [current asset] -> Credit
*
* @param {number} tenantId
* @param {IPaymentReceive} 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.
* @param {number} tenantId
* @param {IPaymentReceive} 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 = (

View File

@@ -0,0 +1,123 @@
import { sumBy } from 'lodash';
import {
AccountNormal,
ILedgerEntry,
IPaymentReceive,
IPaymentReceiveGLCommonEntry,
} from '@/interfaces';
import { Knex } from 'knex';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
export class PaymentReceivedGLCommon {
private ledgerStorage: LedgerStorageService;
/**
* Retrieves the common entry of payment receive.
* @param {IPaymentReceive} paymentReceive
* @returns {IPaymentReceiveGLCommonEntry}
*/
protected getPaymentReceiveCommonEntry = (
paymentReceive: IPaymentReceive
): IPaymentReceiveGLCommonEntry => {
return {
debit: 0,
credit: 0,
currencyCode: paymentReceive.currencyCode,
exchangeRate: paymentReceive.exchangeRate,
transactionId: paymentReceive.id,
transactionType: 'PaymentReceive',
transactionNumber: paymentReceive.paymentReceiveNo,
referenceNumber: paymentReceive.referenceNo,
date: paymentReceive.paymentDate,
userId: paymentReceive.userId,
createdAt: paymentReceive.createdAt,
branchId: paymentReceive.branchId,
};
};
/**
* Retrieves the payment exchange gain/loss entry.
* @param {IPaymentReceive} paymentReceive -
* @param {number} ARAccountId -
* @param {number} exchangeGainOrLossAccountId -
* @param {string} baseCurrencyCode -
* @returns {ILedgerEntry[]}
*/
protected getPaymentExchangeGainLossEntry = (
paymentReceive: IPaymentReceive,
ARAccountId: number,
exchangeGainOrLossAccountId: number,
baseCurrencyCode: string
): ILedgerEntry[] => {
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
const gainOrLoss = this.getPaymentExGainOrLoss(paymentReceive);
const absGainOrLoss = Math.abs(gainOrLoss);
return gainOrLoss
? [
{
...commonJournal,
currencyCode: baseCurrencyCode,
exchangeRate: 1,
debit: gainOrLoss > 0 ? absGainOrLoss : 0,
credit: gainOrLoss < 0 ? absGainOrLoss : 0,
accountId: ARAccountId,
contactId: paymentReceive.customerId,
index: 3,
accountNormal: AccountNormal.CREDIT,
},
{
...commonJournal,
currencyCode: baseCurrencyCode,
exchangeRate: 1,
credit: gainOrLoss > 0 ? absGainOrLoss : 0,
debit: gainOrLoss < 0 ? absGainOrLoss : 0,
accountId: exchangeGainOrLossAccountId,
index: 3,
accountNormal: AccountNormal.DEBIT,
},
]
: [];
};
/**
* Calculates the payment total exchange gain/loss.
* @param {IBillPayment} paymentReceive - Payment receive with entries.
* @returns {number}
*/
private getPaymentExGainOrLoss = (
paymentReceive: IPaymentReceive
): number => {
return sumBy(paymentReceive.entries, (entry) => {
const paymentLocalAmount =
entry.paymentAmount * paymentReceive.exchangeRate;
const invoicePayment = entry.paymentAmount * entry.invoice.exchangeRate;
return paymentLocalAmount - invoicePayment;
});
};
/**
* Reverts the given payment receive GL entries.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {Knex.Transaction} trx
*/
public revertPaymentGLEntries = async (
tenantId: number,
paymentReceiveId: number,
trx?: Knex.Transaction
) => {
await this.ledgerStorage.deleteByReference(
tenantId,
paymentReceiveId,
'PaymentReceive',
trx
);
};
}

View File

@@ -0,0 +1,110 @@
import * as R from 'ramda';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { TenantMetadata } from '@/system/models';
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import { PaymentReceivedGLCommon } from './PaymentReceivedGLCommon';
import {
AccountNormal,
ILedgerEntry,
IPaymentReceive,
IPaymentReceiveEntry,
} from '@/interfaces';
@Service()
export class PaymentReceivedUnearnedGLEntries extends PaymentReceivedGLCommon {
@Inject()
private tenancy: HasTenancyService;
/**
* Writes payment GL entries to the storage.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public writePaymentGLEntries = async (
tenantId: number,
paymentReceiveId: number,
trx?: Knex.Transaction
): Promise<void> => {
const { PaymentReceive } = this.tenancy.models(tenantId);
// Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
// Retrieves the payment receive with associated entries.
const paymentReceive = await PaymentReceive.query(trx)
.findById(paymentReceiveId)
.withGraphFetched('entries.invoice');
if (paymentReceive.unearnedRevenueAccountId) {
return;
}
};
private getPaymentGLEntries = (paymentReceive: IPaymentReceive) => {};
private getPaymentUnearnedGLEntries = R.curry(() => {});
/**
*
* @param {IPaymentReceiveEntry} paymentReceivedEntry -
* @param {IPaymentReceive} paymentReceive -
*/
private getPaymentEntryGLEntries = R.curry(
(
paymentReceivedEntry: IPaymentReceiveEntry,
paymentReceive: IPaymentReceive
) => {
const depositEntry = this.getPaymentDepositGLEntry(
paymentReceivedEntry.paymentAmount,
paymentReceive
);
}
);
/**
* Retrieves the payment deposit GL entry.
* @param {IPaymentReceive} paymentReceive
* @returns {ILedgerEntry}
*/
private getPaymentDepositGLEntry = (
amount: number,
paymentReceive: IPaymentReceive
): ILedgerEntry => {
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
return {
...commonJournal,
credit: amount,
accountId: paymentReceive.unearnedRevenueAccountId,
accountNormal: AccountNormal.CREDIT,
index: 2,
};
};
/**
* Retrieves the payment receivable entry.
* @param {IPaymentReceive} paymentReceive
* @param {number} ARAccountId
* @returns {ILedgerEntry}
*/
private getPaymentReceivableEntry = (
amount: number,
paymentReceive: IPaymentReceive,
ARAccountId: number
): ILedgerEntry => {
const commonJournal = this.getPaymentReceiveCommonEntry(paymentReceive);
return {
...commonJournal,
debit: amount,
contactId: paymentReceive.customerId,
accountId: ARAccountId,
accountNormal: AccountNormal.DEBIT,
index: 1,
};
};
}

View File

@@ -0,0 +1,34 @@
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import { AutoApplyUnearnedRevenue } from '../AutoApplyUnearnedRevenue';
@Service()
export class AutoApplyUnearnedRevenueOnInvoiceCreated {
@Inject()
private autoApplyUnearnedRevenue: AutoApplyUnearnedRevenue;
/**
* Constructor method.
*/
public attach(bus) {
bus.subscribe(
events.saleInvoice.onCreated,
this.handleAutoApplyUnearnedRevenueOnInvoiceCreated.bind(this)
);
}
/**
* Handles the auto apply unearned revenue on invoice creating.
* @param
*/
private async handleAutoApplyUnearnedRevenueOnInvoiceCreated({
tenantId,
saleInvoice,
trx,
}) {
await this.autoApplyUnearnedRevenue.autoApplyUnearnedRevenueToInvoice(
tenantId,
saleInvoice.id,
trx
);
}
}

View File

@@ -102,17 +102,6 @@ function PaymentReceiveForm({
) => {
setSubmitting(true);
// Calculates the total payment amount of entries.
const totalPaymentAmount = sumBy(values.entries, 'payment_amount');
if (totalPaymentAmount <= 0) {
AppToaster.show({
message: intl.get('you_cannot_make_payment_with_zero_total_amount'),
intent: Intent.DANGER,
});
setSubmitting(false);
return;
}
// Transformes the form values to request body.
const form = transformFormToRequest(values);

View File

@@ -124,7 +124,7 @@ export default function PaymentReceiveHeaderFields() {
</FastField>
{/* ------------ Full amount ------------ */}
<Field name={'full_amount'}>
<Field name={'amount'}>
{({
form: {
setFieldValue,
@@ -146,7 +146,7 @@ export default function PaymentReceiveHeaderFields() {
<MoneyInputGroup
value={value}
onChange={(value) => {
setFieldValue('full_amount', value);
setFieldValue('amount', value);
}}
onBlurValue={onFullAmountBlur}
/>

View File

@@ -42,7 +42,7 @@ export const defaultPaymentReceive = {
// Holds the payment number that entered manually only.
payment_receive_no_manually: '',
statement: '',
full_amount: '',
amount: '',
currency_code: '',
branch_id: '',
exchange_rate: 1,