mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
feat: prepard expenses of payment made transactions
This commit is contained in:
@@ -118,7 +118,7 @@ export default class BillsPayments extends BaseController {
|
||||
check('reference').optional().trim().escape(),
|
||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
|
||||
check('entries').exists().isArray({ min: 1 }),
|
||||
check('entries').exists().isArray(),
|
||||
check('entries.*.index').optional().isNumeric().toInt(),
|
||||
check('entries.*.bill_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
|
||||
|
||||
@@ -165,7 +165,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
|
||||
check('entries.*.index').optional().isNumeric().toInt(),
|
||||
check('entries.*.invoice_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.amount_applied').exists().isNumeric().toFloat(),
|
||||
check('entries.*.payment_amount').exists().isNumeric().toFloat(),
|
||||
|
||||
check('attachments').isArray().optional(),
|
||||
check('attachments.*.key').exists().isString(),
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.table('bills_payments', (table) => {
|
||||
table.decimal('applied_amount', 13, 3).defaultTo(0);
|
||||
table
|
||||
.integer('prepard_expenses_account_id')
|
||||
.unsigned()
|
||||
.references('id')
|
||||
.inTable('accounts');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.table('bills_payments', (table) => {
|
||||
table.dropColumn('applied_amount');
|
||||
table.dropColumn('prepard_expenses_account_id');
|
||||
});
|
||||
};
|
||||
@@ -166,3 +166,10 @@ export interface IBillOpenedPayload {
|
||||
oldBill: IBill;
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
|
||||
export interface IBillPrepardExpensesAppliedEventPayload {
|
||||
tenantId: number;
|
||||
billId: number;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
@@ -119,3 +119,11 @@ export enum IPaymentMadeAction {
|
||||
Delete = 'Delete',
|
||||
View = 'View',
|
||||
}
|
||||
|
||||
export interface IPaymentPrepardExpensesAppliedEventPayload {
|
||||
tenantId: number;
|
||||
billPaymentId: number;
|
||||
billId: number;
|
||||
appliedAmount: number;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export interface IPaymentReceiveEntryDTO {
|
||||
index: number;
|
||||
paymentReceiveId: number;
|
||||
invoiceId: number;
|
||||
amountApplied: number;
|
||||
paymentAmount: number;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivesFilter extends IDynamicListFilterDTO {
|
||||
|
||||
@@ -525,9 +525,9 @@ export default class Bill extends mixin(TenantModel, [
|
||||
return notFoundBillsIds;
|
||||
}
|
||||
|
||||
static changePaymentAmount(billId, amount) {
|
||||
static changePaymentAmount(billId, amount, trx) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
return this.query()
|
||||
return this.query(trx)
|
||||
.where('id', billId)
|
||||
[changeMethod]('payment_amount', Math.abs(amount));
|
||||
}
|
||||
|
||||
@@ -38,4 +38,24 @@ export default class ContactTransfromer extends Transformer {
|
||||
? this.formatDate(contact.openingBalanceAt)
|
||||
: '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the unused credit balance.
|
||||
* @param {IContact} contact
|
||||
* @returns {number}
|
||||
*/
|
||||
protected unusedCredit = (contact: IContact): number => {
|
||||
return contact.balance > 0 ? 0 : Math.abs(contact.balance);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted unused credit balance.
|
||||
* @param {IContact} contact
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedUnusedCredit = (contact: IContact): string => {
|
||||
const unusedCredit = this.unusedCredit(contact);
|
||||
|
||||
return formatNumber(unusedCredit, { currencyCode: contact.currencyCode });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ export default class CustomerTransfromer extends ContactTransfromer {
|
||||
'formattedOpeningBalanceAt',
|
||||
'customerType',
|
||||
'formattedCustomerType',
|
||||
'unusedCredit',
|
||||
'formattedUnusedCredit',
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ export class CustomersApplication {
|
||||
|
||||
/**
|
||||
* Creates a new customer.
|
||||
* @param {number} tenantId
|
||||
* @param {ICustomerNewDTO} customerDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @param {number} tenantId
|
||||
* @param {ICustomerNewDTO} customerDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns {Promise<ICustomer>}
|
||||
*/
|
||||
public createCustomer = (tenantId: number, customerDTO: ICustomerNewDTO) => {
|
||||
@@ -56,9 +56,9 @@ export class CustomersApplication {
|
||||
|
||||
/**
|
||||
* Edits details of the given customer.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @param {ICustomerEditDTO} customerDTO
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @param {ICustomerEditDTO} customerDTO
|
||||
* @return {Promise<ICustomer>}
|
||||
*/
|
||||
public editCustomer = (
|
||||
@@ -75,9 +75,9 @@ export class CustomersApplication {
|
||||
|
||||
/**
|
||||
* Deletes the given customer and associated transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteCustomer = (
|
||||
@@ -94,9 +94,9 @@ export class CustomersApplication {
|
||||
|
||||
/**
|
||||
* Changes the opening balance of the given customer.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @param {Date|string} openingBalanceEditDTO
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @param {Date|string} openingBalanceEditDTO
|
||||
* @returns {Promise<ICustomer>}
|
||||
*/
|
||||
public editOpeningBalance = (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Service } from 'typedi';
|
||||
import ContactTransfromer from '../ContactTransformer';
|
||||
|
||||
export default class VendorTransfromer extends ContactTransfromer {
|
||||
@@ -10,7 +9,9 @@ export default class VendorTransfromer extends ContactTransfromer {
|
||||
return [
|
||||
'formattedBalance',
|
||||
'formattedOpeningBalance',
|
||||
'formattedOpeningBalanceAt'
|
||||
'formattedOpeningBalanceAt',
|
||||
'unusedCredit',
|
||||
'formattedUnusedCredit',
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export const DEFAULT_VIEWS = [
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
export const ERRORS = {
|
||||
OPENING_BALANCE_DATE_REQUIRED: 'OPENING_BALANCE_DATE_REQUIRED',
|
||||
CONTACT_ALREADY_INACTIVE: 'CONTACT_ALREADY_INACTIVE',
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import { Knex } from 'knex';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import PromisePool from '@supercharge/promise-pool';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import PromisePool, { ProcessHandler } from '@supercharge/promise-pool';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
IBillPayment,
|
||||
IBillPrepardExpensesAppliedEventPayload,
|
||||
IPaymentPrepardExpensesAppliedEventPayload,
|
||||
} from '@/interfaces';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class AutoApplyPrepardExpenses {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Auto apply prepard expenses to the given bill.
|
||||
* @param {number} tenantId
|
||||
@@ -19,31 +29,53 @@ export class AutoApplyPrepardExpenses {
|
||||
billId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { PaymentMade, Bill } = this.tenancy.models(tenantId);
|
||||
const { BillPayment, Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const unappliedPayments = await PaymentMade.query(trx).where(
|
||||
'unappliedAmount',
|
||||
'>',
|
||||
0
|
||||
);
|
||||
const bill = Bill.query(trx).findById(billId).throwIfNotFound();
|
||||
const bill = await Bill.query(trx).findById(billId).throwIfNotFound();
|
||||
|
||||
const unappliedPayments = await BillPayment.query(trx)
|
||||
.where('vendorId', bill.vendorId)
|
||||
.whereRaw('amount - applied_amount > 0')
|
||||
.whereNotNull('prepardExpensesAccountId');
|
||||
|
||||
let unappliedAmount = bill.total;
|
||||
let appliedTotalAmount = 0; // Total applied amount after applying.
|
||||
|
||||
const precessHandler: ProcessHandler<IBillPayment, void> = async (
|
||||
unappliedPayment: IBillPayment,
|
||||
index: number,
|
||||
pool
|
||||
) => {
|
||||
const appliedAmount = Math.min(unappliedAmount, unappliedPayment.amount);
|
||||
unappliedAmount = unappliedAmount - appliedAmount;
|
||||
appliedTotalAmount += appliedAmount;
|
||||
|
||||
// Stop applying once the unapplied amount reach zero or less.
|
||||
if (appliedAmount <= 0) {
|
||||
pool.stop();
|
||||
return;
|
||||
}
|
||||
await this.applyBillToPaymentMade(
|
||||
tenantId,
|
||||
unappliedPayment.id,
|
||||
bill.id,
|
||||
appliedAmount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
await PromisePool.withConcurrency(1)
|
||||
.for(unappliedPayments)
|
||||
.process(async (unappliedPayment: any) => {
|
||||
const appliedAmount = 1;
|
||||
|
||||
await this.applyBillToPaymentMade(
|
||||
tenantId,
|
||||
unappliedPayment.id,
|
||||
bill.id,
|
||||
appliedAmount,
|
||||
trx
|
||||
);
|
||||
});
|
||||
.process(precessHandler);
|
||||
|
||||
// Increase the paid amount of the purchase invoice.
|
||||
await Bill.changePaymentAmount(billId, 0, trx);
|
||||
await Bill.changePaymentAmount(billId, appliedTotalAmount, trx);
|
||||
|
||||
// Triggers `onBillPrepardExpensesApplied` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onPrepardExpensesApplied, {
|
||||
tenantId,
|
||||
billId,
|
||||
trx,
|
||||
} as IBillPrepardExpensesAppliedEventPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +100,18 @@ export class AutoApplyPrepardExpenses {
|
||||
billId,
|
||||
paymentAmount: appliedAmount,
|
||||
});
|
||||
await BillPayment.query().increment('usedAmount', appliedAmount);
|
||||
await BillPayment.query(trx).increment('appliedAmount', appliedAmount);
|
||||
|
||||
// Triggers `onBillPaymentPrepardExpensesApplied` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.billPayment.onPrepardExpensesApplied,
|
||||
{
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
billId,
|
||||
appliedAmount,
|
||||
trx,
|
||||
} as IPaymentPrepardExpensesAppliedEventPayload
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -254,6 +254,8 @@ export default {
|
||||
|
||||
onOpening: 'onBillOpening',
|
||||
onOpened: 'onBillOpened',
|
||||
|
||||
onPrepardExpensesApplied: 'onBillPrepardExpensesApplied'
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -271,6 +273,8 @@ export default {
|
||||
|
||||
onPublishing: 'onBillPaymentPublishing',
|
||||
onPublished: 'onBillPaymentPublished',
|
||||
|
||||
onPrepardExpensesApplied: 'onBillPaymentPrepardExpensesApplied'
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -160,6 +160,13 @@ export function useCustomersTableColumns() {
|
||||
width: 85,
|
||||
clickable: true,
|
||||
},
|
||||
{
|
||||
id: 'credit_balance',
|
||||
Header: 'Credit Balance',
|
||||
accessor: 'formatted_unused_credit',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
id: 'balance',
|
||||
Header: intl.get('receivable_balance'),
|
||||
|
||||
@@ -183,6 +183,13 @@ export function useVendorsTableColumns() {
|
||||
width: 85,
|
||||
clickable: true,
|
||||
},
|
||||
{
|
||||
id: 'credit_balance',
|
||||
Header: 'Credit Balance',
|
||||
accessor: 'formatted_unused_credit',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
id: 'balance',
|
||||
Header: intl.get('receivable_balance'),
|
||||
|
||||
Reference in New Issue
Block a user