mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
feat: advanced payments
This commit is contained in:
@@ -151,6 +151,8 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||||
|
|
||||||
check('payment_date').exists(),
|
check('payment_date').exists(),
|
||||||
|
check('amount').exists().isNumeric().toFloat(),
|
||||||
|
|
||||||
check('reference_no').optional(),
|
check('reference_no').optional(),
|
||||||
check('deposit_account_id').exists().isNumeric().toInt(),
|
check('deposit_account_id').exists().isNumeric().toInt(),
|
||||||
check('payment_receive_no').optional({ nullable: true }).trim().escape(),
|
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('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.*.id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
check('entries.*.index').optional().isNumeric().toInt(),
|
check('entries.*.index').optional().isNumeric().toInt(),
|
||||||
check('entries.*.invoice_id').exists().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').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
check('unearned_revenue_account_id')
|
||||||
|
.optional({ nullable: true })
|
||||||
|
.isNumeric()
|
||||||
|
.toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -323,4 +323,24 @@ export default [
|
|||||||
index: 1,
|
index: 1,
|
||||||
predefined: 0,
|
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,
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface IPaymentReceive {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
localAmount?: number;
|
localAmount?: number;
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
|
unearnedRevenueAccountId?: number;
|
||||||
}
|
}
|
||||||
export interface IPaymentReceiveCreateDTO {
|
export interface IPaymentReceiveCreateDTO {
|
||||||
customerId: number;
|
customerId: number;
|
||||||
@@ -39,6 +40,8 @@ export interface IPaymentReceiveCreateDTO {
|
|||||||
|
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
attachments?: AttachmentLinkDTO[];
|
attachments?: AttachmentLinkDTO[];
|
||||||
|
|
||||||
|
unearnedRevenueAccountId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceiveEditDTO {
|
export interface IPaymentReceiveEditDTO {
|
||||||
@@ -69,7 +72,7 @@ export interface IPaymentReceiveEntryDTO {
|
|||||||
index: number;
|
index: number;
|
||||||
paymentReceiveId: number;
|
paymentReceiveId: number;
|
||||||
invoiceId: number;
|
invoiceId: number;
|
||||||
paymentAmount: number;
|
amountApplied: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentReceivesFilter extends IDynamicListFilterDTO {
|
export interface IPaymentReceivesFilter extends IDynamicListFilterDTO {
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/
|
|||||||
import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch';
|
import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch';
|
||||||
import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude';
|
import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude';
|
||||||
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
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 () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -274,5 +276,9 @@ export const susbcribers = () => {
|
|||||||
|
|
||||||
// Plaid
|
// Plaid
|
||||||
RecognizeSyncedBankTranasctions,
|
RecognizeSyncedBankTranasctions,
|
||||||
|
|
||||||
|
// Advanced Payments
|
||||||
|
AutoApplyUnearnedRevenueOnInvoiceCreated,
|
||||||
|
AutoApplyPrepardExpensesOnBillCreated
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -78,10 +78,10 @@ export class CreatePaymentReceive {
|
|||||||
paymentReceiveDTO.entries
|
paymentReceiveDTO.entries
|
||||||
);
|
);
|
||||||
// Validate invoice payment amount.
|
// Validate invoice payment amount.
|
||||||
await this.validators.validateInvoicesPaymentsAmount(
|
// await this.validators.validateInvoicesPaymentsAmount(
|
||||||
tenantId,
|
// tenantId,
|
||||||
paymentReceiveDTO.entries
|
// paymentReceiveDTO.entries
|
||||||
);
|
// );
|
||||||
// Validates the payment account currency code.
|
// Validates the payment account currency code.
|
||||||
this.validators.validatePaymentAccountCurrency(
|
this.validators.validatePaymentAccountCurrency(
|
||||||
depositAccount.currencyCode,
|
depositAccount.currencyCode,
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ export class PaymentReceiveDTOTransformer {
|
|||||||
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
|
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
|
||||||
oldPaymentReceive?: IPaymentReceive
|
oldPaymentReceive?: IPaymentReceive
|
||||||
): Promise<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.
|
// Retreive the next invoice number.
|
||||||
const autoNextNumber =
|
const autoNextNumber =
|
||||||
@@ -54,7 +55,7 @@ export class PaymentReceiveDTOTransformer {
|
|||||||
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
|
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
|
||||||
'paymentDate',
|
'paymentDate',
|
||||||
]),
|
]),
|
||||||
amount: paymentAmount,
|
unappliedAmount,
|
||||||
currencyCode: customer.currencyCode,
|
currencyCode: customer.currencyCode,
|
||||||
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
|
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
|
||||||
exchangeRate: paymentReceiveDTO.exchangeRate || 1,
|
exchangeRate: paymentReceiveDTO.exchangeRate || 1,
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { sumBy } from 'lodash';
|
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import Ledger from '@/services/Accounting/Ledger';
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import {
|
import { IPaymentReceive, ILedgerEntry, AccountNormal } from '@/interfaces';
|
||||||
IPaymentReceive,
|
|
||||||
ILedgerEntry,
|
|
||||||
AccountNormal,
|
|
||||||
IPaymentReceiveGLCommonEntry,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||||
import { TenantMetadata } from '@/system/models';
|
import { TenantMetadata } from '@/system/models';
|
||||||
|
import { PaymentReceivedGLCommon } from './PaymentReceivedGLCommon';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PaymentReceiveGLEntries {
|
export class PaymentReceiveGLEntries extends PaymentReceivedGLCommon {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: TenancyService;
|
private tenancy: TenancyService;
|
||||||
|
|
||||||
@@ -22,9 +17,9 @@ export class PaymentReceiveGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes payment GL entries to the storage.
|
* Writes payment GL entries to the storage.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} paymentReceiveId
|
* @param {number} paymentReceiveId - Payment received id.
|
||||||
* @param {Knex.Transaction} trx
|
* @param {Knex.Transaction} trx - Knex transaction.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public writePaymentGLEntries = async (
|
public writePaymentGLEntries = async (
|
||||||
@@ -34,14 +29,19 @@ export class PaymentReceiveGLEntries {
|
|||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
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.
|
// Retrieves the payment receive with associated entries.
|
||||||
const paymentReceive = await PaymentReceive.query(trx)
|
const paymentReceive = await PaymentReceive.query(trx)
|
||||||
.findById(paymentReceiveId)
|
.findById(paymentReceiveId)
|
||||||
.withGraphFetched('entries.invoice');
|
.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.
|
// Retrives the payment receive ledger.
|
||||||
const ledger = await this.getPaymentReceiveGLedger(
|
const ledger = await this.getPaymentReceiveGLedger(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -53,25 +53,6 @@ export class PaymentReceiveGLEntries {
|
|||||||
await this.ledgerStorage.commit(tenantId, ledger, trx);
|
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.
|
* Rewrites the given payment receive GL entries.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -92,10 +73,10 @@ export class PaymentReceiveGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment receive general ledger.
|
* Retrieves the payment receive general ledger.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {IPaymentReceive} paymentReceive -
|
* @param {IPaymentReceive} paymentReceive -
|
||||||
* @param {string} baseCurrencyCode -
|
* @param {string} baseCurrencyCode -
|
||||||
* @param {Knex.Transaction} trx -
|
* @param {Knex.Transaction} trx -
|
||||||
* @returns {Ledger}
|
* @returns {Ledger}
|
||||||
*/
|
*/
|
||||||
public getPaymentReceiveGLedger = async (
|
public getPaymentReceiveGLedger = async (
|
||||||
@@ -126,100 +107,9 @@ export class PaymentReceiveGLEntries {
|
|||||||
return new Ledger(ledgerEntries);
|
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.
|
* Retrieves the payment deposit GL entry.
|
||||||
* @param {IPaymentReceive} paymentReceive
|
* @param {IPaymentReceive} paymentReceive
|
||||||
* @returns {ILedgerEntry}
|
* @returns {ILedgerEntry}
|
||||||
*/
|
*/
|
||||||
private getPaymentDepositGLEntry = (
|
private getPaymentDepositGLEntry = (
|
||||||
@@ -238,8 +128,8 @@ export class PaymentReceiveGLEntries {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the payment receivable entry.
|
* Retrieves the payment receivable entry.
|
||||||
* @param {IPaymentReceive} paymentReceive
|
* @param {IPaymentReceive} paymentReceive
|
||||||
* @param {number} ARAccountId
|
* @param {number} ARAccountId
|
||||||
* @returns {ILedgerEntry}
|
* @returns {ILedgerEntry}
|
||||||
*/
|
*/
|
||||||
private getPaymentReceivableEntry = (
|
private getPaymentReceivableEntry = (
|
||||||
@@ -262,15 +152,15 @@ export class PaymentReceiveGLEntries {
|
|||||||
* Records payment receive journal transactions.
|
* Records payment receive journal transactions.
|
||||||
*
|
*
|
||||||
* Invoice payment journals.
|
* Invoice payment journals.
|
||||||
* --------
|
* ------------
|
||||||
* - Account receivable -> Debit
|
* - Account Receivable -> Debit
|
||||||
* - Payment account [current asset] -> Credit
|
* - Payment Account [current asset] -> Credit
|
||||||
*
|
*
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IPaymentReceive} paymentRecieve - Payment receive model.
|
* @param {IPaymentReceive} paymentRecieve - Payment receive model.
|
||||||
* @param {number} ARAccountId - A/R account id.
|
* @param {number} ARAccountId - A/R account id.
|
||||||
* @param {number} exGainOrLossAccountId - Exchange gain/loss account id.
|
* @param {number} exGainOrLossAccountId - Exchange gain/loss account id.
|
||||||
* @param {string} baseCurrency - Base currency code.
|
* @param {string} baseCurrency - Base currency code.
|
||||||
* @returns {Promise<ILedgerEntry>}
|
* @returns {Promise<ILedgerEntry>}
|
||||||
*/
|
*/
|
||||||
public getPaymentReceiveGLEntries = (
|
public getPaymentReceiveGLEntries = (
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,17 +102,6 @@ function PaymentReceiveForm({
|
|||||||
) => {
|
) => {
|
||||||
setSubmitting(true);
|
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.
|
// Transformes the form values to request body.
|
||||||
const form = transformFormToRequest(values);
|
const form = transformFormToRequest(values);
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export default function PaymentReceiveHeaderFields() {
|
|||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/* ------------ Full amount ------------ */}
|
{/* ------------ Full amount ------------ */}
|
||||||
<Field name={'full_amount'}>
|
<Field name={'amount'}>
|
||||||
{({
|
{({
|
||||||
form: {
|
form: {
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
@@ -146,7 +146,7 @@ export default function PaymentReceiveHeaderFields() {
|
|||||||
<MoneyInputGroup
|
<MoneyInputGroup
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFieldValue('full_amount', value);
|
setFieldValue('amount', value);
|
||||||
}}
|
}}
|
||||||
onBlurValue={onFullAmountBlur}
|
onBlurValue={onFullAmountBlur}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const defaultPaymentReceive = {
|
|||||||
// Holds the payment number that entered manually only.
|
// Holds the payment number that entered manually only.
|
||||||
payment_receive_no_manually: '',
|
payment_receive_no_manually: '',
|
||||||
statement: '',
|
statement: '',
|
||||||
full_amount: '',
|
amount: '',
|
||||||
currency_code: '',
|
currency_code: '',
|
||||||
branch_id: '',
|
branch_id: '',
|
||||||
exchange_rate: 1,
|
exchange_rate: 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user