Merge pull request #545 from bigcapitalhq/excessed-payments-as-credit

Excessed payments as credit
This commit is contained in:
Ahmed Bouhuolia
2024-07-25 18:57:31 +02:00
committed by GitHub
34 changed files with 605 additions and 100 deletions

View File

@@ -111,6 +111,7 @@ export default class BillsPayments extends BaseController {
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('amount').exists().isNumeric().toFloat(),
check('payment_account_id').exists().isNumeric().toInt(),
check('payment_number').optional({ nullable: true }).trim().escape(),
check('payment_date').exists(),
@@ -118,7 +119,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(),

View File

@@ -150,6 +150,7 @@ export default class PaymentReceivesController extends BaseController {
check('customer_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('amount').exists().isNumeric().toFloat(),
check('payment_date').exists(),
check('reference_no').optional(),
check('deposit_account_id').exists().isNumeric().toInt(),
@@ -158,8 +159,7 @@ 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(),

View File

@@ -12,8 +12,7 @@ export default class SeedAccounts extends TenantSeeder {
description: this.i18n.__(account.description),
currencyCode: this.tenant.metadata.baseCurrency,
seededAt: new Date(),
})
);
}));
return knex('accounts').then(async () => {
// Inserts seed entries.
return knex('accounts').insert(data);

View File

@@ -9,6 +9,28 @@ export const TaxPayableAccount = {
predefined: 1,
};
export const UnearnedRevenueAccount = {
name: 'Unearned Revenue',
slug: 'unearned-revenue',
account_type: 'other-current-liability',
parent_account_id: null,
code: '50005',
active: true,
index: 1,
predefined: true,
};
export const PrepardExpenses = {
name: 'Prepaid Expenses',
slug: 'prepaid-expenses',
account_type: 'other-current-asset',
parent_account_id: null,
code: '100010',
active: true,
index: 1,
predefined: true,
};
export default [
{
name: 'Bank Account',
@@ -323,4 +345,6 @@ export default [
index: 1,
predefined: 0,
},
UnearnedRevenueAccount,
PrepardExpenses,
];

View File

@@ -40,7 +40,7 @@ export interface ILedgerEntry {
date: Date | string;
transactionType: string;
transactionSubType: string;
transactionSubType?: string;
transactionId: number;

View File

@@ -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));
}

View File

@@ -2,7 +2,12 @@ import { Account } from 'models';
import TenantRepository from '@/repositories/TenantRepository';
import { IAccount } from '@/interfaces';
import { Knex } from 'knex';
import { TaxPayableAccount } from '@/database/seeds/data/accounts';
import {
PrepardExpenses,
TaxPayableAccount,
UnearnedRevenueAccount,
} from '@/database/seeds/data/accounts';
import { TenantMetadata } from '@/system/models';
export default class AccountRepository extends TenantRepository {
/**
@@ -179,4 +184,67 @@ export default class AccountRepository extends TenantRepository {
}
return result;
};
/**
* Finds or creates the unearned revenue.
* @param {Record<string, string>} extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
public async findOrCreateUnearnedRevenue(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction
) {
// Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({
tenantId: this.tenantId,
});
const _extraAttrs = {
currencyCode: tenantMeta.baseCurrency,
...extraAttrs,
};
let result = await this.model
.query(trx)
.findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...UnearnedRevenueAccount,
..._extraAttrs,
});
}
return result;
}
/**
* Finds or creates the prepard expenses account.
* @param {Record<string, string>} extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
public async findOrCreatePrepardExpenses(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction
) {
// Retrieves the given tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({
tenantId: this.tenantId,
});
const _extraAttrs = {
currencyCode: tenantMeta.baseCurrency,
...extraAttrs,
};
let result = await this.model
.query(trx)
.findOne({ slug: PrepardExpenses.slug, ..._extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...PrepardExpenses,
..._extraAttrs,
});
}
return result;
}
}

View File

@@ -4,12 +4,17 @@ import CachableRepository from './CachableRepository';
export default class TenantRepository extends CachableRepository {
repositoryName: string;
tenantId: number;
/**
* Constructor method.
* @param {number} tenantId
* @param {number} tenantId
*/
constructor(knex, cache, i18n) {
super(knex, cache, i18n);
}
}
setTenantId(tenantId: number) {
this.tenantId = tenantId;
}
}

View File

@@ -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 = (

View File

@@ -4,6 +4,7 @@ import { omit, sumBy } from 'lodash';
import { IBillPayment, IBillPaymentDTO, IVendor } from '@/interfaces';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
import { formatDateFields } from '@/utils';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class CommandBillPaymentDTOTransformer {
@@ -23,11 +24,14 @@ export class CommandBillPaymentDTOTransformer {
vendor: IVendor,
oldBillPayment?: IBillPayment
): Promise<IBillPayment> {
const amount =
billPaymentDTO.amount ?? sumBy(billPaymentDTO.entries, 'paymentAmount');
const initialDTO = {
...formatDateFields(omit(billPaymentDTO, ['attachments']), [
'paymentDate',
]),
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
amount,
currencyCode: vendor.currencyCode,
exchangeRate: billPaymentDTO.exchangeRate || 1,
entries: billPaymentDTO.entries,

View File

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

View File

@@ -77,7 +77,12 @@ export default class HasTenancyService {
const knex = this.knex(tenantId);
const i18n = this.i18n(tenantId);
return tenantRepositoriesLoader(knex, cache, i18n);
const repositories = tenantRepositoriesLoader(knex, cache, i18n);
Object.values(repositories).forEach((repository) => {
repository.setTenantId(tenantId);
});
return repositories;
});
}