mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
Compare commits
20 Commits
users-modu
...
refactor-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1130975efd | ||
|
|
fa180b3ac5 | ||
|
|
90d6bea9b9 | ||
|
|
4366bf478a | ||
|
|
0a57b6e20e | ||
|
|
9a685ffe5d | ||
|
|
51988dba3b | ||
|
|
f87bd341e9 | ||
|
|
5595478e19 | ||
|
|
7247b52fe5 | ||
|
|
deadd5ac80 | ||
|
|
66a2261e50 | ||
|
|
c51347d3ec | ||
|
|
b7a3c42074 | ||
|
|
83c9392b74 | ||
|
|
24bf3dd06d | ||
|
|
2b3f98d8fe | ||
|
|
4e64a9eadb | ||
|
|
0823bfc4e9 | ||
|
|
99fe5a6b0d |
7
packages/server/src/common/config/bankfeed.ts
Normal file
7
packages/server/src/common/config/bankfeed.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('bankfeed', () => ({
|
||||||
|
enabled:
|
||||||
|
process.env.BANK_FEED_ENABLED === 'true' ||
|
||||||
|
process.env.BANK_FEED_ENABLED === 'yes',
|
||||||
|
}));
|
||||||
@@ -13,6 +13,7 @@ import signupRestrictions from './signup-restrictions';
|
|||||||
import jwt from './jwt';
|
import jwt from './jwt';
|
||||||
import mail from './mail';
|
import mail from './mail';
|
||||||
import loops from './loops';
|
import loops from './loops';
|
||||||
|
import bankfeed from './bankfeed';
|
||||||
|
|
||||||
export const config = [
|
export const config = [
|
||||||
systemDatabase,
|
systemDatabase,
|
||||||
@@ -29,5 +30,6 @@ export const config = [
|
|||||||
signupRestrictions,
|
signupRestrictions,
|
||||||
jwt,
|
jwt,
|
||||||
mail,
|
mail,
|
||||||
loops
|
loops,
|
||||||
|
bankfeed,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Map to store all models that have been marked to prevent base currency mutation.
|
||||||
|
* Key is the model name, value is the model class.
|
||||||
|
*/
|
||||||
|
export const preventMutateBaseCurrencyModels = new Map<string, any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator that marks an ORM model to prevent base currency mutation.
|
||||||
|
* When applied to a model class, it adds a static property `preventMutateBaseCurrency` set to true
|
||||||
|
* and registers the model in the preventMutateBaseCurrencyModels map.
|
||||||
|
*
|
||||||
|
* @returns {ClassDecorator} A decorator function that can be applied to a class.
|
||||||
|
*/
|
||||||
|
export function PreventMutateBaseCurrency(): ClassDecorator {
|
||||||
|
return (target: any) => {
|
||||||
|
// Set the static property on the model class
|
||||||
|
target.preventMutateBaseCurrency = true;
|
||||||
|
|
||||||
|
// Register the model in the map
|
||||||
|
const modelName = target.name;
|
||||||
|
preventMutateBaseCurrencyModels.set(modelName, target);
|
||||||
|
|
||||||
|
// Return the modified class
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered models that prevent base currency mutation.
|
||||||
|
*
|
||||||
|
* @returns {Map<string, any>} Map of model names to model classes
|
||||||
|
*/
|
||||||
|
export function getPreventMutateBaseCurrencyModels(): Map<string, any> {
|
||||||
|
return preventMutateBaseCurrencyModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a model is registered to prevent base currency mutation.
|
||||||
|
*
|
||||||
|
* @param {string} modelName - The name of the model to check
|
||||||
|
* @returns {boolean} True if the model is registered, false otherwise
|
||||||
|
*/
|
||||||
|
export function isModelPreventMutateBaseCurrency(modelName: string): boolean {
|
||||||
|
return preventMutateBaseCurrencyModels.has(modelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific model by name that prevents base currency mutation.
|
||||||
|
*
|
||||||
|
* @param {string} modelName - The name of the model to retrieve
|
||||||
|
* @returns {any | undefined} The model class if found, undefined otherwise
|
||||||
|
*/
|
||||||
|
export function getPreventMutateBaseCurrencyModel(
|
||||||
|
modelName: string,
|
||||||
|
): any | undefined {
|
||||||
|
return preventMutateBaseCurrencyModels.get(modelName);
|
||||||
|
}
|
||||||
32
packages/server/src/common/decorators/Validators.ts
Normal file
32
packages/server/src/common/decorators/Validators.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
import { ValidateIf, ValidationOptions } from 'class-validator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator that converts the property value to a number.
|
||||||
|
* @returns PropertyDecorator
|
||||||
|
*/
|
||||||
|
export function ToNumber() {
|
||||||
|
return Transform(({ value, key }) => {
|
||||||
|
const defaultValue = null;
|
||||||
|
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
// If value is an empty string or undefined/null, return it as-is (won’t pass validation)
|
||||||
|
if (value === '' || value === null || value === undefined) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const parsed = Number(value);
|
||||||
|
return !isNaN(parsed) ? parsed : value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if the property is not empty.
|
||||||
|
* @returns PropertyDecorator
|
||||||
|
*/
|
||||||
|
export function IsOptional(validationOptions?: ValidationOptions) {
|
||||||
|
return ValidateIf((_obj, value) => {
|
||||||
|
return value !== null && value !== undefined && value !== '';
|
||||||
|
}, validationOptions);
|
||||||
|
}
|
||||||
@@ -70,7 +70,10 @@ export class SerializeInterceptor implements NestInterceptor<any, any> {
|
|||||||
next: CallHandler<any>,
|
next: CallHandler<any>,
|
||||||
): Observable<any> {
|
): Observable<any> {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
|
|
||||||
|
// Transform both body and query parameters
|
||||||
request.body = this.strategy.in(request.body);
|
request.body = this.strategy.in(request.body);
|
||||||
|
request.query = this.strategy.in(request.query);
|
||||||
|
|
||||||
// handle returns stream..
|
// handle returns stream..
|
||||||
return next.handle().pipe(map(this.strategy.out));
|
return next.handle().pipe(map(this.strategy.out));
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ export class ValidationPipe implements PipeTransform<any> {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
const object = plainToInstance(metatype, value);
|
const object = plainToInstance(metatype, value);
|
||||||
const errors = await validate(object);
|
const errors = await validate(object, {
|
||||||
|
// Strip validated object of any properties that do not have any decorators.
|
||||||
|
whitelist: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
throw new BadRequestException(errors);
|
throw new BadRequestException(errors);
|
||||||
}
|
}
|
||||||
return value;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
private toValidate(metatype: Function): boolean {
|
private toValidate(metatype: Function): boolean {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"previoud_period_date": "{{date}} (PP)",
|
"previoud_period_date": "{date} (PP)",
|
||||||
"fianncial_sheet.previous_period_change": "Change (PP)",
|
"fianncial_sheet.previous_period_change": "Change (PP)",
|
||||||
"previous_period_percentage": "% Change (PP)",
|
"previous_period_percentage": "% Change (PP)",
|
||||||
"previous_year_date": "{{date}} (PY)",
|
"previous_year_date": "{date} (PY)",
|
||||||
"previous_year_change": "Change (PY)",
|
"previous_year_change": "Change (PY)",
|
||||||
"previous_year_percentage": "% Change (PY)",
|
"previous_year_percentage": "% Change (PY)",
|
||||||
"total_row": "Total {{value}}"
|
"total_row": "Total {value}"
|
||||||
}
|
}
|
||||||
|
|||||||
4
packages/server/src/i18n/en/inventory_adjustment.json
Normal file
4
packages/server/src/i18n/en/inventory_adjustment.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"decrement": "Decrement",
|
||||||
|
"increment": "Increment"
|
||||||
|
}
|
||||||
4
packages/server/src/i18n/en/warehouses.json
Normal file
4
packages/server/src/i18n/en/warehouses.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"primary_warehouse": "Primary Warehouse"
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import FormData from 'form-data';
|
import * as FormData from 'form-data';
|
||||||
import { GotenbergUtils } from './GotenbergUtils';
|
import { GotenbergUtils } from './GotenbergUtils';
|
||||||
import { PageProperties } from './_types';
|
import { PageProperties } from './_types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import FormData from 'form-data';
|
import * as FormData from 'form-data';
|
||||||
import Axios from 'axios';
|
import { Axios } from 'axios';
|
||||||
|
|
||||||
export class GotenbergUtils {
|
export class GotenbergUtils {
|
||||||
public static assert(condition: boolean, message: string): asserts condition {
|
public static assert(condition: boolean, message: string): asserts condition {
|
||||||
@@ -10,12 +10,12 @@ export class GotenbergUtils {
|
|||||||
|
|
||||||
public static async fetch(endpoint: string, data: FormData): Promise<Buffer> {
|
public static async fetch(endpoint: string, data: FormData): Promise<Buffer> {
|
||||||
try {
|
try {
|
||||||
const response = await Axios.post(endpoint, data, {
|
const response = await new Axios({
|
||||||
headers: {
|
headers: {
|
||||||
...data.getHeaders(),
|
...data.getHeaders(),
|
||||||
},
|
},
|
||||||
responseType: 'arraybuffer', // This ensures you get a Buffer bac
|
responseType: 'arraybuffer', // This ensures you get a Buffer bac
|
||||||
});
|
}).post(endpoint, data);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { constants, createReadStream, PathLike, promises } from 'fs';
|
import { constants, createReadStream, PathLike, promises } from 'fs';
|
||||||
import FormData from 'form-data';
|
import * as FormData from 'form-data';
|
||||||
import { GotenbergUtils } from './GotenbergUtils';
|
import { GotenbergUtils } from './GotenbergUtils';
|
||||||
import { IConverter, PageProperties } from './_types';
|
import { IConverter, PageProperties } from './_types';
|
||||||
import { PdfFormat, ChromiumRoute } from './_types';
|
import { PdfFormat, ChromiumRoute } from './_types';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import FormData from 'form-data';
|
import * as FormData from 'form-data';
|
||||||
import { IConverter, PageProperties, PdfFormat, ChromiumRoute } from './_types';
|
import { IConverter, PageProperties, PdfFormat, ChromiumRoute } from './_types';
|
||||||
import { ConverterUtils } from './ConvertUtils';
|
import { ConverterUtils } from './ConvertUtils';
|
||||||
import { Converter } from './Converter';
|
import { Converter } from './Converter';
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ export class MutateBaseCurrencyAccounts {
|
|||||||
* Mutates the all accounts or the organziation.
|
* Mutates the all accounts or the organziation.
|
||||||
* @param {string} currencyCode
|
* @param {string} currencyCode
|
||||||
*/
|
*/
|
||||||
mutateAllAccountsCurrency = async (
|
async mutateAllAccountsCurrency(currencyCode: string) {
|
||||||
currencyCode: string,
|
await this.accountModel().query().update({ currencyCode });
|
||||||
) => {
|
}
|
||||||
await Account.query().update({ currencyCode });
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ import { ViewsModule } from '../Views/Views.module';
|
|||||||
import { CurrenciesModule } from '../Currencies/Currencies.module';
|
import { CurrenciesModule } from '../Currencies/Currencies.module';
|
||||||
import { MiscellaneousModule } from '../Miscellaneous/Miscellaneous.module';
|
import { MiscellaneousModule } from '../Miscellaneous/Miscellaneous.module';
|
||||||
import { UsersModule } from '../UsersModule/Users.module';
|
import { UsersModule } from '../UsersModule/Users.module';
|
||||||
|
import { ContactsModule } from '../Contacts/Contacts.module';
|
||||||
|
import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
|
||||||
|
import { BankingCategorizeModule } from '../BankingCategorize/BankingCategorize.module';
|
||||||
|
import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -149,6 +153,7 @@ import { UsersModule } from '../UsersModule/Users.module';
|
|||||||
ScheduleModule.forRoot(),
|
ScheduleModule.forRoot(),
|
||||||
TenancyDatabaseModule,
|
TenancyDatabaseModule,
|
||||||
TenancyModelsModule,
|
TenancyModelsModule,
|
||||||
|
TenantModelsInitializeModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
TenancyModule,
|
TenancyModule,
|
||||||
ChromiumlyTenancyModule,
|
ChromiumlyTenancyModule,
|
||||||
@@ -181,10 +186,12 @@ import { UsersModule } from '../UsersModule/Users.module';
|
|||||||
LedgerModule,
|
LedgerModule,
|
||||||
BankAccountsModule,
|
BankAccountsModule,
|
||||||
BankRulesModule,
|
BankRulesModule,
|
||||||
BankingTransactionsModule,
|
|
||||||
BankingTransactionsExcludeModule,
|
BankingTransactionsExcludeModule,
|
||||||
BankingTransactionsRegonizeModule,
|
BankingTransactionsRegonizeModule,
|
||||||
|
BankingTransactionsModule,
|
||||||
BankingMatchingModule,
|
BankingMatchingModule,
|
||||||
|
BankingPlaidModule,
|
||||||
|
BankingCategorizeModule,
|
||||||
TransactionsLockingModule,
|
TransactionsLockingModule,
|
||||||
SettingsModule,
|
SettingsModule,
|
||||||
FeaturesModule,
|
FeaturesModule,
|
||||||
@@ -210,7 +217,8 @@ import { UsersModule } from '../UsersModule/Users.module';
|
|||||||
ViewsModule,
|
ViewsModule,
|
||||||
CurrenciesModule,
|
CurrenciesModule,
|
||||||
MiscellaneousModule,
|
MiscellaneousModule,
|
||||||
UsersModule
|
UsersModule,
|
||||||
|
ContactsModule
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const models = [
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [S3Module, ...models],
|
imports: [S3Module, ...models],
|
||||||
|
exports: [...models],
|
||||||
controllers: [AttachmentsController],
|
controllers: [AttachmentsController],
|
||||||
providers: [
|
providers: [
|
||||||
DeleteAttachment,
|
DeleteAttachment,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import path from 'path';
|
import * as path from 'path';
|
||||||
// import config from '@/config';
|
// import config from '@/config';
|
||||||
|
|
||||||
export const getUploadedObjectUri = (objectKey: string) => {
|
export const getUploadedObjectUri = (objectKey: string) => {
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ export class EditBankRuleService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Transforms the given edit bank rule dto to model object.
|
||||||
* @param createDTO
|
* @param editDTO
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private transformDTO(createDTO: EditBankRuleDto): ModelObject<BankRule> {
|
private transformDTO(editDTO: EditBankRuleDto): ModelObject<BankRule> {
|
||||||
return {
|
return {
|
||||||
...createDTO,
|
...editDTO,
|
||||||
} as ModelObject<BankRule>;
|
} as ModelObject<BankRule>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { BankRuleComparator } from '../types';
|
import { BankRuleComparator } from '../types';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { ToNumber } from '@/common/decorators/Validators';
|
||||||
|
|
||||||
class BankRuleConditionDto {
|
class BankRuleConditionDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@@ -44,6 +45,8 @@ export class CommandBankRuleDto {
|
|||||||
})
|
})
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ToNumber()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@@ -53,6 +56,7 @@ export class CommandBankRuleDto {
|
|||||||
order: number;
|
order: number;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
@ToNumber()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@@ -61,6 +65,7 @@ export class CommandBankRuleDto {
|
|||||||
})
|
})
|
||||||
applyIfAccountId?: number;
|
applyIfAccountId?: number;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
@IsIn(['deposit', 'withdrawal'])
|
@IsIn(['deposit', 'withdrawal'])
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The transaction type to apply the rule if',
|
description: 'The transaction type to apply the rule if',
|
||||||
@@ -82,11 +87,14 @@ export class CommandBankRuleDto {
|
|||||||
@Type(() => BankRuleConditionDto)
|
@Type(() => BankRuleConditionDto)
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The conditions to apply the rule if',
|
description: 'The conditions to apply the rule if',
|
||||||
example: [{ field: 'description', comparator: 'contains', value: 'Salary' }],
|
example: [
|
||||||
|
{ field: 'description', comparator: 'contains', value: 'Salary' },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
conditions: BankRuleConditionDto[];
|
conditions: BankRuleConditionDto[];
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The category to assign the rule if',
|
description: 'The category to assign the rule if',
|
||||||
example: 'Income:Salary',
|
example: 'Income:Salary',
|
||||||
@@ -95,6 +103,8 @@ export class CommandBankRuleDto {
|
|||||||
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
|
@ToNumber()
|
||||||
|
@IsNotEmpty()
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'The account ID to assign the rule if',
|
description: 'The account ID to assign the rule if',
|
||||||
example: 1,
|
example: 1,
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||||
import { BaseModel } from '@/models/Model';
|
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { initialize } from 'objection';
|
||||||
|
import { MatchedBankTransaction } from '@/modules/BankingMatching/models/MatchedBankTransaction';
|
||||||
|
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||||
|
import { RecognizedBankTransaction } from '@/modules/BankingTranasctionsRegonize/models/RecognizedBankTransaction';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetBankAccountSummary {
|
export class GetBankAccountSummary {
|
||||||
@@ -14,6 +19,19 @@ export class GetBankAccountSummary {
|
|||||||
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||||
typeof UncategorizedBankTransaction
|
typeof UncategorizedBankTransaction
|
||||||
>,
|
>,
|
||||||
|
|
||||||
|
@Inject(MatchedBankTransaction.name)
|
||||||
|
private readonly matchedBankTransactionModel: TenantModelProxy<
|
||||||
|
typeof MatchedBankTransaction
|
||||||
|
>,
|
||||||
|
|
||||||
|
@Inject(RecognizedBankTransaction.name)
|
||||||
|
private readonly recognizedBankTransaction: TenantModelProxy<
|
||||||
|
typeof RecognizedBankTransaction
|
||||||
|
>,
|
||||||
|
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantDb: () => Knex,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,6 +45,11 @@ export class GetBankAccountSummary {
|
|||||||
.findById(bankAccountId)
|
.findById(bankAccountId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
await initialize(this.tenantDb(), [
|
||||||
|
this.uncategorizedBankTransactionModel(),
|
||||||
|
this.matchedBankTransactionModel(),
|
||||||
|
this.recognizedBankTransaction(),
|
||||||
|
]);
|
||||||
const commonQuery = (q) => {
|
const commonQuery = (q) => {
|
||||||
// Include just the given account.
|
// Include just the given account.
|
||||||
q.where('accountId', bankAccountId);
|
q.where('accountId', bankAccountId);
|
||||||
@@ -37,11 +60,6 @@ export class GetBankAccountSummary {
|
|||||||
// Only the not categorized.
|
// Only the not categorized.
|
||||||
q.modify('notCategorized');
|
q.modify('notCategorized');
|
||||||
};
|
};
|
||||||
|
|
||||||
interface UncategorizedTransactionsCount {
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieves the uncategorized transactions count of the given bank account.
|
// Retrieves the uncategorized transactions count of the given bank account.
|
||||||
const uncategorizedTranasctionsCount =
|
const uncategorizedTranasctionsCount =
|
||||||
await this.uncategorizedBankTransactionModel()
|
await this.uncategorizedBankTransactionModel()
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { CategorizeBankTransaction } from './commands/CategorizeBankTransaction';
|
||||||
|
import { UncategorizeBankTransactionService } from './commands/UncategorizeBankTransaction.service';
|
||||||
|
import { UncategorizeBankTransactionsBulk } from './commands/UncategorizeBankTransactionsBulk.service';
|
||||||
|
import { UncategorizedBankTransactionDto } from './dtos/CreateUncategorizedBankTransaction.dto';
|
||||||
|
import { CategorizeBankTransactionDto } from './dtos/CategorizeBankTransaction.dto';
|
||||||
|
import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense';
|
||||||
|
import { CreateUncategorizedTransactionService } from './commands/CreateUncategorizedTransaction.service';
|
||||||
|
import { ICategorizeCashflowTransactioDTO } from './types/BankingCategorize.types';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BankingCategorizeApplication {
|
||||||
|
constructor(
|
||||||
|
private readonly categorizeBankTransaction: CategorizeBankTransaction,
|
||||||
|
private readonly uncategorizeBankTransaction: UncategorizeBankTransactionService,
|
||||||
|
private readonly uncategorizeBankTransactionsBulk: UncategorizeBankTransactionsBulk,
|
||||||
|
private readonly categorizeTransactionAsExpense: CategorizeTransactionAsExpense,
|
||||||
|
private readonly createUncategorizedTransaction: CreateUncategorizedTransactionService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categorize a bank transaction with the given ID and categorization data.
|
||||||
|
* @param {number | Array<number>} uncategorizedTransactionId - The ID(s) of the uncategorized transaction(s) to categorize.
|
||||||
|
* @param {CategorizeBankTransactionDto} categorizeDTO - Data for categorization.
|
||||||
|
* @returns {Promise<any>} The result of the categorization operation.
|
||||||
|
*/
|
||||||
|
public categorizeTransaction(
|
||||||
|
uncategorizedTransactionId: number | Array<number>,
|
||||||
|
categorizeDTO: CategorizeBankTransactionDto,
|
||||||
|
) {
|
||||||
|
return this.categorizeBankTransaction.categorize(
|
||||||
|
uncategorizedTransactionId,
|
||||||
|
categorizeDTO,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncategorize a bank transaction with the given ID.
|
||||||
|
* @param {number} uncategorizedTransactionId - The ID of the transaction to uncategorize.
|
||||||
|
* @returns {Promise<Array<number>>} Array of affected transaction IDs.
|
||||||
|
*/
|
||||||
|
public uncategorizeTransaction(
|
||||||
|
uncategorizedTransactionId: number,
|
||||||
|
): Promise<Array<number>> {
|
||||||
|
return this.uncategorizeBankTransaction.uncategorize(
|
||||||
|
uncategorizedTransactionId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncategorize multiple bank transactions in bulk.
|
||||||
|
* @param {number | Array<number>} uncategorizedTransactionIds - The ID(s) of the transaction(s) to uncategorize.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public uncategorizeTransactionsBulk(
|
||||||
|
uncategorizedTransactionIds: number | Array<number>,
|
||||||
|
) {
|
||||||
|
return this.uncategorizeBankTransactionsBulk.uncategorizeBulk(
|
||||||
|
uncategorizedTransactionIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categorize a transaction as an expense.
|
||||||
|
* @param {number} cashflowTransactionId - The ID of the cashflow transaction to categorize.
|
||||||
|
* @param {ICategorizeCashflowTransactioDTO} transactionDTO - Data for categorization.
|
||||||
|
* @returns {Promise<any>} The result of the categorization operation.
|
||||||
|
*/
|
||||||
|
public categorizeTransactionAsExpenseType(
|
||||||
|
cashflowTransactionId: number,
|
||||||
|
transactionDTO: ICategorizeCashflowTransactioDTO,
|
||||||
|
) {
|
||||||
|
return this.categorizeTransactionAsExpense.categorize(
|
||||||
|
cashflowTransactionId,
|
||||||
|
transactionDTO,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new uncategorized bank transaction.
|
||||||
|
* @param {UncategorizedBankTransactionDto} createDTO - Data for creating the uncategorized transaction.
|
||||||
|
* @param {Knex.Transaction} [trx] - Optional Knex transaction.
|
||||||
|
* @returns {Promise<any>} The created uncategorized transaction.
|
||||||
|
*/
|
||||||
|
public createUncategorizedBankTransaction(
|
||||||
|
createDTO: UncategorizedBankTransactionDto,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
return this.createUncategorizedTransaction.create(createDTO, trx);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { Body, Controller, Delete, Param, Post, Query } from '@nestjs/common';
|
||||||
|
import { castArray, omit } from 'lodash';
|
||||||
|
import { BankingCategorizeApplication } from './BankingCategorize.application';
|
||||||
|
import { CategorizeBankTransactionRouteDto } from './dtos/CategorizeBankTransaction.dto';
|
||||||
|
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
@Controller('banking/categorize')
|
||||||
|
@ApiTags('banking-categorization')
|
||||||
|
export class BankingCategorizeController {
|
||||||
|
constructor(
|
||||||
|
private readonly bankingCategorizeApplication: BankingCategorizeApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: 'Categorize bank transactions.' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'The bank transactions have been categorized successfully.',
|
||||||
|
})
|
||||||
|
public categorizeTransaction(
|
||||||
|
@Body() body: CategorizeBankTransactionRouteDto,
|
||||||
|
) {
|
||||||
|
return this.bankingCategorizeApplication.categorizeTransaction(
|
||||||
|
castArray(body.uncategorizedTransactionIds),
|
||||||
|
omit(body, 'uncategorizedTransactionIds'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/bulk')
|
||||||
|
@ApiOperation({ summary: 'Uncategorize bank transactions.' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'The bank transactions have been uncategorized successfully.',
|
||||||
|
})
|
||||||
|
public uncategorizeTransactionsBulk(
|
||||||
|
@Query() uncategorizedTransactionIds: number[] | number,
|
||||||
|
) {
|
||||||
|
return this.bankingCategorizeApplication.uncategorizeTransactionsBulk(
|
||||||
|
castArray(uncategorizedTransactionIds),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/:id')
|
||||||
|
@ApiOperation({ summary: 'Uncategorize a bank transaction.' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'The bank transaction has been uncategorized successfully.',
|
||||||
|
})
|
||||||
|
public uncategorizeTransaction(
|
||||||
|
@Param('id') uncategorizedTransactionId: number,
|
||||||
|
) {
|
||||||
|
return this.bankingCategorizeApplication.uncategorizeTransaction(
|
||||||
|
Number(uncategorizedTransactionId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,38 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { CreateUncategorizedTransactionService } from './commands/CreateUncategorizedTransaction.service';
|
import { CreateUncategorizedTransactionService } from './commands/CreateUncategorizedTransaction.service';
|
||||||
import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense';
|
import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense';
|
||||||
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
||||||
import { ExpensesModule } from '../Expenses/Expenses.module';
|
import { ExpensesModule } from '../Expenses/Expenses.module';
|
||||||
import { UncategorizedTransactionsImportable } from './commands/UncategorizedTransactionsImportable';
|
import { UncategorizedTransactionsImportable } from './commands/UncategorizedTransactionsImportable';
|
||||||
|
import { BankingCategorizeController } from './BankingCategorize.controller';
|
||||||
|
import { BankingCategorizeApplication } from './BankingCategorize.application';
|
||||||
|
import { CategorizeBankTransaction } from './commands/CategorizeBankTransaction';
|
||||||
|
import { UncategorizeBankTransactionService } from './commands/UncategorizeBankTransaction.service';
|
||||||
|
import { UncategorizeBankTransactionsBulk } from './commands/UncategorizeBankTransactionsBulk.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [BankingTransactionsModule, ExpensesModule],
|
imports: [
|
||||||
|
BankingTransactionsModule,
|
||||||
|
ExpensesModule,
|
||||||
|
forwardRef(() => BankingTransactionsModule),
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
CreateUncategorizedTransactionService,
|
CreateUncategorizedTransactionService,
|
||||||
CategorizeTransactionAsExpense,
|
CategorizeTransactionAsExpense,
|
||||||
UncategorizedTransactionsImportable
|
UncategorizedTransactionsImportable,
|
||||||
|
BankingCategorizeApplication,
|
||||||
|
CategorizeBankTransaction,
|
||||||
|
UncategorizeBankTransactionService,
|
||||||
|
UncategorizeBankTransactionsBulk,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CreateUncategorizedTransactionService,
|
CreateUncategorizedTransactionService,
|
||||||
CategorizeTransactionAsExpense,
|
CategorizeTransactionAsExpense,
|
||||||
|
BankingCategorizeApplication,
|
||||||
|
CategorizeBankTransaction,
|
||||||
|
UncategorizeBankTransactionService,
|
||||||
|
UncategorizeBankTransactionsBulk,
|
||||||
],
|
],
|
||||||
|
controllers: [BankingCategorizeController],
|
||||||
})
|
})
|
||||||
export class BankingCategorizeModule {}
|
export class BankingCategorizeModule {}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Knex } from 'knex';
|
|||||||
import {
|
import {
|
||||||
ICashflowTransactionCategorizedPayload,
|
ICashflowTransactionCategorizedPayload,
|
||||||
ICashflowTransactionUncategorizingPayload,
|
ICashflowTransactionUncategorizingPayload,
|
||||||
ICategorizeCashflowTransactioDTO,
|
|
||||||
} from '../types/BankingCategorize.types';
|
} from '../types/BankingCategorize.types';
|
||||||
import {
|
import {
|
||||||
transformCategorizeTransToCashflow,
|
transformCategorizeTransToCashflow,
|
||||||
@@ -17,9 +16,10 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
|||||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { CategorizeBankTransactionDto } from '../dtos/CategorizeBankTransaction.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CategorizeCashflowTransaction {
|
export class CategorizeBankTransaction {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly eventPublisher: EventEmitter2,
|
private readonly eventPublisher: EventEmitter2,
|
||||||
private readonly uow: UnitOfWork,
|
private readonly uow: UnitOfWork,
|
||||||
@@ -38,7 +38,7 @@ export class CategorizeCashflowTransaction {
|
|||||||
*/
|
*/
|
||||||
public async categorize(
|
public async categorize(
|
||||||
uncategorizedTransactionId: number | Array<number>,
|
uncategorizedTransactionId: number | Array<number>,
|
||||||
categorizeDTO: ICategorizeCashflowTransactioDTO,
|
categorizeDTO: CategorizeBankTransactionDto,
|
||||||
) {
|
) {
|
||||||
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
||||||
|
|
||||||
@@ -68,7 +68,6 @@ export class CategorizeCashflowTransaction {
|
|||||||
await this.eventPublisher.emitAsync(
|
await this.eventPublisher.emitAsync(
|
||||||
events.cashflow.onTransactionCategorizing,
|
events.cashflow.onTransactionCategorizing,
|
||||||
{
|
{
|
||||||
// tenantId,
|
|
||||||
oldUncategorizedTransactions,
|
oldUncategorizedTransactions,
|
||||||
trx,
|
trx,
|
||||||
} as ICashflowTransactionUncategorizingPayload,
|
} as ICashflowTransactionUncategorizingPayload,
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import {
|
import {
|
||||||
CreateUncategorizedTransactionDTO,
|
|
||||||
IUncategorizedTransactionCreatedEventPayload,
|
IUncategorizedTransactionCreatedEventPayload,
|
||||||
IUncategorizedTransactionCreatingEventPayload,
|
IUncategorizedTransactionCreatingEventPayload,
|
||||||
} from '../types/BankingCategorize.types';
|
} from '../types/BankingCategorize.types';
|
||||||
@@ -10,6 +9,7 @@ import { UncategorizedBankTransaction } from '../../BankingTransactions/models/U
|
|||||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { UncategorizedBankTransactionDto } from '../dtos/CreateUncategorizedBankTransaction.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateUncategorizedTransactionService {
|
export class CreateUncategorizedTransactionService {
|
||||||
@@ -30,7 +30,7 @@ export class CreateUncategorizedTransactionService {
|
|||||||
* @returns {Promise<UncategorizedBankTransaction>}
|
* @returns {Promise<UncategorizedBankTransaction>}
|
||||||
*/
|
*/
|
||||||
public create(
|
public create(
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO,
|
createUncategorizedTransactionDTO: UncategorizedBankTransactionDto,
|
||||||
trx?: Knex.Transaction,
|
trx?: Knex.Transaction,
|
||||||
) {
|
) {
|
||||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { UncategorizedBankTransaction } from '../../BankingTransactions/models/U
|
|||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UncategorizeCashflowTransactionService {
|
export class UncategorizeBankTransactionService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly eventPublisher: EventEmitter2,
|
private readonly eventPublisher: EventEmitter2,
|
||||||
private readonly uow: UnitOfWork,
|
private readonly uow: UnitOfWork,
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
import { castArray } from 'lodash';
|
import { castArray } from 'lodash';
|
||||||
import { PromisePool } from '@supercharge/promise-pool';
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { UncategorizeCashflowTransactionService } from './UncategorizeCashflowTransaction.service';
|
import { UncategorizeBankTransactionService } from './UncategorizeBankTransaction.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UncategorizeCashflowTransactionsBulk {
|
export class UncategorizeBankTransactionsBulk {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly uncategorizeTransactionService: UncategorizeCashflowTransactionService
|
private readonly uncategorizeTransactionService: UncategorizeBankTransactionService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uncategorize the given bank transactions in bulk.
|
* Uncategorize the given bank transactions in bulk.
|
||||||
* @param {number} tenantId
|
* @param {number | Array<number>} uncategorizedTransactionId
|
||||||
* @param {number} uncategorizedTransactionId
|
|
||||||
*/
|
*/
|
||||||
public async uncategorizeBulk(
|
public async uncategorizeBulk(
|
||||||
uncategorizedTransactionId: number | Array<number>
|
uncategorizedTransactionId: number | Array<number>
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { ToNumber } from '@/common/decorators/Validators';
|
||||||
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsDateString,
|
||||||
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
} from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for categorizing bank transactions
|
||||||
|
*/
|
||||||
|
export class CategorizeBankTransactionDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'The date of the bank transaction',
|
||||||
|
type: Date,
|
||||||
|
example: '2023-01-01T00:00:00.000Z',
|
||||||
|
})
|
||||||
|
@IsDateString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
date: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'ID of the credit account associated with this transaction',
|
||||||
|
type: Number,
|
||||||
|
example: 1001,
|
||||||
|
})
|
||||||
|
@IsInt()
|
||||||
|
@ToNumber()
|
||||||
|
@IsNotEmpty()
|
||||||
|
creditAccountId: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Optional external reference number',
|
||||||
|
type: String,
|
||||||
|
example: 'REF-001',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
referenceNo: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Optional transaction number or reference',
|
||||||
|
type: String,
|
||||||
|
example: 'TRX-001',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
transactionNumber: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Type of bank transaction (e.g., deposit, withdrawal)',
|
||||||
|
type: String,
|
||||||
|
example: 'deposit',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
transactionType: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Exchange rate for currency conversion',
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
example: 1.15,
|
||||||
|
})
|
||||||
|
@IsNumber()
|
||||||
|
@ToNumber()
|
||||||
|
@IsOptional()
|
||||||
|
exchangeRate: number = 1;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Currency code for the transaction',
|
||||||
|
type: String,
|
||||||
|
example: 'USD',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Description of the bank transaction',
|
||||||
|
type: String,
|
||||||
|
example: 'Monthly rent payment',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'ID of the branch where the transaction occurred',
|
||||||
|
type: Number,
|
||||||
|
example: 101,
|
||||||
|
})
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
branchId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended DTO for categorizing bank transactions with IDs of uncategorized transactions
|
||||||
|
*/
|
||||||
|
export class CategorizeBankTransactionRouteDto extends CategorizeBankTransactionDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Array of uncategorized transaction IDs to be categorized',
|
||||||
|
type: [Number],
|
||||||
|
example: [1001, 1002, 1003],
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
uncategorizedTransactionIds: Array<number>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { IsBoolean, IsDateString, IsNumber, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UncategorizedBankTransactionDto {
|
||||||
|
@IsDateString()
|
||||||
|
date: Date | string;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
accountId: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
amount: number;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
payee?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
referenceNo?: string | null;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
plaidTransactionId?: string | null;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
pending?: boolean;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
pendingPlaidTransactionId?: string | null;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
batch?: string;
|
||||||
|
}
|
||||||
@@ -1,47 +1,44 @@
|
|||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
|
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
|
||||||
import { BankingMatchingApplication } from './BankingMatchingApplication';
|
import { BankingMatchingApplication } from './BankingMatchingApplication';
|
||||||
import { GetMatchedTransactionsFilter, IMatchTransactionDTO } from './types';
|
import { GetMatchedTransactionsFilter } from './types';
|
||||||
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
|
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
|
||||||
|
|
||||||
@Controller('banking/matching')
|
@Controller('banking/matching')
|
||||||
@ApiTags('banking-transactions-matching')
|
@ApiTags('banking-transactions-matching')
|
||||||
export class BankingMatchingController {
|
export class BankingMatchingController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly bankingMatchingApplication: BankingMatchingApplication
|
private readonly bankingMatchingApplication: BankingMatchingApplication,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('matched/transactions')
|
@Get('matched')
|
||||||
@ApiOperation({ summary: 'Retrieves the matched transactions.' })
|
@ApiOperation({ summary: 'Retrieves the matched transactions.' })
|
||||||
async getMatchedTransactions(
|
async getMatchedTransactions(
|
||||||
@Query('uncategorizedTransactionIds') uncategorizedTransactionIds: number[],
|
@Query('uncategorizedTransactionIds') uncategorizedTransactionIds: number[],
|
||||||
@Query() filter: GetMatchedTransactionsFilter
|
@Query() filter: GetMatchedTransactionsFilter,
|
||||||
) {
|
) {
|
||||||
return this.bankingMatchingApplication.getMatchedTransactions(
|
return this.bankingMatchingApplication.getMatchedTransactions(
|
||||||
uncategorizedTransactionIds,
|
uncategorizedTransactionIds,
|
||||||
filter
|
filter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/match/:uncategorizedTransactionId')
|
@Post('/match')
|
||||||
@ApiOperation({ summary: 'Match the given uncategorized transaction.' })
|
@ApiOperation({ summary: 'Match the given uncategorized transaction.' })
|
||||||
async matchTransaction(
|
async matchTransaction(@Body() matchedTransactions: MatchBankTransactionDto) {
|
||||||
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number | number[],
|
|
||||||
@Body() matchedTransactions: MatchBankTransactionDto
|
|
||||||
) {
|
|
||||||
return this.bankingMatchingApplication.matchTransaction(
|
return this.bankingMatchingApplication.matchTransaction(
|
||||||
uncategorizedTransactionId,
|
matchedTransactions.uncategorizedTransactions,
|
||||||
matchedTransactions
|
matchedTransactions.matchedTransactions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/unmatch/:uncategorizedTransactionId')
|
@Post('/unmatch/:uncategorizedTransactionId')
|
||||||
@ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' })
|
@ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' })
|
||||||
async unmatchMatchedTransaction(
|
async unmatchMatchedTransaction(
|
||||||
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number
|
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number,
|
||||||
) {
|
) {
|
||||||
return this.bankingMatchingApplication.unmatchMatchedTransaction(
|
return this.bankingMatchingApplication.unmatchMatchedTransaction(
|
||||||
uncategorizedTransactionId
|
uncategorizedTransactionId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { GetMatchedTransactions } from './queries/GetMatchedTransactions.service';
|
import { GetMatchedTransactions } from './queries/GetMatchedTransactions.service';
|
||||||
import { MatchBankTransactions } from './commands/MatchTransactions';
|
import { MatchBankTransactions } from './commands/MatchTransactions';
|
||||||
import { UnmatchMatchedBankTransaction } from './commands/UnmatchMatchedTransaction.service';
|
import { UnmatchMatchedBankTransaction } from './commands/UnmatchMatchedTransaction.service';
|
||||||
import { GetMatchedTransactionsFilter, IMatchTransactionDTO } from './types';
|
import { GetMatchedTransactionsFilter } from './types';
|
||||||
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
|
import { MatchTransactionEntryDto } from './dtos/MatchBankTransaction.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BankingMatchingApplication {
|
export class BankingMatchingApplication {
|
||||||
@@ -31,17 +31,18 @@ export class BankingMatchingApplication {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches the given uncategorized transaction with the given system transaction.
|
* Matches the given uncategorized transaction with the given system transaction.
|
||||||
* @param {number} uncategorizedTransactionId
|
* @param {IMatchBankTransactionDto} matchedTransactionsDTO
|
||||||
* @param {IMatchTransactionDTO} matchTransactionsDTO
|
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public matchTransaction(
|
public matchTransaction(
|
||||||
uncategorizedTransactionId: number | Array<number>,
|
uncategorizedTransactionId: number | Array<number>,
|
||||||
matchedTransactions: MatchBankTransactionDto,
|
matchedTransactionsDto:
|
||||||
|
| MatchTransactionEntryDto
|
||||||
|
| Array<MatchTransactionEntryDto>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.matchTransactionService.matchTransaction(
|
return this.matchTransactionService.matchTransaction(
|
||||||
uncategorizedTransactionId,
|
uncategorizedTransactionId,
|
||||||
matchedTransactions,
|
matchedTransactionsDto,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { ServiceError } from '@/modules/Items/ServiceError';
|
|||||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { MatchBankTransactionDto } from '../dtos/MatchBankTransaction.dto';
|
import { MatchTransactionEntryDto } from '../dtos/MatchBankTransaction.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MatchBankTransactions {
|
export class MatchBankTransactions {
|
||||||
@@ -107,16 +107,15 @@ export class MatchBankTransactions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches the given uncategorized transaction to the given references.
|
* Matches the given uncategorized transaction to the given references.
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} uncategorizedTransactionId
|
* @param {number} uncategorizedTransactionId
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async matchTransaction(
|
public async matchTransaction(
|
||||||
uncategorizedTransactionId: number | Array<number>,
|
uncategorizedTransactionId: number | Array<number>,
|
||||||
matchedTransactionsDto: MatchBankTransactionDto,
|
matchedTransactionsDto: MatchTransactionEntryDto | Array<MatchTransactionEntryDto>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
||||||
const matchedTransactions = matchedTransactionsDto.entries;
|
const matchedTransactions = castArray(matchedTransactionsDto);
|
||||||
|
|
||||||
// Validates the given matching transactions DTO.
|
// Validates the given matching transactions DTO.
|
||||||
await this.validate(uncategorizedTransactionIds, matchedTransactions);
|
await this.validate(uncategorizedTransactionIds, matchedTransactions);
|
||||||
@@ -131,7 +130,7 @@ export class MatchBankTransactions {
|
|||||||
// Matches the given transactions under promise pool concurrency controlling.
|
// Matches the given transactions under promise pool concurrency controlling.
|
||||||
await PromisePool.withConcurrency(10)
|
await PromisePool.withConcurrency(10)
|
||||||
.for(matchedTransactions)
|
.for(matchedTransactions)
|
||||||
.process(async (matchedTransaction) => {
|
.process(async (matchedTransaction: MatchTransactionEntryDto) => {
|
||||||
const getMatchedTransactionsService =
|
const getMatchedTransactionsService =
|
||||||
this.matchedBankTransactions.registry.get(
|
this.matchedBankTransactions.registry.get(
|
||||||
matchedTransaction.referenceType,
|
matchedTransaction.referenceType,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ArrayMinSize,
|
||||||
IsArray,
|
IsArray,
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
@@ -27,6 +28,10 @@ export class MatchTransactionEntryDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MatchBankTransactionDto {
|
export class MatchBankTransactionDto {
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(1)
|
||||||
|
uncategorizedTransactions: Array<number>
|
||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => MatchTransactionEntryDto)
|
@Type(() => MatchTransactionEntryDto)
|
||||||
@@ -37,5 +42,5 @@ export class MatchBankTransactionDto {
|
|||||||
{ referenceType: 'SaleInvoice', referenceId: 2 },
|
{ referenceType: 'SaleInvoice', referenceId: 2 },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
entries: MatchTransactionEntryDto[];
|
matchedTransactions: MatchTransactionEntryDto[];
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
|
import PromisePool from '@supercharge/promise-pool';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
IBankTransactionMatchedEventPayload,
|
IBankTransactionMatchedEventPayload,
|
||||||
IBankTransactionUnmatchedEventPayload,
|
IBankTransactionUnmatchedEventPayload,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import PromisePool from '@supercharge/promise-pool';
|
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DecrementUncategorizedTransactionOnMatchingSubscriber {
|
export class DecrementUncategorizedTransactionOnMatchingSubscriber {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
import { GetMatchedTransactionCashflowTransformer } from './GetMatchedTransactionCashflowTransformer';
|
import { GetMatchedTransactionCashflowTransformer } from './GetMatchedTransactionCashflowTransformer';
|
||||||
import { GetMatchedTransactionsFilter } from '../types';
|
import { GetMatchedTransactionsFilter } from '../types';
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from '../types';
|
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO, MatchedTransactionsPOJO } from '../types';
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
import { GetMatchedTransactionExpensesTransformer } from './GetMatchedTransactionExpensesTransformer';
|
import { GetMatchedTransactionExpensesTransformer } from './GetMatchedTransactionExpensesTransformer';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { Expense } from '@/modules/Expenses/models/Expense.model';
|
import { Expense } from '@/modules/Expenses/models/Expense.model';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { initialize } from 'objection';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByType {
|
export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByType {
|
||||||
@@ -13,17 +16,26 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
|
|||||||
|
|
||||||
@Inject(Expense.name)
|
@Inject(Expense.name)
|
||||||
protected readonly expenseModel: TenantModelProxy<typeof Expense>,
|
protected readonly expenseModel: TenantModelProxy<typeof Expense>,
|
||||||
|
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantDb: () => Knex,
|
||||||
|
|
||||||
|
@Inject('TENANT_MODELS_INIT')
|
||||||
|
private readonly tenantModelsInit: () => Promise<boolean>,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the matched transactions of expenses.
|
* Retrieves the matched transactions of expenses.
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {GetMatchedTransactionsFilter} filter
|
* @param {GetMatchedTransactionsFilter} filter
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async getMatchedTransactions(filter: GetMatchedTransactionsFilter) {
|
async getMatchedTransactions(
|
||||||
|
filter: GetMatchedTransactionsFilter,
|
||||||
|
): Promise<MatchedTransactionsPOJO> {
|
||||||
|
// await this.tenantModelsInit();
|
||||||
// Retrieve the expense matches.
|
// Retrieve the expense matches.
|
||||||
const expenses = await this.expenseModel()
|
const expenses = await this.expenseModel()
|
||||||
.query()
|
.query()
|
||||||
@@ -49,6 +61,7 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
|
|||||||
}
|
}
|
||||||
query.orderBy('paymentDate', 'DESC');
|
query.orderBy('paymentDate', 'DESC');
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.transformer.transform(
|
return this.transformer.transform(
|
||||||
expenses,
|
expenses,
|
||||||
new GetMatchedTransactionExpensesTransformer(),
|
new GetMatchedTransactionExpensesTransformer(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { first } from 'lodash';
|
import { first } from 'lodash';
|
||||||
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
||||||
@@ -9,7 +10,6 @@ import {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
import { CreatePaymentReceivedService } from '@/modules/PaymentReceived/commands/CreatePaymentReceived.serivce';
|
import { CreatePaymentReceivedService } from '@/modules/PaymentReceived/commands/CreatePaymentReceived.serivce';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
|
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||||
@@ -86,7 +86,6 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the common matched transaction.
|
* Creates the common matched transaction.
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {Array<number>} uncategorizedTransactionIds
|
* @param {Array<number>} uncategorizedTransactionIds
|
||||||
* @param {IMatchTransactionDTO} matchTransactionDTO
|
* @param {IMatchTransactionDTO} matchTransactionDTO
|
||||||
* @param {Knex.Transaction} trx
|
* @param {Knex.Transaction} trx
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { initialize } from 'objection';
|
||||||
import { GetMatchedTransactionManualJournalsTransformer } from './GetMatchedTransactionManualJournalsTransformer';
|
import { GetMatchedTransactionManualJournalsTransformer } from './GetMatchedTransactionManualJournalsTransformer';
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
import { GetMatchedTransactionsFilter } from '../types';
|
import { GetMatchedTransactionsFilter } from '../types';
|
||||||
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
|
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactionsByType {
|
export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactionsByType {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
|||||||
|
|
||||||
export abstract class GetMatchedTransactionsByType {
|
export abstract class GetMatchedTransactionsByType {
|
||||||
@Inject(MatchedBankTransaction.name)
|
@Inject(MatchedBankTransaction.name)
|
||||||
private readonly matchedBankTransactionModel: TenantModelProxy<
|
matchedBankTransactionModel: TenantModelProxy<
|
||||||
typeof MatchedBankTransaction
|
typeof MatchedBankTransaction
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Body, Controller, Post } from '@nestjs/common';
|
||||||
|
import { PlaidApplication } from './PlaidApplication';
|
||||||
|
import { PlaidItemDto } from './dtos/PlaidItem.dto';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
@Controller('banking/plaid')
|
||||||
|
@ApiTags('banking-plaid')
|
||||||
|
export class BankingPlaidController {
|
||||||
|
constructor(private readonly plaidApplication: PlaidApplication) {}
|
||||||
|
|
||||||
|
@Post('link-token')
|
||||||
|
@ApiOperation({ summary: 'Get Plaid link token' })
|
||||||
|
getLinkToken() {
|
||||||
|
return this.plaidApplication.getLinkToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('exchange-token')
|
||||||
|
@ApiOperation({ summary: 'Exchange Plaid access token' })
|
||||||
|
exchangeToken(@Body() itemDTO: PlaidItemDto) {
|
||||||
|
return this.plaidApplication.exchangeToken(itemDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { PlaidUpdateTransactionsOnItemCreatedSubscriber } from './subscribers/PlaidUpdateTransactionsOnItemCreatedSubscriber';
|
import { PlaidUpdateTransactionsOnItemCreatedSubscriber } from './subscribers/PlaidUpdateTransactionsOnItemCreatedSubscriber';
|
||||||
import { PlaidUpdateTransactions } from './command/PlaidUpdateTransactions';
|
import { PlaidUpdateTransactions } from './command/PlaidUpdateTransactions';
|
||||||
@@ -15,6 +16,11 @@ import { PlaidItemService } from './command/PlaidItem';
|
|||||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||||
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
|
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
|
||||||
import { SystemPlaidItem } from './models/SystemPlaidItem';
|
import { SystemPlaidItem } from './models/SystemPlaidItem';
|
||||||
|
import { BankingPlaidController } from './BankingPlaid.controller';
|
||||||
|
import { BankingPlaidWebhooksController } from './BankingPlaidWebhooks.controller';
|
||||||
|
import { SetupPlaidItemTenantService } from './command/SetupPlaidItemTenant.service';
|
||||||
|
import { UpdateBankingPlaidTransitionsQueueJob } from './types/BankingPlaid.types';
|
||||||
|
import { PlaidFetchTransactionsProcessor } from './jobs/PlaidFetchTransactionsJob';
|
||||||
|
|
||||||
const models = [RegisterTenancyModel(PlaidItem)];
|
const models = [RegisterTenancyModel(PlaidItem)];
|
||||||
|
|
||||||
@@ -24,6 +30,7 @@ const models = [RegisterTenancyModel(PlaidItem)];
|
|||||||
AccountsModule,
|
AccountsModule,
|
||||||
BankingCategorizeModule,
|
BankingCategorizeModule,
|
||||||
BankingTransactionsModule,
|
BankingTransactionsModule,
|
||||||
|
BullModule.registerQueue({ name: UpdateBankingPlaidTransitionsQueueJob }),
|
||||||
...models,
|
...models,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -34,9 +41,12 @@ const models = [RegisterTenancyModel(PlaidItem)];
|
|||||||
PlaidWebooks,
|
PlaidWebooks,
|
||||||
PlaidLinkTokenService,
|
PlaidLinkTokenService,
|
||||||
PlaidApplication,
|
PlaidApplication,
|
||||||
PlaidUpdateTransactionsOnItemCreatedSubscriber,
|
SetupPlaidItemTenantService,
|
||||||
TenancyContext,
|
TenancyContext,
|
||||||
|
PlaidFetchTransactionsProcessor,
|
||||||
|
PlaidUpdateTransactionsOnItemCreatedSubscriber,
|
||||||
],
|
],
|
||||||
exports: [...models],
|
exports: [...models],
|
||||||
|
controllers: [BankingPlaidController, BankingPlaidWebhooksController],
|
||||||
})
|
})
|
||||||
export class BankingPlaidModule {}
|
export class BankingPlaidModule {}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Body, Controller, Post } from '@nestjs/common';
|
||||||
|
import { PlaidWebhookDto } from './dtos/PlaidItem.dto';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { PlaidApplication } from './PlaidApplication';
|
||||||
|
import { PublicRoute } from '../Auth/guards/jwt.guard';
|
||||||
|
import { SetupPlaidItemTenantService } from './command/SetupPlaidItemTenant.service';
|
||||||
|
|
||||||
|
@Controller('banking/plaid')
|
||||||
|
@ApiTags('banking-plaid')
|
||||||
|
@PublicRoute()
|
||||||
|
export class BankingPlaidWebhooksController {
|
||||||
|
constructor(
|
||||||
|
private readonly plaidApplication: PlaidApplication,
|
||||||
|
private readonly setupPlaidItemTenantService: SetupPlaidItemTenantService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post('webhooks')
|
||||||
|
@ApiOperation({ summary: 'Listen to Plaid webhooks' })
|
||||||
|
webhooks(@Body() { itemId, webhookType, webhookCode }: PlaidWebhookDto) {
|
||||||
|
return this.setupPlaidItemTenantService.setupPlaidTenant(
|
||||||
|
itemId,
|
||||||
|
() => {
|
||||||
|
return this.plaidApplication.webhooks(
|
||||||
|
itemId,
|
||||||
|
webhookType,
|
||||||
|
webhookCode,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { PlaidLinkTokenService } from './queries/GetPlaidLinkToken.service';
|
import { PlaidLinkTokenService } from './queries/GetPlaidLinkToken.service';
|
||||||
import { PlaidItemService } from './command/PlaidItem';
|
import { PlaidItemService } from './command/PlaidItem';
|
||||||
import { PlaidWebooks } from './command/PlaidWebhooks';
|
import { PlaidWebooks } from './command/PlaidWebhooks';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { PlaidItemDto } from './dtos/PlaidItem.dto';
|
||||||
import { PlaidItemDTO } from './types/BankingPlaid.types';
|
import { SystemPlaidItem } from './models/SystemPlaidItem';
|
||||||
|
import { TenantModel } from '../System/models/TenantModel';
|
||||||
|
import { SystemUser } from '../System/models/SystemUser';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PlaidApplication {
|
export class PlaidApplication {
|
||||||
@@ -10,6 +14,16 @@ export class PlaidApplication {
|
|||||||
private readonly getLinkTokenService: PlaidLinkTokenService,
|
private readonly getLinkTokenService: PlaidLinkTokenService,
|
||||||
private readonly plaidItemService: PlaidItemService,
|
private readonly plaidItemService: PlaidItemService,
|
||||||
private readonly plaidWebhooks: PlaidWebooks,
|
private readonly plaidWebhooks: PlaidWebooks,
|
||||||
|
private readonly clsService: ClsService,
|
||||||
|
|
||||||
|
@Inject(SystemPlaidItem.name)
|
||||||
|
private readonly systemPlaidItemModel: typeof SystemPlaidItem,
|
||||||
|
|
||||||
|
@Inject(TenantModel.name)
|
||||||
|
private readonly tenantModel: typeof TenantModel,
|
||||||
|
|
||||||
|
@Inject(SystemUser.name)
|
||||||
|
private readonly systemUserModel: typeof SystemUser,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +39,7 @@ export class PlaidApplication {
|
|||||||
* @param {PlaidItemDTO} itemDTO
|
* @param {PlaidItemDTO} itemDTO
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public exchangeToken(itemDTO: PlaidItemDTO): Promise<void> {
|
public exchangeToken(itemDTO: PlaidItemDto): Promise<void> {
|
||||||
return this.plaidItemService.item(itemDTO);
|
return this.plaidItemService.item(itemDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,10 +55,33 @@ export class PlaidApplication {
|
|||||||
webhookType: string,
|
webhookType: string,
|
||||||
webhookCode: string,
|
webhookCode: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.plaidWebhooks.webhooks(
|
return this.plaidWebhooks.webhooks(plaidItemId, webhookType, webhookCode);
|
||||||
plaidItemId,
|
}
|
||||||
webhookType,
|
|
||||||
webhookCode,
|
public async setupPlaidTenant(plaidItemId: string, callback: () => void) {
|
||||||
);
|
const plaidItem = await this.systemPlaidItemModel
|
||||||
|
.query()
|
||||||
|
.findOne({ plaidItemId });
|
||||||
|
|
||||||
|
if (!plaidItem) {
|
||||||
|
throw new Error('Plaid item not found');
|
||||||
|
}
|
||||||
|
const tenant = await this.tenantModel
|
||||||
|
.query()
|
||||||
|
.findOne({ id: plaidItem.tenantId })
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const user = await this.systemUserModel
|
||||||
|
.query()
|
||||||
|
.findOne({
|
||||||
|
tenantId: tenant.id,
|
||||||
|
})
|
||||||
|
.modify('active')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
this.clsService.set('organizationId', tenant.organizationId);
|
||||||
|
this.clsService.set('userId', user.id);
|
||||||
|
|
||||||
|
return callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
// import { Request, Response, NextFunction } from 'express';
|
|
||||||
// import { SystemPlaidItem, Tenant } from '@/system/models';
|
|
||||||
// import tenantDependencyInjection from '@/api/middleware/TenantDependencyInjection';
|
|
||||||
|
|
||||||
// export const PlaidWebhookTenantBootMiddleware = async (
|
|
||||||
// req: Request,
|
|
||||||
// res: Response,
|
|
||||||
// next: NextFunction
|
|
||||||
// ) => {
|
|
||||||
// const { item_id: plaidItemId } = req.body;
|
|
||||||
// const plaidItem = await SystemPlaidItem.query().findOne({ plaidItemId });
|
|
||||||
|
|
||||||
// const notFoundOrganization = () => {
|
|
||||||
// return res.boom.unauthorized('Organization identication not found.', {
|
|
||||||
// errors: [{ type: 'ORGANIZATION.ID.NOT.FOUND', code: 100 }],
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
// // In case the given organization not found.
|
|
||||||
// if (!plaidItem) {
|
|
||||||
// return notFoundOrganization();
|
|
||||||
// }
|
|
||||||
// const tenant = await Tenant.query()
|
|
||||||
// .findById(plaidItem.tenantId)
|
|
||||||
// .withGraphFetched('metadata');
|
|
||||||
|
|
||||||
// // When the given organization id not found on the system storage.
|
|
||||||
// if (!tenant) {
|
|
||||||
// return notFoundOrganization();
|
|
||||||
// }
|
|
||||||
// tenantDependencyInjection(req, tenant);
|
|
||||||
// next();
|
|
||||||
// };
|
|
||||||
@@ -6,11 +6,9 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
|
|||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { SystemPlaidItem } from '../models/SystemPlaidItem';
|
import { SystemPlaidItem } from '../models/SystemPlaidItem';
|
||||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
import {
|
import { IPlaidItemCreatedEventPayload } from '../types/BankingPlaid.types';
|
||||||
IPlaidItemCreatedEventPayload,
|
|
||||||
PlaidItemDTO,
|
|
||||||
} from '../types/BankingPlaid.types';
|
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { PlaidItemDto } from '../dtos/PlaidItem.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PlaidItemService {
|
export class PlaidItemService {
|
||||||
@@ -19,9 +17,7 @@ export class PlaidItemService {
|
|||||||
private readonly tenancyContext: TenancyContext,
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
|
||||||
@Inject(SystemPlaidItem.name)
|
@Inject(SystemPlaidItem.name)
|
||||||
private readonly systemPlaidItemModel: TenantModelProxy<
|
private readonly systemPlaidItemModel: typeof SystemPlaidItem,
|
||||||
typeof SystemPlaidItem
|
|
||||||
>,
|
|
||||||
|
|
||||||
@Inject(PlaidItem.name)
|
@Inject(PlaidItem.name)
|
||||||
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||||
@@ -33,10 +29,10 @@ export class PlaidItemService {
|
|||||||
/**
|
/**
|
||||||
* Exchanges the public token to get access token and item id and then creates
|
* Exchanges the public token to get access token and item id and then creates
|
||||||
* a new Plaid item.
|
* a new Plaid item.
|
||||||
* @param {PlaidItemDTO} itemDTO - Plaid item data transfer object.
|
* @param {PlaidItemDto} itemDTO - Plaid item data transfer object.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async item(itemDTO: PlaidItemDTO): Promise<void> {
|
public async item(itemDTO: PlaidItemDto): Promise<void> {
|
||||||
const { publicToken, institutionId } = itemDTO;
|
const { publicToken, institutionId } = itemDTO;
|
||||||
|
|
||||||
const tenant = await this.tenancyContext.getTenant();
|
const tenant = await this.tenancyContext.getTenant();
|
||||||
@@ -57,7 +53,7 @@ export class PlaidItemService {
|
|||||||
plaidInstitutionId: institutionId,
|
plaidInstitutionId: institutionId,
|
||||||
});
|
});
|
||||||
// Stores the Plaid item id on system scope.
|
// Stores the Plaid item id on system scope.
|
||||||
await this.systemPlaidItemModel().query().insert({ tenantId, plaidItemId });
|
await this.systemPlaidItemModel.query().insert({ tenantId, plaidItemId });
|
||||||
|
|
||||||
// Triggers `onPlaidItemCreated` event.
|
// Triggers `onPlaidItemCreated` event.
|
||||||
await this.eventEmitter.emitAsync(events.plaid.onItemCreated, {
|
await this.eventEmitter.emitAsync(events.plaid.onItemCreated, {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import bluebird from 'bluebird';
|
import * as bluebird from 'bluebird';
|
||||||
|
import * as uniqid from 'uniqid';
|
||||||
import { entries, groupBy } from 'lodash';
|
import { entries, groupBy } from 'lodash';
|
||||||
import {
|
import {
|
||||||
AccountBase as PlaidAccountBase,
|
AccountBase as PlaidAccountBase,
|
||||||
@@ -12,7 +13,6 @@ import {
|
|||||||
transformPlaidTrxsToCashflowCreate,
|
transformPlaidTrxsToCashflowCreate,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import uniqid from 'uniqid';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { RemovePendingUncategorizedTransaction } from '../../BankingTransactions/commands/RemovePendingUncategorizedTransaction.service';
|
import { RemovePendingUncategorizedTransaction } from '../../BankingTransactions/commands/RemovePendingUncategorizedTransaction.service';
|
||||||
import { CreateAccountService } from '../../Accounts/CreateAccount.service';
|
import { CreateAccountService } from '../../Accounts/CreateAccount.service';
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PlaidUpdateTransactions {
|
export class PlaidUpdateTransactions {
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {PlaidSyncDb} plaidSync - Plaid sync service.
|
||||||
|
* @param {UnitOfWork} uow - Unit of work.
|
||||||
|
* @param {TenantModelProxy<typeof PlaidItem>} plaidItemModel - Plaid item model.
|
||||||
|
* @param {PlaidApi} plaidClient - Plaid client.
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly plaidSync: PlaidSyncDb,
|
private readonly plaidSync: PlaidSyncDb,
|
||||||
private readonly uow: UnitOfWork,
|
private readonly uow: UnitOfWork,
|
||||||
@@ -28,8 +35,7 @@ export class PlaidUpdateTransactions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles sync the Plaid item to Bigcaptial under UOW.
|
* Handles sync the Plaid item to Bigcaptial under UOW.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {string} plaidItemId - Plaid item id.
|
||||||
* @param {number} plaidItemId - Plaid item id.
|
|
||||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||||
*/
|
*/
|
||||||
public async updateTransactions(plaidItemId: string) {
|
public async updateTransactions(plaidItemId: string) {
|
||||||
@@ -44,9 +50,9 @@ export class PlaidUpdateTransactions {
|
|||||||
* - New bank accounts.
|
* - New bank accounts.
|
||||||
* - Last accounts feeds updated at.
|
* - Last accounts feeds updated at.
|
||||||
* - Turn on the accounts feed flag.
|
* - Turn on the accounts feed flag.
|
||||||
* @param {number} tenantId - Tenant ID.
|
|
||||||
* @param {string} plaidItemId - The Plaid ID for the item.
|
* @param {string} plaidItemId - The Plaid ID for the item.
|
||||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
* @param {Knex.Transaction} trx - Knex transaction.
|
||||||
|
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||||
*/
|
*/
|
||||||
public async updateTransactionsWork(
|
public async updateTransactionsWork(
|
||||||
plaidItemId: string,
|
plaidItemId: string,
|
||||||
@@ -97,7 +103,6 @@ export class PlaidUpdateTransactions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches transactions from the `Plaid API` for a given item.
|
* Fetches transactions from the `Plaid API` for a given item.
|
||||||
* @param {number} tenantId - Tenant ID.
|
|
||||||
* @param {string} plaidItemId - The Plaid ID for the item.
|
* @param {string} plaidItemId - The Plaid ID for the item.
|
||||||
* @returns {Promise<PlaidFetchedTransactionsUpdates>}
|
* @returns {Promise<PlaidFetchedTransactionsUpdates>}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { PlaidItem } from '../models/PlaidItem';
|
import { PlaidItem } from '../models/PlaidItem';
|
||||||
import { PlaidUpdateTransactions } from './PlaidUpdateTransactions';
|
import { PlaidUpdateTransactions } from './PlaidUpdateTransactions';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
@@ -8,7 +9,7 @@ export class PlaidWebooks {
|
|||||||
private readonly updateTransactionsService: PlaidUpdateTransactions,
|
private readonly updateTransactionsService: PlaidUpdateTransactions,
|
||||||
|
|
||||||
@Inject(PlaidItem.name)
|
@Inject(PlaidItem.name)
|
||||||
private readonly plaidItemModel: typeof PlaidItem,
|
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,11 +77,10 @@ export class PlaidWebooks {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async handleTransactionsWebooks(
|
public async handleTransactionsWebooks(
|
||||||
tenantId: number,
|
|
||||||
plaidItemId: string,
|
plaidItemId: string,
|
||||||
webhookCode: string,
|
webhookCode: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const plaidItem = await this.plaidItemModel
|
const plaidItem = await this.plaidItemModel()
|
||||||
.query()
|
.query()
|
||||||
.findOne({ plaidItemId })
|
.findOne({ plaidItemId })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
@@ -122,9 +122,8 @@ export class PlaidWebooks {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all Item webhook events.
|
* Handles all Item webhook events.
|
||||||
* @param {number} tenantId - Tenant ID
|
|
||||||
* @param {string} webhookCode - The webhook code
|
|
||||||
* @param {string} plaidItemId - The Plaid ID for the item
|
* @param {string} plaidItemId - The Plaid ID for the item
|
||||||
|
* @param {string} webhookCode - The webhook code
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async itemsHandler(
|
public async itemsHandler(
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { SystemPlaidItem } from '../models/SystemPlaidItem';
|
||||||
|
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||||
|
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SetupPlaidItemTenantService {
|
||||||
|
constructor(
|
||||||
|
private readonly clsService: ClsService,
|
||||||
|
|
||||||
|
@Inject(SystemPlaidItem.name)
|
||||||
|
private readonly systemPlaidItemModel: typeof SystemPlaidItem,
|
||||||
|
|
||||||
|
@Inject(TenantModel.name)
|
||||||
|
private readonly tenantModel: typeof TenantModel,
|
||||||
|
|
||||||
|
@Inject(SystemUser.name)
|
||||||
|
private readonly systemUserModel: typeof SystemUser,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the Plaid tenant.
|
||||||
|
* @param {string} plaidItemId - The Plaid item id.
|
||||||
|
* @param {() => void} callback - The callback function to execute after setting up the Plaid tenant.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async setupPlaidTenant(plaidItemId: string, callback: () => void) {
|
||||||
|
const plaidItem = await this.systemPlaidItemModel
|
||||||
|
.query()
|
||||||
|
.findOne({ plaidItemId });
|
||||||
|
|
||||||
|
if (!plaidItem) {
|
||||||
|
throw new Error('Plaid item not found');
|
||||||
|
}
|
||||||
|
const tenant = await this.tenantModel
|
||||||
|
.query()
|
||||||
|
.findOne({ id: plaidItem.tenantId })
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const user = await this.systemUserModel
|
||||||
|
.query()
|
||||||
|
.findOne({
|
||||||
|
tenantId: tenant.id,
|
||||||
|
})
|
||||||
|
.modify('active')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
this.clsService.set('organizationId', tenant.organizationId);
|
||||||
|
this.clsService.set('userId', user.id);
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class PlaidItemDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
publicToken: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
institutionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PlaidWebhookDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
itemId: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
webhookType: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
webhookCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,43 +1,46 @@
|
|||||||
// import Container, { Service } from 'typedi';
|
import { Process } from '@nestjs/bull';
|
||||||
// import { PlaidUpdateTransactions } from './PlaidUpdateTransactions';
|
import { UseCls } from 'nestjs-cls';
|
||||||
// import { IPlaidItemCreatedEventPayload } from '@/interfaces';
|
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||||
|
import { Scope } from '@nestjs/common';
|
||||||
|
import { Job } from 'bullmq';
|
||||||
|
import {
|
||||||
|
PlaidFetchTransitonsEventPayload,
|
||||||
|
UpdateBankingPlaidTransitionsJob,
|
||||||
|
UpdateBankingPlaidTransitionsQueueJob,
|
||||||
|
} from '../types/BankingPlaid.types';
|
||||||
|
import { PlaidUpdateTransactions } from '../command/PlaidUpdateTransactions';
|
||||||
|
import { SetupPlaidItemTenantService } from '../command/SetupPlaidItemTenant.service';
|
||||||
|
|
||||||
// @Service()
|
@Processor({
|
||||||
// export class PlaidFetchTransactionsJob {
|
name: UpdateBankingPlaidTransitionsQueueJob,
|
||||||
// /**
|
scope: Scope.REQUEST,
|
||||||
// * Constructor method.
|
})
|
||||||
// */
|
export class PlaidFetchTransactionsProcessor extends WorkerHost {
|
||||||
// constructor(agenda) {
|
constructor(
|
||||||
// agenda.define(
|
private readonly plaidFetchTransactionsService: PlaidUpdateTransactions,
|
||||||
// 'plaid-update-account-transactions',
|
private readonly setupPlaidItemService: SetupPlaidItemTenantService,
|
||||||
// { priority: 'high', concurrency: 2 },
|
) {
|
||||||
// this.handler
|
super();
|
||||||
// );
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Triggers the function.
|
* Triggers the function.
|
||||||
// */
|
*/
|
||||||
// private handler = async (job, done: Function) => {
|
@Process(UpdateBankingPlaidTransitionsJob)
|
||||||
// const { tenantId, plaidItemId } = job.attrs
|
@UseCls()
|
||||||
// .data as IPlaidItemCreatedEventPayload;
|
async process(job: Job<PlaidFetchTransitonsEventPayload>) {
|
||||||
|
const { plaidItemId } = job.data;
|
||||||
|
|
||||||
// const plaidFetchTransactionsService = Container.get(
|
try {
|
||||||
// PlaidUpdateTransactions
|
await this.setupPlaidItemService.setupPlaidTenant(plaidItemId, () => {
|
||||||
// );
|
return this.plaidFetchTransactionsService.updateTransactions(
|
||||||
// const io = Container.get('socket');
|
plaidItemId,
|
||||||
|
);
|
||||||
// try {
|
});
|
||||||
// await plaidFetchTransactionsService.updateTransactions(
|
// Notify the frontend to reflect the new transactions changes.
|
||||||
// tenantId,
|
// io.emit('NEW_TRANSACTIONS_DATA', { plaidItemId });
|
||||||
// plaidItemId
|
} catch (error) {
|
||||||
// );
|
console.log(error);
|
||||||
// // Notify the frontend to reflect the new transactions changes.
|
}
|
||||||
// io.emit('NEW_TRANSACTIONS_DATA', { plaidItemId });
|
}
|
||||||
// done();
|
}
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// done(error);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class SystemPlaidItem extends BaseModel {
|
|||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
static get relationMappings() {
|
static get relationMappings() {
|
||||||
const Tenant = require('system/models/Tenant');
|
const { TenantModel } = require('../../System/models/TenantModel');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +38,7 @@ export class SystemPlaidItem extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
tenant: {
|
tenant: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Tenant.default,
|
modelClass: TenantModel,
|
||||||
join: {
|
join: {
|
||||||
from: 'users.tenantId',
|
from: 'users.tenantId',
|
||||||
to: 'tenants.id',
|
to: 'tenants.id',
|
||||||
|
|||||||
@@ -1,22 +1,34 @@
|
|||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { IPlaidItemCreatedEventPayload } from '../types/BankingPlaid.types';
|
import { Queue } from 'bullmq';
|
||||||
|
import { InjectQueue } from '@nestjs/bullmq';
|
||||||
|
import {
|
||||||
|
IPlaidItemCreatedEventPayload,
|
||||||
|
UpdateBankingPlaidTransitionsJob,
|
||||||
|
UpdateBankingPlaidTransitionsQueueJob,
|
||||||
|
} from '../types/BankingPlaid.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PlaidUpdateTransactionsOnItemCreatedSubscriber {
|
export class PlaidUpdateTransactionsOnItemCreatedSubscriber {
|
||||||
|
constructor(
|
||||||
|
@InjectQueue(UpdateBankingPlaidTransitionsQueueJob)
|
||||||
|
private readonly updateTransitionsQueue: Queue,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the Plaid item transactions
|
* Updates the Plaid item transactions
|
||||||
* @param {IPlaidItemCreatedEventPayload} payload - Event payload.
|
* @param {IPlaidItemCreatedEventPayload} payload - Event payload.
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.plaid.onItemCreated)
|
@OnEvent(events.plaid.onItemCreated)
|
||||||
public async handleUpdateTransactionsOnItemCreated({
|
public async handleUpdateTransactionsOnItemCreated({
|
||||||
tenantId,
|
|
||||||
plaidItemId,
|
plaidItemId,
|
||||||
plaidAccessToken,
|
|
||||||
plaidInstitutionId,
|
|
||||||
}: IPlaidItemCreatedEventPayload) {
|
}: IPlaidItemCreatedEventPayload) {
|
||||||
const payload = { tenantId, plaidItemId };
|
const payload = { plaidItemId };
|
||||||
// await this.agenda.now('plaid-update-account-transactions', payload);
|
|
||||||
};
|
await this.updateTransitionsQueue.add(
|
||||||
|
UpdateBankingPlaidTransitionsJob,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from 'knex';
|
||||||
import { RemovedTransaction, Transaction } from "plaid";
|
import { RemovedTransaction, Transaction } from 'plaid';
|
||||||
|
|
||||||
export interface IPlaidTransactionsSyncedEventPayload {
|
export interface IPlaidTransactionsSyncedEventPayload {
|
||||||
// tenantId: number;
|
// tenantId: number;
|
||||||
plaidAccountId: number;
|
plaidAccountId: number;
|
||||||
batch: string;
|
batch: string;
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlaidItemDTO {
|
export interface PlaidItemDTO {
|
||||||
@@ -13,7 +13,6 @@ export interface PlaidItemDTO {
|
|||||||
institutionId: string;
|
institutionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface PlaidFetchedTransactionsUpdates {
|
export interface PlaidFetchedTransactionsUpdates {
|
||||||
added: Transaction[];
|
added: Transaction[];
|
||||||
modified: Transaction[];
|
modified: Transaction[];
|
||||||
@@ -22,11 +21,20 @@ export interface PlaidFetchedTransactionsUpdates {
|
|||||||
cursor: string;
|
cursor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IPlaidItemCreatedEventPayload {
|
export interface IPlaidItemCreatedEventPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
plaidAccessToken: string;
|
plaidAccessToken: string;
|
||||||
plaidItemId: string;
|
plaidItemId: string;
|
||||||
plaidInstitutionId: string;
|
plaidInstitutionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const UpdateBankingPlaidTransitionsJob =
|
||||||
|
'update-banking-plaid-transitions-job';
|
||||||
|
|
||||||
|
export const UpdateBankingPlaidTransitionsQueueJob =
|
||||||
|
'update-banking-plaid-transitions-query';
|
||||||
|
|
||||||
|
|
||||||
|
export interface PlaidFetchTransitonsEventPayload {
|
||||||
|
plaidItemId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Controller, Get, Param, Query } from '@nestjs/common';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import { RecognizedTransactionsApplication } from './RecognizedTransactions.application';
|
||||||
|
|
||||||
|
@Controller('banking/recognized')
|
||||||
|
@ApiTags('banking-recognized')
|
||||||
|
export class BankingRecognizedTransactionsController {
|
||||||
|
constructor(
|
||||||
|
private readonly recognizedTransactionsApplication: RecognizedTransactionsApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get(':recognizedTransactionId')
|
||||||
|
async getRecognizedTransaction(
|
||||||
|
@Param('recognizedTransactionId') recognizedTransactionId: number,
|
||||||
|
) {
|
||||||
|
return this.recognizedTransactionsApplication.getRecognizedTransaction(
|
||||||
|
Number(recognizedTransactionId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async getRecognizedTransactions(@Query() query: any) {
|
||||||
|
return this.recognizedTransactionsApplication.getRecognizedTransactions(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,46 @@
|
|||||||
import { forwardRef, Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||||
import { RecognizedBankTransaction } from './models/RecognizedBankTransaction';
|
import { RecognizedBankTransaction } from './models/RecognizedBankTransaction';
|
||||||
import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction.service';
|
|
||||||
import { RevertRecognizedTransactionsService } from './commands/RevertRecognizedTransactions.service';
|
import { RevertRecognizedTransactionsService } from './commands/RevertRecognizedTransactions.service';
|
||||||
import { RecognizeTranasctionsService } from './commands/RecognizeTranasctions.service';
|
import { RecognizeTranasctionsService } from './commands/RecognizeTranasctions.service';
|
||||||
import { TriggerRecognizedTransactionsSubscriber } from './events/TriggerRecognizedTransactions';
|
import { TriggerRecognizedTransactionsSubscriber } from './events/TriggerRecognizedTransactions';
|
||||||
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
||||||
import { BankRulesModule } from '../BankRules/BankRules.module';
|
import { BankRulesModule } from '../BankRules/BankRules.module';
|
||||||
|
import { BankingRecognizedTransactionsController } from './BankingRecognizedTransactions.controller';
|
||||||
|
import { RecognizedTransactionsApplication } from './RecognizedTransactions.application';
|
||||||
|
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
|
||||||
|
import { GetRecognizedTransactionService } from './queries/GetRecognizedTransaction.service';
|
||||||
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
|
import { RecognizeUncategorizedTransactionsQueue } from './_types';
|
||||||
|
import { RegonizeTransactionsPrcessor } from './jobs/RecognizeTransactionsJob';
|
||||||
|
import { TenancyModule } from '../Tenancy/Tenancy.module';
|
||||||
|
|
||||||
const models = [RegisterTenancyModel(RecognizedBankTransaction)];
|
const models = [RegisterTenancyModel(RecognizedBankTransaction)];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
BankingTransactionsModule,
|
BankingTransactionsModule,
|
||||||
|
TenancyModule,
|
||||||
forwardRef(() => BankRulesModule),
|
forwardRef(() => BankRulesModule),
|
||||||
|
BullModule.registerQueue({
|
||||||
|
name: RecognizeUncategorizedTransactionsQueue,
|
||||||
|
}),
|
||||||
...models,
|
...models,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
GetAutofillCategorizeTransactionService,
|
RecognizedTransactionsApplication,
|
||||||
|
GetRecognizedTransactionsService,
|
||||||
RevertRecognizedTransactionsService,
|
RevertRecognizedTransactionsService,
|
||||||
RecognizeTranasctionsService,
|
RecognizeTranasctionsService,
|
||||||
TriggerRecognizedTransactionsSubscriber,
|
TriggerRecognizedTransactionsSubscriber,
|
||||||
|
GetRecognizedTransactionService,
|
||||||
|
RegonizeTransactionsPrcessor,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...models,
|
...models,
|
||||||
GetAutofillCategorizeTransactionService,
|
|
||||||
RevertRecognizedTransactionsService,
|
RevertRecognizedTransactionsService,
|
||||||
RecognizeTranasctionsService,
|
RecognizeTranasctionsService,
|
||||||
],
|
],
|
||||||
|
controllers: [BankingRecognizedTransactionsController],
|
||||||
})
|
})
|
||||||
export class BankingTransactionsRegonizeModule {}
|
export class BankingTransactionsRegonizeModule {}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { GetRecognizedTransactionTransformer } from './GetRecognizedTransactionTransformer';
|
import { GetRecognizedTransactionTransformer } from './queries/GetRecognizedTransactionTransformer';
|
||||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../BankingTransactions/models/UncategorizedBankTransaction';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { IGetRecognizedTransactionsQuery } from '../types/BankingTransactions.types';
|
import { IGetRecognizedTransactionsQuery } from '../BankingTransactions/types/BankingTransactions.types';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetRecognizedTransactionsService {
|
export class GetRecognizedTransactionsService {
|
||||||
@@ -10,7 +11,7 @@ export class GetRecognizedTransactionsService {
|
|||||||
private readonly transformer: TransformerInjectable,
|
private readonly transformer: TransformerInjectable,
|
||||||
|
|
||||||
@Inject(UncategorizedBankTransaction.name)
|
@Inject(UncategorizedBankTransaction.name)
|
||||||
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
|
private readonly uncategorizedBankTransactionModel: TenantModelProxy<typeof UncategorizedBankTransaction>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +26,7 @@ export class GetRecognizedTransactionsService {
|
|||||||
...filter,
|
...filter,
|
||||||
};
|
};
|
||||||
const { results, pagination } =
|
const { results, pagination } =
|
||||||
await this.uncategorizedBankTransactionModel.query()
|
await this.uncategorizedBankTransactionModel().query()
|
||||||
.onBuild((q) => {
|
.onBuild((q) => {
|
||||||
q.withGraphFetched('recognizedTransaction.assignAccount');
|
q.withGraphFetched('recognizedTransaction.assignAccount');
|
||||||
q.withGraphFetched('recognizedTransaction.bankRule');
|
q.withGraphFetched('recognizedTransaction.bankRule');
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
|
||||||
|
import { GetRecognizedTransactionService } from './queries/GetRecognizedTransaction.service';
|
||||||
|
import { RevertRecognizedTransactionsService } from './commands/RevertRecognizedTransactions.service';
|
||||||
|
import { IGetRecognizedTransactionsQuery } from '../BankingTransactions/types/BankingTransactions.types';
|
||||||
|
import { RevertRecognizedTransactionsCriteria } from './_types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RecognizedTransactionsApplication {
|
||||||
|
constructor(
|
||||||
|
private readonly getRecognizedTransactionsService: GetRecognizedTransactionsService,
|
||||||
|
private readonly getRecognizedTransactionService: GetRecognizedTransactionService,
|
||||||
|
private readonly revertRecognizedTransactionsService: RevertRecognizedTransactionsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the recognized transactions based on the provided filter.
|
||||||
|
* @param {IGetRecognizedTransactionsQuery} filter - Filter criteria.
|
||||||
|
* @returns {Promise<{ data: any[], pagination: any }>}
|
||||||
|
*/
|
||||||
|
public getRecognizedTransactions(filter: IGetRecognizedTransactionsQuery) {
|
||||||
|
return this.getRecognizedTransactionsService.getRecognizedTranactions(
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a specific recognized transaction by ID.
|
||||||
|
* @param {number} recognizedTransactionId - The ID of the recognized transaction.
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
public getRecognizedTransaction(recognizedTransactionId: number) {
|
||||||
|
return this.getRecognizedTransactionService.getRecognizedTransaction(
|
||||||
|
recognizedTransactionId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts a recognized transaction.
|
||||||
|
* @param {number} ruleId - The ID of the recognized transaction to revert.
|
||||||
|
* @param {RevertRecognizedTransactionsCriteria} transactionsCriteria - The criteria for the recognized transaction to revert.
|
||||||
|
* @param {Knex.Transaction} trx - The transaction to use for the revert operation.
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
public revertRecognizedTransactions(
|
||||||
|
ruleId?: number | Array<number>,
|
||||||
|
transactionsCriteria?: RevertRecognizedTransactionsCriteria,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) {
|
||||||
|
return this.revertRecognizedTransactionsService.revertRecognizedTransactions(
|
||||||
|
ruleId,
|
||||||
|
transactionsCriteria,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { TenantJobPayload } from "@/interfaces/Tenant";
|
||||||
|
|
||||||
export interface RevertRecognizedTransactionsCriteria {
|
export interface RevertRecognizedTransactionsCriteria {
|
||||||
batch?: string;
|
batch?: string;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
@@ -7,3 +9,14 @@ export interface RecognizeTransactionsCriteria {
|
|||||||
batch?: string;
|
batch?: string;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const RecognizeUncategorizedTransactionsJob =
|
||||||
|
'recognize-uncategorized-transactions-job';
|
||||||
|
export const RecognizeUncategorizedTransactionsQueue =
|
||||||
|
'recognize-uncategorized-transactions-queue';
|
||||||
|
|
||||||
|
|
||||||
|
export interface RecognizeUncategorizedTransactionsJobPayload extends TenantJobPayload {
|
||||||
|
ruleId: number,
|
||||||
|
transactionsCriteria: any;
|
||||||
|
}
|
||||||
@@ -2,21 +2,47 @@ import { isEqual, omit } from 'lodash';
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { IBankRuleEventCreatedPayload, IBankRuleEventDeletedPayload, IBankRuleEventEditedPayload } from '@/modules/BankRules/types';
|
import {
|
||||||
|
IBankRuleEventCreatedPayload,
|
||||||
|
IBankRuleEventDeletedPayload,
|
||||||
|
IBankRuleEventEditedPayload,
|
||||||
|
} from '@/modules/BankRules/types';
|
||||||
|
import { Queue } from 'bullmq';
|
||||||
|
import { InjectQueue } from '@nestjs/bullmq';
|
||||||
|
import {
|
||||||
|
RecognizeUncategorizedTransactionsJob,
|
||||||
|
RecognizeUncategorizedTransactionsJobPayload,
|
||||||
|
RecognizeUncategorizedTransactionsQueue,
|
||||||
|
} from '../_types';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TriggerRecognizedTransactionsSubscriber {
|
export class TriggerRecognizedTransactionsSubscriber {
|
||||||
|
constructor(
|
||||||
|
private readonly tenancyContect: TenancyContext,
|
||||||
|
|
||||||
|
@InjectQueue(RecognizeUncategorizedTransactionsQueue)
|
||||||
|
private readonly recognizeTransactionsQueue: Queue,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the recognize uncategorized transactions job on rule created.
|
* Triggers the recognize uncategorized transactions job on rule created.
|
||||||
* @param {IBankRuleEventCreatedPayload} payload -
|
* @param {IBankRuleEventCreatedPayload} payload -
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.bankRules.onCreated)
|
@OnEvent(events.bankRules.onCreated)
|
||||||
private async recognizedTransactionsOnRuleCreated({
|
async recognizedTransactionsOnRuleCreated({
|
||||||
bankRule,
|
bankRule,
|
||||||
}: IBankRuleEventCreatedPayload) {
|
}: IBankRuleEventCreatedPayload) {
|
||||||
const payload = { ruleId: bankRule.id };
|
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
|
||||||
|
const payload = {
|
||||||
|
ruleId: bankRule.id,
|
||||||
|
...tenantPayload,
|
||||||
|
} as RecognizeUncategorizedTransactionsJobPayload;
|
||||||
|
|
||||||
// await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
await this.recognizeTransactionsQueue.add(
|
||||||
|
RecognizeUncategorizedTransactionsJob,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,27 +50,33 @@ export class TriggerRecognizedTransactionsSubscriber {
|
|||||||
* @param {IBankRuleEventEditedPayload} payload -
|
* @param {IBankRuleEventEditedPayload} payload -
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.bankRules.onEdited)
|
@OnEvent(events.bankRules.onEdited)
|
||||||
private async recognizedTransactionsOnRuleEdited({
|
async recognizedTransactionsOnRuleEdited({
|
||||||
editRuleDTO,
|
editRuleDTO,
|
||||||
oldBankRule,
|
oldBankRule,
|
||||||
bankRule,
|
bankRule,
|
||||||
}: IBankRuleEventEditedPayload) {
|
}: IBankRuleEventEditedPayload) {
|
||||||
const payload = { ruleId: bankRule.id };
|
|
||||||
|
|
||||||
// Cannot continue if the new and old bank rule values are the same,
|
// Cannot continue if the new and old bank rule values are the same,
|
||||||
// after excluding `createdAt` and `updatedAt` dates.
|
// after excluding `createdAt` and `updatedAt` dates.
|
||||||
if (
|
if (
|
||||||
isEqual(
|
isEqual(
|
||||||
omit(bankRule, ['createdAt', 'updatedAt']),
|
omit(bankRule, ['createdAt', 'updatedAt']),
|
||||||
omit(oldBankRule, ['createdAt', 'updatedAt'])
|
omit(oldBankRule, ['createdAt', 'updatedAt']),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// await this.agenda.now(
|
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
|
||||||
// 'rerecognize-uncategorized-transactions-job',
|
const payload = {
|
||||||
// payload
|
ruleId: bankRule.id,
|
||||||
// );
|
...tenantPayload,
|
||||||
|
} as RecognizeUncategorizedTransactionsJobPayload;
|
||||||
|
|
||||||
|
// Re-recognize the transactions based on the new rules.
|
||||||
|
await this.recognizeTransactionsQueue.add(
|
||||||
|
RecognizeUncategorizedTransactionsJob,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,15 +84,20 @@ export class TriggerRecognizedTransactionsSubscriber {
|
|||||||
* @param {IBankRuleEventDeletedPayload} payload -
|
* @param {IBankRuleEventDeletedPayload} payload -
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.bankRules.onDeleted)
|
@OnEvent(events.bankRules.onDeleted)
|
||||||
private async recognizedTransactionsOnRuleDeleted({
|
async recognizedTransactionsOnRuleDeleted({
|
||||||
ruleId,
|
ruleId,
|
||||||
}: IBankRuleEventDeletedPayload) {
|
}: IBankRuleEventDeletedPayload) {
|
||||||
const payload = { ruleId };
|
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
|
||||||
|
const payload = {
|
||||||
|
ruleId,
|
||||||
|
...tenantPayload,
|
||||||
|
} as RecognizeUncategorizedTransactionsJobPayload;
|
||||||
|
|
||||||
// await this.agenda.now(
|
// Re-recognize the transactions based on the new rules.
|
||||||
// 'revert-recognized-uncategorized-transactions-job',
|
await this.recognizeTransactionsQueue.add(
|
||||||
// payload
|
RecognizeUncategorizedTransactionsJob,
|
||||||
// );
|
payload,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,7 +105,7 @@ export class TriggerRecognizedTransactionsSubscriber {
|
|||||||
* @param {IImportFileCommitedEventPayload} payload -
|
* @param {IImportFileCommitedEventPayload} payload -
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.import.onImportCommitted)
|
@OnEvent(events.import.onImportCommitted)
|
||||||
private async triggerRecognizeTransactionsOnImportCommitted({
|
async triggerRecognizeTransactionsOnImportCommitted({
|
||||||
importId,
|
importId,
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -76,10 +113,8 @@ export class TriggerRecognizedTransactionsSubscriber {
|
|||||||
// const importFile = await Import.query().findOne({ importId });
|
// const importFile = await Import.query().findOne({ importId });
|
||||||
// const batch = importFile.paramsParsed.batch;
|
// const batch = importFile.paramsParsed.batch;
|
||||||
// const payload = { transactionsCriteria: { batch } };
|
// const payload = { transactionsCriteria: { batch } };
|
||||||
|
|
||||||
// // Cannot continue if the imported resource is not bank account transactions.
|
// // Cannot continue if the imported resource is not bank account transactions.
|
||||||
// if (importFile.resource !== 'UncategorizedCashflowTransaction') return;
|
// if (importFile.resource !== 'UncategorizedCashflowTransaction') return;
|
||||||
|
|
||||||
// await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
// await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,48 @@
|
|||||||
// import Container, { Service } from 'typedi';
|
import { Job } from 'bullmq';
|
||||||
// import { RecognizeTranasctionsService } from '../commands/RecognizeTranasctions.service';
|
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||||
|
import { Scope } from '@nestjs/common';
|
||||||
|
import { ClsService, UseCls } from 'nestjs-cls';
|
||||||
|
import { RecognizeTranasctionsService } from '../commands/RecognizeTranasctions.service';
|
||||||
|
import {
|
||||||
|
RecognizeUncategorizedTransactionsJobPayload,
|
||||||
|
RecognizeUncategorizedTransactionsQueue,
|
||||||
|
} from '../_types';
|
||||||
|
import { Process } from '@nestjs/bull';
|
||||||
|
|
||||||
// @Service()
|
@Processor({
|
||||||
// export class RegonizeTransactionsJob {
|
name: RecognizeUncategorizedTransactionsQueue,
|
||||||
// /**
|
scope: Scope.REQUEST,
|
||||||
// * Constructor method.
|
})
|
||||||
// */
|
export class RegonizeTransactionsPrcessor extends WorkerHost {
|
||||||
// constructor(agenda) {
|
/**
|
||||||
// agenda.define(
|
* @param {RecognizeTranasctionsService} recognizeTranasctionsService -
|
||||||
// 'recognize-uncategorized-transactions-job',
|
* @param {ClsService} clsService -
|
||||||
// { priority: 'high', concurrency: 2 },
|
*/
|
||||||
// this.handler
|
constructor(
|
||||||
// );
|
private readonly recognizeTranasctionsService: RecognizeTranasctionsService,
|
||||||
// }
|
private readonly clsService: ClsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Triggers sending invoice mail.
|
* Triggers sending invoice mail.
|
||||||
// */
|
*/
|
||||||
// private handler = async (job, done: Function) => {
|
@Process(RecognizeUncategorizedTransactionsQueue)
|
||||||
// const { tenantId, ruleId, transactionsCriteria } = job.attrs.data;
|
@UseCls()
|
||||||
// const regonizeTransactions = Container.get(RecognizeTranasctionsService);
|
async process(job: Job<RecognizeUncategorizedTransactionsJobPayload>) {
|
||||||
|
const { ruleId, transactionsCriteria } = job.data;
|
||||||
|
|
||||||
// try {
|
this.clsService.set('organizationId', job.data.organizationId);
|
||||||
// await regonizeTransactions.recognizeTransactions(
|
this.clsService.set('userId', job.data.userId);
|
||||||
// tenantId,
|
|
||||||
// ruleId,
|
try {
|
||||||
// transactionsCriteria
|
await this.recognizeTranasctionsService.recognizeTransactions(
|
||||||
// );
|
ruleId,
|
||||||
// done();
|
transactionsCriteria,
|
||||||
// } catch (error) {
|
);
|
||||||
// console.log(error);
|
} catch (error) {
|
||||||
// done(error);
|
console.log(error);
|
||||||
// }
|
}
|
||||||
// };
|
}
|
||||||
// }
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { GetRecognizedTransactionTransformer } from './GetRecognizedTransactionTransformer';
|
import { GetRecognizedTransactionTransformer } from './GetRecognizedTransactionTransformer';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../../BankingTransactions/models/UncategorizedBankTransaction';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import {
|
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Delete,
|
|
||||||
Get,
|
|
||||||
Param,
|
|
||||||
Post,
|
|
||||||
Query,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
|
||||||
import { BankingTransactionsApplication } from './BankingTransactionsApplication.service';
|
|
||||||
import {
|
|
||||||
IBankAccountsFilter,
|
|
||||||
ICashflowAccountTransactionsQuery,
|
|
||||||
} from './types/BankingTransactions.types';
|
|
||||||
import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto';
|
|
||||||
|
|
||||||
@Controller('banking/transactions')
|
|
||||||
@ApiTags('banking-transactions')
|
|
||||||
export class BankingTransactionsController {
|
|
||||||
constructor(
|
|
||||||
private readonly bankingTransactionsApplication: BankingTransactionsApplication,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
async getBankAccountTransactions(
|
|
||||||
@Query() query: ICashflowAccountTransactionsQuery,
|
|
||||||
) {
|
|
||||||
return this.bankingTransactionsApplication.getBankAccountTransactions(
|
|
||||||
query,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
async createTransaction(@Body() transactionDTO: CreateBankTransactionDto) {
|
|
||||||
return this.bankingTransactionsApplication.createTransaction(
|
|
||||||
transactionDTO,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete(':id')
|
|
||||||
async deleteTransaction(@Param('id') transactionId: string) {
|
|
||||||
return this.bankingTransactionsApplication.deleteTransaction(
|
|
||||||
Number(transactionId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(':id')
|
|
||||||
async getTransaction(@Param('id') transactionId: string) {
|
|
||||||
return this.bankingTransactionsApplication.getTransaction(
|
|
||||||
Number(transactionId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,13 +19,19 @@ import { CommandBankTransactionValidator } from './commands/CommandCasflowValida
|
|||||||
import { BranchTransactionDTOTransformer } from '../Branches/integrations/BranchTransactionDTOTransform';
|
import { BranchTransactionDTOTransformer } from '../Branches/integrations/BranchTransactionDTOTransform';
|
||||||
import { BranchesModule } from '../Branches/Branches.module';
|
import { BranchesModule } from '../Branches/Branches.module';
|
||||||
import { RemovePendingUncategorizedTransaction } from './commands/RemovePendingUncategorizedTransaction.service';
|
import { RemovePendingUncategorizedTransaction } from './commands/RemovePendingUncategorizedTransaction.service';
|
||||||
import { BankingTransactionsController } from './BankingTransactions.controller';
|
import { BankingTransactionsController } from './controllers/BankingTransactions.controller';
|
||||||
import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
||||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||||
import { BankAccount } from './models/BankAccount';
|
import { BankAccount } from './models/BankAccount';
|
||||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||||
import { GetBankAccountTransactionsService } from './queries/GetBankAccountTransactions/GetBankAccountTransactions.service';
|
import { GetBankAccountTransactionsService } from './queries/GetBankAccountTransactions/GetBankAccountTransactions.service';
|
||||||
import { GetBankAccountTransactionsRepository } from './queries/GetBankAccountTransactions/GetBankAccountTransactionsRepo.service';
|
import { GetBankAccountTransactionsRepository } from './queries/GetBankAccountTransactions/GetBankAccountTransactionsRepo.service';
|
||||||
|
import { GetUncategorizedTransactions } from './queries/GetUncategorizedTransactions';
|
||||||
|
import { GetUncategorizedBankTransactionService } from './queries/GetUncategorizedBankTransaction.service';
|
||||||
|
import { BankingUncategorizedTransactionsController } from './controllers/BankingUncategorizedTransactions.controller';
|
||||||
|
import { BankingPendingTransactionsController } from './controllers/BankingPendingTransactions.controller';
|
||||||
|
import { GetPendingBankAccountTransactions } from './queries/GetPendingBankAccountTransaction.service';
|
||||||
|
import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction/GetAutofillCategorizeTransaction.service';
|
||||||
|
|
||||||
const models = [
|
const models = [
|
||||||
RegisterTenancyModel(UncategorizedBankTransaction),
|
RegisterTenancyModel(UncategorizedBankTransaction),
|
||||||
@@ -42,7 +48,11 @@ const models = [
|
|||||||
DynamicListModule,
|
DynamicListModule,
|
||||||
...models,
|
...models,
|
||||||
],
|
],
|
||||||
controllers: [BankingTransactionsController],
|
controllers: [
|
||||||
|
BankingTransactionsController,
|
||||||
|
BankingUncategorizedTransactionsController,
|
||||||
|
BankingPendingTransactionsController,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
BankTransactionAutoIncrement,
|
BankTransactionAutoIncrement,
|
||||||
BankTransactionGLEntriesService,
|
BankTransactionGLEntriesService,
|
||||||
@@ -61,7 +71,16 @@ const models = [
|
|||||||
RemovePendingUncategorizedTransaction,
|
RemovePendingUncategorizedTransaction,
|
||||||
GetBankAccountTransactionsRepository,
|
GetBankAccountTransactionsRepository,
|
||||||
GetBankAccountTransactionsService,
|
GetBankAccountTransactionsService,
|
||||||
|
GetUncategorizedTransactions,
|
||||||
|
GetUncategorizedBankTransactionService,
|
||||||
|
GetPendingBankAccountTransactions,
|
||||||
|
GetAutofillCategorizeTransactionService,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
...models,
|
||||||
|
RemovePendingUncategorizedTransaction,
|
||||||
|
CommandBankTransactionValidator,
|
||||||
|
CreateBankTransactionService
|
||||||
],
|
],
|
||||||
exports: [...models, RemovePendingUncategorizedTransaction],
|
|
||||||
})
|
})
|
||||||
export class BankingTransactionsModule {}
|
export class BankingTransactionsModule {}
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ import {
|
|||||||
import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
||||||
import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto';
|
import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto';
|
||||||
import { GetBankAccountTransactionsService } from './queries/GetBankAccountTransactions/GetBankAccountTransactions.service';
|
import { GetBankAccountTransactionsService } from './queries/GetBankAccountTransactions/GetBankAccountTransactions.service';
|
||||||
|
import { GetUncategorizedTransactions } from './queries/GetUncategorizedTransactions';
|
||||||
|
import { GetUncategorizedBankTransactionService } from './queries/GetUncategorizedBankTransaction.service';
|
||||||
|
import { GetUncategorizedTransactionsQueryDto } from './dtos/GetUncategorizedTransactionsQuery.dto';
|
||||||
|
import { GetPendingBankAccountTransactions } from './queries/GetPendingBankAccountTransaction.service';
|
||||||
|
import { GetPendingTransactionsQueryDto } from './dtos/GetPendingTransactionsQuery.dto';
|
||||||
|
import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction/GetAutofillCategorizeTransaction.service';
|
||||||
|
import { GetBankTransactionsQueryDto } from './dtos/GetBankTranasctionsQuery.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BankingTransactionsApplication {
|
export class BankingTransactionsApplication {
|
||||||
@@ -18,6 +25,10 @@ export class BankingTransactionsApplication {
|
|||||||
private readonly getCashflowTransactionService: GetBankTransactionService,
|
private readonly getCashflowTransactionService: GetBankTransactionService,
|
||||||
private readonly getBankAccountsService: GetBankAccountsService,
|
private readonly getBankAccountsService: GetBankAccountsService,
|
||||||
private readonly getBankAccountTransactionsService: GetBankAccountTransactionsService,
|
private readonly getBankAccountTransactionsService: GetBankAccountTransactionsService,
|
||||||
|
private readonly getBankAccountUncategorizedTransitionsService: GetUncategorizedTransactions,
|
||||||
|
private readonly getBankAccountUncategorizedTransactionService: GetUncategorizedBankTransactionService,
|
||||||
|
private readonly getPendingBankAccountTransactionsService: GetPendingBankAccountTransactions,
|
||||||
|
private readonly getAutofillCategorizeTransactionService: GetAutofillCategorizeTransactionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +55,7 @@ export class BankingTransactionsApplication {
|
|||||||
* Retrieves the bank transactions of the given bank id.
|
* Retrieves the bank transactions of the given bank id.
|
||||||
* @param {ICashflowAccountTransactionsQuery} query
|
* @param {ICashflowAccountTransactionsQuery} query
|
||||||
*/
|
*/
|
||||||
public getBankAccountTransactions(query: ICashflowAccountTransactionsQuery) {
|
public getBankAccountTransactions(query: GetBankTransactionsQueryDto) {
|
||||||
return this.getBankAccountTransactionsService.bankAccountTransactions(
|
return this.getBankAccountTransactionsService.bankAccountTransactions(
|
||||||
query,
|
query,
|
||||||
);
|
);
|
||||||
@@ -68,4 +79,53 @@ export class BankingTransactionsApplication {
|
|||||||
public getBankAccounts(filterDTO: IBankAccountsFilter) {
|
public getBankAccounts(filterDTO: IBankAccountsFilter) {
|
||||||
return this.getBankAccountsService.getBankAccounts(filterDTO);
|
return this.getBankAccountsService.getBankAccounts(filterDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the uncategorized cashflow transactions.
|
||||||
|
* @param {number} accountId - Account id.
|
||||||
|
* @param {IGetUncategorizedTransactionsQuery} query - Query.
|
||||||
|
*/
|
||||||
|
public getBankAccountUncategorizedTransactions(
|
||||||
|
accountId: number,
|
||||||
|
query: GetUncategorizedTransactionsQueryDto,
|
||||||
|
) {
|
||||||
|
return this.getBankAccountUncategorizedTransitionsService.getTransactions(
|
||||||
|
accountId,
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves specific uncategorized cashflow transaction.
|
||||||
|
* @param {number} uncategorizedTransactionId - Uncategorized transaction id.
|
||||||
|
*/
|
||||||
|
public getUncategorizedTransaction(uncategorizedTransactionId: number) {
|
||||||
|
return this.getBankAccountUncategorizedTransactionService.getTransaction(
|
||||||
|
uncategorizedTransactionId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the pending bank account transactions.
|
||||||
|
* @param {GetPendingTransactionsQueryDto} filter - Pending transactions query.
|
||||||
|
*/
|
||||||
|
public getPendingBankAccountTransactions(
|
||||||
|
filter?: GetPendingTransactionsQueryDto,
|
||||||
|
) {
|
||||||
|
return this.getPendingBankAccountTransactionsService.getPendingTransactions(
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the autofill values of categorize transactions form.
|
||||||
|
* @param {Array<number> | number} uncategorizeTransactionsId - Uncategorized transactions ids.
|
||||||
|
*/
|
||||||
|
public getAutofillCategorizeTransaction(
|
||||||
|
uncategorizeTransactionsId: Array<number> | number,
|
||||||
|
) {
|
||||||
|
return this.getAutofillCategorizeTransactionService.getAutofillCategorizeTransaction(
|
||||||
|
uncategorizeTransactionsId,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
import { includes, camelCase, upperFirst, sumBy } from 'lodash';
|
import { includes, camelCase, upperFirst, sumBy } from 'lodash';
|
||||||
import { getCashflowTransactionType } from '../utils';
|
import { getCashflowTransactionType } from '../utils';
|
||||||
import {
|
import {
|
||||||
@@ -6,7 +7,6 @@ import {
|
|||||||
ERRORS,
|
ERRORS,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||||
import { BankTransaction } from '../models/BankTransaction';
|
import { BankTransaction } from '../models/BankTransaction';
|
||||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import * as R from 'ramda';
|
|
||||||
import * as composeAsync from 'async/compose';
|
import * as composeAsync from 'async/compose';
|
||||||
import { CASHFLOW_TRANSACTION_TYPE } from '../constants';
|
import { CASHFLOW_TRANSACTION_TYPE } from '../constants';
|
||||||
import { transformCashflowTransactionType } from '../utils';
|
import { transformCashflowTransactionType } from '../utils';
|
||||||
@@ -14,12 +13,12 @@ import { events } from '@/common/events/events';
|
|||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { BankTransaction } from '../models/BankTransaction';
|
import { BankTransaction } from '../models/BankTransaction';
|
||||||
import {
|
import {
|
||||||
ICashflowNewCommandDTO,
|
|
||||||
ICommandCashflowCreatedPayload,
|
ICommandCashflowCreatedPayload,
|
||||||
ICommandCashflowCreatingPayload,
|
ICommandCashflowCreatingPayload,
|
||||||
} from '../types/BankingTransactions.types';
|
} from '../types/BankingTransactions.types';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto';
|
import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto';
|
||||||
|
import { formatDateFields } from '@/utils/format-date-fields';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateBankTransactionService {
|
export class CreateBankTransactionService {
|
||||||
@@ -42,7 +41,7 @@ export class CreateBankTransactionService {
|
|||||||
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO
|
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO
|
||||||
*/
|
*/
|
||||||
public authorize = async (
|
public authorize = async (
|
||||||
newCashflowTransactionDTO: ICashflowNewCommandDTO,
|
newCashflowTransactionDTO: CreateBankTransactionDto,
|
||||||
creditAccount: Account,
|
creditAccount: Account,
|
||||||
) => {
|
) => {
|
||||||
const transactionType = transformCashflowTransactionType(
|
const transactionType = transformCashflowTransactionType(
|
||||||
@@ -60,7 +59,7 @@ export class CreateBankTransactionService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes owner contribution DTO to cashflow transaction.
|
* Transformes owner contribution DTO to cashflow transaction.
|
||||||
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO - New transaction DTO.
|
* @param {CreateBankTransactionDto} newCashflowTransactionDTO - New transaction DTO.
|
||||||
* @returns {ICashflowTransactionInput} - Cashflow transaction object.
|
* @returns {ICashflowTransactionInput} - Cashflow transaction object.
|
||||||
*/
|
*/
|
||||||
private transformCashflowTransactionDTO = async (
|
private transformCashflowTransactionDTO = async (
|
||||||
@@ -91,7 +90,7 @@ export class CreateBankTransactionService {
|
|||||||
|
|
||||||
const initialDTO = {
|
const initialDTO = {
|
||||||
amount,
|
amount,
|
||||||
...fromDTO,
|
...formatDateFields(fromDTO, ['date']),
|
||||||
transactionNumber,
|
transactionNumber,
|
||||||
currencyCode: cashflowAccount.currencyCode,
|
currencyCode: cashflowAccount.currencyCode,
|
||||||
exchangeRate: fromDTO?.exchangeRate || 1,
|
exchangeRate: fromDTO?.exchangeRate || 1,
|
||||||
@@ -117,7 +116,7 @@ export class CreateBankTransactionService {
|
|||||||
* @returns {Promise<ICashflowTransaction>}
|
* @returns {Promise<ICashflowTransaction>}
|
||||||
*/
|
*/
|
||||||
public newCashflowTransaction = async (
|
public newCashflowTransaction = async (
|
||||||
newTransactionDTO: ICashflowNewCommandDTO,
|
newTransactionDTO: CreateBankTransactionDto,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
): Promise<BankTransaction> => {
|
): Promise<BankTransaction> => {
|
||||||
// Retrieves the cashflow account or throw not found error.
|
// Retrieves the cashflow account or throw not found error.
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Controller, Get, Query } from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
|
||||||
|
import { BankingTransactionsApplication } from '../BankingTransactionsApplication.service';
|
||||||
|
import { GetPendingTransactionsQueryDto } from '../dtos/GetPendingTransactionsQuery.dto';
|
||||||
|
|
||||||
|
@Controller('banking/pending')
|
||||||
|
@ApiTags('banking-pending')
|
||||||
|
export class BankingPendingTransactionsController {
|
||||||
|
constructor(
|
||||||
|
private readonly bankingTransactionsApplication: BankingTransactionsApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get pending bank account transactions' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'Returns a list of pending bank account transactions',
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'page',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
description: 'Page number for pagination',
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'pageSize',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
description: 'Number of items per page',
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'accountId',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
description: 'Filter by bank account ID',
|
||||||
|
})
|
||||||
|
async getPendingTransactions(@Query() query: GetPendingTransactionsQueryDto) {
|
||||||
|
return this.bankingTransactionsApplication.getPendingBankAccountTransactions(
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery, ApiBody } from '@nestjs/swagger';
|
||||||
|
import { BankingTransactionsApplication } from '../BankingTransactionsApplication.service';
|
||||||
|
import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto';
|
||||||
|
import { GetBankTransactionsQueryDto } from '../dtos/GetBankTranasctionsQuery.dto';
|
||||||
|
|
||||||
|
@Controller('banking/transactions')
|
||||||
|
@ApiTags('banking-transactions')
|
||||||
|
export class BankingTransactionsController {
|
||||||
|
constructor(
|
||||||
|
private readonly bankingTransactionsApplication: BankingTransactionsApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get bank account transactions' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'Returns a list of bank account transactions',
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'page',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
description: 'Page number for pagination',
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'pageSize',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
description: 'Number of items per page',
|
||||||
|
})
|
||||||
|
async getBankAccountTransactions(
|
||||||
|
@Query() query: GetBankTransactionsQueryDto,
|
||||||
|
) {
|
||||||
|
return this.bankingTransactionsApplication.getBankAccountTransactions(
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: 'Create a new bank transaction' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 201,
|
||||||
|
description: 'The bank transaction has been successfully created',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: 'Invalid input data',
|
||||||
|
})
|
||||||
|
@ApiBody({ type: CreateBankTransactionDto })
|
||||||
|
async createTransaction(@Body() transactionDTO: CreateBankTransactionDto) {
|
||||||
|
return this.bankingTransactionsApplication.createTransaction(
|
||||||
|
transactionDTO,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: 'Delete a bank transaction' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'The bank transaction has been successfully deleted',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 404,
|
||||||
|
description: 'Bank transaction not found',
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'id',
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
description: 'Bank transaction ID',
|
||||||
|
})
|
||||||
|
async deleteTransaction(@Param('id') transactionId: string) {
|
||||||
|
return this.bankingTransactionsApplication.deleteTransaction(
|
||||||
|
Number(transactionId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get a specific bank transaction by ID' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'Returns the bank transaction details',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 404,
|
||||||
|
description: 'Bank transaction not found',
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'id',
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
description: 'Bank transaction ID',
|
||||||
|
})
|
||||||
|
async getTransaction(@Param('id') transactionId: string) {
|
||||||
|
return this.bankingTransactionsApplication.getTransaction(
|
||||||
|
Number(transactionId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { Controller, Get, Param, Query } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiOperation,
|
||||||
|
ApiResponse,
|
||||||
|
ApiParam,
|
||||||
|
ApiQuery,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import { GetUncategorizedTransactionsQueryDto } from '../dtos/GetUncategorizedTransactionsQuery.dto';
|
||||||
|
import { BankingTransactionsApplication } from '../BankingTransactionsApplication.service';
|
||||||
|
|
||||||
|
@Controller('banking/uncategorized')
|
||||||
|
@ApiTags('banking-uncategorized')
|
||||||
|
export class BankingUncategorizedTransactionsController {
|
||||||
|
constructor(
|
||||||
|
private readonly bankingTransactionsApplication: BankingTransactionsApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('autofill')
|
||||||
|
@ApiOperation({ summary: 'Get autofill values for categorize transactions' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'Returns autofill values for categorize transactions',
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'accountId',
|
||||||
|
required: true,
|
||||||
|
type: Number,
|
||||||
|
description: 'Bank account ID',
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'uncategorizeTransactionsId',
|
||||||
|
required: true,
|
||||||
|
type: Number,
|
||||||
|
description: 'Uncategorize transactions ID',
|
||||||
|
})
|
||||||
|
async getAutofillCategorizeTransaction(
|
||||||
|
@Query('uncategorizedTransactionIds') uncategorizedTransactionIds: Array<number> | number,
|
||||||
|
) {
|
||||||
|
console.log(uncategorizedTransactionIds)
|
||||||
|
return this.bankingTransactionsApplication.getAutofillCategorizeTransaction(
|
||||||
|
uncategorizedTransactionIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('accounts/:accountId')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Get uncategorized transactions for a specific bank account',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description:
|
||||||
|
'Returns a list of uncategorized transactions for the specified bank account',
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'accountId',
|
||||||
|
required: true,
|
||||||
|
type: Number,
|
||||||
|
description: 'Bank account ID',
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'page',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
description: 'Page number for pagination',
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'pageSize',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
description: 'Number of items per page',
|
||||||
|
})
|
||||||
|
async getBankAccountUncategorizedTransactions(
|
||||||
|
@Param('accountId') accountId: number,
|
||||||
|
@Query() query: GetUncategorizedTransactionsQueryDto,
|
||||||
|
) {
|
||||||
|
return this.bankingTransactionsApplication.getBankAccountUncategorizedTransactions(
|
||||||
|
accountId,
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':uncategorizedTransactionId')
|
||||||
|
@ApiOperation({ summary: 'Get a specific uncategorized transaction by ID' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'Returns the uncategorized transaction details',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 404,
|
||||||
|
description: 'Uncategorized transaction not found',
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'uncategorizedTransactionId',
|
||||||
|
required: true,
|
||||||
|
type: Number,
|
||||||
|
description: 'Uncategorized transaction ID',
|
||||||
|
})
|
||||||
|
async getUncategorizedTransaction(
|
||||||
|
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number,
|
||||||
|
) {
|
||||||
|
return this.bankingTransactionsApplication.getUncategorizedTransaction(
|
||||||
|
Number(uncategorizedTransactionId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +1,153 @@
|
|||||||
|
import { ToNumber } from '@/common/decorators/Validators';
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsDate,
|
IsDateString,
|
||||||
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class CreateBankTransactionDto {
|
export class CreateBankTransactionDto {
|
||||||
@IsDate()
|
@ApiProperty({
|
||||||
|
description: 'The date of the bank transaction',
|
||||||
|
type: Date,
|
||||||
|
example: '2023-01-01T00:00:00.000Z',
|
||||||
|
})
|
||||||
|
@IsDateString()
|
||||||
|
@IsNotEmpty()
|
||||||
date: Date;
|
date: Date;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Optional transaction number or reference',
|
||||||
|
type: String,
|
||||||
|
example: 'TRX-001',
|
||||||
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
transactionNumber: string;
|
@IsOptional()
|
||||||
|
transactionNumber?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Optional external reference number',
|
||||||
|
type: String,
|
||||||
|
example: 'REF-001',
|
||||||
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
referenceNo: string;
|
@IsOptional()
|
||||||
|
referenceNo?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Type of bank transaction (e.g., deposit, withdrawal)',
|
||||||
|
type: String,
|
||||||
|
example: 'deposit',
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
transactionType: string;
|
transactionType: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Description of the bank transaction',
|
||||||
|
type: String,
|
||||||
|
example: 'Monthly rent payment',
|
||||||
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Transaction amount',
|
||||||
|
type: Number,
|
||||||
|
example: 1000.5,
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ToNumber()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Exchange rate for currency conversion',
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
example: 1.15,
|
||||||
|
})
|
||||||
|
@ToNumber()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
exchangeRate: number;
|
exchangeRate: number = 1;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Currency code for the transaction',
|
||||||
|
type: String,
|
||||||
|
example: 'USD',
|
||||||
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
|
|
||||||
@IsNumber()
|
@ApiProperty({
|
||||||
|
description: 'ID of the credit account associated with this transaction',
|
||||||
|
type: Number,
|
||||||
|
example: 1001,
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ToNumber()
|
||||||
|
@IsInt()
|
||||||
creditAccountId: number;
|
creditAccountId: number;
|
||||||
|
|
||||||
@IsNumber()
|
@ApiProperty({
|
||||||
|
description: 'ID of the cashflow account associated with this transaction',
|
||||||
|
type: Number,
|
||||||
|
example: 2001,
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ToNumber()
|
||||||
|
@IsInt()
|
||||||
cashflowAccountId: number;
|
cashflowAccountId: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Whether the transaction should be published',
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
publish: boolean;
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
publish: boolean = true;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'ID of the branch where the transaction occurred',
|
||||||
|
type: Number,
|
||||||
|
example: 101,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@ToNumber()
|
||||||
|
@IsInt()
|
||||||
branchId?: number;
|
branchId?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Plaid transaction ID if imported from Plaid',
|
||||||
|
type: String,
|
||||||
|
example: 'plaid_trx_12345',
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
plaidTransactionId?: string;
|
plaidTransactionId?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Plaid account ID if imported from Plaid',
|
||||||
|
type: String,
|
||||||
|
example: 'plaid_acc_67890',
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
plaidAccountId?: string;
|
plaidAccountId?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
'ID of the uncategorized transaction if this is categorizing an existing transaction',
|
||||||
|
type: Number,
|
||||||
|
example: 5001,
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsInt()
|
||||||
uncategorizedTransactionId?: number;
|
uncategorizedTransactionId?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsNumberString,
|
||||||
|
IsOptional,
|
||||||
|
} from 'class-validator';
|
||||||
|
import { NumberFormatQueryDto } from './NumberFormatQuery.dto';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class GetBankTransactionsQueryDto {
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsNumber()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Page number for pagination',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 1
|
||||||
|
})
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsNumber()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Number of items per page',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 10
|
||||||
|
})
|
||||||
|
pageSize: number;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsNumber()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Bank account ID',
|
||||||
|
required: true,
|
||||||
|
type: Number,
|
||||||
|
example: 1
|
||||||
|
})
|
||||||
|
accountId: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Number format options',
|
||||||
|
required: false,
|
||||||
|
type: NumberFormatQueryDto
|
||||||
|
})
|
||||||
|
numberFormat: NumberFormatQueryDto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { IsOptional } from "class-validator";
|
||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
export class GetPendingTransactionsQueryDto {
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Page number for pagination',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 1
|
||||||
|
})
|
||||||
|
page?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Number of items per page',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 10
|
||||||
|
})
|
||||||
|
pageSize?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Filter by bank account ID',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 1
|
||||||
|
})
|
||||||
|
accountId?: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { IsOptional } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class GetUncategorizedTransactionsQueryDto {
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Page number for pagination',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 1
|
||||||
|
})
|
||||||
|
page?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Number of items per page',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 10
|
||||||
|
})
|
||||||
|
pageSize?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Minimum date for filtering transactions',
|
||||||
|
required: false,
|
||||||
|
type: Date,
|
||||||
|
example: '2023-01-01'
|
||||||
|
})
|
||||||
|
minDate?: Date;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Maximum date for filtering transactions',
|
||||||
|
required: false,
|
||||||
|
type: Date,
|
||||||
|
example: '2023-12-31'
|
||||||
|
})
|
||||||
|
maxDate?: Date;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Minimum amount for filtering transactions',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 100
|
||||||
|
})
|
||||||
|
minAmount?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Maximum amount for filtering transactions',
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
example: 1000
|
||||||
|
})
|
||||||
|
maxAmount?: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Type } from "class-transformer";
|
||||||
|
import { IsBoolean, IsEnum, IsNumber, IsOptional, IsPositive } from "class-validator";
|
||||||
|
|
||||||
|
export class NumberFormatQueryDto {
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsNumber()
|
||||||
|
@IsPositive()
|
||||||
|
@IsOptional()
|
||||||
|
readonly precision: number;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
readonly divideOn1000: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
readonly showZero: boolean;
|
||||||
|
|
||||||
|
@IsEnum(['total', 'always', 'none'])
|
||||||
|
@IsOptional()
|
||||||
|
readonly formatMoney: 'total' | 'always' | 'none';
|
||||||
|
|
||||||
|
@IsEnum(['parentheses', 'mines'])
|
||||||
|
@IsOptional()
|
||||||
|
readonly negativeFormat: 'parentheses' | 'mines';
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable global-require */
|
/* eslint-disable global-require */
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
import { BaseModel } from '@/models/Model';
|
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
export class UncategorizedBankTransaction extends BaseModel {
|
export class UncategorizedBankTransaction extends TenantBaseModel {
|
||||||
readonly amount!: number;
|
readonly amount!: number;
|
||||||
readonly date!: Date | string;
|
readonly date!: Date | string;
|
||||||
readonly categorized!: boolean;
|
readonly categorized!: boolean;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { getBankAccountTransactionsDefaultQuery } from './_utils';
|
import { getBankAccountTransactionsDefaultQuery } from './_utils';
|
||||||
import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service';
|
import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service';
|
||||||
import { GetBankAccountTransactions } from './GetBankAccountTransactions';
|
import { GetBankAccountTransactions } from './GetBankAccountTransactions';
|
||||||
import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types';
|
import { GetBankTransactionsQueryDto } from '../../dtos/GetBankTranasctionsQuery.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetBankAccountTransactionsService {
|
export class GetBankAccountTransactionsService {
|
||||||
@@ -16,7 +16,7 @@ export class GetBankAccountTransactionsService {
|
|||||||
* @return {Promise<IInvetoryItemDetailDOO>}
|
* @return {Promise<IInvetoryItemDetailDOO>}
|
||||||
*/
|
*/
|
||||||
public async bankAccountTransactions(
|
public async bankAccountTransactions(
|
||||||
query: ICashflowAccountTransactionsQuery,
|
query: GetBankTransactionsQueryDto,
|
||||||
) {
|
) {
|
||||||
const parsedQuery = {
|
const parsedQuery = {
|
||||||
...getBankAccountTransactionsDefaultQuery(),
|
...getBankAccountTransactionsDefaultQuery(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { first, isEmpty } from 'lodash';
|
import { first, isEmpty } from 'lodash';
|
||||||
import {
|
import {
|
||||||
ICashflowAccountTransaction,
|
ICashflowAccountTransaction,
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { Injectable, Scope } from '@nestjs/common';
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||||
import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types';
|
import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types';
|
||||||
import {
|
import {
|
||||||
groupMatchedBankTransactions,
|
groupMatchedBankTransactions,
|
||||||
groupUncategorizedTransactions,
|
groupUncategorizedTransactions,
|
||||||
} from './_utils';
|
} from './_utils';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||||
|
import { UncategorizedBankTransaction } from '../../models/UncategorizedBankTransaction';
|
||||||
|
import { MatchedBankTransaction } from '@/modules/BankingMatching/models/MatchedBankTransaction';
|
||||||
|
|
||||||
@Injectable({ scope: Scope.REQUEST })
|
@Injectable({ scope: Scope.REQUEST })
|
||||||
export class GetBankAccountTransactionsRepository {
|
export class GetBankAccountTransactionsRepository {
|
||||||
private models: any;
|
|
||||||
public query: ICashflowAccountTransactionsQuery;
|
public query: ICashflowAccountTransactionsQuery;
|
||||||
public transactions: any;
|
public transactions: any;
|
||||||
public uncategorizedTransactions: any;
|
public uncategorizedTransactions: any;
|
||||||
@@ -17,6 +20,28 @@ export class GetBankAccountTransactionsRepository {
|
|||||||
public pagination: any;
|
public pagination: any;
|
||||||
public openingBalance: any;
|
public openingBalance: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {TenantModelProxy<typeof AccountTransaction>} accountTransactionModel - Account transaction model.
|
||||||
|
* @param {TenantModelProxy<typeof UncategorizedBankTransaction>} uncategorizedBankTransactionModel - Uncategorized transaction model
|
||||||
|
* @param {TenantModelProxy<typeof MatchedBankTransaction>} matchedBankTransactionModel - Matched bank transaction model.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
@Inject(AccountTransaction.name)
|
||||||
|
private readonly accountTransactionModel: TenantModelProxy<
|
||||||
|
typeof AccountTransaction
|
||||||
|
>,
|
||||||
|
|
||||||
|
@Inject(UncategorizedBankTransaction.name)
|
||||||
|
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||||
|
typeof UncategorizedBankTransaction
|
||||||
|
>,
|
||||||
|
|
||||||
|
@Inject(MatchedBankTransaction.name)
|
||||||
|
private readonly matchedBankTransactionModel: TenantModelProxy<
|
||||||
|
typeof MatchedBankTransaction
|
||||||
|
>,
|
||||||
|
) {}
|
||||||
|
|
||||||
setQuery(query: ICashflowAccountTransactionsQuery) {
|
setQuery(query: ICashflowAccountTransactionsQuery) {
|
||||||
this.query = query;
|
this.query = query;
|
||||||
}
|
}
|
||||||
@@ -37,9 +62,8 @@ export class GetBankAccountTransactionsRepository {
|
|||||||
* @param {ICashflowAccountTransactionsQuery} query -
|
* @param {ICashflowAccountTransactionsQuery} query -
|
||||||
*/
|
*/
|
||||||
async initCashflowAccountTransactions() {
|
async initCashflowAccountTransactions() {
|
||||||
const { AccountTransaction } = this.models;
|
const { results, pagination } = await this.accountTransactionModel()
|
||||||
|
.query()
|
||||||
const { results, pagination } = await AccountTransaction.query()
|
|
||||||
.where('account_id', this.query.accountId)
|
.where('account_id', this.query.accountId)
|
||||||
.orderBy([
|
.orderBy([
|
||||||
{ column: 'date', order: 'desc' },
|
{ column: 'date', order: 'desc' },
|
||||||
@@ -59,10 +83,9 @@ export class GetBankAccountTransactionsRepository {
|
|||||||
* @return {Promise<number>}
|
* @return {Promise<number>}
|
||||||
*/
|
*/
|
||||||
async initCashflowAccountOpeningBalance(): Promise<void> {
|
async initCashflowAccountOpeningBalance(): Promise<void> {
|
||||||
const { AccountTransaction } = this.models;
|
|
||||||
|
|
||||||
// Retrieve the opening balance of credit and debit balances.
|
// Retrieve the opening balance of credit and debit balances.
|
||||||
const openingBalancesSubquery = AccountTransaction.query()
|
const openingBalancesSubquery = this.accountTransactionModel()
|
||||||
|
.query()
|
||||||
.where('account_id', this.query.accountId)
|
.where('account_id', this.query.accountId)
|
||||||
.orderBy([
|
.orderBy([
|
||||||
{ column: 'date', order: 'desc' },
|
{ column: 'date', order: 'desc' },
|
||||||
@@ -72,7 +95,8 @@ export class GetBankAccountTransactionsRepository {
|
|||||||
.offset(this.pagination.pageSize * (this.pagination.page - 1));
|
.offset(this.pagination.pageSize * (this.pagination.page - 1));
|
||||||
|
|
||||||
// Sumation of credit and debit balance.
|
// Sumation of credit and debit balance.
|
||||||
const openingBalances = await AccountTransaction.query()
|
const openingBalances = await this.accountTransactionModel()
|
||||||
|
.query()
|
||||||
.sum('credit as credit')
|
.sum('credit as credit')
|
||||||
.sum('debit as debit')
|
.sum('debit as debit')
|
||||||
.from(openingBalancesSubquery.as('T'))
|
.from(openingBalancesSubquery.as('T'))
|
||||||
@@ -87,14 +111,11 @@ export class GetBankAccountTransactionsRepository {
|
|||||||
* Initialize the uncategorized transactions of the bank account.
|
* Initialize the uncategorized transactions of the bank account.
|
||||||
*/
|
*/
|
||||||
async initCategorizedTransactions() {
|
async initCategorizedTransactions() {
|
||||||
const { UncategorizedCashflowTransaction } = this.models;
|
|
||||||
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
|
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
|
||||||
|
|
||||||
const uncategorizedTransactions =
|
const uncategorizedTransactions =
|
||||||
await UncategorizedCashflowTransaction.query().whereIn(
|
await this.uncategorizedBankTransactionModel()
|
||||||
['categorizeRefType', 'categorizeRefId'],
|
.query()
|
||||||
refs,
|
.whereIn(['categorizeRefType', 'categorizeRefId'], refs);
|
||||||
);
|
|
||||||
|
|
||||||
this.uncategorizedTransactions = uncategorizedTransactions;
|
this.uncategorizedTransactions = uncategorizedTransactions;
|
||||||
this.uncategorizedTransactionsMapByRef = groupUncategorizedTransactions(
|
this.uncategorizedTransactionsMapByRef = groupUncategorizedTransactions(
|
||||||
@@ -106,14 +127,11 @@ export class GetBankAccountTransactionsRepository {
|
|||||||
* Initialize the matched bank transactions of the bank account.
|
* Initialize the matched bank transactions of the bank account.
|
||||||
*/
|
*/
|
||||||
async initMatchedTransactions(): Promise<void> {
|
async initMatchedTransactions(): Promise<void> {
|
||||||
const { MatchedBankTransaction } = this.models;
|
|
||||||
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
|
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
|
||||||
|
|
||||||
const matchedBankTransactions =
|
const matchedBankTransactions = await this.matchedBankTransactionModel()
|
||||||
await MatchedBankTransaction.query().whereIn(
|
.query()
|
||||||
['referenceType', 'referenceId'],
|
.whereIn(['referenceType', 'referenceId'], refs);
|
||||||
refs,
|
|
||||||
);
|
|
||||||
this.matchedBankTransactions = matchedBankTransactions;
|
this.matchedBankTransactions = matchedBankTransactions;
|
||||||
this.matchedBankTransactionsMapByRef = groupMatchedBankTransactions(
|
this.matchedBankTransactionsMapByRef = groupMatchedBankTransactions(
|
||||||
matchedBankTransactions,
|
matchedBankTransactions,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import { GetPendingBankAccountTransactionTransformer } from './GetPendingBankAccountTransactionTransformer';
|
import { GetPendingBankAccountTransactionTransformer } from './GetPendingBankAccountTransactionTransformer';
|
||||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { GetPendingTransactionsQueryDto } from '../dtos/GetPendingTransactionsQuery.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetPendingBankAccountTransactions {
|
export class GetPendingBankAccountTransactions {
|
||||||
@@ -9,40 +11,37 @@ export class GetPendingBankAccountTransactions {
|
|||||||
private readonly transformerService: TransformerInjectable,
|
private readonly transformerService: TransformerInjectable,
|
||||||
|
|
||||||
@Inject(UncategorizedBankTransaction.name)
|
@Inject(UncategorizedBankTransaction.name)
|
||||||
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction
|
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||||
|
typeof UncategorizedBankTransaction
|
||||||
|
>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the given bank accounts pending transaction.
|
* Retrieves the given bank accounts pending transaction.
|
||||||
* @param {GetPendingTransactionsQuery} filter - Pending transactions query.
|
* @param {GetPendingTransactionsQueryDto} filter - Pending transactions query.
|
||||||
*/
|
*/
|
||||||
async getPendingTransactions(filter?: GetPendingTransactionsQuery) {
|
async getPendingTransactions(filter?: GetPendingTransactionsQueryDto) {
|
||||||
const _filter = {
|
const _filter = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
...filter,
|
...filter,
|
||||||
};
|
};
|
||||||
const { results, pagination } =
|
const { results, pagination } =
|
||||||
await this.uncategorizedBankTransactionModel.query()
|
await this.uncategorizedBankTransactionModel()
|
||||||
|
.query()
|
||||||
.onBuild((q) => {
|
.onBuild((q) => {
|
||||||
q.modify('pending');
|
q.modify('pending');
|
||||||
|
|
||||||
if (_filter?.accountId) {
|
if (_filter?.accountId) {
|
||||||
q.where('accountId', _filter.accountId);
|
q.where('accountId', _filter.accountId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.pagination(_filter.page - 1, _filter.pageSize);
|
.pagination(_filter.page - 1, _filter.pageSize);
|
||||||
|
|
||||||
const data = await this.transformerService.transform(
|
const data = await this.transformerService.transform(
|
||||||
results,
|
results,
|
||||||
new GetPendingBankAccountTransactionTransformer()
|
new GetPendingBankAccountTransactionTransformer(),
|
||||||
);
|
);
|
||||||
return { data, pagination };
|
return { data, pagination };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetPendingTransactionsQuery {
|
|
||||||
page?: number;
|
|
||||||
pageSize?: number;
|
|
||||||
accountId?: number;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { UncategorizedTransactionTransformer } from '../../BankingCategorize/commands/UncategorizedTransaction.transformer';
|
import { UncategorizedTransactionTransformer } from '../../BankingCategorize/commands/UncategorizedTransaction.transformer';
|
||||||
import { IGetUncategorizedTransactionsQuery } from '../types/BankingTransactions.types';
|
import { GetUncategorizedTransactionsQueryDto } from '../dtos/GetUncategorizedTransactionsQuery.dto';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetUncategorizedTransactions {
|
export class GetUncategorizedTransactions {
|
||||||
|
/**
|
||||||
|
* @param {TransformerInjectable} transformer
|
||||||
|
* @param {UncategorizedBankTransaction.name} uncategorizedBankTransactionModel
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly transformer: TransformerInjectable,
|
private readonly transformer: TransformerInjectable,
|
||||||
|
|
||||||
@Inject(UncategorizedBankTransaction.name)
|
@Inject(UncategorizedBankTransaction.name)
|
||||||
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
|
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||||
|
typeof UncategorizedBankTransaction
|
||||||
|
>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the uncategorized cashflow transactions.
|
* Retrieves the uncategorized cashflow transactions.
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {number} accountId - Account Id.
|
* @param {number} accountId - Account Id.
|
||||||
|
* @param {IGetUncategorizedTransactionsQuery} query - Query.
|
||||||
*/
|
*/
|
||||||
public async getTransactions(
|
public async getTransactions(
|
||||||
accountId: number,
|
accountId: number,
|
||||||
query: IGetUncategorizedTransactionsQuery
|
query: GetUncategorizedTransactionsQueryDto,
|
||||||
) {
|
) {
|
||||||
// Parsed query with default values.
|
// Parsed query with default values.
|
||||||
const _query = {
|
const _query = {
|
||||||
@@ -28,9 +35,9 @@ export class GetUncategorizedTransactions {
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { results, pagination } =
|
const { results, pagination } =
|
||||||
await this.uncategorizedBankTransactionModel.query()
|
await this.uncategorizedBankTransactionModel()
|
||||||
|
.query()
|
||||||
.onBuild((q) => {
|
.onBuild((q) => {
|
||||||
q.where('accountId', accountId);
|
q.where('accountId', accountId);
|
||||||
q.where('categorized', false);
|
q.where('categorized', false);
|
||||||
@@ -63,7 +70,7 @@ export class GetUncategorizedTransactions {
|
|||||||
|
|
||||||
const data = await this.transformer.transform(
|
const data = await this.transformer.transform(
|
||||||
results,
|
results,
|
||||||
new UncategorizedTransactionTransformer()
|
new UncategorizedTransactionTransformer(),
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { OnEvent } from '@nestjs/event-emitter';
|
|||||||
import { BankTransactionAutoIncrement } from '../commands/BankTransactionAutoIncrement.service';
|
import { BankTransactionAutoIncrement } from '../commands/BankTransactionAutoIncrement.service';
|
||||||
import { BankTransactionGLEntriesService } from '../commands/BankTransactionGLEntries';
|
import { BankTransactionGLEntriesService } from '../commands/BankTransactionGLEntries';
|
||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { ICommandCashflowCreatedPayload, ICommandCashflowDeletedPayload } from '../types/BankingTransactions.types';
|
import {
|
||||||
|
ICommandCashflowCreatedPayload,
|
||||||
|
ICommandCashflowDeletedPayload,
|
||||||
|
} from '../types/BankingTransactions.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BankingTransactionGLEntriesSubscriber {
|
export class BankingTransactionGLEntriesSubscriber {
|
||||||
@@ -56,5 +59,5 @@ export class BankingTransactionGLEntriesSubscriber {
|
|||||||
cashflowTransactionId,
|
cashflowTransactionId,
|
||||||
trx,
|
trx,
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(Account.name)
|
@Inject(Account.name)
|
||||||
private readonly accountModel: typeof Account,
|
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +34,7 @@ export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
|||||||
if (uncategorizedTransaction.isPending) {
|
if (uncategorizedTransaction.isPending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.accountModel
|
await this.accountModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.decrement('uncategorizedTransactions', 1);
|
.decrement('uncategorizedTransactions', 1);
|
||||||
@@ -58,7 +59,7 @@ export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
|||||||
if (uncategorizedTransaction.isPending) {
|
if (uncategorizedTransaction.isPending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.accountModel
|
await this.accountModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.increment('uncategorizedTransactions', 1);
|
.increment('uncategorizedTransactions', 1);
|
||||||
@@ -80,7 +81,7 @@ export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
|||||||
// Cannot continue if the transaction is still pending.
|
// Cannot continue if the transaction is still pending.
|
||||||
if (uncategorizedTransaction.isPending) return;
|
if (uncategorizedTransaction.isPending) return;
|
||||||
|
|
||||||
await this.accountModel
|
await this.accountModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.increment('uncategorizedTransactions', 1);
|
.increment('uncategorizedTransactions', 1);
|
||||||
|
|||||||
@@ -5,19 +5,34 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
|
Put,
|
||||||
Query,
|
Query,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ExcludeBankTransactionsApplication } from './ExcludeBankTransactionsApplication';
|
import { ExcludeBankTransactionsApplication } from './ExcludeBankTransactionsApplication';
|
||||||
import { ExcludedBankTransactionsQuery } from './types/BankTransactionsExclude.types';
|
import { ExcludedBankTransactionsQuery } from './types/BankTransactionsExclude.types';
|
||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
@Controller('banking/transactions')
|
@Controller('banking/exclude')
|
||||||
@ApiTags('banking-transactions')
|
@ApiTags('banking-transactions')
|
||||||
export class BankingTransactionsExcludeController {
|
export class BankingTransactionsExcludeController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly excludeBankTransactionsApplication: ExcludeBankTransactionsApplication,
|
private readonly excludeBankTransactionsApplication: ExcludeBankTransactionsApplication,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@Put('bulk')
|
||||||
|
@ApiOperation({ summary: 'Exclude the given bank transactions.' })
|
||||||
|
public excludeBankTransactions(@Body('ids') ids: number[]) {
|
||||||
|
return this.excludeBankTransactionsApplication.excludeBankTransactions(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('bulk')
|
||||||
|
@ApiOperation({ summary: 'Unexclude the given bank transactions.' })
|
||||||
|
public unexcludeBankTransactions(@Body('ids') ids: number[]) {
|
||||||
|
return this.excludeBankTransactionsApplication.unexcludeBankTransactions(
|
||||||
|
ids,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiOperation({ summary: 'Retrieves the excluded bank transactions.' })
|
@ApiOperation({ summary: 'Retrieves the excluded bank transactions.' })
|
||||||
public getExcludedBankTransactions(
|
public getExcludedBankTransactions(
|
||||||
@@ -28,7 +43,7 @@ export class BankingTransactionsExcludeController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/exclude')
|
@Put(':id')
|
||||||
@ApiOperation({ summary: 'Exclude the given bank transaction.' })
|
@ApiOperation({ summary: 'Exclude the given bank transaction.' })
|
||||||
public excludeBankTransaction(@Param('id') id: string) {
|
public excludeBankTransaction(@Param('id') id: string) {
|
||||||
return this.excludeBankTransactionsApplication.excludeBankTransaction(
|
return this.excludeBankTransactionsApplication.excludeBankTransaction(
|
||||||
@@ -36,25 +51,11 @@ export class BankingTransactionsExcludeController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id/exclude')
|
@Delete(':id')
|
||||||
@ApiOperation({ summary: 'Unexclude the given bank transaction.' })
|
@ApiOperation({ summary: 'Unexclude the given bank transaction.' })
|
||||||
public unexcludeBankTransaction(@Param('id') id: string) {
|
public unexcludeBankTransaction(@Param('id') id: string) {
|
||||||
return this.excludeBankTransactionsApplication.unexcludeBankTransaction(
|
return this.excludeBankTransactionsApplication.unexcludeBankTransaction(
|
||||||
Number(id),
|
Number(id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('bulk/exclude')
|
|
||||||
@ApiOperation({ summary: 'Exclude the given bank transactions.' })
|
|
||||||
public excludeBankTransactions(@Body('ids') ids: number[]) {
|
|
||||||
return this.excludeBankTransactionsApplication.excludeBankTransactions(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('bulk/exclude')
|
|
||||||
@ApiOperation({ summary: 'Unexclude the given bank transactions.' })
|
|
||||||
public unexcludeBankTransactions(@Body('ids') ids: number[]) {
|
|
||||||
return this.excludeBankTransactionsApplication.unexcludeBankTransactions(
|
|
||||||
ids,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { ExcludeBankTransactionsApplication } from './ExcludeBankTransactionsApplication';
|
import { ExcludeBankTransactionsApplication } from './ExcludeBankTransactionsApplication';
|
||||||
import { ExcludeBankTransactionService } from './commands/ExcludeBankTransaction.service';
|
import { ExcludeBankTransactionService } from './commands/ExcludeBankTransaction.service';
|
||||||
import { UnexcludeBankTransactionService } from './commands/UnexcludeBankTransaction.service';
|
import { UnexcludeBankTransactionService } from './commands/UnexcludeBankTransaction.service';
|
||||||
@@ -10,7 +10,9 @@ import { BankingTransactionsExcludeController } from './BankingTransactionsExclu
|
|||||||
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [BankingTransactionsModule],
|
imports: [
|
||||||
|
forwardRef(() => BankingTransactionsModule),
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ExcludeBankTransactionsApplication,
|
ExcludeBankTransactionsApplication,
|
||||||
ExcludeBankTransactionService,
|
ExcludeBankTransactionService,
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { difference, sumBy } from 'lodash';
|
||||||
|
import {
|
||||||
|
ILandedCostItemDTO,
|
||||||
|
ILandedCostDTO,
|
||||||
|
IBillLandedCostTransaction,
|
||||||
|
ILandedCostTransaction,
|
||||||
|
ILandedCostTransactionEntry,
|
||||||
|
} from './types/BillLandedCosts.types';
|
||||||
|
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||||
|
import { BillLandedCost } from './models/BillLandedCost';
|
||||||
|
import { ServiceError } from '../Items/ServiceError';
|
||||||
|
import { CONFIG, ERRORS } from './utils';
|
||||||
|
import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry';
|
||||||
|
import { Bill } from '../Bills/models/Bill';
|
||||||
|
import { TransactionLandedCost } from './commands/TransctionLandedCost.service';
|
||||||
|
|
||||||
|
export class BaseLandedCostService {
|
||||||
|
@Inject()
|
||||||
|
public readonly transactionLandedCost: TransactionLandedCost;
|
||||||
|
|
||||||
|
@Inject(BillLandedCost.name)
|
||||||
|
private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates allocate cost items association with the purchase invoice entries.
|
||||||
|
* @param {IItemEntry[]} purchaseInvoiceEntries
|
||||||
|
* @param {ILandedCostItemDTO[]} landedCostItems
|
||||||
|
*/
|
||||||
|
protected validateAllocateCostItems = (
|
||||||
|
purchaseInvoiceEntries: ItemEntry[],
|
||||||
|
landedCostItems: ILandedCostItemDTO[],
|
||||||
|
): void => {
|
||||||
|
// Purchase invoice entries items ids.
|
||||||
|
const purchaseInvoiceItems = purchaseInvoiceEntries.map((e) => e.id);
|
||||||
|
const landedCostItemsIds = landedCostItems.map((item) => item.entryId);
|
||||||
|
|
||||||
|
// Not found items ids.
|
||||||
|
const notFoundItemsIds = difference(
|
||||||
|
purchaseInvoiceItems,
|
||||||
|
landedCostItemsIds,
|
||||||
|
);
|
||||||
|
// Throw items ids not found service error.
|
||||||
|
if (notFoundItemsIds.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.LANDED_COST_ITEMS_IDS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes DTO to bill landed cost model object.
|
||||||
|
* @param {ILandedCostDTO} landedCostDTO
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {ILandedCostTransaction} costTransaction
|
||||||
|
* @param {ILandedCostTransactionEntry} costTransactionEntry
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected transformToBillLandedCost(
|
||||||
|
landedCostDTO: ILandedCostDTO,
|
||||||
|
bill: Bill,
|
||||||
|
costTransaction: ILandedCostTransaction,
|
||||||
|
costTransactionEntry: ILandedCostTransactionEntry,
|
||||||
|
) {
|
||||||
|
const amount = sumBy(landedCostDTO.items, 'cost');
|
||||||
|
|
||||||
|
return {
|
||||||
|
billId: bill.id,
|
||||||
|
|
||||||
|
fromTransactionType: landedCostDTO.transactionType,
|
||||||
|
fromTransactionId: landedCostDTO.transactionId,
|
||||||
|
fromTransactionEntryId: landedCostDTO.transactionEntryId,
|
||||||
|
|
||||||
|
amount,
|
||||||
|
currencyCode: costTransaction.currencyCode,
|
||||||
|
exchangeRate: costTransaction.exchangeRate || 1,
|
||||||
|
|
||||||
|
allocationMethod: landedCostDTO.allocationMethod,
|
||||||
|
allocateEntries: landedCostDTO.items,
|
||||||
|
|
||||||
|
description: landedCostDTO.description,
|
||||||
|
costAccountId: costTransactionEntry.costAccountId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the cost transaction or throw not found error.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {transactionType} transactionType -
|
||||||
|
* @param {transactionId} transactionId -
|
||||||
|
*/
|
||||||
|
public getLandedCostOrThrowError = async (
|
||||||
|
transactionType: string,
|
||||||
|
transactionId: number,
|
||||||
|
) => {
|
||||||
|
const Model = this.transactionLandedCost.getModel(
|
||||||
|
transactionType,
|
||||||
|
);
|
||||||
|
const model = await Model.query().findById(transactionId);
|
||||||
|
|
||||||
|
if (!model) {
|
||||||
|
throw new ServiceError(ERRORS.LANDED_COST_TRANSACTION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return this.transactionLandedCost.transformToLandedCost(
|
||||||
|
transactionType,
|
||||||
|
model,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the landed cost entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {string} transactionType
|
||||||
|
* @param {number} transactionId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getLandedCostEntry = async (
|
||||||
|
transactionType: string,
|
||||||
|
transactionId: number,
|
||||||
|
transactionEntryId: number,
|
||||||
|
): Promise<any> => {
|
||||||
|
const Model = this.transactionLandedCost.getModel(
|
||||||
|
tenantId,
|
||||||
|
transactionType,
|
||||||
|
);
|
||||||
|
const relation = CONFIG.COST_TYPES[transactionType].entries;
|
||||||
|
|
||||||
|
const entry = await Model.relatedQuery(relation)
|
||||||
|
.for(transactionId)
|
||||||
|
.findOne('id', transactionEntryId)
|
||||||
|
.where('landedCost', true)
|
||||||
|
.onBuild((q) => {
|
||||||
|
if (transactionType === 'Bill') {
|
||||||
|
q.withGraphFetched('item');
|
||||||
|
} else if (transactionType === 'Expense') {
|
||||||
|
q.withGraphFetched('expenseAccount');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
throw new ServiceError(ERRORS.LANDED_COST_ENTRY_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return this.transactionLandedCost.transformToLandedCostEntry(
|
||||||
|
transactionType,
|
||||||
|
entry,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve allocate items cost total.
|
||||||
|
* @param {ILandedCostDTO} landedCostDTO
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
protected getAllocateItemsCostTotal = (
|
||||||
|
landedCostDTO: ILandedCostDTO,
|
||||||
|
): number => {
|
||||||
|
return sumBy(landedCostDTO.items, 'cost');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the landed cost entry amount.
|
||||||
|
* @param {number} unallocatedCost -
|
||||||
|
* @param {number} amount -
|
||||||
|
*/
|
||||||
|
protected validateLandedCostEntryAmount = (
|
||||||
|
unallocatedCost: number,
|
||||||
|
amount: number,
|
||||||
|
): void => {
|
||||||
|
if (unallocatedCost < amount) {
|
||||||
|
throw new ServiceError(ERRORS.COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the give bill landed cost or throw not found service error.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} landedCostId - Landed cost id.
|
||||||
|
* @returns {Promise<IBillLandedCost>}
|
||||||
|
*/
|
||||||
|
public getBillLandedCostOrThrowError = async (
|
||||||
|
landedCostId: number,
|
||||||
|
): Promise<BillLandedCost> => {
|
||||||
|
// Retrieve the bill landed cost model.
|
||||||
|
const billLandedCost = await this.billLandedCostModel()
|
||||||
|
.query()
|
||||||
|
.findById(landedCostId);
|
||||||
|
|
||||||
|
if (!billLandedCost) {
|
||||||
|
throw new ServiceError(ERRORS.BILL_LANDED_COST_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return billLandedCost;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,8 +1,32 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TransactionLandedCostEntriesService } from './TransactionLandedCostEntries.service';
|
import { TransactionLandedCostEntriesService } from './TransactionLandedCostEntries.service';
|
||||||
|
import { AllocateLandedCostService } from './commands/AllocateLandedCost.service';
|
||||||
|
import { LandedCostGLEntriesSubscriber } from './commands/LandedCostGLEntries.subscriber';
|
||||||
|
import { LandedCostGLEntries } from './commands/LandedCostGLEntries.service';
|
||||||
|
import { LandedCostSyncCostTransactions } from './commands/LandedCostSyncCostTransactions.service';
|
||||||
|
import { LandedCostSyncCostTransactionsSubscriber } from './commands/LandedCostSyncCostTransactions.subscriber';
|
||||||
|
import { BillAllocatedLandedCostTransactions } from './commands/BillAllocatedLandedCostTransactions.service';
|
||||||
|
import { BillAllocateLandedCostController } from './LandedCost.controller';
|
||||||
|
import { RevertAllocatedLandedCost } from './commands/RevertAllocatedLandedCost.service';
|
||||||
|
import LandedCostTranasctions from './commands/LandedCostTransactions.service';
|
||||||
|
import { LandedCostInventoryTransactions } from './commands/LandedCostInventoryTransactions.service';
|
||||||
|
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [TransactionLandedCostEntriesService],
|
imports: [InventoryCostModule],
|
||||||
|
providers: [
|
||||||
|
AllocateLandedCostService,
|
||||||
|
TransactionLandedCostEntriesService,
|
||||||
|
BillAllocatedLandedCostTransactions,
|
||||||
|
LandedCostGLEntriesSubscriber,
|
||||||
|
LandedCostGLEntries,
|
||||||
|
LandedCostSyncCostTransactions,
|
||||||
|
RevertAllocatedLandedCost,
|
||||||
|
LandedCostInventoryTransactions,
|
||||||
|
LandedCostTranasctions,
|
||||||
|
LandedCostSyncCostTransactionsSubscriber,
|
||||||
|
],
|
||||||
exports: [TransactionLandedCostEntriesService],
|
exports: [TransactionLandedCostEntriesService],
|
||||||
|
controllers: [BillAllocateLandedCostController],
|
||||||
})
|
})
|
||||||
export class BillLandedCostsModule {}
|
export class BillLandedCostsModule {}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AllocateBillLandedCostDto } from './dtos/AllocateBillLandedCost.dto';
|
||||||
|
import { AllocateLandedCostService } from './commands/AllocateLandedCost.service';
|
||||||
|
import { BillAllocatedLandedCostTransactions } from './commands/BillAllocatedLandedCostTransactions.service';
|
||||||
|
import { RevertAllocatedLandedCost } from './commands/RevertAllocatedLandedCost.service';
|
||||||
|
import { LandedCostTranasctions } from './commands/LandedCostTransactions.service';
|
||||||
|
|
||||||
|
@Controller('landed-cost')
|
||||||
|
export class BillAllocateLandedCostController {
|
||||||
|
constructor(
|
||||||
|
private allocateLandedCost: AllocateLandedCostService,
|
||||||
|
private billAllocatedCostTransactions: BillAllocatedLandedCostTransactions,
|
||||||
|
private revertAllocatedLandedCost: RevertAllocatedLandedCost,
|
||||||
|
private landedCostTranasctions: LandedCostTranasctions,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('/transactions')
|
||||||
|
async getLandedCostTransactions(
|
||||||
|
@Query('transaction_type') transactionType: string,
|
||||||
|
) {
|
||||||
|
const transactions =
|
||||||
|
await this.landedCostTranasctions.getLandedCostTransactions(transactionType);
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/bills/:billId/allocate')
|
||||||
|
public async calculateLandedCost(
|
||||||
|
@Param('billId') billId: number,
|
||||||
|
@Body() landedCostDTO: AllocateBillLandedCostDto,
|
||||||
|
) {
|
||||||
|
const billLandedCost = await this.allocateLandedCost.allocateLandedCost(
|
||||||
|
landedCostDTO,
|
||||||
|
billId,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
id: billLandedCost.id,
|
||||||
|
message: 'The items cost are located successfully.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/:allocatedLandedCostId')
|
||||||
|
public async deleteAllocatedLandedCost(
|
||||||
|
@Param('allocatedLandedCostId') allocatedLandedCostId: number,
|
||||||
|
) {
|
||||||
|
await this.revertAllocatedLandedCost.deleteAllocatedLandedCost(
|
||||||
|
allocatedLandedCostId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: allocatedLandedCostId,
|
||||||
|
message: 'The allocated landed cost are delete successfully.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listLandedCosts(
|
||||||
|
) {
|
||||||
|
const transactions =
|
||||||
|
await this.landedCostTranasctions.getLandedCostTransactions(query);
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Get('/bills/:billId/transactions')
|
||||||
|
async getBillLandedCostTransactions(@Param('billId') billId: number) {
|
||||||
|
const transactions =
|
||||||
|
await this.billAllocatedCostTransactions.getBillLandedCostTransactions(
|
||||||
|
billId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
billId,
|
||||||
|
transactions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ServiceError } from '../Items/ServiceError';
|
import { ServiceError } from '../Items/ServiceError';
|
||||||
import { transformToMap } from '@/utils/transform-to-key';
|
import { transformToMap } from '@/utils/transform-to-key';
|
||||||
import { ICommonLandedCostEntry, ICommonLandedCostEntryDTO } from './types/BillLandedCosts.types';
|
import {
|
||||||
|
ICommonLandedCostEntry,
|
||||||
|
ICommonLandedCostEntryDTO,
|
||||||
|
} from './types/BillLandedCosts.types';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
|
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
|
||||||
@@ -19,7 +22,7 @@ export class TransactionLandedCostEntriesService {
|
|||||||
*/
|
*/
|
||||||
public getLandedCostEntriesDeleted(
|
public getLandedCostEntriesDeleted(
|
||||||
oldCommonEntries: ICommonLandedCostEntry[],
|
oldCommonEntries: ICommonLandedCostEntry[],
|
||||||
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
|
newCommonEntriesDTO: ICommonLandedCostEntryDTO[],
|
||||||
): ICommonLandedCostEntry[] {
|
): ICommonLandedCostEntry[] {
|
||||||
const newBillEntriesById = transformToMap(newCommonEntriesDTO, 'id');
|
const newBillEntriesById = transformToMap(newCommonEntriesDTO, 'id');
|
||||||
|
|
||||||
@@ -40,11 +43,11 @@ export class TransactionLandedCostEntriesService {
|
|||||||
*/
|
*/
|
||||||
public validateLandedCostEntriesNotDeleted(
|
public validateLandedCostEntriesNotDeleted(
|
||||||
oldCommonEntries: ICommonLandedCostEntry[],
|
oldCommonEntries: ICommonLandedCostEntry[],
|
||||||
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
|
newCommonEntriesDTO: ICommonLandedCostEntryDTO[],
|
||||||
): void {
|
): void {
|
||||||
const entriesDeleted = this.getLandedCostEntriesDeleted(
|
const entriesDeleted = this.getLandedCostEntriesDeleted(
|
||||||
oldCommonEntries,
|
oldCommonEntries,
|
||||||
newCommonEntriesDTO
|
newCommonEntriesDTO,
|
||||||
);
|
);
|
||||||
if (entriesDeleted.length > 0) {
|
if (entriesDeleted.length > 0) {
|
||||||
throw new ServiceError(ERRORS.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED);
|
throw new ServiceError(ERRORS.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED);
|
||||||
@@ -58,7 +61,7 @@ export class TransactionLandedCostEntriesService {
|
|||||||
*/
|
*/
|
||||||
public validateLocatedCostEntriesSmallerThanNewEntries(
|
public validateLocatedCostEntriesSmallerThanNewEntries(
|
||||||
oldCommonEntries: ICommonLandedCostEntry[],
|
oldCommonEntries: ICommonLandedCostEntry[],
|
||||||
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
|
newCommonEntriesDTO: ICommonLandedCostEntryDTO[],
|
||||||
): void {
|
): void {
|
||||||
const oldBillEntriesById = transformToMap(oldCommonEntries, 'id');
|
const oldBillEntriesById = transformToMap(oldCommonEntries, 'id');
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ export class TransactionLandedCostEntriesService {
|
|||||||
|
|
||||||
if (oldEntry && oldEntry.allocatedCostAmount > entry.amount) {
|
if (oldEntry && oldEntry.allocatedCostAmount > entry.amount) {
|
||||||
throw new ServiceError(
|
throw new ServiceError(
|
||||||
ERRORS.LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES
|
ERRORS.LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import {
|
||||||
|
IAllocatedLandedCostCreatedPayload,
|
||||||
|
ILandedCostDTO,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { BillLandedCost } from '../models/BillLandedCost';
|
||||||
|
import { BaseLandedCostService } from '../BaseLandedCost.service';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { AllocateBillLandedCostDto } from '../dtos/AllocateBillLandedCost.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AllocateLandedCostService extends BaseLandedCostService {
|
||||||
|
constructor(
|
||||||
|
private readonly uow: UnitOfWork,
|
||||||
|
private readonly eventPublisher: EventEmitter2,
|
||||||
|
|
||||||
|
@Inject(Bill.name)
|
||||||
|
private readonly billModel: TenantModelProxy<typeof Bill>,
|
||||||
|
|
||||||
|
@Inject(BillLandedCost.name)
|
||||||
|
private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =================================
|
||||||
|
* - Allocate landed cost.
|
||||||
|
* =================================
|
||||||
|
* - Validates the allocate cost not the same purchase invoice id.
|
||||||
|
* - Get the given bill (purchase invoice) or throw not found error.
|
||||||
|
* - Get the given landed cost transaction or throw not found error.
|
||||||
|
* - Validate landed cost transaction has enough unallocated cost amount.
|
||||||
|
* - Validate landed cost transaction entry has enough unallocated cost amount.
|
||||||
|
* - Validate allocate entries existance and associated with cost bill transaction.
|
||||||
|
* - Writes inventory landed cost transaction.
|
||||||
|
* - Increment the allocated landed cost transaction.
|
||||||
|
* - Increment the allocated landed cost transaction entry.
|
||||||
|
* --------------------------------
|
||||||
|
* @param {ILandedCostDTO} landedCostDTO - Landed cost DTO.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} billId - Purchase invoice id.
|
||||||
|
*/
|
||||||
|
public async allocateLandedCost(
|
||||||
|
allocateCostDTO: AllocateBillLandedCostDto,
|
||||||
|
billId: number,
|
||||||
|
): Promise<BillLandedCost> {
|
||||||
|
// Retrieve total cost of allocated items.
|
||||||
|
const amount = this.getAllocateItemsCostTotal(allocateCostDTO);
|
||||||
|
|
||||||
|
// Retrieve the purchase invoice or throw not found error.
|
||||||
|
const bill = await Bill.query()
|
||||||
|
.findById(billId)
|
||||||
|
.withGraphFetched('entries')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Retrieve landed cost transaction or throw not found service error.
|
||||||
|
const costTransaction = await this.getLandedCostOrThrowError(
|
||||||
|
allocateCostDTO.transactionType,
|
||||||
|
allocateCostDTO.transactionId,
|
||||||
|
);
|
||||||
|
// Retrieve landed cost transaction entries.
|
||||||
|
const costTransactionEntry = await this.getLandedCostEntry(
|
||||||
|
allocateCostDTO.transactionType,
|
||||||
|
allocateCostDTO.transactionId,
|
||||||
|
allocateCostDTO.transactionEntryId,
|
||||||
|
);
|
||||||
|
// Validates allocate cost items association with the purchase invoice entries.
|
||||||
|
this.validateAllocateCostItems(bill.entries, allocateCostDTO.items);
|
||||||
|
|
||||||
|
// Validate the amount of cost with unallocated landed cost.
|
||||||
|
this.validateLandedCostEntryAmount(
|
||||||
|
costTransactionEntry.unallocatedCostAmount,
|
||||||
|
amount,
|
||||||
|
);
|
||||||
|
// Transformes DTO to bill landed cost model object.
|
||||||
|
const billLandedCostObj = this.transformToBillLandedCost(
|
||||||
|
allocateCostDTO,
|
||||||
|
bill,
|
||||||
|
costTransaction,
|
||||||
|
costTransactionEntry,
|
||||||
|
);
|
||||||
|
// Saves landed cost transactions with associated tranasctions under
|
||||||
|
// unit-of-work eniverment.
|
||||||
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
|
// Save the bill landed cost model.
|
||||||
|
const billLandedCost =
|
||||||
|
await BillLandedCost.query(trx).insertGraph(billLandedCostObj);
|
||||||
|
// Triggers `onBillLandedCostCreated` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.billLandedCost.onCreated, {
|
||||||
|
bill,
|
||||||
|
billLandedCostId: billLandedCost.id,
|
||||||
|
billLandedCost,
|
||||||
|
costTransaction,
|
||||||
|
costTransactionEntry,
|
||||||
|
trx,
|
||||||
|
} as IAllocatedLandedCostCreatedPayload);
|
||||||
|
|
||||||
|
return billLandedCost;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { BillLandedCost } from '../models/BillLandedCost';
|
||||||
|
import { IBillLandedCostTransaction } from '../types/BillLandedCosts.types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BillAllocatedLandedCostTransactions {
|
||||||
|
constructor(
|
||||||
|
private readonly i18nService: I18nService,
|
||||||
|
|
||||||
|
@Inject(Bill.name)
|
||||||
|
private readonly billModel: TenantModelProxy<typeof Bill>,
|
||||||
|
|
||||||
|
@Inject(BillLandedCost.name)
|
||||||
|
private readonly billLandedCostModel: TenantModelProxy<
|
||||||
|
typeof BillLandedCost
|
||||||
|
>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the bill associated landed cost transactions.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} billId - Bill id.
|
||||||
|
* @return {Promise<IBillLandedCostTransaction>}
|
||||||
|
*/
|
||||||
|
public getBillLandedCostTransactions = async (
|
||||||
|
billId: number,
|
||||||
|
): Promise<IBillLandedCostTransaction> => {
|
||||||
|
// Retrieve the given bill id or throw not found service error.
|
||||||
|
const bill = await this.billModel()
|
||||||
|
.query()
|
||||||
|
.findById(billId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Retrieve the bill associated allocated landed cost with bill and expense entry.
|
||||||
|
const landedCostTransactions = await this.billLandedCostModel()
|
||||||
|
.query()
|
||||||
|
.where('bill_id', billId)
|
||||||
|
.withGraphFetched('allocateEntries')
|
||||||
|
.withGraphFetched('allocatedFromBillEntry.item')
|
||||||
|
.withGraphFetched('allocatedFromExpenseEntry.expenseAccount')
|
||||||
|
.withGraphFetched('bill');
|
||||||
|
|
||||||
|
const transactionsJson = this.i18nService.i18nApply(
|
||||||
|
[[qim.$each, 'allocationMethodFormatted']],
|
||||||
|
landedCostTransactions.map((a) => a.toJSON()),
|
||||||
|
tenantId,
|
||||||
|
);
|
||||||
|
return this.transformBillLandedCostTransactions(transactionsJson);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IBillLandedCostTransaction[]} landedCostTransactions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private transformBillLandedCostTransactions = (
|
||||||
|
landedCostTransactions: IBillLandedCostTransaction[],
|
||||||
|
) => {
|
||||||
|
return landedCostTransactions.map(this.transformBillLandedCostTransaction);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IBillLandedCostTransaction} transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private transformBillLandedCostTransaction = (
|
||||||
|
transaction: IBillLandedCostTransaction,
|
||||||
|
) => {
|
||||||
|
const getTransactionName = R.curry(this.condBillLandedTransactionName)(
|
||||||
|
transaction.fromTransactionType,
|
||||||
|
);
|
||||||
|
const getTransactionDesc = R.curry(
|
||||||
|
this.condBillLandedTransactionDescription,
|
||||||
|
)(transaction.fromTransactionType);
|
||||||
|
|
||||||
|
return {
|
||||||
|
formattedAmount: formatNumber(transaction.amount, {
|
||||||
|
currencyCode: transaction.currencyCode,
|
||||||
|
}),
|
||||||
|
...omit(transaction, [
|
||||||
|
'allocatedFromBillEntry',
|
||||||
|
'allocatedFromExpenseEntry',
|
||||||
|
]),
|
||||||
|
name: getTransactionName(transaction),
|
||||||
|
description: getTransactionDesc(transaction),
|
||||||
|
formattedLocalAmount: formatNumber(transaction.localAmount, {
|
||||||
|
currencyCode: 'USD',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve bill landed cost tranaction name based on the given transaction type.
|
||||||
|
* @param transactionType
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private condBillLandedTransactionName = (
|
||||||
|
transactionType: string,
|
||||||
|
transaction,
|
||||||
|
) => {
|
||||||
|
return R.cond([
|
||||||
|
[
|
||||||
|
R.always(R.equals(transactionType, 'Bill')),
|
||||||
|
this.getLandedBillTransactionName,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
R.always(R.equals(transactionType, 'Expense')),
|
||||||
|
this.getLandedExpenseTransactionName,
|
||||||
|
],
|
||||||
|
])(transaction);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedBillTransactionName = (transaction): string => {
|
||||||
|
return transaction.allocatedFromBillEntry.item.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedExpenseTransactionName = (transaction): string => {
|
||||||
|
return transaction.allocatedFromExpenseEntry.expenseAccount.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve landed cost.
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedBillTransactionDescription = (transaction): string => {
|
||||||
|
return transaction.allocatedFromBillEntry.description;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedExpenseTransactionDescription = (transaction): string => {
|
||||||
|
return transaction.allocatedFromExpenseEntry.description;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the bill landed cost transaction description based on transaction type.
|
||||||
|
* @param {string} tranasctionType
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private condBillLandedTransactionDescription = (
|
||||||
|
tranasctionType: string,
|
||||||
|
transaction,
|
||||||
|
) => {
|
||||||
|
return R.cond([
|
||||||
|
[
|
||||||
|
R.always(R.equals(tranasctionType, 'Bill')),
|
||||||
|
this.getLandedBillTransactionDescription,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
R.always(R.equals(tranasctionType, 'Expense')),
|
||||||
|
this.getLandedExpenseTransactionDescription,
|
||||||
|
],
|
||||||
|
])(transaction);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { BaseLandedCostService } from '../BaseLandedCost.service';
|
||||||
|
import { BillLandedCost } from '../models/BillLandedCost';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { BillLandedCostEntry } from '../models/BillLandedCostEntry';
|
||||||
|
import { ILedger, ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||||
|
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostGLEntries extends BaseLandedCostService {
|
||||||
|
constructor(
|
||||||
|
private readonly journalService: JournalPosterService,
|
||||||
|
private readonly ledgerRepository: LedgerRepository,
|
||||||
|
|
||||||
|
@Inject(BillLandedCost.name)
|
||||||
|
private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the landed cost GL common entry.
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedCostGLCommonEntry = (
|
||||||
|
bill: Bill,
|
||||||
|
allocatedLandedCost: BillLandedCost
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
date: bill.billDate,
|
||||||
|
currencyCode: allocatedLandedCost.currencyCode,
|
||||||
|
exchangeRate: allocatedLandedCost.exchangeRate,
|
||||||
|
|
||||||
|
transactionType: 'LandedCost',
|
||||||
|
transactionId: allocatedLandedCost.id,
|
||||||
|
transactionNumber: bill.billNumber,
|
||||||
|
|
||||||
|
referenceNumber: bill.referenceNo,
|
||||||
|
|
||||||
|
credit: 0,
|
||||||
|
debit: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the landed cost GL inventory entry.
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @param {IBillLandedCostEntry} allocatedEntry
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getLandedCostGLInventoryEntry = (
|
||||||
|
bill: Bill,
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
allocatedEntry: BillLandedCostEntry
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonEntry = this.getLandedCostGLCommonEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...commonEntry,
|
||||||
|
debit: allocatedLandedCost.localAmount,
|
||||||
|
accountId: allocatedEntry.itemEntry.item.inventoryAccountId,
|
||||||
|
index: 1,
|
||||||
|
accountNormal: AccountNormal.DEBIT,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the landed cost GL cost entry.
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getLandedCostGLCostEntry = (
|
||||||
|
bill: Bill,
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
fromTransactionEntry: ILandedCostTransactionEntry
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonEntry = this.getLandedCostGLCommonEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...commonEntry,
|
||||||
|
credit: allocatedLandedCost.localAmount,
|
||||||
|
accountId: fromTransactionEntry.costAccountId,
|
||||||
|
index: 2,
|
||||||
|
accountNormal: AccountNormal.CREDIT,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve allocated landed cost entry GL entries.
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||||
|
* @param {IBillLandedCostEntry} allocatedEntry
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getLandedCostGLAllocateEntry = R.curry(
|
||||||
|
(
|
||||||
|
bill: Bill,
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
fromTransactionEntry: LandedCostTransactionEntry,
|
||||||
|
allocatedEntry: BillLandedCostEntry
|
||||||
|
): ILedgerEntry[] => {
|
||||||
|
const inventoryEntry = this.getLandedCostGLInventoryEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost,
|
||||||
|
allocatedEntry
|
||||||
|
);
|
||||||
|
const costEntry = this.getLandedCostGLCostEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost,
|
||||||
|
fromTransactionEntry
|
||||||
|
);
|
||||||
|
return [inventoryEntry, costEntry];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose the landed cost GL entries.
|
||||||
|
* @param {BillLandedCost} allocatedLandedCost
|
||||||
|
* @param {Bill} bill
|
||||||
|
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||||
|
* @returns {ILedgerEntry[]}
|
||||||
|
*/
|
||||||
|
public getLandedCostGLEntries = (
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
bill: Bill,
|
||||||
|
fromTransactionEntry: LandedCostTransactionEntry
|
||||||
|
): ILedgerEntry[] => {
|
||||||
|
const getEntry = this.getLandedCostGLAllocateEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost,
|
||||||
|
fromTransactionEntry
|
||||||
|
);
|
||||||
|
return allocatedLandedCost.allocateEntries.map(getEntry).flat();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the landed cost GL ledger.
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @param {Bill} bill
|
||||||
|
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||||
|
* @returns {ILedger}
|
||||||
|
*/
|
||||||
|
public getLandedCostLedger = (
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
bill: Bill,
|
||||||
|
fromTransactionEntry: LandedCostTransactionEntry
|
||||||
|
): ILedger => {
|
||||||
|
const entries = this.getLandedCostGLEntries(
|
||||||
|
allocatedLandedCost,
|
||||||
|
bill,
|
||||||
|
fromTransactionEntry
|
||||||
|
);
|
||||||
|
return new Ledger(entries);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes landed cost GL entries to the storage layer.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
*/
|
||||||
|
public writeLandedCostGLEntries = async (
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
bill: Bill,
|
||||||
|
fromTransactionEntry: ILandedCostTransactionEntry,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
const ledgerEntries = this.getLandedCostGLEntries(
|
||||||
|
allocatedLandedCost,
|
||||||
|
bill,
|
||||||
|
fromTransactionEntry
|
||||||
|
);
|
||||||
|
await this.ledgerRepository.saveLedgerEntries(ledgerEntries, trx);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates and writes GL entries of the given landed cost.
|
||||||
|
* @param {number} billLandedCostId
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public createLandedCostGLEntries = async (
|
||||||
|
billLandedCostId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
// Retrieve the bill landed cost transacion with associated
|
||||||
|
// allocated entries and items.
|
||||||
|
const allocatedLandedCost = await this.billLandedCostModel().query(trx)
|
||||||
|
.findById(billLandedCostId)
|
||||||
|
.withGraphFetched('bill')
|
||||||
|
.withGraphFetched('allocateEntries.itemEntry.item');
|
||||||
|
|
||||||
|
// Retrieve the allocated from transactione entry.
|
||||||
|
const transactionEntry = await this.getLandedCostEntry(
|
||||||
|
allocatedLandedCost.fromTransactionType,
|
||||||
|
allocatedLandedCost.fromTransactionId,
|
||||||
|
allocatedLandedCost.fromTransactionEntryId
|
||||||
|
);
|
||||||
|
// Writes the given landed cost GL entries to the storage layer.
|
||||||
|
await this.writeLandedCostGLEntries(
|
||||||
|
allocatedLandedCost,
|
||||||
|
allocatedLandedCost.bill,
|
||||||
|
transactionEntry,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts GL entries of the given allocated landed cost transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} landedCostId
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public revertLandedCostGLEntries = async (
|
||||||
|
landedCostId: number,
|
||||||
|
trx: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
await this.journalService.revertJournalTransactions(
|
||||||
|
landedCostId,
|
||||||
|
'LandedCost',
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
IAllocatedLandedCostCreatedPayload,
|
||||||
|
IAllocatedLandedCostDeletedPayload,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import { LandedCostGLEntries } from './LandedCostGLEntries.service';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostGLEntriesSubscriber {
|
||||||
|
constructor(
|
||||||
|
private readonly billLandedCostGLEntries: LandedCostGLEntries,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes GL entries once landed cost transaction created.
|
||||||
|
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onCreated)
|
||||||
|
async writeGLEntriesOnceLandedCostCreated({
|
||||||
|
billLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostCreatedPayload) {
|
||||||
|
await this.billLandedCostGLEntries.createLandedCostGLEntries(
|
||||||
|
billLandedCost.id,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts GL entries associated to landed cost transaction once deleted.
|
||||||
|
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onDeleted)
|
||||||
|
async revertGLEnteriesOnceLandedCostDeleted({
|
||||||
|
oldBillLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostDeletedPayload) {
|
||||||
|
await this.billLandedCostGLEntries.revertLandedCostGLEntries(
|
||||||
|
oldBillLandedCost.id,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IBillLandedCostTransaction } from '../types/BillLandedCosts.types';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { mergeLocatedWithBillEntries } from '../utils';
|
||||||
|
import { InventoryTransactionsService } from '@/modules/InventoryCost/commands/InventoryTransactions.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostInventoryTransactions {
|
||||||
|
constructor(
|
||||||
|
private readonly inventoryTransactionsService: InventoryTransactionsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records inventory transactions.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IBillLandedCostTransaction} billLandedCost
|
||||||
|
* @param {IBill} bill -
|
||||||
|
*/
|
||||||
|
public recordInventoryTransactions = async (
|
||||||
|
billLandedCost: IBillLandedCostTransaction,
|
||||||
|
bill: Bill,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) => {
|
||||||
|
// Retrieve the merged allocated entries with bill entries.
|
||||||
|
const allocateEntries = mergeLocatedWithBillEntries(
|
||||||
|
billLandedCost.allocateEntries,
|
||||||
|
bill.entries,
|
||||||
|
);
|
||||||
|
// Mappes the allocate cost entries to inventory transactions.
|
||||||
|
const inventoryTransactions = allocateEntries.map((allocateEntry) => ({
|
||||||
|
date: bill.billDate,
|
||||||
|
itemId: allocateEntry.entry.itemId,
|
||||||
|
direction: 'IN',
|
||||||
|
quantity: null,
|
||||||
|
rate: allocateEntry.cost,
|
||||||
|
transactionType: 'LandedCost',
|
||||||
|
transactionId: billLandedCost.id,
|
||||||
|
entryId: allocateEntry.entryId,
|
||||||
|
}));
|
||||||
|
// Writes inventory transactions.
|
||||||
|
return this.inventoryTransactionsService.recordInventoryTransactions(
|
||||||
|
inventoryTransactions,
|
||||||
|
false,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the inventory transaction.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} landedCostId - Landed cost id.
|
||||||
|
* @param {Knex.Transaction} trx - Knex transactions.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public removeInventoryTransactions = (
|
||||||
|
landedCostId: number,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) => {
|
||||||
|
return this.inventoryTransactionsService.deleteInventoryTransactions(
|
||||||
|
landedCostId,
|
||||||
|
'LandedCost',
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import {
|
||||||
|
IAllocatedLandedCostCreatedPayload,
|
||||||
|
IAllocatedLandedCostDeletedPayload,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { LandedCostInventoryTransactions } from './LandedCostInventoryTransactions.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostInventoryTransactionsSubscriber {
|
||||||
|
constructor(
|
||||||
|
private readonly landedCostInventory: LandedCostInventoryTransactions,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes inventory transactions of the landed cost transaction once created.
|
||||||
|
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onCreated)
|
||||||
|
async writeInventoryTransactionsOnceCreated({
|
||||||
|
billLandedCost,
|
||||||
|
trx,
|
||||||
|
bill,
|
||||||
|
}: IAllocatedLandedCostCreatedPayload) {
|
||||||
|
// Records the inventory transactions.
|
||||||
|
await this.landedCostInventory.recordInventoryTransactions(
|
||||||
|
billLandedCost,
|
||||||
|
bill,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts inventory transactions of the landed cost transaction once deleted.
|
||||||
|
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onDeleted)
|
||||||
|
async revertInventoryTransactionsOnceDeleted({
|
||||||
|
oldBillLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostDeletedPayload) {
|
||||||
|
// Removes the inventory transactions.
|
||||||
|
await this.landedCostInventory.removeInventoryTransactions(
|
||||||
|
oldBillLandedCost.id,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { CONFIG } from '../utils';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { TransactionLandedCost } from './TransctionLandedCost.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostSyncCostTransactions {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionLandedCost: TransactionLandedCost,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate the landed cost amount to cost transactions.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {string} transactionType
|
||||||
|
* @param {number} transactionId
|
||||||
|
*/
|
||||||
|
public incrementLandedCostAmount = async (
|
||||||
|
transactionType: string,
|
||||||
|
transactionId: number,
|
||||||
|
transactionEntryId: number,
|
||||||
|
amount: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> => {
|
||||||
|
const Model = this.transactionLandedCost.getModel(
|
||||||
|
transactionType
|
||||||
|
);
|
||||||
|
const relation = CONFIG.COST_TYPES[transactionType].entries;
|
||||||
|
|
||||||
|
// Increment the landed cost transaction amount.
|
||||||
|
await Model.query(trx)
|
||||||
|
.where('id', transactionId)
|
||||||
|
.increment('allocatedCostAmount', amount);
|
||||||
|
|
||||||
|
// Increment the landed cost entry.
|
||||||
|
await Model.relatedQuery(relation, trx)
|
||||||
|
.for(transactionId)
|
||||||
|
.where('id', transactionEntryId)
|
||||||
|
.increment('allocatedCostAmount', amount);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts the landed cost amount to cost transaction.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {string} transactionType - Transaction type.
|
||||||
|
* @param {number} transactionId - Transaction id.
|
||||||
|
* @param {number} transactionEntryId - Transaction entry id.
|
||||||
|
* @param {number} amount - Amount
|
||||||
|
*/
|
||||||
|
public revertLandedCostAmount = async (
|
||||||
|
transactionType: string,
|
||||||
|
transactionId: number,
|
||||||
|
transactionEntryId: number,
|
||||||
|
amount: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
const Model = this.transactionLandedCost.getModel(
|
||||||
|
transactionType
|
||||||
|
);
|
||||||
|
const relation = CONFIG.COST_TYPES[transactionType].entries;
|
||||||
|
|
||||||
|
// Decrement the allocate cost amount of cost transaction.
|
||||||
|
await Model.query(trx)
|
||||||
|
.where('id', transactionId)
|
||||||
|
.decrement('allocatedCostAmount', amount);
|
||||||
|
|
||||||
|
// Decrement the allocated cost amount cost transaction entry.
|
||||||
|
await Model.relatedQuery(relation, trx)
|
||||||
|
.for(transactionId)
|
||||||
|
.where('id', transactionEntryId)
|
||||||
|
.decrement('allocatedCostAmount', amount);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
IAllocatedLandedCostCreatedPayload,
|
||||||
|
IAllocatedLandedCostDeletedPayload,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { LandedCostSyncCostTransactions } from './LandedCostSyncCostTransactions.service';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostSyncCostTransactionsSubscriber {
|
||||||
|
constructor(
|
||||||
|
private landedCostSyncCostTransaction: LandedCostSyncCostTransactions,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment cost transactions once the landed cost allocated.
|
||||||
|
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onCreated)
|
||||||
|
async incrementCostTransactionsOnceCreated({
|
||||||
|
billLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostCreatedPayload) {
|
||||||
|
// Increment landed cost amount on transaction and entry.
|
||||||
|
await this.landedCostSyncCostTransaction.incrementLandedCostAmount(
|
||||||
|
billLandedCost.fromTransactionType,
|
||||||
|
billLandedCost.fromTransactionId,
|
||||||
|
billLandedCost.fromTransactionEntryId,
|
||||||
|
billLandedCost.amount,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrement cost transactions once the allocated landed cost reverted.
|
||||||
|
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onDeleted)
|
||||||
|
async decrementCostTransactionsOnceDeleted({
|
||||||
|
oldBillLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostDeletedPayload) {
|
||||||
|
// Reverts the landed cost amount to the cost transaction.
|
||||||
|
await this.landedCostSyncCostTransaction.revertLandedCostAmount(
|
||||||
|
oldBillLandedCost.fromTransactionType,
|
||||||
|
oldBillLandedCost.fromTransactionId,
|
||||||
|
oldBillLandedCost.fromTransactionEntryId,
|
||||||
|
oldBillLandedCost.amount,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user