mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: migrate manual journal to nestjs
This commit is contained in:
@@ -45,6 +45,7 @@ import { SaleEstimatesModule } from '../SaleEstimates/SaleEstimates.module';
|
||||
import { BillsModule } from '../Bills/Bills.module';
|
||||
import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
|
||||
import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module';
|
||||
import { ManualJournalsModule } from '../ManualJournals/ManualJournals.module';
|
||||
// import { BillPaymentsModule } from '../BillPayments/BillPayments.module';
|
||||
|
||||
@Module({
|
||||
@@ -115,6 +116,7 @@ import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module';
|
||||
SaleEstimatesModule,
|
||||
SaleReceiptsModule,
|
||||
BillsModule,
|
||||
ManualJournalsModule
|
||||
// BillPaymentsModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
|
||||
@@ -14,6 +14,7 @@ import { BranchesApplication } from './BranchesApplication.service';
|
||||
import { BranchesSettingsService } from './BranchesSettings';
|
||||
import { BranchCommandValidator } from './commands/BranchCommandValidator.service';
|
||||
import { BranchTransactionDTOTransformer } from './integrations/BranchTransactionDTOTransform';
|
||||
import { ManualJournalBranchesDTOTransformer } from './integrations/ManualJournals/ManualJournalDTOTransformer.service';
|
||||
|
||||
@Module({
|
||||
imports: [TenancyDatabaseModule],
|
||||
@@ -31,8 +32,13 @@ import { BranchTransactionDTOTransformer } from './integrations/BranchTransactio
|
||||
TenancyContext,
|
||||
TransformerInjectable,
|
||||
BranchCommandValidator,
|
||||
BranchTransactionDTOTransformer
|
||||
BranchTransactionDTOTransformer,
|
||||
ManualJournalBranchesDTOTransformer,
|
||||
],
|
||||
exports: [
|
||||
BranchesSettingsService,
|
||||
BranchTransactionDTOTransformer,
|
||||
ManualJournalBranchesDTOTransformer,
|
||||
],
|
||||
exports: [BranchTransactionDTOTransformer],
|
||||
})
|
||||
export class BranchesModule {}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { omit } from 'lodash';
|
||||
import { BranchesSettingsService } from '../../BranchesSettings';
|
||||
import { IManualJournalDTO } from '@/modules/ManualJournals/types/ManualJournals.types';
|
||||
|
||||
@Injectable()
|
||||
export class ManualJournalBranchesDTOTransformer {
|
||||
constructor(
|
||||
@Inject()
|
||||
private readonly branchesSettings: BranchesSettingsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param DTO
|
||||
* @returns
|
||||
*/
|
||||
private excludeDTOBranchIdWhenInactive = (
|
||||
DTO: IManualJournalDTO,
|
||||
): IManualJournalDTO => {
|
||||
const isActive = this.branchesSettings.isMultiBranchesActive();
|
||||
|
||||
if (isActive) return DTO;
|
||||
|
||||
return {
|
||||
...DTO,
|
||||
entries: DTO.entries.map((e) => omit(e, ['branchId'])),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public transformDTO = (DTO: IManualJournalDTO): IManualJournalDTO => {
|
||||
return this.excludeDTOBranchIdWhenInactive(DTO);
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// import { omit } from 'lodash';
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import { IManualJournal } from '@/interfaces';
|
||||
// import { BranchesSettings } from '../../BranchesSettings';
|
||||
|
||||
// @Service()
|
||||
// export class ManualJournalBranchesDTOTransformer {
|
||||
// @Inject()
|
||||
// branchesSettings: BranchesSettings;
|
||||
|
||||
// private excludeDTOBranchIdWhenInactive = (
|
||||
// tenantId: number,
|
||||
// DTO: IManualJournal
|
||||
// ): IManualJournal => {
|
||||
// const isActive = this.branchesSettings.isMultiBranchesActive(tenantId);
|
||||
|
||||
// if (isActive) return DTO;
|
||||
|
||||
// return {
|
||||
// ...DTO,
|
||||
// entries: DTO.entries.map((e) => omit(e, ['branchId'])),
|
||||
// };
|
||||
// };
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// public transformDTO =
|
||||
// (tenantId: number) =>
|
||||
// (DTO: IManualJournal): IManualJournal => {
|
||||
// return this.excludeDTOBranchIdWhenInactive(tenantId, DTO);
|
||||
// };
|
||||
// }
|
||||
@@ -132,13 +132,13 @@ export class Contact extends BaseModel {
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const SaleEstimate = require('models/SaleEstimate');
|
||||
const SaleReceipt = require('models/SaleReceipt');
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
const PaymentReceive = require('models/PaymentReceive');
|
||||
const Bill = require('models/Bill');
|
||||
const BillPayment = require('models/BillPayment');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate');
|
||||
const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt');
|
||||
const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
|
||||
const { PaymentReceived } = require('../../PaymentReceived/models/PaymentReceived');
|
||||
const { Bill } = require('../../Bills/models/Bill');
|
||||
const { BillPayment } = require('../../BillPayments/models/BillPayment');
|
||||
const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -146,7 +146,7 @@ export class Contact extends BaseModel {
|
||||
*/
|
||||
salesInvoices: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
modelClass: SaleInvoice,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'sales_invoices.customerId',
|
||||
@@ -158,7 +158,7 @@ export class Contact extends BaseModel {
|
||||
*/
|
||||
salesEstimates: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleEstimate.default,
|
||||
modelClass: SaleEstimate,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'sales_estimates.customerId',
|
||||
@@ -170,7 +170,7 @@ export class Contact extends BaseModel {
|
||||
*/
|
||||
salesReceipts: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleReceipt.default,
|
||||
modelClass: SaleReceipt,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'sales_receipts.customerId',
|
||||
@@ -182,7 +182,7 @@ export class Contact extends BaseModel {
|
||||
*/
|
||||
paymentReceives: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: PaymentReceive.default,
|
||||
modelClass: PaymentReceived,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'payment_receives.customerId',
|
||||
@@ -194,7 +194,7 @@ export class Contact extends BaseModel {
|
||||
*/
|
||||
bills: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Bill.default,
|
||||
modelClass: Bill,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'bills.vendorId',
|
||||
@@ -206,7 +206,7 @@ export class Contact extends BaseModel {
|
||||
*/
|
||||
billPayments: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: BillPayment.default,
|
||||
modelClass: BillPayment,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'bills_payments.vendorId',
|
||||
@@ -218,7 +218,7 @@ export class Contact extends BaseModel {
|
||||
*/
|
||||
accountsTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
modelClass: AccountTransaction,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'accounts_transactions.contactId',
|
||||
|
||||
@@ -30,9 +30,9 @@ export class CommandExpenseValidator {
|
||||
/**
|
||||
* Retrieve expense accounts or throw error in case one of the given accounts
|
||||
* not found not the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseAccountsIds
|
||||
* @throws {ServiceError}
|
||||
* @param {Array<Account>} tenantId
|
||||
* @param {number} expenseAccountsIds
|
||||
* @throws {ServiceError}
|
||||
* @returns {Promise<IAccount[]>}
|
||||
*/
|
||||
public validateExpensesAccountsExistance(
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CustomersImportable } from '../Contacts/Customers/CustomersImportable';
|
||||
import { VendorsImportable } from '../Contacts/Vendors/VendorsImportable';
|
||||
import { ItemsImportable } from '../Items/ItemsImportable';
|
||||
import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable';
|
||||
import { ManualJournalImportable } from '../ManualJournals/ManualJournalsImport';
|
||||
import { ManualJournalImportable } from '../ManualJournals/commands/ManualJournalsImport';
|
||||
import { BillsImportable } from '../Purchases/Bills/BillsImportable';
|
||||
import { ExpensesImportable } from '../Expenses/ExpensesImportable';
|
||||
import { SaleInvoicesImportable } from '../Sales/Invoices/SaleInvoicesImportable';
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ManualJournalsApplication } from './ManualJournalsApplication.service';
|
||||
import { IManualJournalDTO } from './types/ManualJournals.types';
|
||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||
|
||||
@Controller('manual-journals')
|
||||
@PublicRoute()
|
||||
export class ManualJournalsController {
|
||||
constructor(private manualJournalsApplication: ManualJournalsApplication) {}
|
||||
|
||||
@Post()
|
||||
public createManualJournal(@Body() manualJournalDTO: IManualJournalDTO) {
|
||||
return this.manualJournalsApplication.createManualJournal(manualJournalDTO);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
public editManualJournal(
|
||||
@Param('id') manualJournalId: number,
|
||||
@Body() manualJournalDTO: IManualJournalDTO,
|
||||
) {
|
||||
return this.manualJournalsApplication.editManualJournal(
|
||||
manualJournalId,
|
||||
manualJournalDTO,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
public deleteManualJournal(@Param('id') manualJournalId: number) {
|
||||
return this.manualJournalsApplication.deleteManualJournal(manualJournalId);
|
||||
}
|
||||
|
||||
@Post(':id/publish')
|
||||
public publishManualJournal(@Param('id') manualJournalId: number) {
|
||||
return this.manualJournalsApplication.publishManualJournal(manualJournalId);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
public getManualJournal(@Param('id') manualJournalId: number) {
|
||||
return this.manualJournalsApplication.getManualJournal(manualJournalId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CreateManualJournalService } from './commands/CreateManualJournal.service';
|
||||
import { EditManualJournal } from './commands/EditManualJournal.service';
|
||||
import { DeleteManualJournalService } from './commands/DeleteManualJournal.service';
|
||||
import { PublishManualJournal } from './commands/PublishManualJournal.service';
|
||||
import { CommandManualJournalValidators } from './commands/CommandManualJournalValidators.service';
|
||||
import { AutoIncrementManualJournal } from './commands/AutoIncrementManualJournal.service';
|
||||
import { ManualJournalBranchesDTOTransformer } from '../Branches/integrations/ManualJournals/ManualJournalDTOTransformer.service';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { AutoIncrementOrdersService } from '../AutoIncrementOrders/AutoIncrementOrders.service';
|
||||
import { BranchesModule } from '../Branches/Branches.module';
|
||||
import { ManualJournalsController } from './ManualJournals.controller';
|
||||
import { ManualJournalsApplication } from './ManualJournalsApplication.service';
|
||||
import { GetManualJournal } from './queries/GetManualJournal.service';
|
||||
|
||||
@Module({
|
||||
imports: [BranchesModule],
|
||||
controllers: [ManualJournalsController],
|
||||
providers: [
|
||||
TenancyContext,
|
||||
CreateManualJournalService,
|
||||
EditManualJournal,
|
||||
DeleteManualJournalService,
|
||||
PublishManualJournal,
|
||||
CommandManualJournalValidators,
|
||||
AutoIncrementManualJournal,
|
||||
CommandManualJournalValidators,
|
||||
ManualJournalBranchesDTOTransformer,
|
||||
AutoIncrementOrdersService,
|
||||
ManualJournalsApplication,
|
||||
GetManualJournal
|
||||
],
|
||||
})
|
||||
export class ManualJournalsModule {}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateManualJournalService } from './commands/CreateManualJournal.service';
|
||||
import { EditManualJournal } from './commands/EditManualJournal.service';
|
||||
import { PublishManualJournal } from './commands/PublishManualJournal.service';
|
||||
import { GetManualJournal } from './queries/GetManualJournal.service';
|
||||
import { DeleteManualJournalService } from './commands/DeleteManualJournal.service';
|
||||
import { IManualJournalDTO, } from './types/ManualJournals.types';
|
||||
// import { GetManualJournals } from './queries/GetManualJournals';
|
||||
|
||||
@Injectable()
|
||||
export class ManualJournalsApplication {
|
||||
constructor(
|
||||
private createManualJournalService: CreateManualJournalService,
|
||||
private editManualJournalService: EditManualJournal,
|
||||
private deleteManualJournalService: DeleteManualJournalService,
|
||||
private publishManualJournalService: PublishManualJournal,
|
||||
private getManualJournalService: GetManualJournal,
|
||||
// private getManualJournalsService: GetManualJournals,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Make journal entries.
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
* @returns {Promise<IManualJournal>}
|
||||
*/
|
||||
public createManualJournal = (manualJournalDTO: IManualJournalDTO) => {
|
||||
return this.createManualJournalService.makeJournalEntries(manualJournalDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits jouranl entries.
|
||||
* @param {number} manualJournalId
|
||||
* @param {IMakeJournalDTO} manualJournalDTO
|
||||
*/
|
||||
public editManualJournal = (
|
||||
manualJournalId: number,
|
||||
manualJournalDTO: IManualJournalDTO,
|
||||
) => {
|
||||
return this.editManualJournalService.editJournalEntries(
|
||||
manualJournalId,
|
||||
manualJournalDTO,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given manual journal
|
||||
* @param {number} manualJournalId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public deleteManualJournal = (manualJournalId: number) => {
|
||||
return this.deleteManualJournalService.deleteManualJournal(
|
||||
manualJournalId,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Publish the given manual journal.
|
||||
* @param {number} manualJournalId - Manual journal id.
|
||||
*/
|
||||
public publishManualJournal = (manualJournalId: number) => {
|
||||
return this.publishManualJournalService.publishManualJournal(
|
||||
manualJournalId,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the specific manual journal.
|
||||
* @param {number} manualJournalId
|
||||
* @returns
|
||||
*/
|
||||
public getManualJournal = (manualJournalId: number) => {
|
||||
return this.getManualJournalService.getManualJournal(
|
||||
manualJournalId,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the paginated manual journals.
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalsFilter} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
// public getManualJournals = (
|
||||
// filterDTO: IManualJournalsFilter,
|
||||
// ) => {
|
||||
// // return this.getManualJournalsService.getManualJournals(filterDTO);
|
||||
// };
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AutoIncrementOrdersService } from '@/modules/AutoIncrementOrders/AutoIncrementOrders.service';
|
||||
|
||||
@Injectable()
|
||||
export class AutoIncrementManualJournal {
|
||||
/**
|
||||
*
|
||||
* @param autoIncrementOrdersService
|
||||
*/
|
||||
constructor(
|
||||
private readonly autoIncrementOrdersService: AutoIncrementOrdersService
|
||||
) {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public autoIncrementEnabled = () => {
|
||||
return this.autoIncrementOrdersService.autoIncrementEnabled(
|
||||
'manual_journals'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the next journal number.
|
||||
*/
|
||||
public getNextJournalNumber = (): string => {
|
||||
return this.autoIncrementOrdersService.getNextTransactionNumber(
|
||||
'manual_journals'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Increment the manual journal number.
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
public incrementNextJournalNumber = () => {
|
||||
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
|
||||
'manual_journals'
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { difference, isEmpty, round, sumBy } from 'lodash';
|
||||
import {
|
||||
IManualJournalDTO,
|
||||
IManualJournalEntryDTO,
|
||||
} from '../types/ManualJournals.types';
|
||||
import { ERRORS } from '../constants';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
import { Contact } from '@/modules/Contacts/models/Contact';
|
||||
|
||||
@Injectable()
|
||||
export class CommandManualJournalValidators {
|
||||
constructor(
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
|
||||
@Inject(ManualJournal.name)
|
||||
private readonly manualJournalModel: typeof ManualJournal,
|
||||
|
||||
@Inject(Contact.name)
|
||||
private readonly contactModel: typeof Contact,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validate manual journal credit and debit should be equal.
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
*/
|
||||
public valdiateCreditDebitTotalEquals(manualJournalDTO: IManualJournalDTO) {
|
||||
const totalCredit = round(
|
||||
sumBy(manualJournalDTO.entries, (entry) => entry.credit || 0),
|
||||
2,
|
||||
);
|
||||
const totalDebit = round(
|
||||
sumBy(manualJournalDTO.entries, (entry) => entry.debit || 0),
|
||||
2,
|
||||
);
|
||||
if (totalCredit <= 0 || totalDebit <= 0) {
|
||||
throw new ServiceError(ERRORS.CREDIT_DEBIT_NOT_EQUAL_ZERO);
|
||||
}
|
||||
if (totalCredit !== totalDebit) {
|
||||
throw new ServiceError(ERRORS.CREDIT_DEBIT_NOT_EQUAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate manual entries accounts existance on the storage.
|
||||
* @param {number} tenantId -
|
||||
* @param {IManualJournalDTO} manualJournalDTO -
|
||||
*/
|
||||
public async validateAccountsExistance(manualJournalDTO: IManualJournalDTO) {
|
||||
const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId);
|
||||
const accounts = await this.accountModel
|
||||
.query()
|
||||
.whereIn('id', manualAccountsIds);
|
||||
|
||||
const storedAccountsIds = accounts.map((account) => account.id);
|
||||
|
||||
if (difference(manualAccountsIds, storedAccountsIds).length > 0) {
|
||||
throw new ServiceError(ERRORS.ACCOUNTS_IDS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate manual journal number unique.
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
*/
|
||||
public async validateManualJournalNoUnique(
|
||||
journalNumber: string,
|
||||
notId?: number,
|
||||
) {
|
||||
const journals = await this.manualJournalModel
|
||||
.query()
|
||||
.where('journal_number', journalNumber)
|
||||
.onBuild((builder) => {
|
||||
if (notId) {
|
||||
builder.whereNot('id', notId);
|
||||
}
|
||||
});
|
||||
if (journals.length > 0) {
|
||||
throw new ServiceError(
|
||||
ERRORS.JOURNAL_NUMBER_EXISTS,
|
||||
'The journal number is already exist.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate accounts with contact type.
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
* @param {string} accountBySlug
|
||||
* @param {string} contactType
|
||||
*/
|
||||
public async validateAccountWithContactType(
|
||||
entriesDTO: IManualJournalEntryDTO[],
|
||||
accountBySlug: string,
|
||||
contactType: string,
|
||||
): Promise<void | ServiceError> {
|
||||
// Retrieve account meta by the given account slug.
|
||||
const account = await this.accountModel
|
||||
.query()
|
||||
.findOne('slug', accountBySlug);
|
||||
|
||||
// Retrieve all stored contacts on the storage from contacts entries.
|
||||
const storedContacts = await this.contactModel.query().whereIn(
|
||||
'id',
|
||||
entriesDTO
|
||||
.filter((entry) => entry.contactId)
|
||||
.map((entry) => entry.contactId),
|
||||
);
|
||||
// Converts the stored contacts to map with id as key and entry as value.
|
||||
const storedContactsMap = new Map(
|
||||
storedContacts.map((contact) => [contact.id, contact]),
|
||||
);
|
||||
|
||||
// Filter all entries of the given account.
|
||||
const accountEntries = entriesDTO.filter(
|
||||
(entry) => entry.accountId === account.id,
|
||||
);
|
||||
// Can't continue if there is no entry that associate to the given account.
|
||||
if (accountEntries.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Filter entries that have no contact type or not equal the valid type.
|
||||
const entriesNoContact = accountEntries.filter((entry) => {
|
||||
const contact = storedContactsMap.get(entry.contactId);
|
||||
return !contact || contact.contactService !== contactType;
|
||||
});
|
||||
// Throw error in case one of entries that has invalid contact type.
|
||||
if (entriesNoContact.length > 0) {
|
||||
const indexes = entriesNoContact.map((e) => e.index);
|
||||
|
||||
return new ServiceError(ERRORS.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT, '', {
|
||||
accountSlug: accountBySlug,
|
||||
contactType,
|
||||
indexes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic validates accounts with contacts.
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
*/
|
||||
public async dynamicValidateAccountsWithContactType(
|
||||
entriesDTO: IManualJournalEntryDTO[],
|
||||
): Promise<any> {
|
||||
return Promise.all([
|
||||
this.validateAccountWithContactType(
|
||||
entriesDTO,
|
||||
'accounts-receivable',
|
||||
'customer',
|
||||
),
|
||||
this.validateAccountWithContactType(
|
||||
entriesDTO,
|
||||
'accounts-payable',
|
||||
'vendor',
|
||||
),
|
||||
]).then((results) => {
|
||||
const metadataErrors = results
|
||||
.filter((result) => result instanceof ServiceError)
|
||||
.map((result: ServiceError) => result.payload);
|
||||
|
||||
if (metadataErrors.length > 0) {
|
||||
throw new ServiceError(
|
||||
ERRORS.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT,
|
||||
'',
|
||||
metadataErrors,
|
||||
);
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate entries contacts existance.
|
||||
* @param {number} tenantId -
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
*/
|
||||
public async validateContactsExistance(manualJournalDTO: IManualJournalDTO) {
|
||||
// Filters the entries that have contact only.
|
||||
const entriesContactPairs = manualJournalDTO.entries.filter(
|
||||
(entry) => entry.contactId,
|
||||
);
|
||||
|
||||
if (entriesContactPairs.length > 0) {
|
||||
const entriesContactsIds = entriesContactPairs.map(
|
||||
(entry) => entry.contactId,
|
||||
);
|
||||
// Retrieve all stored contacts on the storage from contacts entries.
|
||||
const storedContacts = await this.contactModel
|
||||
.query()
|
||||
.whereIn('id', entriesContactsIds);
|
||||
|
||||
// Converts the stored contacts to map with id as key and entry as value.
|
||||
const storedContactsMap = new Map(
|
||||
storedContacts.map((contact) => [contact.id, contact]),
|
||||
);
|
||||
const notFoundContactsIds = [];
|
||||
|
||||
entriesContactPairs.forEach((contactEntry) => {
|
||||
const storedContact = storedContactsMap.get(contactEntry.contactId);
|
||||
|
||||
// in case the contact id not found.
|
||||
if (!storedContact) {
|
||||
notFoundContactsIds.push(storedContact);
|
||||
}
|
||||
});
|
||||
if (notFoundContactsIds.length > 0) {
|
||||
throw new ServiceError(ERRORS.CONTACTS_NOT_FOUND, '', {
|
||||
contactsIds: notFoundContactsIds,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates expenses is not already published before.
|
||||
* @param {ManualJournal} manualJournal
|
||||
*/
|
||||
public validateManualJournalIsNotPublished(manualJournal: ManualJournal) {
|
||||
if (manualJournal.publishedAt) {
|
||||
throw new ServiceError(ERRORS.MANUAL_JOURNAL_ALREADY_PUBLISHED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the manual journal number require.
|
||||
* @param {string} journalNumber
|
||||
* @throws {ServiceError(ERRORS.MANUAL_JOURNAL_NO_REQUIRED)}
|
||||
*/
|
||||
public validateJournalNoRequireWhenAutoNotEnabled = (
|
||||
journalNumber: string,
|
||||
) => {
|
||||
if (isEmpty(journalNumber)) {
|
||||
throw new ServiceError(ERRORS.MANUAL_JOURNAL_NO_REQUIRED);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters the not published manual jorunals.
|
||||
* @param {IManualJournal[]} manualJournal - Manual journal.
|
||||
* @return {IManualJournal[]}
|
||||
*/
|
||||
public getNonePublishedManualJournals(
|
||||
manualJournals: ManualJournal[],
|
||||
): ManualJournal[] {
|
||||
return manualJournals.filter((manualJournal) => !manualJournal.publishedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the published manual journals.
|
||||
* @param {IManualJournal[]} manualJournal - Manual journal.
|
||||
* @return {IManualJournal[]}
|
||||
*/
|
||||
public getPublishedManualJournals(
|
||||
manualJournals: ManualJournal[],
|
||||
): ManualJournal[] {
|
||||
return manualJournals.filter((expense) => expense.publishedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
*/
|
||||
public validateJournalCurrencyWithAccountsCurrency = async (
|
||||
manualJournalDTO: IManualJournalDTO,
|
||||
baseCurrency: string,
|
||||
) => {
|
||||
const accountsIds = manualJournalDTO.entries.map((e) => e.accountId);
|
||||
const accounts = await this.accountModel.query().whereIn('id', accountsIds);
|
||||
|
||||
// Filters the accounts that has no base currency or DTO currency.
|
||||
const notSupportedCurrency = accounts.filter((account) => {
|
||||
if (
|
||||
account.currencyCode === baseCurrency ||
|
||||
account.currencyCode === manualJournalDTO.currencyCode
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (notSupportedCurrency.length > 0) {
|
||||
throw new ServiceError(
|
||||
ERRORS.COULD_NOT_ASSIGN_DIFFERENT_CURRENCY_TO_ACCOUNTS,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { sumBy, omit } from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import {
|
||||
IManualJournalDTO,
|
||||
IManualJournalEventCreatedPayload,
|
||||
IManualJournalCreatingPayload,
|
||||
} from '../types/ManualJournals.types';
|
||||
import { CommandManualJournalValidators } from './CommandManualJournalValidators.service';
|
||||
import { AutoIncrementManualJournal } from './AutoIncrementManualJournal.service';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { ManualJournalBranchesDTOTransformer } from '@/modules/Branches/integrations/ManualJournals/ManualJournalDTOTransformer.service';
|
||||
|
||||
@Injectable()
|
||||
export class CreateManualJournalService {
|
||||
constructor(
|
||||
private tenancyContext: TenancyContext,
|
||||
private eventPublisher: EventEmitter2,
|
||||
private uow: UnitOfWork,
|
||||
private validator: CommandManualJournalValidators,
|
||||
private autoIncrement: AutoIncrementManualJournal,
|
||||
private branchesDTOTransformer: ManualJournalBranchesDTOTransformer,
|
||||
|
||||
@Inject(ManualJournal.name)
|
||||
private manualJournalModel: typeof ManualJournal,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Transform the new manual journal DTO to upsert graph operation.
|
||||
* @param {IManualJournalDTO} manualJournalDTO - Manual jorunal DTO.
|
||||
* @returns {Promise<ManualJournal>}
|
||||
*/
|
||||
private async transformNewDTOToModel(
|
||||
manualJournalDTO: IManualJournalDTO,
|
||||
): Promise<ManualJournal> {
|
||||
const amount = sumBy(manualJournalDTO.entries, 'credit') || 0;
|
||||
const date = moment(manualJournalDTO.date).format('YYYY-MM-DD');
|
||||
|
||||
// Retrieve the next manual journal number.
|
||||
const autoNextNumber = this.autoIncrement.getNextJournalNumber();
|
||||
|
||||
// The manual or auto-increment journal number.
|
||||
const journalNumber = manualJournalDTO.journalNumber || autoNextNumber;
|
||||
|
||||
const tenant = await this.tenancyContext.getTenant(true);
|
||||
const authorizedUser = await this.tenancyContext.getSystemUser();
|
||||
|
||||
const entries = R.compose(
|
||||
// Associate the default index to each item entry.
|
||||
assocItemEntriesDefaultIndex,
|
||||
)(manualJournalDTO.entries);
|
||||
|
||||
const initialDTO = {
|
||||
...omit(manualJournalDTO, ['publish', 'attachments']),
|
||||
...(manualJournalDTO.publish
|
||||
? { publishedAt: moment().toMySqlDateTime() }
|
||||
: {}),
|
||||
amount,
|
||||
date,
|
||||
currencyCode:
|
||||
manualJournalDTO.currencyCode || tenant?.metadata?.baseCurrency,
|
||||
exchangeRate: manualJournalDTO.exchangeRate || 1,
|
||||
journalNumber,
|
||||
entries,
|
||||
userId: authorizedUser.id,
|
||||
};
|
||||
return R.compose(
|
||||
// Omits the `branchId` from entries if multiply branches feature not active.
|
||||
this.branchesDTOTransformer.transformDTO,
|
||||
)(initialDTO) as ManualJournal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize the manual journal creating.
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
private authorize = async (manualJournalDTO: IManualJournalDTO) => {
|
||||
const tenant = await this.tenancyContext.getTenant(true);
|
||||
|
||||
// Validate the total credit should equals debit.
|
||||
this.validator.valdiateCreditDebitTotalEquals(manualJournalDTO);
|
||||
|
||||
// Validate the contacts existance.
|
||||
await this.validator.validateContactsExistance(manualJournalDTO);
|
||||
|
||||
// Validate entries accounts existance.
|
||||
await this.validator.validateAccountsExistance(manualJournalDTO);
|
||||
|
||||
// Validate manual journal number require when auto-increment not enabled.
|
||||
this.validator.validateJournalNoRequireWhenAutoNotEnabled(
|
||||
manualJournalDTO.journalNumber,
|
||||
);
|
||||
// Validate manual journal uniquiness on the storage.
|
||||
if (manualJournalDTO.journalNumber) {
|
||||
await this.validator.validateManualJournalNoUnique(
|
||||
manualJournalDTO.journalNumber,
|
||||
);
|
||||
}
|
||||
// Validate accounts with contact type from the given config.
|
||||
await this.validator.dynamicValidateAccountsWithContactType(
|
||||
manualJournalDTO.entries,
|
||||
)
|
||||
// Validates the accounts currency with journal currency.
|
||||
await this.validator.validateJournalCurrencyWithAccountsCurrency(
|
||||
manualJournalDTO,
|
||||
tenant.metadata.baseCurrency,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make journal entries.
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
public makeJournalEntries = async (
|
||||
manualJournalDTO: IManualJournalDTO,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<ManualJournal> => {
|
||||
// Authorize manual journal creating.
|
||||
await this.authorize(manualJournalDTO);
|
||||
// Transformes the next DTO to model.
|
||||
const manualJournalObj = await this.transformNewDTOToModel(manualJournalDTO);
|
||||
|
||||
// Creates a manual journal transactions with associated transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onManualJournalCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.manualJournals.onCreating, {
|
||||
manualJournalDTO,
|
||||
trx,
|
||||
} as IManualJournalCreatingPayload);
|
||||
|
||||
// Upsert the manual journal object.
|
||||
const manualJournal = await this.manualJournalModel
|
||||
.query(trx)
|
||||
.upsertGraph({
|
||||
...manualJournalObj,
|
||||
});
|
||||
// Triggers `onManualJournalCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.manualJournals.onCreated, {
|
||||
manualJournal,
|
||||
manualJournalDTO,
|
||||
trx,
|
||||
} as IManualJournalEventCreatedPayload);
|
||||
|
||||
return manualJournal;
|
||||
}, trx);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import {
|
||||
IManualJournalEventDeletedPayload,
|
||||
IManualJournalDeletingPayload,
|
||||
} from '../types/ManualJournals.types';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
import { ManualJournalEntry } from '../models/ManualJournalEntry';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteManualJournalService {
|
||||
constructor(
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@Inject(ManualJournal.name)
|
||||
private readonly manualJournalModel: typeof ManualJournal,
|
||||
|
||||
@Inject(ManualJournalEntry.name)
|
||||
private readonly manualJournalEntryModel: typeof ManualJournalEntry,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deletes the given manual journal
|
||||
* @param {number} manualJournalId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public deleteManualJournal = async (
|
||||
manualJournalId: number,
|
||||
): Promise<{
|
||||
oldManualJournal: ManualJournal;
|
||||
}> => {
|
||||
// Validate the manual journal exists on the storage.
|
||||
const oldManualJournal = await this.manualJournalModel.query()
|
||||
.findById(manualJournalId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Deletes the manual journal with associated transactions under unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onManualJournalDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.manualJournals.onDeleting, {
|
||||
oldManualJournal,
|
||||
trx,
|
||||
} as IManualJournalDeletingPayload);
|
||||
|
||||
// Deletes the manual journal entries.
|
||||
await this.manualJournalEntryModel
|
||||
.query(trx)
|
||||
.where('manualJournalId', manualJournalId)
|
||||
.delete();
|
||||
|
||||
// Deletes the manual journal transaction.
|
||||
await this.manualJournalModel
|
||||
.query(trx)
|
||||
.findById(manualJournalId)
|
||||
.delete();
|
||||
|
||||
// Triggers `onManualJournalDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.manualJournals.onDeleted, {
|
||||
manualJournalId,
|
||||
oldManualJournal,
|
||||
trx,
|
||||
} as IManualJournalEventDeletedPayload);
|
||||
|
||||
return { oldManualJournal };
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Knex } from 'knex';
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
IManualJournalDTO,
|
||||
IManualJournalEventEditedPayload,
|
||||
IManualJournalEditingPayload,
|
||||
} from '../types/ManualJournals.types';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { CommandManualJournalValidators } from './CommandManualJournalValidators.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
|
||||
@Injectable()
|
||||
export class EditManualJournal {
|
||||
constructor(
|
||||
private eventPublisher: EventEmitter2,
|
||||
private uow: UnitOfWork,
|
||||
private validator: CommandManualJournalValidators,
|
||||
|
||||
@Inject(ManualJournal.name)
|
||||
private manualJournalModel: typeof ManualJournal,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize the manual journal editing.
|
||||
* @param {number} tenantId
|
||||
* @param {number} manualJournalId
|
||||
* @param {IManualJournalDTO} manualJournalDTO
|
||||
*/
|
||||
private authorize = async (
|
||||
manualJournalId: number,
|
||||
manualJournalDTO: IManualJournalDTO,
|
||||
) => {
|
||||
// Validates the total credit and debit to be equals.
|
||||
this.validator.valdiateCreditDebitTotalEquals(manualJournalDTO);
|
||||
|
||||
// Validate the contacts existance.
|
||||
await this.validator.validateContactsExistance(manualJournalDTO);
|
||||
|
||||
// Validates entries accounts existance.
|
||||
await this.validator.validateAccountsExistance(manualJournalDTO);
|
||||
|
||||
// Validates the manual journal number uniquiness.
|
||||
if (manualJournalDTO.journalNumber) {
|
||||
await this.validator.validateManualJournalNoUnique(
|
||||
manualJournalDTO.journalNumber,
|
||||
manualJournalId,
|
||||
);
|
||||
}
|
||||
// Validate accounts with contact type from the given config.
|
||||
await this.validator.dynamicValidateAccountsWithContactType(
|
||||
manualJournalDTO.entries,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform the edit manual journal DTO to upsert graph operation.
|
||||
* @param {IManualJournalDTO} manualJournalDTO - Manual jorunal DTO.
|
||||
* @param {IManualJournal} oldManualJournal
|
||||
*/
|
||||
private transformEditDTOToModel = (
|
||||
manualJournalDTO: IManualJournalDTO,
|
||||
oldManualJournal: ManualJournal,
|
||||
) => {
|
||||
const amount = sumBy(manualJournalDTO.entries, 'credit') || 0;
|
||||
const date = moment(manualJournalDTO.date).format('YYYY-MM-DD');
|
||||
|
||||
return {
|
||||
id: oldManualJournal.id,
|
||||
...omit(manualJournalDTO, ['publish', 'attachments']),
|
||||
...(manualJournalDTO.publish && !oldManualJournal.publishedAt
|
||||
? { publishedAt: moment().toMySqlDateTime() }
|
||||
: {}),
|
||||
amount,
|
||||
date,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits jouranl entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} manualJournalId
|
||||
* @param {IMakeJournalDTO} manualJournalDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
public async editJournalEntries(
|
||||
manualJournalId: number,
|
||||
manualJournalDTO: IManualJournalDTO,
|
||||
): Promise<{
|
||||
manualJournal: ManualJournal;
|
||||
oldManualJournal: ManualJournal;
|
||||
}> {
|
||||
// Validates the manual journal existance on the storage.
|
||||
const oldManualJournal = await ManualJournal.query()
|
||||
.findById(manualJournalId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Authorize manual journal editing.
|
||||
await this.authorize(manualJournalId, manualJournalDTO);
|
||||
|
||||
// Transform manual journal DTO to model.
|
||||
const manualJournalObj = this.transformEditDTOToModel(
|
||||
manualJournalDTO,
|
||||
oldManualJournal,
|
||||
);
|
||||
// Edits the manual journal transactions with associated transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onManualJournalEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.manualJournals.onEditing, {
|
||||
manualJournalDTO,
|
||||
oldManualJournal,
|
||||
trx,
|
||||
} as IManualJournalEditingPayload);
|
||||
|
||||
// Upserts the manual journal graph to the storage.
|
||||
await this.manualJournalModel.query(trx).upsertGraph({
|
||||
...manualJournalObj,
|
||||
});
|
||||
// Retrieve the given manual journal with associated entries after modifications.
|
||||
const manualJournal = await this.manualJournalModel.query(trx)
|
||||
.findById(manualJournalId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
// Triggers `onManualJournalEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.manualJournals.onEdited, {
|
||||
manualJournal,
|
||||
oldManualJournal,
|
||||
manualJournalDTO,
|
||||
trx,
|
||||
} as IManualJournalEventEditedPayload);
|
||||
|
||||
return { manualJournal, oldManualJournal };
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import { IManualJournalsFilter } from '@/interfaces';
|
||||
// import { Exportable } from '../../Export/Exportable';
|
||||
// import { ManualJournalsApplication } from '../ManualJournalsApplication';
|
||||
// import { EXPORT_SIZE_LIMIT } from '../../Export/constants';
|
||||
|
||||
// @Service()
|
||||
// export class ManualJournalsExportable extends Exportable {
|
||||
// @Inject()
|
||||
// private manualJournalsApplication: ManualJournalsApplication;
|
||||
|
||||
// /**
|
||||
// * Retrieves the manual journals data to exportable sheet.
|
||||
// * @param {number} tenantId
|
||||
// * @returns
|
||||
// */
|
||||
// public exportable(tenantId: number, query: IManualJournalsFilter) {
|
||||
// const parsedQuery = {
|
||||
// sortOrder: 'desc',
|
||||
// columnSortBy: 'created_at',
|
||||
// ...query,
|
||||
// page: 1,
|
||||
// pageSize: EXPORT_SIZE_LIMIT,
|
||||
// } as IManualJournalsFilter;
|
||||
|
||||
// return this.manualJournalsApplication
|
||||
// .getManualJournals(tenantId, parsedQuery)
|
||||
// .then((output) => output.manualJournals);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,161 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IManualJournal,
|
||||
// IManualJournalEntry,
|
||||
// ILedgerEntry,
|
||||
// } from '@/interfaces';
|
||||
// import { Knex } from 'knex';
|
||||
// import Ledger from '@/services/Accounting/Ledger';
|
||||
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
// @Service()
|
||||
// export class ManualJournalGLEntries {
|
||||
// @Inject()
|
||||
// private ledgerStorage: LedgerStorageService;
|
||||
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
|
||||
// /**
|
||||
// * Create manual journal GL entries.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} manualJournalId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public createManualJournalGLEntries = async (
|
||||
// tenantId: number,
|
||||
// manualJournalId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// const { ManualJournal } = this.tenancy.models(tenantId);
|
||||
|
||||
// // Retrieves the given manual journal with associated entries.
|
||||
// const manualJournal = await ManualJournal.query(trx)
|
||||
// .findById(manualJournalId)
|
||||
// .withGraphFetched('entries.account');
|
||||
|
||||
// // Retrieves the ledger entries of the given manual journal.
|
||||
// const ledger = this.getManualJournalGLedger(manualJournal);
|
||||
|
||||
// // Commits the given ledger on the storage.
|
||||
// await this.ledgerStorage.commit(tenantId, ledger, trx);
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Edits manual journal GL entries.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} manualJournalId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public editManualJournalGLEntries = async (
|
||||
// tenantId: number,
|
||||
// manualJournalId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// // Reverts the manual journal GL entries.
|
||||
// await this.revertManualJournalGLEntries(tenantId, manualJournalId, trx);
|
||||
|
||||
// // Write the manual journal GL entries.
|
||||
// await this.createManualJournalGLEntries(tenantId, manualJournalId, trx);
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Deletes the manual journal GL entries.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} manualJournalId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public revertManualJournalGLEntries = async (
|
||||
// tenantId: number,
|
||||
// manualJournalId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ): Promise<void> => {
|
||||
// return this.ledgerStorage.deleteByReference(
|
||||
// tenantId,
|
||||
// manualJournalId,
|
||||
// 'Journal',
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Retrieves the ledger of the given manual journal.
|
||||
// * @param {IManualJournal} manualJournal
|
||||
// * @returns {Ledger}
|
||||
// */
|
||||
// private getManualJournalGLedger = (manualJournal: IManualJournal) => {
|
||||
// const entries = this.getManualJournalGLEntries(manualJournal);
|
||||
|
||||
// return new Ledger(entries);
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Retrieves the common entry details of the manual journal
|
||||
// * @param {IManualJournal} manualJournal
|
||||
// * @returns {Partial<ILedgerEntry>}
|
||||
// */
|
||||
// private getManualJournalCommonEntry = (
|
||||
// manualJournal: IManualJournal
|
||||
// ): Partial<ILedgerEntry> => {
|
||||
// return {
|
||||
// transactionNumber: manualJournal.journalNumber,
|
||||
// referenceNumber: manualJournal.reference,
|
||||
// createdAt: manualJournal.createdAt,
|
||||
// date: manualJournal.date,
|
||||
// currencyCode: manualJournal.currencyCode,
|
||||
// exchangeRate: manualJournal.exchangeRate,
|
||||
|
||||
// transactionType: 'Journal',
|
||||
// transactionId: manualJournal.id,
|
||||
|
||||
// userId: manualJournal.userId,
|
||||
// };
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Retrieves the ledger entry of the given manual journal and
|
||||
// * its associated entry.
|
||||
// * @param {IManualJournal} manualJournal -
|
||||
// * @param {IManualJournalEntry} entry -
|
||||
// * @returns {ILedgerEntry}
|
||||
// */
|
||||
// private getManualJournalEntry = R.curry(
|
||||
// (
|
||||
// manualJournal: IManualJournal,
|
||||
// entry: IManualJournalEntry
|
||||
// ): ILedgerEntry => {
|
||||
// const commonEntry = this.getManualJournalCommonEntry(manualJournal);
|
||||
|
||||
// return {
|
||||
// ...commonEntry,
|
||||
// debit: entry.debit,
|
||||
// credit: entry.credit,
|
||||
// accountId: entry.accountId,
|
||||
|
||||
// contactId: entry.contactId,
|
||||
// note: entry.note,
|
||||
|
||||
// index: entry.index,
|
||||
// accountNormal: entry.account.accountNormal,
|
||||
|
||||
// branchId: entry.branchId,
|
||||
// projectId: entry.projectId,
|
||||
// };
|
||||
// }
|
||||
// );
|
||||
|
||||
// /**
|
||||
// * Retrieves the ledger of the given manual journal.
|
||||
// * @param {IManualJournal} manualJournal
|
||||
// * @returns {ILedgerEntry[]}
|
||||
// */
|
||||
// private getManualJournalGLEntries = (
|
||||
// manualJournal: IManualJournal
|
||||
// ): ILedgerEntry[] => {
|
||||
// const transformEntry = this.getManualJournalEntry(manualJournal);
|
||||
|
||||
// return manualJournal.entries.map(transformEntry).flat();
|
||||
// };
|
||||
// }
|
||||
@@ -0,0 +1,131 @@
|
||||
// import { Inject } from 'typedi';
|
||||
// import { EventSubscriber } from 'event-dispatch';
|
||||
// import {
|
||||
// IManualJournalEventCreatedPayload,
|
||||
// IManualJournalEventEditedPayload,
|
||||
// IManualJournalEventPublishedPayload,
|
||||
// IManualJournalEventDeletedPayload,
|
||||
// } from '@/interfaces';
|
||||
// import events from '@/subscribers/events';
|
||||
// import { ManualJournalGLEntries } from './ManualJournalGLEntries';
|
||||
// import { AutoIncrementManualJournal } from './AutoIncrementManualJournal.service';
|
||||
|
||||
// @EventSubscriber()
|
||||
// export class ManualJournalWriteGLSubscriber {
|
||||
// @Inject()
|
||||
// private manualJournalGLEntries: ManualJournalGLEntries;
|
||||
|
||||
// @Inject()
|
||||
// private manualJournalAutoIncrement: AutoIncrementManualJournal;
|
||||
|
||||
// /**
|
||||
// * Attaches events with handlers.
|
||||
// * @param bus
|
||||
// */
|
||||
// public attach(bus) {
|
||||
// bus.subscribe(
|
||||
// events.manualJournals.onCreated,
|
||||
// this.handleWriteJournalEntriesOnCreated
|
||||
// );
|
||||
// bus.subscribe(
|
||||
// events.manualJournals.onCreated,
|
||||
// this.handleJournalNumberIncrement
|
||||
// );
|
||||
// bus.subscribe(
|
||||
// events.manualJournals.onEdited,
|
||||
// this.handleRewriteJournalEntriesOnEdited
|
||||
// );
|
||||
// bus.subscribe(
|
||||
// events.manualJournals.onPublished,
|
||||
// this.handleWriteJournalEntriesOnPublished
|
||||
// );
|
||||
// bus.subscribe(
|
||||
// events.manualJournals.onDeleted,
|
||||
// this.handleRevertJournalEntries
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Handle manual journal created event.
|
||||
// * @param {IManualJournalEventCreatedPayload} payload -
|
||||
// * @returns {Promise<void>}
|
||||
// */
|
||||
// private handleWriteJournalEntriesOnCreated = async ({
|
||||
// tenantId,
|
||||
// manualJournal,
|
||||
// trx,
|
||||
// }: IManualJournalEventCreatedPayload) => {
|
||||
// // Ingore writing manual journal journal entries in case was not published.
|
||||
// if (!manualJournal.publishedAt) return;
|
||||
|
||||
// await this.manualJournalGLEntries.createManualJournalGLEntries(
|
||||
// tenantId,
|
||||
// manualJournal.id,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Handles the manual journal next number increment once the journal be created.
|
||||
// * @param {IManualJournalEventCreatedPayload} payload -
|
||||
// * @return {Promise<void>}
|
||||
// */
|
||||
// private handleJournalNumberIncrement = async ({
|
||||
// tenantId,
|
||||
// }: IManualJournalEventCreatedPayload) => {
|
||||
// await this.manualJournalAutoIncrement.incrementNextJournalNumber(tenantId);
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Handle manual journal edited event.
|
||||
// * @param {IManualJournalEventEditedPayload}
|
||||
// * @return {Promise<void>}
|
||||
// */
|
||||
// private handleRewriteJournalEntriesOnEdited = async ({
|
||||
// tenantId,
|
||||
// manualJournal,
|
||||
// oldManualJournal,
|
||||
// trx,
|
||||
// }: IManualJournalEventEditedPayload) => {
|
||||
// if (manualJournal.publishedAt) {
|
||||
// await this.manualJournalGLEntries.editManualJournalGLEntries(
|
||||
// tenantId,
|
||||
// manualJournal.id,
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Handles writing journal entries once the manula journal publish.
|
||||
// * @param {IManualJournalEventPublishedPayload} payload -
|
||||
// * @return {Promise<void>}
|
||||
// */
|
||||
// private handleWriteJournalEntriesOnPublished = async ({
|
||||
// tenantId,
|
||||
// manualJournal,
|
||||
// trx,
|
||||
// }: IManualJournalEventPublishedPayload) => {
|
||||
// await this.manualJournalGLEntries.createManualJournalGLEntries(
|
||||
// tenantId,
|
||||
// manualJournal.id,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Handle manual journal deleted event.
|
||||
// * @param {IManualJournalEventDeletedPayload} payload -
|
||||
// */
|
||||
// private handleRevertJournalEntries = async ({
|
||||
// tenantId,
|
||||
// manualJournalId,
|
||||
// trx,
|
||||
// }: IManualJournalEventDeletedPayload) => {
|
||||
// await this.manualJournalGLEntries.revertManualJournalGLEntries(
|
||||
// tenantId,
|
||||
// manualJournalId,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
@@ -0,0 +1,60 @@
|
||||
// import { Inject } from 'typedi';
|
||||
// import { Knex } from 'knex';
|
||||
// import * as Yup from 'yup';
|
||||
// import { Importable } from '../../Import/Importable';
|
||||
// import { CreateManualJournalService } from './CreateManualJournal.service';
|
||||
// import { IManualJournalDTO } from '@/interfaces';
|
||||
// import { ImportableContext } from '../../Import/interfaces';
|
||||
// import { ManualJournalsSampleData } from '../constants';
|
||||
|
||||
// export class ManualJournalImportable extends Importable {
|
||||
// @Inject()
|
||||
// private createManualJournalService: CreateManualJournalService;
|
||||
|
||||
// /**
|
||||
// * Importing to account service.
|
||||
// * @param {number} tenantId
|
||||
// * @param {IAccountCreateDTO} createAccountDTO
|
||||
// * @returns
|
||||
// */
|
||||
// public importable(
|
||||
// tenantId: number,
|
||||
// createJournalDTO: IManualJournalDTO,
|
||||
// trx?: Knex.Transaction
|
||||
// ) {
|
||||
// return this.createManualJournalService.makeJournalEntries(
|
||||
// tenantId,
|
||||
// createJournalDTO,
|
||||
// {},
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Transformes the DTO before passing it to importable and validation.
|
||||
// * @param {Record<string, any>} createDTO
|
||||
// * @param {ImportableContext} context
|
||||
// * @returns {Record<string, any>}
|
||||
// */
|
||||
// public transform(createDTO: Record<string, any>, context: ImportableContext) {
|
||||
// return createDTO;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Params validation schema.
|
||||
// * @returns {ValidationSchema[]}
|
||||
// */
|
||||
// public paramsValidationSchema() {
|
||||
// return Yup.object().shape({
|
||||
// autoIncrement: Yup.boolean(),
|
||||
// });
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieves the sample data of manual journals that used to download sample sheet.
|
||||
// * @returns {Record<string, any>}
|
||||
// */
|
||||
// public sampleData(): Record<string, any>[] {
|
||||
// return ManualJournalsSampleData;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as moment from 'moment';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import {
|
||||
IManualJournalEventPublishedPayload,
|
||||
IManualJournalPublishingPayload,
|
||||
} from '../types/ManualJournals.types';
|
||||
import { CommandManualJournalValidators } from './CommandManualJournalValidators.service';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class PublishManualJournal {
|
||||
constructor(
|
||||
private eventPublisher: EventEmitter2,
|
||||
private uow: UnitOfWork,
|
||||
private validator: CommandManualJournalValidators,
|
||||
|
||||
@Inject(ManualJournal.name)
|
||||
private manualJournalModel: typeof ManualJournal,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize the manual journal publishing.
|
||||
* @param {number} manualJournalId - Manual journal id.
|
||||
*/
|
||||
private authorize = (oldManualJournal: ManualJournal) => {
|
||||
// Validate the manual journal is not published.
|
||||
this.validator.validateManualJournalIsNotPublished(oldManualJournal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Publish the given manual journal.
|
||||
* @param {number} manualJournalId - Manual journal id.
|
||||
*/
|
||||
public async publishManualJournal(manualJournalId: number): Promise<void> {
|
||||
// Find the old manual journal or throw not found error.
|
||||
const oldManualJournal = await this.manualJournalModel
|
||||
.query()
|
||||
.findById(manualJournalId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Authorize the manual journal publishing.
|
||||
await this.authorize(oldManualJournal);
|
||||
|
||||
// Publishes the manual journal with associated transactions.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onManualJournalPublishing` event.
|
||||
await this.eventPublisher.emitAsync(events.manualJournals.onPublishing, {
|
||||
oldManualJournal,
|
||||
trx,
|
||||
} as IManualJournalPublishingPayload);
|
||||
|
||||
// Mark the given manual journal as published.
|
||||
await this.manualJournalModel.query(trx).findById(manualJournalId).patch({
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
// Retrieve the manual journal with enrties after modification.
|
||||
const manualJournal = await this.manualJournalModel.query()
|
||||
.findById(manualJournalId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
// Triggers `onManualJournalPublishedBulk` event.
|
||||
await this.eventPublisher.emitAsync(events.manualJournals.onPublished, {
|
||||
manualJournal,
|
||||
oldManualJournal,
|
||||
trx,
|
||||
} as IManualJournalEventPublishedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
64
packages/server-nest/src/modules/ManualJournals/constants.ts
Normal file
64
packages/server-nest/src/modules/ManualJournals/constants.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
export const ERRORS = {
|
||||
NOT_FOUND: 'manual_journal_not_found',
|
||||
CREDIT_DEBIT_NOT_EQUAL_ZERO: 'credit_debit_not_equal_zero',
|
||||
CREDIT_DEBIT_NOT_EQUAL: 'credit_debit_not_equal',
|
||||
ACCOUNTS_IDS_NOT_FOUND: 'accounts_ids_not_found',
|
||||
JOURNAL_NUMBER_EXISTS: 'journal_number_exists',
|
||||
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
||||
CONTACTS_NOT_FOUND: 'contacts_not_found',
|
||||
ENTRIES_CONTACTS_NOT_FOUND: 'ENTRIES_CONTACTS_NOT_FOUND',
|
||||
MANUAL_JOURNAL_ALREADY_PUBLISHED: 'MANUAL_JOURNAL_ALREADY_PUBLISHED',
|
||||
MANUAL_JOURNAL_NO_REQUIRED: 'MANUAL_JOURNAL_NO_REQUIRED',
|
||||
COULD_NOT_ASSIGN_DIFFERENT_CURRENCY_TO_ACCOUNTS:
|
||||
'COULD_NOT_ASSIGN_DIFFERENT_CURRENCY_TO_ACCOUNTS',
|
||||
MANUAL_JOURNAL_ENTRIES_HAVE_NO_BRANCH_ID:
|
||||
'MANUAL_JOURNAL_ENTRIES_HAVE_NO_BRANCH_ID',
|
||||
};
|
||||
|
||||
export const CONTACTS_CONFIG = [
|
||||
{
|
||||
accountBySlug: 'accounts-receivable',
|
||||
contactService: 'customer',
|
||||
assignRequired: true,
|
||||
},
|
||||
{
|
||||
accountBySlug: 'accounts-payable',
|
||||
contactService: 'vendor',
|
||||
assignRequired: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_VIEWS = [];
|
||||
|
||||
export const ManualJournalsSampleData = [
|
||||
{
|
||||
Date: '2024-02-02',
|
||||
'Journal No': 'J-100022',
|
||||
'Reference No.': 'REF-10000',
|
||||
'Currency Code': '',
|
||||
'Exchange Rate': '',
|
||||
'Journal Type': '',
|
||||
Description: 'Animi quasi qui itaque aut possimus illum est magnam enim.',
|
||||
Credit: 1000,
|
||||
Debit: 0,
|
||||
Note: 'Qui reprehenderit voluptate.',
|
||||
Account: 'Bank Account',
|
||||
Contact: '',
|
||||
Publish: 'T',
|
||||
},
|
||||
{
|
||||
Date: '2024-02-02',
|
||||
'Journal No': 'J-100022',
|
||||
'Reference No.': 'REF-10000',
|
||||
'Currency Code': '',
|
||||
'Exchange Rate': '',
|
||||
'Journal Type': '',
|
||||
Description: 'In assumenda dicta autem non est corrupti non et.',
|
||||
Credit: 0,
|
||||
Debit: 1000,
|
||||
Note: 'Omnis tempora qui fugiat neque dolor voluptatem aut repudiandae nihil.',
|
||||
Account: 'Bank Account',
|
||||
Contact: '',
|
||||
Publish: 'T',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,207 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
// import TenantModel from 'models/TenantModel';
|
||||
// import { formatNumber } from 'utils';
|
||||
// import ModelSetting from './ModelSetting';
|
||||
// import ManualJournalSettings from './ManualJournal.Settings';
|
||||
// import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
// import { DEFAULT_VIEWS } from '@/services/ManualJournals/constants';
|
||||
// import ModelSearchable from './ModelSearchable';
|
||||
import { ManualJournalEntry } from './ManualJournalEntry';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
|
||||
|
||||
export class ManualJournal extends BaseModel {
|
||||
date: Date;
|
||||
journalNumber: string;
|
||||
journalType: string;
|
||||
reference: string;
|
||||
amount: number;
|
||||
currencyCode: string;
|
||||
exchangeRate: number | null;
|
||||
publishedAt: Date | string | null;
|
||||
description: string;
|
||||
userId?: number;
|
||||
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
|
||||
entries!: ManualJournalEntry[];
|
||||
attachments!: Document[];
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'manual_journals';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['isPublished', 'amountFormatted'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the amount formatted value.
|
||||
*/
|
||||
// get amountFormatted() {
|
||||
// return formatNumber(this.amount, { currencyCode: this.currencyCode });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Detarmines whether the invoice is published.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPublished() {
|
||||
return !!this.publishedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Sort by status query.
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`PUBLISHED_AT IS NULL ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by draft status.
|
||||
*/
|
||||
filterByDraft(query) {
|
||||
query.whereNull('publishedAt');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by published status.
|
||||
*/
|
||||
filterByPublished(query) {
|
||||
query.whereNotNull('publishedAt');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by the given status.
|
||||
*/
|
||||
filterByStatus(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('filterByDraft');
|
||||
break;
|
||||
case 'published':
|
||||
default:
|
||||
query.modify('filterByPublished');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
|
||||
const { ManualJournalEntry } = require('./ManualJournalEntry');
|
||||
const { Document } = require('../../ChromiumlyTenancy/models/Document');
|
||||
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||
|
||||
return {
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ManualJournalEntry,
|
||||
join: {
|
||||
from: 'manual_journals.id',
|
||||
to: 'manual_journals_entries.manualJournalId',
|
||||
},
|
||||
filter(query) {
|
||||
query.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction,
|
||||
join: {
|
||||
from: 'manual_journals.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.where('referenceType', 'Journal');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual journal may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document,
|
||||
join: {
|
||||
from: 'manual_journals.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'ManualJournal');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual journal may belongs to matched bank transaction.
|
||||
*/
|
||||
// matchedBankTransaction: {
|
||||
// relation: Model.BelongsToOneRelation,
|
||||
// modelClass: MatchedBankTransaction,
|
||||
// join: {
|
||||
// from: 'manual_journals.id',
|
||||
// to: 'matched_bank_transactions.referenceId',
|
||||
// },
|
||||
// filter(query) {
|
||||
// query.where('reference_type', 'ManualJournal');
|
||||
// },
|
||||
// },
|
||||
};
|
||||
}
|
||||
|
||||
// static get meta() {
|
||||
// return ManualJournalSettings;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve the default custom views, roles and columns.
|
||||
// */
|
||||
// static get defaultViews() {
|
||||
// return DEFAULT_VIEWS;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'journal_number', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
export class ManualJournalEntry extends BaseModel {
|
||||
index: number;
|
||||
credit: number;
|
||||
debit: number;
|
||||
accountId: number;
|
||||
note: string;
|
||||
contactId?: number;
|
||||
|
||||
branchId!: number;
|
||||
projectId?: number;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'manual_journals_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { Account } = require('../../Accounts/models/Account.model');
|
||||
const { Contact } = require('../../Contacts/models/Contact');
|
||||
const { Branch } = require('../../Branches/models/Branch.model');
|
||||
|
||||
return {
|
||||
account: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'manual_journals_entries.accountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact,
|
||||
join: {
|
||||
from: 'manual_journals_entries.contactId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch,
|
||||
join: {
|
||||
from: 'manual_journals_entries.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { ManualJournalTransfromer } from './ManualJournalTransformer';
|
||||
|
||||
@Injectable()
|
||||
export class GetManualJournal {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(ManualJournal.name)
|
||||
private readonly manualJournalModel: typeof ManualJournal,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve manual journal details with associated journal transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} manualJournalId
|
||||
*/
|
||||
public getManualJournal = async (manualJournalId: number) => {
|
||||
const manualJournal = await this.manualJournalModel
|
||||
.query()
|
||||
.findById(manualJournalId)
|
||||
.withGraphFetched('entries.account')
|
||||
.withGraphFetched('entries.contact')
|
||||
.withGraphFetched('entries.branch')
|
||||
.withGraphFetched('transactions')
|
||||
.withGraphFetched('attachments')
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(
|
||||
manualJournal,
|
||||
new ManualJournalTransfromer(),
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IManualJournalsFilter,
|
||||
// IManualJournal,
|
||||
// IPaginationMeta,
|
||||
// IFilterMeta,
|
||||
// } from '@/interfaces';
|
||||
// import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
// import { ManualJournalTransfromer } from './ManualJournalTransformer';
|
||||
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
// @Service()
|
||||
// export class GetManualJournals {
|
||||
// @Inject()
|
||||
// private tenancy: TenancyService;
|
||||
|
||||
// @Inject()
|
||||
// private dynamicListService: DynamicListingService;
|
||||
|
||||
// @Inject()
|
||||
// private transformer: TransformerInjectable;
|
||||
|
||||
// /**
|
||||
// * Parses filter DTO of the manual journals list.
|
||||
// * @param filterDTO
|
||||
// */
|
||||
// private parseListFilterDTO = (filterDTO) => {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Retrieve manual journals datatable list.
|
||||
// * @param {number} tenantId -
|
||||
// * @param {IManualJournalsFilter} filter -
|
||||
// */
|
||||
// public getManualJournals = async (
|
||||
// tenantId: number,
|
||||
// filterDTO: IManualJournalsFilter
|
||||
// ): Promise<{
|
||||
// manualJournals: IManualJournal[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> => {
|
||||
// const { ManualJournal } = this.tenancy.models(tenantId);
|
||||
|
||||
// // Parses filter DTO.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// // Dynamic service.
|
||||
// const dynamicService = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// ManualJournal,
|
||||
// filter
|
||||
// );
|
||||
// const { results, pagination } = await ManualJournal.query()
|
||||
// .onBuild((builder) => {
|
||||
// dynamicService.buildQuery()(builder);
|
||||
// builder.withGraphFetched('entries.account');
|
||||
// })
|
||||
// .pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Transformes the manual journals models to POJO.
|
||||
// const manualJournals = await this.transformer.transform(
|
||||
// tenantId,
|
||||
// results,
|
||||
// new ManualJournalTransfromer()
|
||||
// );
|
||||
|
||||
// return {
|
||||
// manualJournals,
|
||||
// pagination,
|
||||
// filterMeta: dynamicService.getResponseMeta(),
|
||||
// };
|
||||
// };
|
||||
// }
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Transformer } from '@/modules/Transformer/Transformer';
|
||||
import { AttachmentTransformer } from '@/modules/Attachments/Attachment.transformer';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
|
||||
export class ManualJournalTransfromer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to expense object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'formattedAmount',
|
||||
'formattedDate',
|
||||
'formattedPublishedAt',
|
||||
'formattedCreatedAt',
|
||||
'attachments',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted journal amount.
|
||||
* @param {IManualJournal} manualJournal
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (manualJorunal: ManualJournal): string => {
|
||||
return this.formatNumber(manualJorunal.amount, {
|
||||
currencyCode: manualJorunal.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted date.
|
||||
* @param {ManualJournal} manualJournal
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDate = (manualJorunal: ManualJournal): string => {
|
||||
return this.formatDate(manualJorunal.date);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted created at date.
|
||||
* @param {ManualJournal} manualJournal
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedCreatedAt = (manualJorunal: ManualJournal): string => {
|
||||
return this.formatDate(manualJorunal.createdAt);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted published at date.
|
||||
* @param {ManualJournal} manualJournal
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedPublishedAt = (manualJorunal: ManualJournal): string => {
|
||||
return this.formatDate(manualJorunal.publishedAt);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the manual journal attachments.
|
||||
* @param {ManualJournal} manualJorunal
|
||||
* @returns
|
||||
*/
|
||||
protected attachments = (manualJorunal: ManualJournal) => {
|
||||
return this.item(manualJorunal.attachments, new AttachmentTransformer());
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Knex } from 'knex';
|
||||
// import { IDynamicListFilterDTO } from './DynamicFilter';
|
||||
// import { ISystemUser } from './User';
|
||||
// import { IAccount } from './Account';
|
||||
// import { AttachmentLinkDTO } from './Attachments';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types';
|
||||
|
||||
export interface IManualJournalEntryDTO {
|
||||
index: number;
|
||||
credit: number;
|
||||
debit: number;
|
||||
accountId: number;
|
||||
note: string;
|
||||
contactId?: number;
|
||||
branchId?: number
|
||||
projectId?: number;
|
||||
}
|
||||
|
||||
export interface IManualJournalDTO {
|
||||
date: Date;
|
||||
currencyCode?: string;
|
||||
exchangeRate?: number;
|
||||
journalNumber: string;
|
||||
journalType: string;
|
||||
reference?: string;
|
||||
description?: string;
|
||||
publish?: boolean;
|
||||
branchId?: number;
|
||||
entries: IManualJournalEntryDTO[];
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
}
|
||||
|
||||
// export interface IManualJournalsFilter extends IDynamicListFilterDTO {
|
||||
// stringifiedFilterRoles?: string;
|
||||
// page: number;
|
||||
// pageSize: number;
|
||||
// }
|
||||
|
||||
export interface IManualJournalEventPublishedPayload {
|
||||
// tenantId: number;
|
||||
manualJournal: ManualJournal;
|
||||
// manualJournalId: number;
|
||||
oldManualJournal: ManualJournal;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IManualJournalPublishingPayload {
|
||||
oldManualJournal: ManualJournal;
|
||||
trx: Knex.Transaction;
|
||||
// tenantId: number;
|
||||
}
|
||||
|
||||
export interface IManualJournalEventDeletedPayload {
|
||||
// tenantId: number;
|
||||
manualJournalId: number;
|
||||
oldManualJournal: ManualJournal;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IManualJournalDeletingPayload {
|
||||
// tenantId: number;
|
||||
oldManualJournal: ManualJournal;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IManualJournalEventEditedPayload {
|
||||
// tenantId: number;
|
||||
manualJournal: ManualJournal;
|
||||
oldManualJournal: ManualJournal;
|
||||
manualJournalDTO: IManualJournalDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface IManualJournalEditingPayload {
|
||||
// tenantId: number;
|
||||
oldManualJournal: ManualJournal;
|
||||
manualJournalDTO: IManualJournalDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IManualJournalCreatingPayload {
|
||||
// tenantId: number;
|
||||
manualJournalDTO: IManualJournalDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IManualJournalEventCreatedPayload {
|
||||
// tenantId: number;
|
||||
manualJournal: ManualJournal;
|
||||
// manualJournalId: number;
|
||||
manualJournalDTO: IManualJournalDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export enum ManualJournalAction {
|
||||
Create = 'Create',
|
||||
View = 'View',
|
||||
Edit = 'Edit',
|
||||
Delete = 'Delete',
|
||||
}
|
||||
@@ -7,18 +7,19 @@ import { findByIsoCountryCode } from '@bigcapital/utils';
|
||||
// import { getUploadedObjectUri } from '../../services/Attachments/utils';
|
||||
|
||||
export class TenantMetadata extends BaseModel {
|
||||
baseCurrency!: string;
|
||||
name!: string;
|
||||
tenantId!: number;
|
||||
industry!: string;
|
||||
location!: string;
|
||||
language!: string;
|
||||
timezone!: string;
|
||||
dateFormat!: string;
|
||||
fiscalYear!: string;
|
||||
primaryColor!: string;
|
||||
logoKey!: string;
|
||||
address!: Record<string, any>;
|
||||
public baseCurrency!: string;
|
||||
public name!: string;
|
||||
public tenantId!: number;
|
||||
public industry!: string;
|
||||
public location!: string;
|
||||
public language!: string;
|
||||
public timezone!: string;
|
||||
public dateFormat!: string;
|
||||
public fiscalYear!: string;
|
||||
public primaryColor!: string;
|
||||
public logoKey!: string;
|
||||
public logoUri!: string;
|
||||
public address!: Record<string, any>;
|
||||
|
||||
/**
|
||||
* Json schema.
|
||||
|
||||
@@ -8,7 +8,7 @@ export class TenantModel extends BaseModel {
|
||||
public readonly initializedAt: string;
|
||||
public readonly seededAt: boolean;
|
||||
public readonly builtAt: string;
|
||||
public readonly metadata: any;
|
||||
public readonly metadata: TenantMetadata;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
|
||||
@@ -21,7 +21,8 @@ export class TemplateInjectable {
|
||||
|
||||
return templateRender(filename, {
|
||||
organizationName: organization.metadata.name,
|
||||
organizationEmail: organization.metadata.email,
|
||||
// @todo email
|
||||
organizationEmail: '',
|
||||
__: this.i18n.t,
|
||||
...options,
|
||||
});
|
||||
|
||||
@@ -32,6 +32,8 @@ import { CreditNoteAppliedInvoice } from '@/modules/CreditNotes/models/CreditNot
|
||||
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
|
||||
import { PaymentLink } from '@/modules/PaymentLinks/models/PaymentLink';
|
||||
import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt';
|
||||
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
|
||||
import { ManualJournalEntry } from '@/modules/ManualJournals/models/ManualJournalEntry';
|
||||
|
||||
const models = [
|
||||
Item,
|
||||
@@ -63,7 +65,9 @@ const models = [
|
||||
CreditNoteAppliedInvoice,
|
||||
CreditNote,
|
||||
PaymentLink,
|
||||
SaleReceipt
|
||||
SaleReceipt,
|
||||
ManualJournal,
|
||||
ManualJournalEntry
|
||||
];
|
||||
|
||||
const modelProviders = models.map((model) => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import {
|
||||
getExlusiveTaxAmount,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import moment from 'moment';
|
||||
import * as moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
||||
// import { EXPORT_DTE_FORMAT } from '@/services/Export/constants';
|
||||
|
||||
Reference in New Issue
Block a user