mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 23:30:32 +00:00
Compare commits
1 Commits
refactor-l
...
users-modu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
985e1dbc01 |
@@ -1,7 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('bankfeed', () => ({
|
|
||||||
enabled:
|
|
||||||
process.env.BANK_FEED_ENABLED === 'true' ||
|
|
||||||
process.env.BANK_FEED_ENABLED === 'yes',
|
|
||||||
}));
|
|
||||||
@@ -13,7 +13,6 @@ 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,
|
||||||
@@ -30,6 +29,5 @@ export const config = [
|
|||||||
signupRestrictions,
|
signupRestrictions,
|
||||||
jwt,
|
jwt,
|
||||||
mail,
|
mail,
|
||||||
loops,
|
loops
|
||||||
bankfeed,
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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,10 +70,7 @@ 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,15 +14,12 @@ 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 object;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
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}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"decrement": "Decrement",
|
|
||||||
"increment": "Increment"
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"primary_warehouse": "Primary Warehouse"
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as FormData from 'form-data';
|
import 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 * as FormData from 'form-data';
|
import 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 new Axios({
|
const response = await Axios.post(endpoint, data, {
|
||||||
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 * as FormData from 'form-data';
|
import 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 * as FormData from 'form-data';
|
import 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,7 +13,9 @@ export class MutateBaseCurrencyAccounts {
|
|||||||
* Mutates the all accounts or the organziation.
|
* Mutates the all accounts or the organziation.
|
||||||
* @param {string} currencyCode
|
* @param {string} currencyCode
|
||||||
*/
|
*/
|
||||||
async mutateAllAccountsCurrency(currencyCode: string) {
|
mutateAllAccountsCurrency = async (
|
||||||
await this.accountModel().query().update({ currencyCode });
|
currencyCode: string,
|
||||||
}
|
) => {
|
||||||
|
await Account.query().update({ currencyCode });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,10 +88,6 @@ 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: [
|
||||||
@@ -153,7 +149,6 @@ import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.
|
|||||||
ScheduleModule.forRoot(),
|
ScheduleModule.forRoot(),
|
||||||
TenancyDatabaseModule,
|
TenancyDatabaseModule,
|
||||||
TenancyModelsModule,
|
TenancyModelsModule,
|
||||||
TenantModelsInitializeModule,
|
|
||||||
AuthModule,
|
AuthModule,
|
||||||
TenancyModule,
|
TenancyModule,
|
||||||
ChromiumlyTenancyModule,
|
ChromiumlyTenancyModule,
|
||||||
@@ -186,12 +181,10 @@ import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.
|
|||||||
LedgerModule,
|
LedgerModule,
|
||||||
BankAccountsModule,
|
BankAccountsModule,
|
||||||
BankRulesModule,
|
BankRulesModule,
|
||||||
|
BankingTransactionsModule,
|
||||||
BankingTransactionsExcludeModule,
|
BankingTransactionsExcludeModule,
|
||||||
BankingTransactionsRegonizeModule,
|
BankingTransactionsRegonizeModule,
|
||||||
BankingTransactionsModule,
|
|
||||||
BankingMatchingModule,
|
BankingMatchingModule,
|
||||||
BankingPlaidModule,
|
|
||||||
BankingCategorizeModule,
|
|
||||||
TransactionsLockingModule,
|
TransactionsLockingModule,
|
||||||
SettingsModule,
|
SettingsModule,
|
||||||
FeaturesModule,
|
FeaturesModule,
|
||||||
@@ -217,8 +210,7 @@ import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.
|
|||||||
ViewsModule,
|
ViewsModule,
|
||||||
CurrenciesModule,
|
CurrenciesModule,
|
||||||
MiscellaneousModule,
|
MiscellaneousModule,
|
||||||
UsersModule,
|
UsersModule
|
||||||
ContactsModule
|
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ 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 * as path from 'path';
|
import 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 editDTO
|
* @param createDTO
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private transformDTO(editDTO: EditBankRuleDto): ModelObject<BankRule> {
|
private transformDTO(createDTO: EditBankRuleDto): ModelObject<BankRule> {
|
||||||
return {
|
return {
|
||||||
...editDTO,
|
...createDTO,
|
||||||
} as ModelObject<BankRule>;
|
} as ModelObject<BankRule>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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()
|
||||||
@@ -45,8 +44,6 @@ export class CommandBankRuleDto {
|
|||||||
})
|
})
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@ToNumber()
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@@ -56,7 +53,6 @@ export class CommandBankRuleDto {
|
|||||||
order: number;
|
order: number;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ToNumber()
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@@ -65,7 +61,6 @@ 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',
|
||||||
@@ -87,14 +82,11 @@ 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: [
|
example: [{ field: 'description', comparator: 'contains', value: 'Salary' }],
|
||||||
{ 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',
|
||||||
@@ -103,8 +95,6 @@ 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,13 +1,8 @@
|
|||||||
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 {
|
||||||
@@ -19,19 +14,6 @@ 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,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,11 +27,6 @@ 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);
|
||||||
@@ -60,6 +37,11 @@ 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()
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
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,38 +1,20 @@
|
|||||||
import { forwardRef, Module } from '@nestjs/common';
|
import { 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: [
|
imports: [BankingTransactionsModule, ExpensesModule],
|
||||||
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,6 +5,7 @@ import { Knex } from 'knex';
|
|||||||
import {
|
import {
|
||||||
ICashflowTransactionCategorizedPayload,
|
ICashflowTransactionCategorizedPayload,
|
||||||
ICashflowTransactionUncategorizingPayload,
|
ICashflowTransactionUncategorizingPayload,
|
||||||
|
ICategorizeCashflowTransactioDTO,
|
||||||
} from '../types/BankingCategorize.types';
|
} from '../types/BankingCategorize.types';
|
||||||
import {
|
import {
|
||||||
transformCategorizeTransToCashflow,
|
transformCategorizeTransToCashflow,
|
||||||
@@ -16,10 +17,9 @@ 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 CategorizeBankTransaction {
|
export class CategorizeCashflowTransaction {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly eventPublisher: EventEmitter2,
|
private readonly eventPublisher: EventEmitter2,
|
||||||
private readonly uow: UnitOfWork,
|
private readonly uow: UnitOfWork,
|
||||||
@@ -38,7 +38,7 @@ export class CategorizeBankTransaction {
|
|||||||
*/
|
*/
|
||||||
public async categorize(
|
public async categorize(
|
||||||
uncategorizedTransactionId: number | Array<number>,
|
uncategorizedTransactionId: number | Array<number>,
|
||||||
categorizeDTO: CategorizeBankTransactionDto,
|
categorizeDTO: ICategorizeCashflowTransactioDTO,
|
||||||
) {
|
) {
|
||||||
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
||||||
|
|
||||||
@@ -68,6 +68,7 @@ export class CategorizeBankTransaction {
|
|||||||
await this.eventPublisher.emitAsync(
|
await this.eventPublisher.emitAsync(
|
||||||
events.cashflow.onTransactionCategorizing,
|
events.cashflow.onTransactionCategorizing,
|
||||||
{
|
{
|
||||||
|
// tenantId,
|
||||||
oldUncategorizedTransactions,
|
oldUncategorizedTransactions,
|
||||||
trx,
|
trx,
|
||||||
} as ICashflowTransactionUncategorizingPayload,
|
} as ICashflowTransactionUncategorizingPayload,
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import {
|
import {
|
||||||
|
CreateUncategorizedTransactionDTO,
|
||||||
IUncategorizedTransactionCreatedEventPayload,
|
IUncategorizedTransactionCreatedEventPayload,
|
||||||
IUncategorizedTransactionCreatingEventPayload,
|
IUncategorizedTransactionCreatingEventPayload,
|
||||||
} from '../types/BankingCategorize.types';
|
} from '../types/BankingCategorize.types';
|
||||||
@@ -9,7 +10,6 @@ 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: UncategorizedBankTransactionDto,
|
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO,
|
||||||
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 UncategorizeBankTransactionService {
|
export class UncategorizeCashflowTransactionService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly eventPublisher: EventEmitter2,
|
private readonly eventPublisher: EventEmitter2,
|
||||||
private readonly uow: UnitOfWork,
|
private readonly uow: UnitOfWork,
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
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 { UncategorizeBankTransactionService } from './UncategorizeBankTransaction.service';
|
import { UncategorizeCashflowTransactionService } from './UncategorizeCashflowTransaction.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UncategorizeBankTransactionsBulk {
|
export class UncategorizeCashflowTransactionsBulk {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly uncategorizeTransactionService: UncategorizeBankTransactionService
|
private readonly uncategorizeTransactionService: UncategorizeCashflowTransactionService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uncategorize the given bank transactions in bulk.
|
* Uncategorize the given bank transactions in bulk.
|
||||||
* @param {number | Array<number>} uncategorizedTransactionId
|
* @param {number} tenantId
|
||||||
|
* @param {number} uncategorizedTransactionId
|
||||||
*/
|
*/
|
||||||
public async uncategorizeBulk(
|
public async uncategorizeBulk(
|
||||||
uncategorizedTransactionId: number | Array<number>
|
uncategorizedTransactionId: number | Array<number>
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
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>;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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,44 +1,47 @@
|
|||||||
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 } from './types';
|
import { GetMatchedTransactionsFilter, IMatchTransactionDTO } 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')
|
@Get('matched/transactions')
|
||||||
@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')
|
@Post('/match/:uncategorizedTransactionId')
|
||||||
@ApiOperation({ summary: 'Match the given uncategorized transaction.' })
|
@ApiOperation({ summary: 'Match the given uncategorized transaction.' })
|
||||||
async matchTransaction(@Body() matchedTransactions: MatchBankTransactionDto) {
|
async matchTransaction(
|
||||||
|
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number | number[],
|
||||||
|
@Body() matchedTransactions: MatchBankTransactionDto
|
||||||
|
) {
|
||||||
return this.bankingMatchingApplication.matchTransaction(
|
return this.bankingMatchingApplication.matchTransaction(
|
||||||
matchedTransactions.uncategorizedTransactions,
|
uncategorizedTransactionId,
|
||||||
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 } from './types';
|
import { GetMatchedTransactionsFilter, IMatchTransactionDTO } from './types';
|
||||||
import { MatchTransactionEntryDto } from './dtos/MatchBankTransaction.dto';
|
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BankingMatchingApplication {
|
export class BankingMatchingApplication {
|
||||||
@@ -31,18 +31,17 @@ export class BankingMatchingApplication {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches the given uncategorized transaction with the given system transaction.
|
* Matches the given uncategorized transaction with the given system transaction.
|
||||||
* @param {IMatchBankTransactionDto} matchedTransactionsDTO
|
* @param {number} uncategorizedTransactionId
|
||||||
|
* @param {IMatchTransactionDTO} matchTransactionsDTO
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public matchTransaction(
|
public matchTransaction(
|
||||||
uncategorizedTransactionId: number | Array<number>,
|
uncategorizedTransactionId: number | Array<number>,
|
||||||
matchedTransactionsDto:
|
matchedTransactions: MatchBankTransactionDto,
|
||||||
| MatchTransactionEntryDto
|
|
||||||
| Array<MatchTransactionEntryDto>,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.matchTransactionService.matchTransaction(
|
return this.matchTransactionService.matchTransaction(
|
||||||
uncategorizedTransactionId,
|
uncategorizedTransactionId,
|
||||||
matchedTransactionsDto,
|
matchedTransactions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 { MatchTransactionEntryDto } from '../dtos/MatchBankTransaction.dto';
|
import { MatchBankTransactionDto } from '../dtos/MatchBankTransaction.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MatchBankTransactions {
|
export class MatchBankTransactions {
|
||||||
@@ -107,15 +107,16 @@ 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: MatchTransactionEntryDto | Array<MatchTransactionEntryDto>,
|
matchedTransactionsDto: MatchBankTransactionDto,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
||||||
const matchedTransactions = castArray(matchedTransactionsDto);
|
const matchedTransactions = matchedTransactionsDto.entries;
|
||||||
|
|
||||||
// Validates the given matching transactions DTO.
|
// Validates the given matching transactions DTO.
|
||||||
await this.validate(uncategorizedTransactionIds, matchedTransactions);
|
await this.validate(uncategorizedTransactionIds, matchedTransactions);
|
||||||
@@ -130,7 +131,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: MatchTransactionEntryDto) => {
|
.process(async (matchedTransaction) => {
|
||||||
const getMatchedTransactionsService =
|
const getMatchedTransactionsService =
|
||||||
this.matchedBankTransactions.registry.get(
|
this.matchedBankTransactions.registry.get(
|
||||||
matchedTransaction.referenceType,
|
matchedTransaction.referenceType,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
ArrayMinSize,
|
|
||||||
IsArray,
|
IsArray,
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
@@ -28,10 +27,6 @@ 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)
|
||||||
@@ -42,5 +37,5 @@ export class MatchBankTransactionDto {
|
|||||||
{ referenceType: 'SaleInvoice', referenceId: 2 },
|
{ referenceType: 'SaleInvoice', referenceId: 2 },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
matchedTransactions: MatchTransactionEntryDto[];
|
entries: 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 { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
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,4 +1,3 @@
|
|||||||
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,13 +1,10 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO, MatchedTransactionsPOJO } from '../types';
|
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } 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 {
|
||||||
@@ -16,26 +13,17 @@ 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(
|
async getMatchedTransactions(filter: GetMatchedTransactionsFilter) {
|
||||||
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()
|
||||||
@@ -61,7 +49,6 @@ 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,4 +1,3 @@
|
|||||||
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';
|
||||||
@@ -10,6 +9,7 @@ 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,6 +86,7 @@ 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,13 +1,10 @@
|
|||||||
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)
|
||||||
matchedBankTransactionModel: TenantModelProxy<
|
private readonly matchedBankTransactionModel: TenantModelProxy<
|
||||||
typeof MatchedBankTransaction
|
typeof MatchedBankTransaction
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
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,4 +1,3 @@
|
|||||||
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';
|
||||||
@@ -16,11 +15,6 @@ 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)];
|
||||||
|
|
||||||
@@ -30,7 +24,6 @@ const models = [RegisterTenancyModel(PlaidItem)];
|
|||||||
AccountsModule,
|
AccountsModule,
|
||||||
BankingCategorizeModule,
|
BankingCategorizeModule,
|
||||||
BankingTransactionsModule,
|
BankingTransactionsModule,
|
||||||
BullModule.registerQueue({ name: UpdateBankingPlaidTransitionsQueueJob }),
|
|
||||||
...models,
|
...models,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -41,12 +34,9 @@ const models = [RegisterTenancyModel(PlaidItem)];
|
|||||||
PlaidWebooks,
|
PlaidWebooks,
|
||||||
PlaidLinkTokenService,
|
PlaidLinkTokenService,
|
||||||
PlaidApplication,
|
PlaidApplication,
|
||||||
SetupPlaidItemTenantService,
|
|
||||||
TenancyContext,
|
|
||||||
PlaidFetchTransactionsProcessor,
|
|
||||||
PlaidUpdateTransactionsOnItemCreatedSubscriber,
|
PlaidUpdateTransactionsOnItemCreatedSubscriber,
|
||||||
|
TenancyContext,
|
||||||
],
|
],
|
||||||
exports: [...models],
|
exports: [...models],
|
||||||
controllers: [BankingPlaidController, BankingPlaidWebhooksController],
|
|
||||||
})
|
})
|
||||||
export class BankingPlaidModule {}
|
export class BankingPlaidModule {}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
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,12 +1,8 @@
|
|||||||
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 { PlaidItemDto } from './dtos/PlaidItem.dto';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { SystemPlaidItem } from './models/SystemPlaidItem';
|
import { PlaidItemDTO } from './types/BankingPlaid.types';
|
||||||
import { TenantModel } from '../System/models/TenantModel';
|
|
||||||
import { SystemUser } from '../System/models/SystemUser';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PlaidApplication {
|
export class PlaidApplication {
|
||||||
@@ -14,16 +10,6 @@ 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,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +25,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,33 +41,10 @@ export class PlaidApplication {
|
|||||||
webhookType: string,
|
webhookType: string,
|
||||||
webhookCode: string,
|
webhookCode: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.plaidWebhooks.webhooks(plaidItemId, webhookType, webhookCode);
|
return this.plaidWebhooks.webhooks(
|
||||||
}
|
plaidItemId,
|
||||||
|
webhookType,
|
||||||
public async setupPlaidTenant(plaidItemId: string, callback: () => void) {
|
webhookCode,
|
||||||
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,32 @@
|
|||||||
|
// 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,9 +6,11 @@ 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 { IPlaidItemCreatedEventPayload } from '../types/BankingPlaid.types';
|
import {
|
||||||
|
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 {
|
||||||
@@ -17,7 +19,9 @@ export class PlaidItemService {
|
|||||||
private readonly tenancyContext: TenancyContext,
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
|
||||||
@Inject(SystemPlaidItem.name)
|
@Inject(SystemPlaidItem.name)
|
||||||
private readonly systemPlaidItemModel: typeof SystemPlaidItem,
|
private readonly systemPlaidItemModel: TenantModelProxy<
|
||||||
|
typeof SystemPlaidItem
|
||||||
|
>,
|
||||||
|
|
||||||
@Inject(PlaidItem.name)
|
@Inject(PlaidItem.name)
|
||||||
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||||
@@ -29,10 +33,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();
|
||||||
@@ -53,7 +57,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,6 +1,5 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import * as bluebird from 'bluebird';
|
import 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,
|
||||||
@@ -13,6 +12,7 @@ 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,13 +15,6 @@ 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,
|
||||||
@@ -35,7 +28,8 @@ export class PlaidUpdateTransactions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles sync the Plaid item to Bigcaptial under UOW.
|
* Handles sync the Plaid item to Bigcaptial under UOW.
|
||||||
* @param {string} plaidItemId - Plaid item id.
|
* @param {number} tenantId - Tenant 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) {
|
||||||
@@ -50,8 +44,8 @@ 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.
|
||||||
* @param {Knex.Transaction} trx - Knex transaction.
|
|
||||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||||
*/
|
*/
|
||||||
public async updateTransactionsWork(
|
public async updateTransactionsWork(
|
||||||
@@ -103,6 +97,7 @@ 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,4 +1,3 @@
|
|||||||
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';
|
||||||
@@ -9,7 +8,7 @@ export class PlaidWebooks {
|
|||||||
private readonly updateTransactionsService: PlaidUpdateTransactions,
|
private readonly updateTransactionsService: PlaidUpdateTransactions,
|
||||||
|
|
||||||
@Inject(PlaidItem.name)
|
@Inject(PlaidItem.name)
|
||||||
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
private readonly plaidItemModel: typeof PlaidItem,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,10 +76,11 @@ 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,8 +122,9 @@ export class PlaidWebooks {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all Item webhook events.
|
* Handles all Item webhook events.
|
||||||
* @param {string} plaidItemId - The Plaid ID for the item
|
* @param {number} tenantId - Tenant ID
|
||||||
* @param {string} webhookCode - The webhook code
|
* @param {string} webhookCode - The webhook code
|
||||||
|
* @param {string} plaidItemId - The Plaid ID for the item
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async itemsHandler(
|
public async itemsHandler(
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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,46 +1,43 @@
|
|||||||
import { Process } from '@nestjs/bull';
|
// import Container, { Service } from 'typedi';
|
||||||
import { UseCls } from 'nestjs-cls';
|
// import { PlaidUpdateTransactions } from './PlaidUpdateTransactions';
|
||||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
// import { IPlaidItemCreatedEventPayload } from '@/interfaces';
|
||||||
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';
|
|
||||||
|
|
||||||
@Processor({
|
// @Service()
|
||||||
name: UpdateBankingPlaidTransitionsQueueJob,
|
// export class PlaidFetchTransactionsJob {
|
||||||
scope: Scope.REQUEST,
|
// /**
|
||||||
})
|
// * Constructor method.
|
||||||
export class PlaidFetchTransactionsProcessor extends WorkerHost {
|
// */
|
||||||
constructor(
|
// constructor(agenda) {
|
||||||
private readonly plaidFetchTransactionsService: PlaidUpdateTransactions,
|
// agenda.define(
|
||||||
private readonly setupPlaidItemService: SetupPlaidItemTenantService,
|
// 'plaid-update-account-transactions',
|
||||||
) {
|
// { priority: 'high', concurrency: 2 },
|
||||||
super();
|
// this.handler
|
||||||
}
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Triggers the function.
|
// * Triggers the function.
|
||||||
*/
|
// */
|
||||||
@Process(UpdateBankingPlaidTransitionsJob)
|
// private handler = async (job, done: Function) => {
|
||||||
@UseCls()
|
// const { tenantId, plaidItemId } = job.attrs
|
||||||
async process(job: Job<PlaidFetchTransitonsEventPayload>) {
|
// .data as IPlaidItemCreatedEventPayload;
|
||||||
const { plaidItemId } = job.data;
|
|
||||||
|
|
||||||
try {
|
// const plaidFetchTransactionsService = Container.get(
|
||||||
await this.setupPlaidItemService.setupPlaidTenant(plaidItemId, () => {
|
// PlaidUpdateTransactions
|
||||||
return this.plaidFetchTransactionsService.updateTransactions(
|
// );
|
||||||
plaidItemId,
|
// const io = Container.get('socket');
|
||||||
);
|
|
||||||
});
|
// try {
|
||||||
// Notify the frontend to reflect the new transactions changes.
|
// await plaidFetchTransactionsService.updateTransactions(
|
||||||
// io.emit('NEW_TRANSACTIONS_DATA', { plaidItemId });
|
// tenantId,
|
||||||
} catch (error) {
|
// plaidItemId
|
||||||
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 { TenantModel } = require('../../System/models/TenantModel');
|
const Tenant = require('system/models/Tenant');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +38,7 @@ export class SystemPlaidItem extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
tenant: {
|
tenant: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: TenantModel,
|
modelClass: Tenant.default,
|
||||||
join: {
|
join: {
|
||||||
from: 'users.tenantId',
|
from: 'users.tenantId',
|
||||||
to: 'tenants.id',
|
to: 'tenants.id',
|
||||||
|
|||||||
@@ -1,34 +1,22 @@
|
|||||||
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 { Queue } from 'bullmq';
|
import { IPlaidItemCreatedEventPayload } from '../types/BankingPlaid.types';
|
||||||
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 = { plaidItemId };
|
const payload = { tenantId, 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,6 +13,7 @@ export interface PlaidItemDTO {
|
|||||||
institutionId: string;
|
institutionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface PlaidFetchedTransactionsUpdates {
|
export interface PlaidFetchedTransactionsUpdates {
|
||||||
added: Transaction[];
|
added: Transaction[];
|
||||||
modified: Transaction[];
|
modified: Transaction[];
|
||||||
@@ -21,20 +22,11 @@ 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;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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,46 +1,32 @@
|
|||||||
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: [
|
||||||
RecognizedTransactionsApplication,
|
GetAutofillCategorizeTransactionService,
|
||||||
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,57 +0,0 @@
|
|||||||
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,5 +1,3 @@
|
|||||||
import { TenantJobPayload } from "@/interfaces/Tenant";
|
|
||||||
|
|
||||||
export interface RevertRecognizedTransactionsCriteria {
|
export interface RevertRecognizedTransactionsCriteria {
|
||||||
batch?: string;
|
batch?: string;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
@@ -9,14 +7,3 @@ 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,47 +2,21 @@ 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 {
|
import { IBankRuleEventCreatedPayload, IBankRuleEventDeletedPayload, IBankRuleEventEditedPayload } from '@/modules/BankRules/types';
|
||||||
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)
|
||||||
async recognizedTransactionsOnRuleCreated({
|
private async recognizedTransactionsOnRuleCreated({
|
||||||
bankRule,
|
bankRule,
|
||||||
}: IBankRuleEventCreatedPayload) {
|
}: IBankRuleEventCreatedPayload) {
|
||||||
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
|
const payload = { ruleId: bankRule.id };
|
||||||
const payload = {
|
|
||||||
ruleId: bankRule.id,
|
|
||||||
...tenantPayload,
|
|
||||||
} as RecognizeUncategorizedTransactionsJobPayload;
|
|
||||||
|
|
||||||
await this.recognizeTransactionsQueue.add(
|
// await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
||||||
RecognizeUncategorizedTransactionsJob,
|
|
||||||
payload,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,33 +24,27 @@ export class TriggerRecognizedTransactionsSubscriber {
|
|||||||
* @param {IBankRuleEventEditedPayload} payload -
|
* @param {IBankRuleEventEditedPayload} payload -
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.bankRules.onEdited)
|
@OnEvent(events.bankRules.onEdited)
|
||||||
async recognizedTransactionsOnRuleEdited({
|
private 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;
|
||||||
}
|
}
|
||||||
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
|
// await this.agenda.now(
|
||||||
const payload = {
|
// 'rerecognize-uncategorized-transactions-job',
|
||||||
ruleId: bankRule.id,
|
// payload
|
||||||
...tenantPayload,
|
// );
|
||||||
} as RecognizeUncategorizedTransactionsJobPayload;
|
|
||||||
|
|
||||||
// Re-recognize the transactions based on the new rules.
|
|
||||||
await this.recognizeTransactionsQueue.add(
|
|
||||||
RecognizeUncategorizedTransactionsJob,
|
|
||||||
payload,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,20 +52,15 @@ export class TriggerRecognizedTransactionsSubscriber {
|
|||||||
* @param {IBankRuleEventDeletedPayload} payload -
|
* @param {IBankRuleEventDeletedPayload} payload -
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.bankRules.onDeleted)
|
@OnEvent(events.bankRules.onDeleted)
|
||||||
async recognizedTransactionsOnRuleDeleted({
|
private async recognizedTransactionsOnRuleDeleted({
|
||||||
ruleId,
|
ruleId,
|
||||||
}: IBankRuleEventDeletedPayload) {
|
}: IBankRuleEventDeletedPayload) {
|
||||||
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
|
const payload = { ruleId };
|
||||||
const payload = {
|
|
||||||
ruleId,
|
|
||||||
...tenantPayload,
|
|
||||||
} as RecognizeUncategorizedTransactionsJobPayload;
|
|
||||||
|
|
||||||
// Re-recognize the transactions based on the new rules.
|
// await this.agenda.now(
|
||||||
await this.recognizeTransactionsQueue.add(
|
// 'revert-recognized-uncategorized-transactions-job',
|
||||||
RecognizeUncategorizedTransactionsJob,
|
// payload
|
||||||
payload,
|
// );
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +68,7 @@ export class TriggerRecognizedTransactionsSubscriber {
|
|||||||
* @param {IImportFileCommitedEventPayload} payload -
|
* @param {IImportFileCommitedEventPayload} payload -
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.import.onImportCommitted)
|
@OnEvent(events.import.onImportCommitted)
|
||||||
async triggerRecognizeTransactionsOnImportCommitted({
|
private async triggerRecognizeTransactionsOnImportCommitted({
|
||||||
importId,
|
importId,
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -113,8 +76,10 @@ 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,48 +1,36 @@
|
|||||||
import { Job } from 'bullmq';
|
// import Container, { Service } from 'typedi';
|
||||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
// import { RecognizeTranasctionsService } from '../commands/RecognizeTranasctions.service';
|
||||||
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';
|
|
||||||
|
|
||||||
@Processor({
|
// @Service()
|
||||||
name: RecognizeUncategorizedTransactionsQueue,
|
// export class RegonizeTransactionsJob {
|
||||||
scope: Scope.REQUEST,
|
// /**
|
||||||
})
|
// * Constructor method.
|
||||||
export class RegonizeTransactionsPrcessor extends WorkerHost {
|
// */
|
||||||
/**
|
// constructor(agenda) {
|
||||||
* @param {RecognizeTranasctionsService} recognizeTranasctionsService -
|
// agenda.define(
|
||||||
* @param {ClsService} clsService -
|
// 'recognize-uncategorized-transactions-job',
|
||||||
*/
|
// { priority: 'high', concurrency: 2 },
|
||||||
constructor(
|
// this.handler
|
||||||
private readonly recognizeTranasctionsService: RecognizeTranasctionsService,
|
// );
|
||||||
private readonly clsService: ClsService,
|
// }
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Triggers sending invoice mail.
|
// * Triggers sending invoice mail.
|
||||||
*/
|
// */
|
||||||
@Process(RecognizeUncategorizedTransactionsQueue)
|
// private handler = async (job, done: Function) => {
|
||||||
@UseCls()
|
// const { tenantId, ruleId, transactionsCriteria } = job.attrs.data;
|
||||||
async process(job: Job<RecognizeUncategorizedTransactionsJobPayload>) {
|
// const regonizeTransactions = Container.get(RecognizeTranasctionsService);
|
||||||
const { ruleId, transactionsCriteria } = job.data;
|
|
||||||
|
|
||||||
this.clsService.set('organizationId', job.data.organizationId);
|
// try {
|
||||||
this.clsService.set('userId', job.data.userId);
|
// await regonizeTransactions.recognizeTransactions(
|
||||||
|
// tenantId,
|
||||||
try {
|
// ruleId,
|
||||||
await this.recognizeTranasctionsService.recognizeTransactions(
|
// transactionsCriteria
|
||||||
ruleId,
|
// );
|
||||||
transactionsCriteria,
|
// done();
|
||||||
);
|
// } catch (error) {
|
||||||
} catch (error) {
|
// console.log(error);
|
||||||
console.log(error);
|
// done(error);
|
||||||
}
|
// }
|
||||||
}
|
// };
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
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,19 +19,13 @@ 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 './controllers/BankingTransactions.controller';
|
import { BankingTransactionsController } from './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),
|
||||||
@@ -48,11 +42,7 @@ const models = [
|
|||||||
DynamicListModule,
|
DynamicListModule,
|
||||||
...models,
|
...models,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [BankingTransactionsController],
|
||||||
BankingTransactionsController,
|
|
||||||
BankingUncategorizedTransactionsController,
|
|
||||||
BankingPendingTransactionsController,
|
|
||||||
],
|
|
||||||
providers: [
|
providers: [
|
||||||
BankTransactionAutoIncrement,
|
BankTransactionAutoIncrement,
|
||||||
BankTransactionGLEntriesService,
|
BankTransactionGLEntriesService,
|
||||||
@@ -71,16 +61,7 @@ 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,13 +9,6 @@ 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 {
|
||||||
@@ -25,10 +18,6 @@ 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,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +44,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: GetBankTransactionsQueryDto) {
|
public getBankAccountTransactions(query: ICashflowAccountTransactionsQuery) {
|
||||||
return this.getBankAccountTransactionsService.bankAccountTransactions(
|
return this.getBankAccountTransactionsService.bankAccountTransactions(
|
||||||
query,
|
query,
|
||||||
);
|
);
|
||||||
@@ -79,53 +68,4 @@ 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,4 +1,3 @@
|
|||||||
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 {
|
||||||
@@ -7,6 +6,7 @@ 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,6 +1,7 @@
|
|||||||
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';
|
||||||
@@ -13,12 +14,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 {
|
||||||
@@ -41,7 +42,7 @@ export class CreateBankTransactionService {
|
|||||||
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO
|
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO
|
||||||
*/
|
*/
|
||||||
public authorize = async (
|
public authorize = async (
|
||||||
newCashflowTransactionDTO: CreateBankTransactionDto,
|
newCashflowTransactionDTO: ICashflowNewCommandDTO,
|
||||||
creditAccount: Account,
|
creditAccount: Account,
|
||||||
) => {
|
) => {
|
||||||
const transactionType = transformCashflowTransactionType(
|
const transactionType = transformCashflowTransactionType(
|
||||||
@@ -59,7 +60,7 @@ export class CreateBankTransactionService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes owner contribution DTO to cashflow transaction.
|
* Transformes owner contribution DTO to cashflow transaction.
|
||||||
* @param {CreateBankTransactionDto} newCashflowTransactionDTO - New transaction DTO.
|
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO - New transaction DTO.
|
||||||
* @returns {ICashflowTransactionInput} - Cashflow transaction object.
|
* @returns {ICashflowTransactionInput} - Cashflow transaction object.
|
||||||
*/
|
*/
|
||||||
private transformCashflowTransactionDTO = async (
|
private transformCashflowTransactionDTO = async (
|
||||||
@@ -90,7 +91,7 @@ export class CreateBankTransactionService {
|
|||||||
|
|
||||||
const initialDTO = {
|
const initialDTO = {
|
||||||
amount,
|
amount,
|
||||||
...formatDateFields(fromDTO, ['date']),
|
...fromDTO,
|
||||||
transactionNumber,
|
transactionNumber,
|
||||||
currencyCode: cashflowAccount.currencyCode,
|
currencyCode: cashflowAccount.currencyCode,
|
||||||
exchangeRate: fromDTO?.exchangeRate || 1,
|
exchangeRate: fromDTO?.exchangeRate || 1,
|
||||||
@@ -116,7 +117,7 @@ export class CreateBankTransactionService {
|
|||||||
* @returns {Promise<ICashflowTransaction>}
|
* @returns {Promise<ICashflowTransaction>}
|
||||||
*/
|
*/
|
||||||
public newCashflowTransaction = async (
|
public newCashflowTransaction = async (
|
||||||
newTransactionDTO: CreateBankTransactionDto,
|
newTransactionDTO: ICashflowNewCommandDTO,
|
||||||
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.
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
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,153 +1,58 @@
|
|||||||
import { ToNumber } from '@/common/decorators/Validators';
|
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsDateString,
|
IsDate,
|
||||||
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 {
|
||||||
@ApiProperty({
|
@IsDate()
|
||||||
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()
|
||||||
@IsOptional()
|
transactionNumber: string;
|
||||||
transactionNumber?: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: 'Optional external reference number',
|
|
||||||
type: String,
|
|
||||||
example: 'REF-001',
|
|
||||||
})
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
referenceNo: string;
|
||||||
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 = 1;
|
exchangeRate: number;
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: 'Currency code for the transaction',
|
|
||||||
type: String,
|
|
||||||
example: 'USD',
|
|
||||||
})
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@IsNumber()
|
||||||
description: 'ID of the credit account associated with this transaction',
|
|
||||||
type: Number,
|
|
||||||
example: 1001,
|
|
||||||
})
|
|
||||||
@IsNotEmpty()
|
|
||||||
@ToNumber()
|
|
||||||
@IsInt()
|
|
||||||
creditAccountId: number;
|
creditAccountId: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@IsNumber()
|
||||||
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()
|
||||||
@IsOptional()
|
publish: boolean;
|
||||||
publish: boolean = true;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: 'ID of the branch where the transaction occurred',
|
|
||||||
type: Number,
|
|
||||||
example: 101,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ToNumber()
|
@IsNumber()
|
||||||
@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()
|
||||||
@IsInt()
|
@IsNumber()
|
||||||
uncategorizedTransactionId?: number;
|
uncategorizedTransactionId?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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 { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
import { BaseModel } from '@/models/Model';
|
||||||
|
|
||||||
export class UncategorizedBankTransaction extends TenantBaseModel {
|
export class UncategorizedBankTransaction extends BaseModel {
|
||||||
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 { GetBankTransactionsQueryDto } from '../../dtos/GetBankTranasctionsQuery.dto';
|
import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types';
|
||||||
|
|
||||||
@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: GetBankTransactionsQueryDto,
|
query: ICashflowAccountTransactionsQuery,
|
||||||
) {
|
) {
|
||||||
const parsedQuery = {
|
const parsedQuery = {
|
||||||
...getBankAccountTransactionsDefaultQuery(),
|
...getBankAccountTransactionsDefaultQuery(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import * as R from 'ramda';
|
import R from 'ramda';
|
||||||
import * as moment from 'moment';
|
import moment from 'moment';
|
||||||
import { first, isEmpty } from 'lodash';
|
import { first, isEmpty } from 'lodash';
|
||||||
import {
|
import {
|
||||||
ICashflowAccountTransaction,
|
ICashflowAccountTransaction,
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
import { 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;
|
||||||
@@ -20,28 +17,6 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -62,8 +37,9 @@ export class GetBankAccountTransactionsRepository {
|
|||||||
* @param {ICashflowAccountTransactionsQuery} query -
|
* @param {ICashflowAccountTransactionsQuery} query -
|
||||||
*/
|
*/
|
||||||
async initCashflowAccountTransactions() {
|
async initCashflowAccountTransactions() {
|
||||||
const { results, pagination } = await this.accountTransactionModel()
|
const { AccountTransaction } = this.models;
|
||||||
.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' },
|
||||||
@@ -83,9 +59,10 @@ 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 = this.accountTransactionModel()
|
const openingBalancesSubquery = AccountTransaction.query()
|
||||||
.query()
|
|
||||||
.where('account_id', this.query.accountId)
|
.where('account_id', this.query.accountId)
|
||||||
.orderBy([
|
.orderBy([
|
||||||
{ column: 'date', order: 'desc' },
|
{ column: 'date', order: 'desc' },
|
||||||
@@ -95,8 +72,7 @@ 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 this.accountTransactionModel()
|
const openingBalances = await AccountTransaction.query()
|
||||||
.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'))
|
||||||
@@ -111,11 +87,14 @@ 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 this.uncategorizedBankTransactionModel()
|
await UncategorizedCashflowTransaction.query().whereIn(
|
||||||
.query()
|
['categorizeRefType', 'categorizeRefId'],
|
||||||
.whereIn(['categorizeRefType', 'categorizeRefId'], refs);
|
refs,
|
||||||
|
);
|
||||||
|
|
||||||
this.uncategorizedTransactions = uncategorizedTransactions;
|
this.uncategorizedTransactions = uncategorizedTransactions;
|
||||||
this.uncategorizedTransactionsMapByRef = groupUncategorizedTransactions(
|
this.uncategorizedTransactionsMapByRef = groupUncategorizedTransactions(
|
||||||
@@ -127,11 +106,14 @@ 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 = await this.matchedBankTransactionModel()
|
const matchedBankTransactions =
|
||||||
.query()
|
await MatchedBankTransaction.query().whereIn(
|
||||||
.whereIn(['referenceType', 'referenceId'], refs);
|
['referenceType', 'referenceId'],
|
||||||
|
refs,
|
||||||
|
);
|
||||||
this.matchedBankTransactions = matchedBankTransactions;
|
this.matchedBankTransactions = matchedBankTransactions;
|
||||||
this.matchedBankTransactionsMapByRef = groupMatchedBankTransactions(
|
this.matchedBankTransactionsMapByRef = groupMatchedBankTransactions(
|
||||||
matchedBankTransactions,
|
matchedBankTransactions,
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ 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 {
|
||||||
@@ -11,24 +9,21 @@ export class GetPendingBankAccountTransactions {
|
|||||||
private readonly transformerService: TransformerInjectable,
|
private readonly transformerService: TransformerInjectable,
|
||||||
|
|
||||||
@Inject(UncategorizedBankTransaction.name)
|
@Inject(UncategorizedBankTransaction.name)
|
||||||
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction
|
||||||
typeof UncategorizedBankTransaction
|
|
||||||
>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the given bank accounts pending transaction.
|
* Retrieves the given bank accounts pending transaction.
|
||||||
* @param {GetPendingTransactionsQueryDto} filter - Pending transactions query.
|
* @param {GetPendingTransactionsQuery} filter - Pending transactions query.
|
||||||
*/
|
*/
|
||||||
async getPendingTransactions(filter?: GetPendingTransactionsQueryDto) {
|
async getPendingTransactions(filter?: GetPendingTransactionsQuery) {
|
||||||
const _filter = {
|
const _filter = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
...filter,
|
...filter,
|
||||||
};
|
};
|
||||||
const { results, pagination } =
|
const { results, pagination } =
|
||||||
await this.uncategorizedBankTransactionModel()
|
await this.uncategorizedBankTransactionModel.query()
|
||||||
.query()
|
|
||||||
.onBuild((q) => {
|
.onBuild((q) => {
|
||||||
q.modify('pending');
|
q.modify('pending');
|
||||||
|
|
||||||
@@ -40,8 +35,14 @@ export class GetPendingBankAccountTransactions {
|
|||||||
|
|
||||||
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,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 '../../BankingTransactions/models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { GetRecognizedTransactionTransformer } from './queries/GetRecognizedTransactionTransformer';
|
import { GetRecognizedTransactionTransformer } from './GetRecognizedTransactionTransformer';
|
||||||
import { UncategorizedBankTransaction } from '../BankingTransactions/models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { IGetRecognizedTransactionsQuery } from '../BankingTransactions/types/BankingTransactions.types';
|
import { IGetRecognizedTransactionsQuery } from '../types/BankingTransactions.types';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetRecognizedTransactionsService {
|
export class GetRecognizedTransactionsService {
|
||||||
@@ -11,7 +10,7 @@ export class GetRecognizedTransactionsService {
|
|||||||
private readonly transformer: TransformerInjectable,
|
private readonly transformer: TransformerInjectable,
|
||||||
|
|
||||||
@Inject(UncategorizedBankTransaction.name)
|
@Inject(UncategorizedBankTransaction.name)
|
||||||
private readonly uncategorizedBankTransactionModel: TenantModelProxy<typeof UncategorizedBankTransaction>,
|
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,7 +25,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');
|
||||||
@@ -1,33 +1,26 @@
|
|||||||
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 { GetUncategorizedTransactionsQueryDto } from '../dtos/GetUncategorizedTransactionsQuery.dto';
|
import { IGetUncategorizedTransactionsQuery } from '../types/BankingTransactions.types';
|
||||||
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: TenantModelProxy<
|
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
|
||||||
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: GetUncategorizedTransactionsQueryDto,
|
query: IGetUncategorizedTransactionsQuery
|
||||||
) {
|
) {
|
||||||
// Parsed query with default values.
|
// Parsed query with default values.
|
||||||
const _query = {
|
const _query = {
|
||||||
@@ -35,9 +28,9 @@ export class GetUncategorizedTransactions {
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { results, pagination } =
|
const { results, pagination } =
|
||||||
await this.uncategorizedBankTransactionModel()
|
await this.uncategorizedBankTransactionModel.query()
|
||||||
.query()
|
|
||||||
.onBuild((q) => {
|
.onBuild((q) => {
|
||||||
q.where('accountId', accountId);
|
q.where('accountId', accountId);
|
||||||
q.where('categorized', false);
|
q.where('categorized', false);
|
||||||
@@ -70,7 +63,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,10 +3,7 @@ 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 {
|
import { ICommandCashflowCreatedPayload, ICommandCashflowDeletedPayload } from '../types/BankingTransactions.types';
|
||||||
ICommandCashflowCreatedPayload,
|
|
||||||
ICommandCashflowDeletedPayload,
|
|
||||||
} from '../types/BankingTransactions.types';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BankingTransactionGLEntriesSubscriber {
|
export class BankingTransactionGLEntriesSubscriber {
|
||||||
@@ -59,5 +56,5 @@ export class BankingTransactionGLEntriesSubscriber {
|
|||||||
cashflowTransactionId,
|
cashflowTransactionId,
|
||||||
trx,
|
trx,
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,12 @@ 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: TenantModelProxy<typeof Account>,
|
private readonly accountModel: typeof Account,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +33,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);
|
||||||
@@ -59,7 +58,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);
|
||||||
@@ -81,7 +80,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,34 +5,19 @@ 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/exclude')
|
@Controller('banking/transactions')
|
||||||
@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(
|
||||||
@@ -43,7 +28,7 @@ export class BankingTransactionsExcludeController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Post(':id/exclude')
|
||||||
@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(
|
||||||
@@ -51,11 +36,25 @@ export class BankingTransactionsExcludeController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id/exclude')
|
||||||
@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 { forwardRef, Module } from '@nestjs/common';
|
import { 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,9 +10,7 @@ import { BankingTransactionsExcludeController } from './BankingTransactionsExclu
|
|||||||
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [BankingTransactionsModule],
|
||||||
forwardRef(() => BankingTransactionsModule),
|
|
||||||
],
|
|
||||||
providers: [
|
providers: [
|
||||||
ExcludeBankTransactionsApplication,
|
ExcludeBankTransactionsApplication,
|
||||||
ExcludeBankTransactionService,
|
ExcludeBankTransactionService,
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
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,32 +1,8 @@
|
|||||||
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({
|
||||||
imports: [InventoryCostModule],
|
providers: [TransactionLandedCostEntriesService],
|
||||||
providers: [
|
|
||||||
AllocateLandedCostService,
|
|
||||||
TransactionLandedCostEntriesService,
|
|
||||||
BillAllocatedLandedCostTransactions,
|
|
||||||
LandedCostGLEntriesSubscriber,
|
|
||||||
LandedCostGLEntries,
|
|
||||||
LandedCostSyncCostTransactions,
|
|
||||||
RevertAllocatedLandedCost,
|
|
||||||
LandedCostInventoryTransactions,
|
|
||||||
LandedCostTranasctions,
|
|
||||||
LandedCostSyncCostTransactionsSubscriber,
|
|
||||||
],
|
|
||||||
exports: [TransactionLandedCostEntriesService],
|
exports: [TransactionLandedCostEntriesService],
|
||||||
controllers: [BillAllocateLandedCostController],
|
|
||||||
})
|
})
|
||||||
export class BillLandedCostsModule {}
|
export class BillLandedCostsModule {}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
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,10 +1,7 @@
|
|||||||
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 {
|
import { ICommonLandedCostEntry, ICommonLandedCostEntryDTO } from './types/BillLandedCosts.types';
|
||||||
ICommonLandedCostEntry,
|
|
||||||
ICommonLandedCostEntryDTO,
|
|
||||||
} from './types/BillLandedCosts.types';
|
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
|
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
|
||||||
@@ -22,7 +19,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');
|
||||||
|
|
||||||
@@ -43,11 +40,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);
|
||||||
@@ -61,7 +58,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');
|
||||||
|
|
||||||
@@ -70,7 +67,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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
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;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
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
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
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,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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