mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +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 { BillsModule } from '../Bills/Bills.module';
|
||||||
import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
|
import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
|
||||||
import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module';
|
import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module';
|
||||||
|
import { ManualJournalsModule } from '../ManualJournals/ManualJournals.module';
|
||||||
// import { BillPaymentsModule } from '../BillPayments/BillPayments.module';
|
// import { BillPaymentsModule } from '../BillPayments/BillPayments.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -115,6 +116,7 @@ import { SaleReceiptsModule } from '../SaleReceipts/SaleReceipts.module';
|
|||||||
SaleEstimatesModule,
|
SaleEstimatesModule,
|
||||||
SaleReceiptsModule,
|
SaleReceiptsModule,
|
||||||
BillsModule,
|
BillsModule,
|
||||||
|
ManualJournalsModule
|
||||||
// BillPaymentsModule,
|
// BillPaymentsModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { BranchesApplication } from './BranchesApplication.service';
|
|||||||
import { BranchesSettingsService } from './BranchesSettings';
|
import { BranchesSettingsService } from './BranchesSettings';
|
||||||
import { BranchCommandValidator } from './commands/BranchCommandValidator.service';
|
import { BranchCommandValidator } from './commands/BranchCommandValidator.service';
|
||||||
import { BranchTransactionDTOTransformer } from './integrations/BranchTransactionDTOTransform';
|
import { BranchTransactionDTOTransformer } from './integrations/BranchTransactionDTOTransform';
|
||||||
|
import { ManualJournalBranchesDTOTransformer } from './integrations/ManualJournals/ManualJournalDTOTransformer.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TenancyDatabaseModule],
|
imports: [TenancyDatabaseModule],
|
||||||
@@ -31,8 +32,13 @@ import { BranchTransactionDTOTransformer } from './integrations/BranchTransactio
|
|||||||
TenancyContext,
|
TenancyContext,
|
||||||
TransformerInjectable,
|
TransformerInjectable,
|
||||||
BranchCommandValidator,
|
BranchCommandValidator,
|
||||||
BranchTransactionDTOTransformer
|
BranchTransactionDTOTransformer,
|
||||||
|
ManualJournalBranchesDTOTransformer,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
BranchesSettingsService,
|
||||||
|
BranchTransactionDTOTransformer,
|
||||||
|
ManualJournalBranchesDTOTransformer,
|
||||||
],
|
],
|
||||||
exports: [BranchTransactionDTOTransformer],
|
|
||||||
})
|
})
|
||||||
export class BranchesModule {}
|
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.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
static get relationMappings() {
|
static get relationMappings() {
|
||||||
const SaleEstimate = require('models/SaleEstimate');
|
const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate');
|
||||||
const SaleReceipt = require('models/SaleReceipt');
|
const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt');
|
||||||
const SaleInvoice = require('models/SaleInvoice');
|
const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
|
||||||
const PaymentReceive = require('models/PaymentReceive');
|
const { PaymentReceived } = require('../../PaymentReceived/models/PaymentReceived');
|
||||||
const Bill = require('models/Bill');
|
const { Bill } = require('../../Bills/models/Bill');
|
||||||
const BillPayment = require('models/BillPayment');
|
const { BillPayment } = require('../../BillPayments/models/BillPayment');
|
||||||
const AccountTransaction = require('models/AccountTransaction');
|
const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -146,7 +146,7 @@ export class Contact extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
salesInvoices: {
|
salesInvoices: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: SaleInvoice.default,
|
modelClass: SaleInvoice,
|
||||||
join: {
|
join: {
|
||||||
from: 'contacts.id',
|
from: 'contacts.id',
|
||||||
to: 'sales_invoices.customerId',
|
to: 'sales_invoices.customerId',
|
||||||
@@ -158,7 +158,7 @@ export class Contact extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
salesEstimates: {
|
salesEstimates: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: SaleEstimate.default,
|
modelClass: SaleEstimate,
|
||||||
join: {
|
join: {
|
||||||
from: 'contacts.id',
|
from: 'contacts.id',
|
||||||
to: 'sales_estimates.customerId',
|
to: 'sales_estimates.customerId',
|
||||||
@@ -170,7 +170,7 @@ export class Contact extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
salesReceipts: {
|
salesReceipts: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: SaleReceipt.default,
|
modelClass: SaleReceipt,
|
||||||
join: {
|
join: {
|
||||||
from: 'contacts.id',
|
from: 'contacts.id',
|
||||||
to: 'sales_receipts.customerId',
|
to: 'sales_receipts.customerId',
|
||||||
@@ -182,7 +182,7 @@ export class Contact extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
paymentReceives: {
|
paymentReceives: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: PaymentReceive.default,
|
modelClass: PaymentReceived,
|
||||||
join: {
|
join: {
|
||||||
from: 'contacts.id',
|
from: 'contacts.id',
|
||||||
to: 'payment_receives.customerId',
|
to: 'payment_receives.customerId',
|
||||||
@@ -194,7 +194,7 @@ export class Contact extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
bills: {
|
bills: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: Bill.default,
|
modelClass: Bill,
|
||||||
join: {
|
join: {
|
||||||
from: 'contacts.id',
|
from: 'contacts.id',
|
||||||
to: 'bills.vendorId',
|
to: 'bills.vendorId',
|
||||||
@@ -206,7 +206,7 @@ export class Contact extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
billPayments: {
|
billPayments: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: BillPayment.default,
|
modelClass: BillPayment,
|
||||||
join: {
|
join: {
|
||||||
from: 'contacts.id',
|
from: 'contacts.id',
|
||||||
to: 'bills_payments.vendorId',
|
to: 'bills_payments.vendorId',
|
||||||
@@ -218,7 +218,7 @@ export class Contact extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
accountsTransactions: {
|
accountsTransactions: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: AccountTransaction.default,
|
modelClass: AccountTransaction,
|
||||||
join: {
|
join: {
|
||||||
from: 'contacts.id',
|
from: 'contacts.id',
|
||||||
to: 'accounts_transactions.contactId',
|
to: 'accounts_transactions.contactId',
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ export class CommandExpenseValidator {
|
|||||||
/**
|
/**
|
||||||
* Retrieve expense accounts or throw error in case one of the given accounts
|
* Retrieve expense accounts or throw error in case one of the given accounts
|
||||||
* not found not the storage.
|
* not found not the storage.
|
||||||
* @param {number} tenantId
|
* @param {Array<Account>} tenantId
|
||||||
* @param {number} expenseAccountsIds
|
* @param {number} expenseAccountsIds
|
||||||
* @throws {ServiceError}
|
* @throws {ServiceError}
|
||||||
* @returns {Promise<IAccount[]>}
|
* @returns {Promise<IAccount[]>}
|
||||||
*/
|
*/
|
||||||
public validateExpensesAccountsExistance(
|
public validateExpensesAccountsExistance(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { CustomersImportable } from '../Contacts/Customers/CustomersImportable';
|
|||||||
import { VendorsImportable } from '../Contacts/Vendors/VendorsImportable';
|
import { VendorsImportable } from '../Contacts/Vendors/VendorsImportable';
|
||||||
import { ItemsImportable } from '../Items/ItemsImportable';
|
import { ItemsImportable } from '../Items/ItemsImportable';
|
||||||
import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable';
|
import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable';
|
||||||
import { ManualJournalImportable } from '../ManualJournals/ManualJournalsImport';
|
import { ManualJournalImportable } from '../ManualJournals/commands/ManualJournalsImport';
|
||||||
import { BillsImportable } from '../Purchases/Bills/BillsImportable';
|
import { BillsImportable } from '../Purchases/Bills/BillsImportable';
|
||||||
import { ExpensesImportable } from '../Expenses/ExpensesImportable';
|
import { ExpensesImportable } from '../Expenses/ExpensesImportable';
|
||||||
import { SaleInvoicesImportable } from '../Sales/Invoices/SaleInvoicesImportable';
|
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';
|
// import { getUploadedObjectUri } from '../../services/Attachments/utils';
|
||||||
|
|
||||||
export class TenantMetadata extends BaseModel {
|
export class TenantMetadata extends BaseModel {
|
||||||
baseCurrency!: string;
|
public baseCurrency!: string;
|
||||||
name!: string;
|
public name!: string;
|
||||||
tenantId!: number;
|
public tenantId!: number;
|
||||||
industry!: string;
|
public industry!: string;
|
||||||
location!: string;
|
public location!: string;
|
||||||
language!: string;
|
public language!: string;
|
||||||
timezone!: string;
|
public timezone!: string;
|
||||||
dateFormat!: string;
|
public dateFormat!: string;
|
||||||
fiscalYear!: string;
|
public fiscalYear!: string;
|
||||||
primaryColor!: string;
|
public primaryColor!: string;
|
||||||
logoKey!: string;
|
public logoKey!: string;
|
||||||
address!: Record<string, any>;
|
public logoUri!: string;
|
||||||
|
public address!: Record<string, any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Json schema.
|
* Json schema.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class TenantModel extends BaseModel {
|
|||||||
public readonly initializedAt: string;
|
public readonly initializedAt: string;
|
||||||
public readonly seededAt: boolean;
|
public readonly seededAt: boolean;
|
||||||
public readonly builtAt: string;
|
public readonly builtAt: string;
|
||||||
public readonly metadata: any;
|
public readonly metadata: TenantMetadata;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export class TemplateInjectable {
|
|||||||
|
|
||||||
return templateRender(filename, {
|
return templateRender(filename, {
|
||||||
organizationName: organization.metadata.name,
|
organizationName: organization.metadata.name,
|
||||||
organizationEmail: organization.metadata.email,
|
// @todo email
|
||||||
|
organizationEmail: '',
|
||||||
__: this.i18n.t,
|
__: this.i18n.t,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import { CreditNoteAppliedInvoice } from '@/modules/CreditNotes/models/CreditNot
|
|||||||
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
|
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
|
||||||
import { PaymentLink } from '@/modules/PaymentLinks/models/PaymentLink';
|
import { PaymentLink } from '@/modules/PaymentLinks/models/PaymentLink';
|
||||||
import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt';
|
import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt';
|
||||||
|
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
|
||||||
|
import { ManualJournalEntry } from '@/modules/ManualJournals/models/ManualJournalEntry';
|
||||||
|
|
||||||
const models = [
|
const models = [
|
||||||
Item,
|
Item,
|
||||||
@@ -63,7 +65,9 @@ const models = [
|
|||||||
CreditNoteAppliedInvoice,
|
CreditNoteAppliedInvoice,
|
||||||
CreditNote,
|
CreditNote,
|
||||||
PaymentLink,
|
PaymentLink,
|
||||||
SaleReceipt
|
SaleReceipt,
|
||||||
|
ManualJournal,
|
||||||
|
ManualJournalEntry
|
||||||
];
|
];
|
||||||
|
|
||||||
const modelProviders = models.map((model) => {
|
const modelProviders = models.map((model) => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Model } from 'objection';
|
|
||||||
import { BaseModel } from '@/models/Model';
|
import { BaseModel } from '@/models/Model';
|
||||||
import {
|
import {
|
||||||
getExlusiveTaxAmount,
|
getExlusiveTaxAmount,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
||||||
// import { EXPORT_DTE_FORMAT } from '@/services/Export/constants';
|
// import { EXPORT_DTE_FORMAT } from '@/services/Export/constants';
|
||||||
|
|||||||
95
packages/server-nest/test/manual-journal.e2e-spec.ts
Normal file
95
packages/server-nest/test/manual-journal.e2e-spec.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import * as request from 'supertest';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { app } from './init-app-test';
|
||||||
|
|
||||||
|
const makeManualJournalRequest = () => ({
|
||||||
|
date: '2022-06-01',
|
||||||
|
reference: faker.string.uuid(),
|
||||||
|
journalNumber: faker.string.uuid(),
|
||||||
|
publish: false,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
credit: 1000,
|
||||||
|
debit: 0,
|
||||||
|
accountId: 1003,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
credit: 0,
|
||||||
|
debit: 1000,
|
||||||
|
accountId: 1004,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Manual Journals (e2e)', () => {
|
||||||
|
it('/manual-journals (POST)', () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.post('/manual-journals')
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send(makeManualJournalRequest())
|
||||||
|
.expect(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/manual-journals/:id (DELETE)', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post('/manual-journals')
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send(makeManualJournalRequest());
|
||||||
|
|
||||||
|
const journalId = response.body.id;
|
||||||
|
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.delete(`/manual-journals/${journalId}`)
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send()
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/manual-journals/:id (GET)', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post('/manual-journals')
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send(makeManualJournalRequest());
|
||||||
|
|
||||||
|
const journalId = response.body.id;
|
||||||
|
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/manual-journals/${journalId}`)
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send()
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/manual-journals/:id (PUT)', async () => {
|
||||||
|
const manualJournal = makeManualJournalRequest();
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post('/manual-journals')
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send(manualJournal);
|
||||||
|
|
||||||
|
const journalId = response.body.id;
|
||||||
|
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.put(`/manual-journals/${journalId}`)
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send(manualJournal)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/manual-journals/:id/publish (POST)', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post('/manual-journals')
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send(makeManualJournalRequest());
|
||||||
|
|
||||||
|
const journalId = response.body.id;
|
||||||
|
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.post(`/manual-journals/${journalId}/publish`)
|
||||||
|
.set('organization-id', '4064541lv40nhca')
|
||||||
|
.send()
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user