mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
feat: advanced payments
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,24 +345,6 @@ 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,
|
||||
}
|
||||
UnearnedRevenueAccount,
|
||||
PrepardExpenses,
|
||||
];
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface IBillPaymentEntryDTO {
|
||||
|
||||
export interface IBillPaymentDTO {
|
||||
vendorId: number;
|
||||
amount: number;
|
||||
paymentAccountId: number;
|
||||
paymentNumber?: string;
|
||||
paymentDate: Date;
|
||||
@@ -50,6 +51,7 @@ export interface IBillPaymentDTO {
|
||||
entries: IBillPaymentEntryDTO[];
|
||||
branchId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
prepardExpensesAccountId?: number;
|
||||
}
|
||||
|
||||
export interface IBillReceivePageEntry {
|
||||
|
||||
@@ -27,7 +27,11 @@ export interface IPaymentReceive {
|
||||
branchId?: number;
|
||||
unearnedRevenueAccountId?: number;
|
||||
}
|
||||
export interface IPaymentReceiveCreateDTO {
|
||||
|
||||
interface IPaymentReceivedCommonDTO {
|
||||
unearnedRevenueAccountId?: number;
|
||||
}
|
||||
export interface IPaymentReceiveCreateDTO extends IPaymentReceivedCommonDTO {
|
||||
customerId: number;
|
||||
paymentDate: Date;
|
||||
amount: number;
|
||||
@@ -40,11 +44,9 @@ export interface IPaymentReceiveCreateDTO {
|
||||
|
||||
branchId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
|
||||
unearnedRevenueAccountId?: number;
|
||||
}
|
||||
|
||||
export interface IPaymentReceiveEditDTO {
|
||||
export interface IPaymentReceiveEditDTO extends IPaymentReceivedCommonDTO {
|
||||
customerId: number;
|
||||
paymentDate: Date;
|
||||
amount: number;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ 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 {
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Transforms create/edit DTO to model.
|
||||
* @param {number} tenantId
|
||||
@@ -23,8 +27,18 @@ export class CommandBillPaymentDTOTransformer {
|
||||
vendor: IVendor,
|
||||
oldBillPayment?: IBillPayment
|
||||
): Promise<IBillPayment> {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const appliedAmount = sumBy(billPaymentDTO.entries, 'paymentAmount');
|
||||
|
||||
const hasPrepardExpenses = appliedAmount < billPaymentDTO.amount;
|
||||
const prepardExpensesAccount = hasPrepardExpenses
|
||||
? await accountRepository.findOrCreatePrepardExpenses()
|
||||
: null;
|
||||
const prepardExpensesAccountId =
|
||||
hasPrepardExpenses && prepardExpensesAccount
|
||||
? billPaymentDTO.prepardExpensesAccountId ?? prepardExpensesAccount?.id
|
||||
: billPaymentDTO.prepardExpensesAccountId;
|
||||
|
||||
const initialDTO = {
|
||||
...formatDateFields(omit(billPaymentDTO, ['attachments']), [
|
||||
'paymentDate',
|
||||
@@ -33,6 +47,7 @@ export class CommandBillPaymentDTOTransformer {
|
||||
currencyCode: vendor.currencyCode,
|
||||
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
||||
entries: billPaymentDTO.entries,
|
||||
prepardExpensesAccountId,
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO<IBillPayment>(tenantId)
|
||||
|
||||
@@ -11,6 +11,7 @@ import { PaymentReceiveValidators } from './PaymentReceiveValidators';
|
||||
import { PaymentReceiveIncrement } from './PaymentReceiveIncrement';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { formatDateFields } from '@/utils';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class PaymentReceiveDTOTransformer {
|
||||
@@ -23,6 +24,9 @@ export class PaymentReceiveDTOTransformer {
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Transformes the create payment receive DTO to model object.
|
||||
* @param {number} tenantId
|
||||
@@ -36,6 +40,7 @@ export class PaymentReceiveDTOTransformer {
|
||||
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
|
||||
oldPaymentReceive?: IPaymentReceive
|
||||
): Promise<IPaymentReceive> {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const appliedAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
||||
|
||||
// Retreive the next invoice number.
|
||||
@@ -50,6 +55,17 @@ export class PaymentReceiveDTOTransformer {
|
||||
|
||||
this.validators.validatePaymentNoRequire(paymentReceiveNo);
|
||||
|
||||
const hasUnearnedPayment = appliedAmount < paymentReceiveDTO.amount;
|
||||
const unearnedRevenueAccount = hasUnearnedPayment
|
||||
? await accountRepository.findOrCreateUnearnedRevenue()
|
||||
: null;
|
||||
|
||||
const unearnedRevenueAccountId =
|
||||
hasUnearnedPayment && unearnedRevenueAccount
|
||||
? paymentReceiveDTO.unearnedRevenueAccountId ??
|
||||
unearnedRevenueAccount?.id
|
||||
: paymentReceiveDTO.unearnedRevenueAccountId;
|
||||
|
||||
const initialDTO = {
|
||||
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
|
||||
'paymentDate',
|
||||
@@ -61,6 +77,7 @@ export class PaymentReceiveDTOTransformer {
|
||||
entries: paymentReceiveDTO.entries.map((entry) => ({
|
||||
...entry,
|
||||
})),
|
||||
unearnedRevenueAccountId,
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO<IPaymentReceive>(tenantId)
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user