diff --git a/packages/server/src/database/seeds/core/20190423085242_seed_accounts.ts b/packages/server/src/database/seeds/core/20190423085242_seed_accounts.ts index e5e39dba9..7fcbd0e5f 100644 --- a/packages/server/src/database/seeds/core/20190423085242_seed_accounts.ts +++ b/packages/server/src/database/seeds/core/20190423085242_seed_accounts.ts @@ -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); diff --git a/packages/server/src/database/seeds/data/accounts.js b/packages/server/src/database/seeds/data/accounts.js index 9721997e8..8e55665bd 100644 --- a/packages/server/src/database/seeds/data/accounts.js +++ b/packages/server/src/database/seeds/data/accounts.js @@ -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, ]; diff --git a/packages/server/src/interfaces/BillPayment.ts b/packages/server/src/interfaces/BillPayment.ts index 094f534d4..ec0bd2df3 100644 --- a/packages/server/src/interfaces/BillPayment.ts +++ b/packages/server/src/interfaces/BillPayment.ts @@ -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 { diff --git a/packages/server/src/interfaces/PaymentReceive.ts b/packages/server/src/interfaces/PaymentReceive.ts index 1aac795b8..42d2285d8 100644 --- a/packages/server/src/interfaces/PaymentReceive.ts +++ b/packages/server/src/interfaces/PaymentReceive.ts @@ -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; diff --git a/packages/server/src/repositories/AccountRepository.ts b/packages/server/src/repositories/AccountRepository.ts index 8bc6bf7d1..19aaaa70f 100644 --- a/packages/server/src/repositories/AccountRepository.ts +++ b/packages/server/src/repositories/AccountRepository.ts @@ -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} extraAttrs + * @param {Knex.Transaction} trx + * @returns + */ + public async findOrCreateUnearnedRevenue( + extraAttrs: Record = {}, + 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} extraAttrs + * @param {Knex.Transaction} trx + * @returns + */ + public async findOrCreatePrepardExpenses( + extraAttrs: Record = {}, + 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; + } } diff --git a/packages/server/src/repositories/TenantRepository.ts b/packages/server/src/repositories/TenantRepository.ts index b24b9d079..ac85a82a7 100644 --- a/packages/server/src/repositories/TenantRepository.ts +++ b/packages/server/src/repositories/TenantRepository.ts @@ -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); } -} \ No newline at end of file + + setTenantId(tenantId: number) { + this.tenantId = tenantId; + } +} diff --git a/packages/server/src/services/Purchases/BillPayments/CommandBillPaymentDTOTransformer.ts b/packages/server/src/services/Purchases/BillPayments/CommandBillPaymentDTOTransformer.ts index c9834da6c..db69a692c 100644 --- a/packages/server/src/services/Purchases/BillPayments/CommandBillPaymentDTOTransformer.ts +++ b/packages/server/src/services/Purchases/BillPayments/CommandBillPaymentDTOTransformer.ts @@ -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 { + 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(tenantId) diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveDTOTransformer.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveDTOTransformer.ts index dc13c44e8..e4723109e 100644 --- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveDTOTransformer.ts +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveDTOTransformer.ts @@ -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 { + 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(tenantId) diff --git a/packages/server/src/services/Tenancy/TenancyService.ts b/packages/server/src/services/Tenancy/TenancyService.ts index 68414c827..213b2d9c9 100644 --- a/packages/server/src/services/Tenancy/TenancyService.ts +++ b/packages/server/src/services/Tenancy/TenancyService.ts @@ -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; }); }