From e7178a657531db317a64b459256d457cebed9ccc Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 26 Jun 2025 17:04:46 +0200 Subject: [PATCH] fix: adjust contact balance --- packages/server/src/models/Model.ts | 31 ++++++++++++++----- .../modules/Customers/Customers.controller.ts | 3 +- .../Customers/CustomersApplication.service.ts | 8 +++-- .../Customers/dtos/CreateCustomer.dto.ts | 2 +- .../Customers/dtos/GetCustomersQuery.dto.ts | 26 ++++++++++++++++ .../src/modules/Customers/models/Customer.ts | 24 ++++++++++++-- .../Customers/queries/GetCustomers.service.ts | 7 +++-- .../dtos/DynamicFilterQuery.dto.ts | 2 +- .../Ledger/LedgerContactStorage.service.ts | 10 ++---- .../Ledger/LedgetAccountStorage.service.ts | 9 ++---- .../Organization/Organization.controller.ts | 9 ++++++ .../Organization/Organization.swagger.ts | 18 +++++++++++ .../src/modules/Vendors/Vendors.controller.ts | 3 +- .../Vendors/VendorsApplication.service.ts | 3 +- .../modules/Vendors/dtos/CreateVendor.dto.ts | 14 ++++++--- .../Vendors/dtos/GetVendorsQuery.dto.ts | 26 ++++++++++++++++ .../src/modules/Vendors/models/Vendor.ts | 31 ++++++++++++------- .../Vendors/queries/GetVendors.service.ts | 3 +- .../WarehousesItemsQuantitySync.ts | 5 +-- 19 files changed, 180 insertions(+), 54 deletions(-) create mode 100644 packages/server/src/modules/Customers/dtos/GetCustomersQuery.dto.ts create mode 100644 packages/server/src/modules/Organization/Organization.swagger.ts create mode 100644 packages/server/src/modules/Vendors/dtos/GetVendorsQuery.dto.ts diff --git a/packages/server/src/models/Model.ts b/packages/server/src/models/Model.ts index 45d09b2f3..a5f7ebbb5 100644 --- a/packages/server/src/models/Model.ts +++ b/packages/server/src/models/Model.ts @@ -14,13 +14,13 @@ export type PaginationQueryBuilderType = QueryBuilder< PaginationResult >; -class PaginationQueryBuilder extends QueryBuilder< - M, - R -> { +export class PaginationQueryBuilder< + M extends Model, + R = M[], +> extends QueryBuilder { pagination(page: number, pageSize: number): PaginationQueryBuilderType { const query = super.page(page, pageSize); - + return query.runAfter(({ results, total }) => { return { results, @@ -34,10 +34,27 @@ class PaginationQueryBuilder extends QueryBuilder< } } +// New BaseQueryBuilder extending PaginationQueryBuilder +export class BaseQueryBuilder< + M extends Model, + R = M[], +> extends PaginationQueryBuilder { + // You can add more shared query methods here in the future + + changeAmount(whereAttributes, attribute, amount) { + const changeMethod = amount > 0 ? 'increment' : 'decrement'; + + return this.where(whereAttributes)[changeMethod]( + attribute, + Math.abs(amount), + ); + } +} + export class BaseModel extends Model { public readonly id: number; public readonly tableName: string; - QueryBuilderType!: PaginationQueryBuilder; - static QueryBuilder = PaginationQueryBuilder; + QueryBuilderType!: BaseQueryBuilder; + static QueryBuilder = BaseQueryBuilder; } diff --git a/packages/server/src/modules/Customers/Customers.controller.ts b/packages/server/src/modules/Customers/Customers.controller.ts index ac589f587..4d91efa0f 100644 --- a/packages/server/src/modules/Customers/Customers.controller.ts +++ b/packages/server/src/modules/Customers/Customers.controller.ts @@ -23,6 +23,7 @@ import { import { CreateCustomerDto } from './dtos/CreateCustomer.dto'; import { EditCustomerDto } from './dtos/EditCustomer.dto'; import { CustomerResponseDto } from './dtos/CustomerResponse.dto'; +import { GetCustomersQueryDto } from './dtos/GetCustomersQuery.dto'; @Controller('customers') @ApiTags('Customers') @@ -51,7 +52,7 @@ export class CustomersController { items: { $ref: getSchemaPath(CustomerResponseDto) }, }, }) - getCustomers(@Query() filterDTO: Partial) { + getCustomers(@Query() filterDTO: GetCustomersQueryDto) { return this.customersApplication.getCustomers(filterDTO); } diff --git a/packages/server/src/modules/Customers/CustomersApplication.service.ts b/packages/server/src/modules/Customers/CustomersApplication.service.ts index 01984a0af..114eb7711 100644 --- a/packages/server/src/modules/Customers/CustomersApplication.service.ts +++ b/packages/server/src/modules/Customers/CustomersApplication.service.ts @@ -4,10 +4,14 @@ import { CreateCustomer } from './commands/CreateCustomer.service'; import { EditCustomer } from './commands/EditCustomer.service'; import { DeleteCustomer } from './commands/DeleteCustomer.service'; import { EditOpeningBalanceCustomer } from './commands/EditOpeningBalanceCustomer.service'; -import { ICustomerOpeningBalanceEditDTO, ICustomersFilter } from './types/Customers.types'; +import { + ICustomerOpeningBalanceEditDTO, + ICustomersFilter, +} from './types/Customers.types'; import { CreateCustomerDto } from './dtos/CreateCustomer.dto'; import { EditCustomerDto } from './dtos/EditCustomer.dto'; import { GetCustomers } from './queries/GetCustomers.service'; +import { GetCustomersQueryDto } from './dtos/GetCustomersQuery.dto'; @Injectable() export class CustomersApplication { @@ -76,7 +80,7 @@ export class CustomersApplication { * Retrieve customers paginated list. * @param {ICustomersFilter} filter - Cusotmers filter. */ - public getCustomers = (filterDTO: Partial) => { + public getCustomers = (filterDTO: GetCustomersQueryDto) => { return this.getCustomersService.getCustomersList(filterDTO); }; } diff --git a/packages/server/src/modules/Customers/dtos/CreateCustomer.dto.ts b/packages/server/src/modules/Customers/dtos/CreateCustomer.dto.ts index cbdbf379a..78b60c8ba 100644 --- a/packages/server/src/modules/Customers/dtos/CreateCustomer.dto.ts +++ b/packages/server/src/modules/Customers/dtos/CreateCustomer.dto.ts @@ -3,11 +3,11 @@ import { IsEmail, IsNotEmpty, IsNumber, - IsOptional, IsString, } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { ContactAddressDto } from './ContactAddress.dto'; +import { IsOptional } from '@/common/decorators/Validators'; export class CreateCustomerDto extends ContactAddressDto { @ApiProperty({ diff --git a/packages/server/src/modules/Customers/dtos/GetCustomersQuery.dto.ts b/packages/server/src/modules/Customers/dtos/GetCustomersQuery.dto.ts new file mode 100644 index 000000000..5d0ece539 --- /dev/null +++ b/packages/server/src/modules/Customers/dtos/GetCustomersQuery.dto.ts @@ -0,0 +1,26 @@ +import { IsInt, IsOptional, IsString, IsBoolean } from 'class-validator'; +import { ToNumber } from '@/common/decorators/Validators'; +import { DynamicFilterQueryDto } from '@/modules/DynamicListing/dtos/DynamicFilterQuery.dto'; +import { parseBoolean } from '@/utils/parse-boolean'; +import { Transform } from 'class-transformer'; + +export class GetCustomersQueryDto extends DynamicFilterQueryDto { + @IsString() + @IsOptional() + stringifiedFilterRoles?: string; + + @IsOptional() + @IsInt() + @ToNumber() + page?: number; + + @IsOptional() + @IsInt() + @ToNumber() + pageSize?: number; + + @IsOptional() + @IsBoolean() + @Transform(({ value }) => parseBoolean(value, false)) + inactiveMode?: boolean; +} diff --git a/packages/server/src/modules/Customers/models/Customer.ts b/packages/server/src/modules/Customers/models/Customer.ts index 743bbff81..47aa6f59e 100644 --- a/packages/server/src/modules/Customers/models/Customer.ts +++ b/packages/server/src/modules/Customers/models/Customer.ts @@ -1,8 +1,27 @@ +import { Model } from 'objection'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator'; import { CustomerMeta } from './Customer.meta'; import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator'; import { CustomerDefaultViews } from '../constants'; +import { BaseQueryBuilder } from '@/models/Model'; +import { Knex } from 'knex'; + +export class CustomerQueryBuilder< + M extends Model, + R = M[], +> extends BaseQueryBuilder { + constructor(...args) { + // @ts-ignore + super(...args); + + this.onBuild((builder) => { + if (builder.isFind() || builder.isDelete() || builder.isUpdate()) { + builder.where('contact_service', 'customer'); + } + }); + } +} @InjectModelMeta(CustomerMeta) @InjectModelDefaultViews(CustomerDefaultViews) @@ -54,9 +73,7 @@ export class Customer extends TenantBaseModel { /** * Query builder. */ - // static get QueryBuilder() { - // return CustomerQueryBuilder; - // } + static QueryBuilder = CustomerQueryBuilder; /** * Table name @@ -152,6 +169,7 @@ export class Customer extends TenantBaseModel { ); query.having('countOverdue', '>', 0); }, + /** * Filters the unpaid customers. */ diff --git a/packages/server/src/modules/Customers/queries/GetCustomers.service.ts b/packages/server/src/modules/Customers/queries/GetCustomers.service.ts index 255cf7976..acf4ad245 100644 --- a/packages/server/src/modules/Customers/queries/GetCustomers.service.ts +++ b/packages/server/src/modules/Customers/queries/GetCustomers.service.ts @@ -9,6 +9,7 @@ import { ICustomersFilter, } from '../types/Customers.types'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { GetCustomersQueryDto } from '../dtos/GetCustomersQuery.dto'; @Injectable() export class GetCustomers { @@ -29,12 +30,12 @@ export class GetCustomers { } /** - * Retrieve customers paginated list. - * @param {ICustomersFilter} filter - Cusotmers filter. + * Retrieves customers paginated list. + * @param {GetCustomersQueryDto} filter - Cusotmers filter. * @returns {Promise} */ public async getCustomersList( - filterDto: Partial, + filterDto: GetCustomersQueryDto, ): Promise { const _filterDto = { inactiveMode: false, diff --git a/packages/server/src/modules/DynamicListing/dtos/DynamicFilterQuery.dto.ts b/packages/server/src/modules/DynamicListing/dtos/DynamicFilterQuery.dto.ts index fe414fddc..54f26fcb2 100644 --- a/packages/server/src/modules/DynamicListing/dtos/DynamicFilterQuery.dto.ts +++ b/packages/server/src/modules/DynamicListing/dtos/DynamicFilterQuery.dto.ts @@ -1,5 +1,5 @@ import { ToNumber } from '@/common/decorators/Validators'; -import { IsArray, IsEnum, IsInt, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsOptional, IsString } from 'class-validator'; import { IFilterRole, ISortOrder } from '../DynamicFilter/DynamicFilter.types'; export class DynamicFilterQueryDto { diff --git a/packages/server/src/modules/Ledger/LedgerContactStorage.service.ts b/packages/server/src/modules/Ledger/LedgerContactStorage.service.ts index ee9ceafbb..09f81d46f 100644 --- a/packages/server/src/modules/Ledger/LedgerContactStorage.service.ts +++ b/packages/server/src/modules/Ledger/LedgerContactStorage.service.ts @@ -85,7 +85,6 @@ export class LedgerContactsBalanceStorage { /** * - * @param {number} tenantId * @param {ILedger} ledger * @param {number} contactId * @returns {Promise} @@ -130,11 +129,8 @@ export class LedgerContactsBalanceStorage { change: number, trx?: Knex.Transaction, ) => { - // return this.contactModel.changeAmount( - // { id: contactId }, - // 'balance', - // change, - // trx, - // ); + return this.contactModel() + .query(trx) + .changeAmount({ id: contactId }, 'balance', change); }; } diff --git a/packages/server/src/modules/Ledger/LedgetAccountStorage.service.ts b/packages/server/src/modules/Ledger/LedgetAccountStorage.service.ts index bd2c7780c..9dee14e91 100644 --- a/packages/server/src/modules/Ledger/LedgetAccountStorage.service.ts +++ b/packages/server/src/modules/Ledger/LedgetAccountStorage.service.ts @@ -157,11 +157,8 @@ export class LedegrAccountsStorage { .whereNull('amount') .patch({ amount: 0 }); - // await this.accountModel.changeAmount( - // { id: accountId }, - // 'amount', - // change, - // trx, - // ); + await this.accountModel() + .query(trx) + .changeAmount({ id: accountId }, 'amount', change); }; } diff --git a/packages/server/src/modules/Organization/Organization.controller.ts b/packages/server/src/modules/Organization/Organization.controller.ts index 14937d1a8..e112ceafc 100644 --- a/packages/server/src/modules/Organization/Organization.controller.ts +++ b/packages/server/src/modules/Organization/Organization.controller.ts @@ -25,6 +25,10 @@ import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitializ import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service'; import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service'; +import { + OrganizationBuildResponseExample, + OrganizationBuiltResponseExample, +} from './Organization.swagger'; @ApiTags('Organization') @Controller('organization') @@ -46,6 +50,11 @@ export class OrganizationController { @ApiResponse({ status: 200, description: 'The organization database has been initialized', + example: OrganizationBuildResponseExample, + }) + @ApiResponse({ + status: 500, + example: OrganizationBuiltResponseExample, }) async build(@Body() buildDTO: BuildOrganizationDto) { const result = await this.buildOrganizationService.buildRunJob(buildDTO); diff --git a/packages/server/src/modules/Organization/Organization.swagger.ts b/packages/server/src/modules/Organization/Organization.swagger.ts new file mode 100644 index 000000000..81cf38967 --- /dev/null +++ b/packages/server/src/modules/Organization/Organization.swagger.ts @@ -0,0 +1,18 @@ +export const OrganizationBuildResponseExample = { + type: 'success', + code: 'ORGANIZATION.DATABASE.INITIALIZED', + message: 'The organization database has been initialized.', + data: { + job_id: '1', + }, +}; + +export const OrganizationBuiltResponseExample = { + errors: [ + { + statusCode: 500, + type: 'TENANT_ALREADY_BUILT', + message: null, + }, + ], +}; diff --git a/packages/server/src/modules/Vendors/Vendors.controller.ts b/packages/server/src/modules/Vendors/Vendors.controller.ts index c21486f3f..063c0a3bb 100644 --- a/packages/server/src/modules/Vendors/Vendors.controller.ts +++ b/packages/server/src/modules/Vendors/Vendors.controller.ts @@ -16,6 +16,7 @@ import { import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { CreateVendorDto } from './dtos/CreateVendor.dto'; import { EditVendorDto } from './dtos/EditVendor.dto'; +import { GetVendorsQueryDto } from './dtos/GetVendorsQuery.dto'; @Controller('vendors') @ApiTags('vendors') @@ -24,7 +25,7 @@ export class VendorsController { @Get() @ApiOperation({ summary: 'Retrieves the vendors.' }) - getVendors(@Query() filterDTO: Partial) { + getVendors(@Query() filterDTO: GetVendorsQueryDto) { return this.vendorsApplication.getVendors(filterDTO); } diff --git a/packages/server/src/modules/Vendors/VendorsApplication.service.ts b/packages/server/src/modules/Vendors/VendorsApplication.service.ts index 1fb854cdd..c382f9083 100644 --- a/packages/server/src/modules/Vendors/VendorsApplication.service.ts +++ b/packages/server/src/modules/Vendors/VendorsApplication.service.ts @@ -12,6 +12,7 @@ import { import { GetVendorsService } from './queries/GetVendors.service'; import { CreateVendorDto } from './dtos/CreateVendor.dto'; import { EditVendorDto } from './dtos/EditVendor.dto'; +import { GetVendorsQueryDto } from './dtos/GetVendorsQuery.dto'; @Injectable() export class VendorsApplication { @@ -82,7 +83,7 @@ export class VendorsApplication { * @param {Partial} filterDTO * @returns {Promise<{ vendors: Vendor[], pagination: IPaginationMeta, filterMeta: IFilterMeta }>>} */ - public getVendors(filterDTO: Partial) { + public getVendors(filterDTO: GetVendorsQueryDto) { return this.getVendorsService.getVendorsList(filterDTO); } } diff --git a/packages/server/src/modules/Vendors/dtos/CreateVendor.dto.ts b/packages/server/src/modules/Vendors/dtos/CreateVendor.dto.ts index a83e0bbc7..b865b1225 100644 --- a/packages/server/src/modules/Vendors/dtos/CreateVendor.dto.ts +++ b/packages/server/src/modules/Vendors/dtos/CreateVendor.dto.ts @@ -1,9 +1,15 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, IsEmail, IsString } from 'class-validator'; +import { + IsISO8601, + IsInt, + IsNumber, + Min, + IsBoolean, + IsEmail, + IsString, +} from 'class-validator'; import { ContactAddressDto } from '@/modules/Customers/dtos/ContactAddress.dto'; -import { IsInt, IsNumber } from 'class-validator'; -import { IsOptional, Min } from 'class-validator'; -import { IsISO8601 } from 'class-validator'; +import { IsOptional } from '@/common/decorators/Validators'; export class CreateVendorDto extends ContactAddressDto { @ApiProperty({ required: false, description: 'Vendor opening balance' }) diff --git a/packages/server/src/modules/Vendors/dtos/GetVendorsQuery.dto.ts b/packages/server/src/modules/Vendors/dtos/GetVendorsQuery.dto.ts new file mode 100644 index 000000000..2d57d0079 --- /dev/null +++ b/packages/server/src/modules/Vendors/dtos/GetVendorsQuery.dto.ts @@ -0,0 +1,26 @@ +import { ToNumber } from '@/common/decorators/Validators'; +import { DynamicFilterQueryDto } from '@/modules/DynamicListing/dtos/DynamicFilterQuery.dto'; +import { parseBoolean } from '@/utils/parse-boolean'; +import { Transform } from 'class-transformer'; +import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator'; + +export class GetVendorsQueryDto extends DynamicFilterQueryDto { + @IsString() + @IsOptional() + stringifiedFilterRoles?: string; + + @IsOptional() + @IsInt() + @ToNumber() + page?: number; + + @IsOptional() + @IsInt() + @ToNumber() + pageSize?: number; + + @IsOptional() + @IsBoolean() + @Transform(({ value }) => parseBoolean(value, false)) + inactiveMode?: boolean; +} diff --git a/packages/server/src/modules/Vendors/models/Vendor.ts b/packages/server/src/modules/Vendors/models/Vendor.ts index f98d53a47..50fc57207 100644 --- a/packages/server/src/modules/Vendors/models/Vendor.ts +++ b/packages/server/src/modules/Vendors/models/Vendor.ts @@ -1,12 +1,5 @@ -import { Model, mixin } from 'objection'; -// import TenantModel from 'models/TenantModel'; -// import PaginationQueryBuilder from './Pagination'; -// import ModelSetting from './ModelSetting'; -// import VendorSettings from './Vendor.Settings'; -// import CustomViewBaseModel from './CustomViewBaseModel'; -// import { DEFAULT_VIEWS } from '@/services/Contacts/Vendors/constants'; -// import ModelSearchable from './ModelSearchable'; -import { BaseModel } from '@/models/Model'; +import { Model } from 'objection'; +import { BaseQueryBuilder } from '@/models/Model'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator'; import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator'; @@ -14,6 +7,22 @@ import { VendorMeta } from './Vendor.meta'; import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator'; import { VendorDefaultViews } from '../constants'; +export class VendorQueryBuilder< + M extends Model, + R = M[], +> extends BaseQueryBuilder { + constructor(...args) { + // @ts-ignore + super(...args); + + this.onBuild((builder) => { + if (builder.isFind() || builder.isDelete() || builder.isUpdate()) { + builder.where('contact_service', 'vendor'); + } + }); + } +} + @ExportableModel() @InjectModelMeta(VendorMeta) @InjectModelDefaultViews(VendorDefaultViews) @@ -64,9 +73,7 @@ export class Vendor extends TenantBaseModel { /** * Query builder. */ - // static get QueryBuilder() { - // return VendorQueryBuilder; - // } + static QueryBuilder = VendorQueryBuilder; /** * Table name diff --git a/packages/server/src/modules/Vendors/queries/GetVendors.service.ts b/packages/server/src/modules/Vendors/queries/GetVendors.service.ts index b9c4bd31b..3f06adcfd 100644 --- a/packages/server/src/modules/Vendors/queries/GetVendors.service.ts +++ b/packages/server/src/modules/Vendors/queries/GetVendors.service.ts @@ -6,6 +6,7 @@ import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectab import { VendorTransfromer } from './VendorTransformer'; import { GetVendorsResponse, IVendorsFilter } from '../types/Vendors.types'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { GetVendorsQueryDto } from '../dtos/GetVendorsQuery.dto'; @Injectable() export class GetVendorsService { @@ -28,7 +29,7 @@ export class GetVendorsService { * @returns {Promise} */ public async getVendorsList( - filterDto: Partial, + filterDto: GetVendorsQueryDto, ): Promise { const _filterDto = { inactiveMode: false, diff --git a/packages/server/src/modules/Warehouses/Integrations/WarehousesItemsQuantitySync.ts b/packages/server/src/modules/Warehouses/Integrations/WarehousesItemsQuantitySync.ts index 870bc845d..607241d6b 100644 --- a/packages/server/src/modules/Warehouses/Integrations/WarehousesItemsQuantitySync.ts +++ b/packages/server/src/modules/Warehouses/Integrations/WarehousesItemsQuantitySync.ts @@ -76,15 +76,13 @@ export class WarehousesItemsQuantitySync { .first(); if (itemWarehouseQuantity) { - // @ts-ignore - await ItemWarehouseQuantity.changeAmount( + await this.itemWarehouseQuantityModel().query(trx).changeAmount( { itemId: warehouseItemQuantity.itemId, warehouseId: warehouseItemQuantity.warehouseId, }, 'quantityOnHand', warehouseItemQuantity.amount, - trx, ); } else { await ItemWarehouseQuantity.query(trx).insert({ @@ -96,7 +94,6 @@ export class WarehousesItemsQuantitySync { /** * Mutates warehouses items quantity from inventory transactions. - * @param {number} tenantId - * @param {IInventoryTransaction[]} inventoryTransactions - * @param {Knex.Transaction} */