mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
refactor(nestjs): bank transactions matching
This commit is contained in:
@@ -2,14 +2,21 @@ 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,
|
||||
) {
|
||||
@@ -20,6 +27,11 @@ export class BankingCategorizeController {
|
||||
}
|
||||
|
||||
@Delete('/bulk')
|
||||
@ApiOperation({ summary: 'Uncategorize bank transactions.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The bank transactions have been uncategorized successfully.',
|
||||
})
|
||||
public uncategorizeTransactionsBulk(
|
||||
@Query() uncategorizedTransactionIds: number[] | number,
|
||||
) {
|
||||
@@ -29,6 +41,11 @@ export class BankingCategorizeController {
|
||||
}
|
||||
|
||||
@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,
|
||||
) {
|
||||
|
||||
@@ -8,48 +8,106 @@ import {
|
||||
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>;
|
||||
}
|
||||
|
||||
@@ -23,16 +23,12 @@ export class BankingMatchingController {
|
||||
);
|
||||
}
|
||||
|
||||
@Post('/match/:uncategorizedTransactionId')
|
||||
@Post('/match')
|
||||
@ApiOperation({ summary: 'Match the given uncategorized transaction.' })
|
||||
async matchTransaction(
|
||||
@Param('uncategorizedTransactionId')
|
||||
uncategorizedTransactionId: number | number[],
|
||||
@Body() matchedTransactions: MatchBankTransactionDto,
|
||||
) {
|
||||
async matchTransaction(@Body() matchedTransactions: MatchBankTransactionDto) {
|
||||
return this.bankingMatchingApplication.matchTransaction(
|
||||
uncategorizedTransactionId,
|
||||
matchedTransactions,
|
||||
matchedTransactions.uncategorizedTransactions,
|
||||
matchedTransactions.matchedTransactions,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||
import { GetMatchedTransactions } from './queries/GetMatchedTransactions.service';
|
||||
import { MatchBankTransactions } from './commands/MatchTransactions';
|
||||
import { UnmatchMatchedBankTransaction } from './commands/UnmatchMatchedTransaction.service';
|
||||
import { GetMatchedTransactionsFilter, IMatchTransactionDTO } from './types';
|
||||
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
|
||||
import { GetMatchedTransactionsFilter } from './types';
|
||||
import { MatchTransactionEntryDto } from './dtos/MatchBankTransaction.dto';
|
||||
|
||||
@Injectable()
|
||||
export class BankingMatchingApplication {
|
||||
@@ -31,17 +31,18 @@ export class BankingMatchingApplication {
|
||||
|
||||
/**
|
||||
* Matches the given uncategorized transaction with the given system transaction.
|
||||
* @param {number} uncategorizedTransactionId
|
||||
* @param {IMatchTransactionDTO} matchTransactionsDTO
|
||||
* @param {IMatchBankTransactionDto} matchedTransactionsDTO
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public matchTransaction(
|
||||
uncategorizedTransactionId: number | Array<number>,
|
||||
matchedTransactions: MatchBankTransactionDto,
|
||||
matchedTransactionsDto:
|
||||
| MatchTransactionEntryDto
|
||||
| Array<MatchTransactionEntryDto>,
|
||||
): Promise<void> {
|
||||
return this.matchTransactionService.matchTransaction(
|
||||
uncategorizedTransactionId,
|
||||
matchedTransactions,
|
||||
matchedTransactionsDto,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||
import { events } from '@/common/events/events';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { MatchBankTransactionDto } from '../dtos/MatchBankTransaction.dto';
|
||||
import { MatchTransactionEntryDto } from '../dtos/MatchBankTransaction.dto';
|
||||
|
||||
@Injectable()
|
||||
export class MatchBankTransactions {
|
||||
@@ -107,16 +107,15 @@ export class MatchBankTransactions {
|
||||
|
||||
/**
|
||||
* Matches the given uncategorized transaction to the given references.
|
||||
* @param {number} tenantId
|
||||
* @param {number} uncategorizedTransactionId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async matchTransaction(
|
||||
uncategorizedTransactionId: number | Array<number>,
|
||||
matchedTransactionsDto: MatchBankTransactionDto,
|
||||
matchedTransactionsDto: MatchTransactionEntryDto | Array<MatchTransactionEntryDto>,
|
||||
): Promise<void> {
|
||||
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
|
||||
const matchedTransactions = matchedTransactionsDto.entries;
|
||||
const matchedTransactions = castArray(matchedTransactionsDto);
|
||||
|
||||
// Validates the given matching transactions DTO.
|
||||
await this.validate(uncategorizedTransactionIds, matchedTransactions);
|
||||
@@ -131,7 +130,7 @@ export class MatchBankTransactions {
|
||||
// Matches the given transactions under promise pool concurrency controlling.
|
||||
await PromisePool.withConcurrency(10)
|
||||
.for(matchedTransactions)
|
||||
.process(async (matchedTransaction) => {
|
||||
.process(async (matchedTransaction: MatchTransactionEntryDto) => {
|
||||
const getMatchedTransactionsService =
|
||||
this.matchedBankTransactions.registry.get(
|
||||
matchedTransaction.referenceType,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
@@ -27,6 +28,10 @@ export class MatchTransactionEntryDto {
|
||||
}
|
||||
|
||||
export class MatchBankTransactionDto {
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
uncategorizedTransactions: Array<number>
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => MatchTransactionEntryDto)
|
||||
@@ -37,5 +42,5 @@ export class MatchBankTransactionDto {
|
||||
{ referenceType: 'SaleInvoice', referenceId: 2 },
|
||||
],
|
||||
})
|
||||
entries: MatchTransactionEntryDto[];
|
||||
matchedTransactions: MatchTransactionEntryDto[];
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import PromisePool from '@supercharge/promise-pool';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
IBankTransactionMatchedEventPayload,
|
||||
IBankTransactionUnmatchedEventPayload,
|
||||
} from '../types';
|
||||
import PromisePool from '@supercharge/promise-pool';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||
import { events } from '@/common/events/events';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class DecrementUncategorizedTransactionOnMatchingSubscriber {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { PlaidWebhookDto } from './dtos/PlaidItem.dto';
|
||||
import { ApiOperation } from '@nestjs/swagger';
|
||||
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(
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { events } from '@/common/events/events';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { Queue } from 'bullmq';
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
import {
|
||||
IPlaidItemCreatedEventPayload,
|
||||
UpdateBankingPlaidTransitionsJob,
|
||||
UpdateBankingPlaidTransitionsQueueJob,
|
||||
} from '../types/BankingPlaid.types';
|
||||
import { Queue } from 'bullmq';
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class PlaidUpdateTransactionsOnItemCreatedSubscriber {
|
||||
|
||||
@@ -8,67 +8,145 @@ import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class CreateBankTransactionDto {
|
||||
@ApiProperty({
|
||||
description: 'The date of the bank transaction',
|
||||
type: Date,
|
||||
example: '2023-01-01T00:00:00.000Z',
|
||||
})
|
||||
@IsDateString()
|
||||
@IsNotEmpty()
|
||||
date: Date;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Optional transaction number or reference',
|
||||
type: String,
|
||||
example: 'TRX-001',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
transactionNumber?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Optional external reference number',
|
||||
type: String,
|
||||
example: 'REF-001',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
referenceNo?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Type of bank transaction (e.g., deposit, withdrawal)',
|
||||
type: String,
|
||||
example: 'deposit',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
transactionType: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Description of the bank transaction',
|
||||
type: String,
|
||||
example: 'Monthly rent payment',
|
||||
})
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Transaction amount',
|
||||
type: Number,
|
||||
example: 1000.5,
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
@IsNumber()
|
||||
amount: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Exchange rate for currency conversion',
|
||||
type: Number,
|
||||
default: 1,
|
||||
example: 1.15,
|
||||
})
|
||||
@ToNumber()
|
||||
@IsNumber()
|
||||
exchangeRate: number = 1;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Currency code for the transaction',
|
||||
type: String,
|
||||
example: 'USD',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
currencyCode: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'ID of the credit account associated with this transaction',
|
||||
type: Number,
|
||||
example: 1001,
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
@IsInt()
|
||||
@IsInt()
|
||||
creditAccountId: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'ID of the cashflow account associated with this transaction',
|
||||
type: Number,
|
||||
example: 2001,
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
@IsInt()
|
||||
@IsInt()
|
||||
cashflowAccountId: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Whether the transaction should be published',
|
||||
type: Boolean,
|
||||
default: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
publish: boolean = true;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'ID of the branch where the transaction occurred',
|
||||
type: Number,
|
||||
example: 101,
|
||||
})
|
||||
@IsOptional()
|
||||
@ToNumber()
|
||||
@IsInt()
|
||||
branchId?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Plaid transaction ID if imported from Plaid',
|
||||
type: String,
|
||||
example: 'plaid_trx_12345',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
plaidTransactionId?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Plaid account ID if imported from Plaid',
|
||||
type: String,
|
||||
example: 'plaid_acc_67890',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
plaidAccountId?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'ID of the uncategorized transaction if this is categorizing an existing transaction',
|
||||
type: Number,
|
||||
example: 5001,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
uncategorizedTransactionId?: number;
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import * as moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import type { Knex } from 'knex';
|
||||
import { Model, raw } from 'objection';
|
||||
import { castArray, difference, defaultTo } from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
// import TenantModel from 'models/TenantModel';
|
||||
// import BillSettings from './Bill.Settings';
|
||||
// import ModelSetting from './ModelSetting';
|
||||
// import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
// import { DEFAULT_VIEWS } from '@/services/Purchases/Bills/constants';
|
||||
// import ModelSearchable from './ModelSearchable';
|
||||
import { BaseModel, PaginationQueryBuilderType } from '@/models/Model';
|
||||
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
||||
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ToNumber } from '@/common/decorators/Validators';
|
||||
import { IsArray, IsEnum, IsInt, IsOptional, IsString } from 'class-validator';
|
||||
import { IFilterRole, ISortOrder } from '../DynamicFilter/DynamicFilter.types';
|
||||
|
||||
export class DynamicFilterQueryDto {
|
||||
@IsOptional()
|
||||
@ToNumber()
|
||||
customViewId?: number;
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
filterRoles?: IFilterRole[];
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
columnSortBy: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
sortOrder: ISortOrder;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
stringifiedFilterRoles?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
searchKeyword?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
viewSlug?: string;
|
||||
}
|
||||
@@ -52,8 +52,6 @@ export class ExportResourceService {
|
||||
const data = await this.getExportableData(resource);
|
||||
const transformed = this.transformExportedData(resource, data);
|
||||
|
||||
console.log(format);
|
||||
|
||||
// Returns the csv, xlsx format.
|
||||
if (format === ExportFormat.Csv || format === ExportFormat.Xlsx) {
|
||||
const exportableColumns = this.getExportableColumns(resourceColumns);
|
||||
|
||||
@@ -17,8 +17,8 @@ export class InventoryAdjustmentTransformer extends Transformer {
|
||||
*/
|
||||
formattedType(inventoryAdjustment: InventoryAdjustment) {
|
||||
const types = {
|
||||
increment: 'inventory_adjustment.type.increment',
|
||||
decrement: 'inventory_adjustment.type.decrement',
|
||||
increment: 'inventory_adjustment.increment',
|
||||
decrement: 'inventory_adjustment.decrement',
|
||||
};
|
||||
return this.context.i18n.t(types[inventoryAdjustment.type] || '');
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsPositive,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
|
||||
import { parseBoolean } from '@/utils/parse-boolean';
|
||||
|
||||
enum IAdjustmentTypes {
|
||||
INCREMENT = 'increment',
|
||||
@@ -19,8 +21,7 @@ enum IAdjustmentTypes {
|
||||
export class CreateQuickInventoryAdjustmentDto {
|
||||
@ApiProperty({ description: 'Date of the inventory adjustment' })
|
||||
@IsNotEmpty()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
@IsDateString()
|
||||
date: Date;
|
||||
|
||||
@ApiProperty({ description: 'Type of adjustment', enum: IAdjustmentTypes })
|
||||
@@ -30,7 +31,8 @@ export class CreateQuickInventoryAdjustmentDto {
|
||||
|
||||
@ApiProperty({ description: 'ID of the adjustment account' })
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@ToNumber()
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
adjustmentAccountId: number;
|
||||
|
||||
@@ -40,47 +42,52 @@ export class CreateQuickInventoryAdjustmentDto {
|
||||
reason: string;
|
||||
|
||||
@ApiProperty({ description: 'Description of the adjustment' })
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@ApiProperty({ description: 'Reference number' })
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
referenceNo: string;
|
||||
|
||||
@ApiProperty({ description: 'ID of the item being adjusted' })
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
itemId: number;
|
||||
|
||||
@ApiProperty({ description: 'Quantity to adjust' })
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
quantity: number;
|
||||
|
||||
@ApiProperty({ description: 'Cost of the item' })
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
@ToNumber()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
cost: number;
|
||||
|
||||
@ApiProperty({ description: 'Whether to publish the adjustment immediately' })
|
||||
@IsNotEmpty()
|
||||
@Transform((param) => parseBoolean(param.value, false))
|
||||
@IsBoolean()
|
||||
publish: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'ID of the warehouse (optional)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ToNumber()
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
warehouseId?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'ID of the branch (optional)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ToNumber()
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
branchId?: number;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { GetItemsInventoryValuationListService } from './queries/GetItemsInventoryValuationList.service';
|
||||
import { GetInventoyItemsCostQueryDto } from './dtos/GetInventoryItemsCostQuery.dto';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@Controller('inventory-cost')
|
||||
@ApiTags('inventory-cost')
|
||||
export class InventoryCostController {
|
||||
constructor(
|
||||
private readonly inventoryItemCost: GetItemsInventoryValuationListService,
|
||||
) {}
|
||||
|
||||
@Get('items')
|
||||
@ApiOperation({ summary: 'Get items inventory valuation list' })
|
||||
async getItemsCost(
|
||||
@Query() itemsCostsQueryDto: GetInventoyItemsCostQueryDto,
|
||||
) {
|
||||
const costs = await this.inventoryItemCost.getItemsInventoryValuationList(
|
||||
itemsCostsQueryDto.itemsIds,
|
||||
itemsCostsQueryDto.date,
|
||||
);
|
||||
return { costs };
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import { InventoryItemOpeningAvgCostService } from './commands/InventoryItemOpen
|
||||
import { InventoryCostSubscriber } from './subscribers/InventoryCost.subscriber';
|
||||
import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
|
||||
import { ImportModule } from '../Import/Import.module';
|
||||
import { GetItemsInventoryValuationListService } from './queries/GetItemsInventoryValuationList.service';
|
||||
import { InventoryCostController } from './InventoryCost.controller';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(InventoryCostLotTracker),
|
||||
@@ -54,6 +56,7 @@ const models = [
|
||||
InventoryItemCostService,
|
||||
InventoryItemOpeningAvgCostService,
|
||||
InventoryCostSubscriber,
|
||||
GetItemsInventoryValuationListService
|
||||
],
|
||||
exports: [
|
||||
...models,
|
||||
@@ -61,5 +64,6 @@ const models = [
|
||||
InventoryItemCostService,
|
||||
InventoryComputeCostService,
|
||||
],
|
||||
controllers: [InventoryCostController]
|
||||
})
|
||||
export class InventoryCostModule {}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsNotEmpty,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class GetInventoyItemsCostQueryDto {
|
||||
@IsDateString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
description: 'The date to get the inventory cost for',
|
||||
example: '2021-01-01',
|
||||
})
|
||||
date: Date;
|
||||
|
||||
@IsArray()
|
||||
@IsNotEmpty()
|
||||
@ArrayMinSize(1)
|
||||
@ApiProperty({
|
||||
description: 'The ids of the items to get the inventory cost for',
|
||||
example: [1, 2, 3],
|
||||
})
|
||||
itemsIds: Array<number>;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InventoryItemCostService } from '../commands/InventoryCosts.service';
|
||||
import { IInventoryItemCostMeta } from '../types/InventoryCost.types';
|
||||
|
||||
@Injectable()
|
||||
export class GetItemsInventoryValuationListService {
|
||||
constructor(private readonly inventoryCost: InventoryItemCostService) {}
|
||||
|
||||
/**
|
||||
* Retrieves the items inventory valuation list.
|
||||
* @param {number[]} itemsId
|
||||
* @param {Date} date
|
||||
* @returns {Promise<IInventoryItemCostMeta[]>}
|
||||
*/
|
||||
public getItemsInventoryValuationList = async (
|
||||
itemsId: number[],
|
||||
date: Date,
|
||||
): Promise<IInventoryItemCostMeta[]> => {
|
||||
const itemsMap = await this.inventoryCost.getItemsInventoryValuation(
|
||||
itemsId,
|
||||
date,
|
||||
);
|
||||
return [...itemsMap.values()];
|
||||
};
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { IItemsFilter } from './types/Items.types';
|
||||
import { ItemTransformer } from './Item.transformer';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { ISortOrder } from '../DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||
import { GetItemsQueryDto } from './dtos/GetItemsQuery.dto';
|
||||
|
||||
@Injectable()
|
||||
export class GetItemsService {
|
||||
@@ -32,7 +33,7 @@ export class GetItemsService {
|
||||
* Retrieves items datatable list.
|
||||
* @param {IItemsFilter} itemsFilter - Items filter.
|
||||
*/
|
||||
public async getItems(filterDto: Partial<IItemsFilter>) {
|
||||
public async getItems(filterDto: Partial<GetItemsQueryDto>) {
|
||||
const _filterDto = {
|
||||
sortOrder: ISortOrder.DESC,
|
||||
columnSortBy: 'created_at',
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from '@nestjs/swagger';
|
||||
import { IItemsFilter } from './types/Items.types';
|
||||
import { CreateItemDto, EditItemDto } from './dtos/Item.dto';
|
||||
import { GetItemsQueryDto } from './dtos/GetItemsQuery.dto';
|
||||
|
||||
@Controller('/items')
|
||||
@UseGuards(SubscriptionGuard)
|
||||
@@ -99,7 +100,7 @@ export class ItemsController extends TenantController {
|
||||
type: Boolean,
|
||||
description: 'Filter for inactive items',
|
||||
})
|
||||
async getItems(@Query() filterDTO: IItemsFilter): Promise<any> {
|
||||
async getItems(@Query() filterDTO: GetItemsQueryDto): Promise<any> {
|
||||
return this.itemsApplication.getItems(filterDTO);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { GetItemsService } from './GetItems.service';
|
||||
import { IItemsFilter } from './types/Items.types';
|
||||
import { EditItemDto, CreateItemDto } from './dtos/Item.dto';
|
||||
import { GetItemsQueryDto } from './dtos/GetItemsQuery.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ItemsApplicationService {
|
||||
@@ -94,7 +95,7 @@ export class ItemsApplicationService {
|
||||
* Retrieves the paginated filterable items list.
|
||||
* @param {Partial<IItemsFilter>} filterDTO
|
||||
*/
|
||||
async getItems(filterDTO: Partial<IItemsFilter>) {
|
||||
async getItems(filterDTO: Partial<GetItemsQueryDto>) {
|
||||
return this.getItemsService.getItems(filterDTO);
|
||||
}
|
||||
|
||||
|
||||
22
packages/server/src/modules/Items/dtos/GetItemsQuery.dto.ts
Normal file
22
packages/server/src/modules/Items/dtos/GetItemsQuery.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ToNumber } from '@/common/decorators/Validators';
|
||||
import { DynamicFilterQueryDto } from '@/modules/DynamicListing/dtos/DynamicFilterQuery.dto';
|
||||
import { parseBoolean } from '@/utils/parse-boolean';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
|
||||
|
||||
export class GetItemsQueryDto extends DynamicFilterQueryDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@ToNumber()
|
||||
page?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@ToNumber()
|
||||
pageSize?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Transform((param) => parseBoolean(param.value, false))
|
||||
@IsBoolean()
|
||||
inactiveMode?: boolean;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import async from 'async';
|
||||
import * as async from 'async';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ILedger,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
import async from 'async';
|
||||
import * as async from 'async';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { transformLedgerEntryToTransaction } from './utils';
|
||||
import {
|
||||
@@ -33,7 +33,7 @@ export class LedgerEntriesStorageService {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public saveEntries = async (ledger: ILedger, trx?: Knex.Transaction) => {
|
||||
const saveEntryQueue = async.queue(this.saveEntryTask, 10);
|
||||
const saveEntryQueue = async.queue(this.saveEntryTask.bind(this), 10);
|
||||
const entries = ledger.filter(filterBlankEntry).getEntries();
|
||||
|
||||
entries.forEach((entry) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import async from 'async';
|
||||
import * as async from 'async';
|
||||
import { Knex } from 'knex';
|
||||
import { uniq } from 'lodash';
|
||||
import {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as moment from 'moment';
|
||||
import { AccountTransaction } from "../Accounts/models/AccountTransaction.model";
|
||||
import { ILedgerEntry } from "./types/Ledger.types";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
import async from 'async';
|
||||
import * as async from 'async';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { PaymentReceivedGLEntries } from '../PaymentReceived/commands/PaymentReceivedGLEntries';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { Model, raw } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import { MomentInput, unitOfTime } from 'moment';
|
||||
import { defaultTo } from 'ramda';
|
||||
import { TaxRateTransaction } from '@/modules/TaxRates/models/TaxRateTransaction.model';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import moment from 'moment';
|
||||
import * as moment from 'moment';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
export class UserInvite extends BaseModel {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { IEditWarehouseDTO, IWarehouse } from '../Warehouse.types';
|
||||
import { WarehouseValidator } from './WarehouseValidator.service';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ToNumber } from '@/common/decorators/Validators';
|
||||
import { IsInt, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class GetWarehouseTransfersQueryDto {
|
||||
@IsInt()
|
||||
@ToNumber()
|
||||
@IsOptional()
|
||||
page: number;
|
||||
|
||||
@IsInt()
|
||||
@ToNumber()
|
||||
@IsOptional()
|
||||
pageSize: number;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
searchKeyword: string;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
CreateWarehouseTransferDto,
|
||||
EditWarehouseTransferDto,
|
||||
} from './dtos/WarehouseTransfer.dto';
|
||||
import { GetWarehouseTransfersQueryDto } from '../Warehouses/dtos/GetWarehouseTransfersQuery.dto';
|
||||
|
||||
@Injectable()
|
||||
export class WarehouseTransferApplication {
|
||||
@@ -86,7 +87,7 @@ export class WarehouseTransferApplication {
|
||||
* @returns {Promise<IWarehouseTransfer>}
|
||||
*/
|
||||
public getWarehousesTransfers = (
|
||||
filterDTO: IGetWarehousesTransfersFilterDTO,
|
||||
filterDTO: GetWarehouseTransfersQueryDto,
|
||||
) => {
|
||||
return this.getWarehousesTransfersService.getWarehouseTransfers(filterDTO);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
CreateWarehouseTransferDto,
|
||||
EditWarehouseTransferDto,
|
||||
} from './dtos/WarehouseTransfer.dto';
|
||||
import { GetWarehouseTransfersQueryDto } from '../Warehouses/dtos/GetWarehouseTransfersQuery.dto';
|
||||
|
||||
@Controller('warehouse-transfers')
|
||||
@ApiTags('warehouse-transfers')
|
||||
@@ -129,7 +130,7 @@ export class WarehouseTransfersController {
|
||||
description:
|
||||
'The warehouse transfer transactions have been retrieved successfully.',
|
||||
})
|
||||
async getWarehousesTransfers(@Query() query: any) {
|
||||
async getWarehousesTransfers(@Query() query: GetWarehouseTransfersQueryDto) {
|
||||
const { warehousesTransfers, pagination, filter } =
|
||||
await this.warehouseTransferApplication.getWarehousesTransfers(query);
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { ToNumber } from '@/common/decorators/Validators';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsDecimal,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsPositive,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
ArrayMinSize,
|
||||
IsDateString,
|
||||
} from 'class-validator';
|
||||
|
||||
export class WarehouseTransferEntryDto {
|
||||
@@ -39,6 +39,7 @@ export class WarehouseTransferEntryDto {
|
||||
|
||||
export class CommandWarehouseTransferDto {
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
@IsInt()
|
||||
@ApiProperty({
|
||||
description: 'The id of the warehouse to transfer from',
|
||||
@@ -47,6 +48,7 @@ export class CommandWarehouseTransferDto {
|
||||
fromWarehouseId: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
@IsInt()
|
||||
@ApiProperty({
|
||||
description: 'The id of the warehouse to transfer to',
|
||||
@@ -55,7 +57,7 @@ export class CommandWarehouseTransferDto {
|
||||
toWarehouseId: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsDate()
|
||||
@IsDateString()
|
||||
@ApiProperty({
|
||||
description: 'The date of the warehouse transfer',
|
||||
example: '2021-01-01',
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
export const WarehouseTransferMeta = {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortField: 'name',
|
||||
sortOrder: 'DESC',
|
||||
},
|
||||
columns: {
|
||||
date: {
|
||||
name: 'warehouse_transfer.field.date',
|
||||
type: 'date',
|
||||
exportable: true,
|
||||
},
|
||||
transaction_number: {
|
||||
name: 'warehouse_transfer.field.transaction_number',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
status: {
|
||||
name: 'warehouse_transfer.field.status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', label: 'Draft' },
|
||||
{ key: 'in-transit', label: 'In Transit' },
|
||||
{ key: 'transferred', label: 'Transferred' },
|
||||
],
|
||||
sortable: false,
|
||||
},
|
||||
created_at: {
|
||||
name: 'warehouse_transfer.field.created_at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
date: {
|
||||
name: 'warehouse_transfer.field.date',
|
||||
column: 'date',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
transaction_number: {
|
||||
name: 'warehouse_transfer.field.transaction_number',
|
||||
column: 'transaction_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'warehouse_transfer.field.status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', label: 'Draft' },
|
||||
{ key: 'in-transit', label: 'In Transit' },
|
||||
{ key: 'transferred', label: 'Transferred' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortable: false,
|
||||
},
|
||||
created_at: {
|
||||
name: 'warehouse_transfer.field.created_at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -2,7 +2,10 @@ import { Model, mixin } from 'objection';
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
|
||||
import { WarehouseTransferEntry } from './WarehouseTransferEntry';
|
||||
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
||||
import { WarehouseTransferMeta } from './WarehouseTransfer.meta';
|
||||
|
||||
@InjectModelMeta(WarehouseTransferMeta)
|
||||
export class WarehouseTransfer extends TenantBaseModel {
|
||||
public date!: Date;
|
||||
public transferInitiatedAt!: Date;
|
||||
@@ -14,7 +17,6 @@ export class WarehouseTransfer extends TenantBaseModel {
|
||||
public fromWarehouse!: Warehouse;
|
||||
public toWarehouse!: Warehouse;
|
||||
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -104,7 +106,7 @@ export class WarehouseTransfer extends TenantBaseModel {
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
fromWarehouse: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
@@ -126,27 +128,13 @@ export class WarehouseTransfer extends TenantBaseModel {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
// static get meta() {
|
||||
// return WarehouseTransferSettings;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve the default custom views, roles and columns.
|
||||
// */
|
||||
// static get defaultViews() {
|
||||
// return DEFAULT_VIEWS;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
// { fieldKey: 'name', comparator: 'contains' },
|
||||
// { condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
{ fieldKey: 'name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { TransformerInjectable } from '../../Transformer/TransformerInjectable.s
|
||||
import { DynamicListService } from '../../DynamicListing/DynamicList.service';
|
||||
import { TenantModelProxy } from '../../System/models/TenantBaseModel';
|
||||
import { WarehouseTransfer } from '../models/WarehouseTransfer';
|
||||
import { GetWarehouseTransfersQueryDto } from '@/modules/Warehouses/dtos/GetWarehouseTransfersQuery.dto';
|
||||
|
||||
@Injectable()
|
||||
export class GetWarehouseTransfers {
|
||||
@@ -30,16 +31,19 @@ export class GetWarehouseTransfers {
|
||||
|
||||
/**
|
||||
* Retrieves warehouse transfers paginated list.
|
||||
* @param {number} tenantId
|
||||
* @param {IGetWarehousesTransfersFilterDTO} filterDTO
|
||||
* @returns {}
|
||||
* @param {IGetWarehousesTransfersFilterDTO} filterDTO
|
||||
*/
|
||||
public getWarehouseTransfers = async (
|
||||
filterDTO: IGetWarehousesTransfersFilterDTO,
|
||||
filterDTO: GetWarehouseTransfersQueryDto,
|
||||
) => {
|
||||
// Parses stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
const filter = this.parseListFilterDTO({
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...filterDTO,
|
||||
});
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
this.warehouseTransferModel(),
|
||||
|
||||
Reference in New Issue
Block a user