refactor: nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-03-22 20:36:48 +02:00
parent 136cc907bb
commit 2eb56e5850
45 changed files with 1210 additions and 198 deletions

View File

@@ -20,6 +20,16 @@ import { IFilterMeta } from '@/interfaces/Model';
@Injectable()
export class AccountsApplication {
/**
* @param {CreateAccountService} createAccountService - The create account service.
* @param {EditAccount} editAccountService - The edit account service.
* @param {DeleteAccount} deleteAccountService - The delete account service.
* @param {ActivateAccount} activateAccountService - The activate account service.
* @param {GetAccountTypesService} getAccountTypesService - The get account types service.
* @param {GetAccount} getAccountService - The get account service.
* @param {GetAccountTransactionsService} getAccountTransactionsService - The get account transactions service.
* @param {GetAccountsService} getAccountsService - The get accounts service.
*/
constructor(
private readonly createAccountService: CreateAccountService,
private readonly editAccountService: EditAccount,

View File

@@ -10,6 +10,12 @@ import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class ActivateAccount {
/**
* @param {EventEmitter2} eventEmitter - The event emitter.
* @param {UnitOfWork} uow - The unit of work.
* @param {AccountRepository} accountRepository - The account repository.
* @param {TenantModelProxy<typeof Account>} accountModel - The account model.
*/
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
@@ -21,8 +27,8 @@ export class ActivateAccount {
/**
* Activates/Inactivates the given account.
* @param {number} accountId
* @param {boolean} activate
* @param {number} accountId - The account id.
* @param {boolean} activate - Activate or inactivate the account.
*/
public activateAccount = async (accountId: number, activate?: boolean) => {
// Retrieve the given account or throw not found error.

View File

@@ -1,3 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsString,
IsOptional,
@@ -11,41 +12,92 @@ export class CreateAccountDTO {
@IsString()
@MinLength(3)
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
@ApiProperty({
description: 'Account name',
example: 'Cash Account',
minLength: 3,
maxLength: 255,
})
name: string;
@IsOptional()
@IsString()
@MinLength(3)
@MaxLength(6)
@ApiProperty({
description: 'Account code',
example: 'CA001',
required: false,
minLength: 3,
maxLength: 6,
})
code?: string;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Currency code for the account',
example: 'USD',
required: false,
})
currencyCode?: string;
@IsString()
@MinLength(3)
@MaxLength(255)
@ApiProperty({
description: 'Type of account',
example: 'asset',
minLength: 3,
maxLength: 255,
})
accountType: string;
@IsOptional()
@IsString()
@MaxLength(65535)
@ApiProperty({
description: 'Account description',
example: 'Main cash account for daily operations',
required: false,
maxLength: 65535,
})
description?: string;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'ID of the parent account',
example: 1,
required: false,
})
parentAccountId?: number;
@IsOptional()
@IsBoolean()
@ApiProperty({
description: 'Whether the account is active',
example: true,
required: false,
default: true,
})
active?: boolean;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Plaid account ID for syncing',
example: 'plaid_account_123456',
required: false,
})
plaidAccountId?: string;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Plaid item ID for syncing',
example: 'plaid_item_123456',
required: false,
})
plaidItemId?: string;
}

View File

@@ -1,3 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsString,
IsOptional,
@@ -9,26 +10,45 @@ import {
export class EditAccountDTO {
@IsString()
@MinLength(3)
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
@MaxLength(255)
@ApiProperty({
description: 'The name of the account',
example: 'Bank Account',
})
name: string;
@IsOptional()
@IsString()
@MinLength(3)
@MaxLength(6)
@ApiProperty({
description: 'The code of the account',
example: '123456',
})
code?: string;
@IsString()
@MinLength(3)
@MaxLength(255) // Assuming DATATYPES_LENGTH.STRING is 255
@MaxLength(255)
@ApiProperty({
description: 'The type of the account',
example: 'Bank Account',
})
accountType: string;
@IsOptional()
@IsString()
@MaxLength(65535) // Assuming DATATYPES_LENGTH.TEXT is 65535
@ApiProperty({
description: 'The description of the account',
example: 'This is a description',
})
description?: string;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The parent account ID of the account',
example: 1,
})
parentAccountId?: number;
}

View File

@@ -11,6 +11,7 @@ import {
IsNotEmpty,
} from 'class-validator';
import { BankRuleComparator } from '../types';
import { ApiProperty } from '@nestjs/swagger';
class BankRuleConditionDto {
@IsNotEmpty()
@@ -37,43 +38,83 @@ class BankRuleConditionDto {
export class CommandBankRuleDto {
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The name of the bank rule',
example: 'Monthly Salary',
})
name: string;
@IsInt()
@Min(0)
@ApiProperty({
description: 'The order of the bank rule',
example: 1,
})
order: number;
@IsOptional()
@IsInt()
@Min(0)
@ApiProperty({
description: 'The account ID to apply the rule if',
example: 1,
})
applyIfAccountId?: number;
@IsIn(['deposit', 'withdrawal'])
@ApiProperty({
description: 'The transaction type to apply the rule if',
example: 'deposit',
})
applyIfTransactionType: 'deposit' | 'withdrawal';
@IsString()
@IsIn(['and', 'or'])
@ApiProperty({
description: 'The conditions type to apply the rule if',
example: 'and',
})
conditionsType: 'and' | 'or' = 'and';
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => BankRuleConditionDto)
@ApiProperty({
description: 'The conditions to apply the rule if',
example: [{ field: 'description', comparator: 'contains', value: 'Salary' }],
})
conditions: BankRuleConditionDto[];
@IsString()
@ApiProperty({
description: 'The category to assign the rule if',
example: 'Income:Salary',
})
assignCategory: string;
@IsInt()
@Min(0)
@ApiProperty({
description: 'The account ID to assign the rule if',
example: 1,
})
assignAccountId: number;
@IsOptional()
@IsString()
@ApiProperty({
description: 'The payee to assign the rule if',
example: 'Employer Inc.',
})
assignPayee?: string;
@IsOptional()
@IsString()
@ApiProperty({
description: 'The memo to assign the rule if',
example: 'Monthly Salary',
})
assignMemo?: string;
}

View File

@@ -15,7 +15,7 @@ export class GetBankRuleService {
/**
* Retrieves the bank rule.
* @param {number} ruleId
* @param {number} ruleId - Rule id.
* @returns {Promise<any>}
*/
async getBankRule(ruleId: number): Promise<any> {

View File

@@ -6,14 +6,23 @@ import {
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
export class MatchTransactionEntryDto {
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The type of the reference',
example: 'SaleInvoice',
})
referenceType: string;
@IsNumber()
@IsNotEmpty()
@ApiProperty({
description: 'The ID of the reference',
example: 1,
})
referenceId: number;
}
@@ -21,5 +30,12 @@ export class MatchBankTransactionDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => MatchTransactionEntryDto)
@ApiProperty({
description: 'The entries to match',
example: [
{ referenceType: 'SaleInvoice', referenceId: 1 },
{ referenceType: 'SaleInvoice', referenceId: 2 },
],
})
entries: MatchTransactionEntryDto[];
}
}

View File

@@ -16,6 +16,15 @@ import { CreateBranchDto, EditBranchDto } from './dtos/Branch.dto';
@Injectable()
export class BranchesApplication {
/**
* @param {CreateBranchService} createBranchService - Create branch service.
* @param {EditBranchService} editBranchService - Edit branch service.
* @param {DeleteBranchService} deleteBranchService - Delete branch service.
* @param {GetBranchService} getBranchService - Get branch service.
* @param {GetBranchesService} getBranchesService - Get branches service.
* @param {ActivateBranches} activateBranchesService - Activate branches service.
* @param {MarkBranchAsPrimaryService} markBranchAsPrimaryService - Mark branch as primary service.
*/
constructor(
private readonly createBranchService: CreateBranchService,
private readonly editBranchService: EditBranchService,

View File

@@ -9,48 +9,76 @@ import {
} from 'class-validator';
class CommandBranchDto {
@ApiProperty({ description: 'Branch name' })
@ApiProperty({
description: 'Branch name',
example: 'Main Branch',
})
@IsNotEmpty()
@IsString()
name: string;
@ApiPropertyOptional({ description: 'Branch code' })
@ApiPropertyOptional({
description: 'Whether this is the primary branch',
example: true,
default: false,
})
@IsOptional()
@IsBoolean()
primary?: boolean;
@ApiPropertyOptional({ description: 'Branch code' })
@ApiPropertyOptional({
description: 'Branch code',
example: 'BR001',
})
@IsOptional()
@IsString()
code?: string;
@ApiPropertyOptional({ description: 'Branch address' })
@ApiPropertyOptional({
description: 'Branch address',
example: '123 Main Street',
})
@IsOptional()
@IsString()
address?: string;
@ApiPropertyOptional({ description: 'Branch city' })
@ApiPropertyOptional({
description: 'Branch city',
example: 'New York',
})
@IsOptional()
@IsString()
city?: string;
@ApiPropertyOptional({ description: 'Branch country' })
@ApiPropertyOptional({
description: 'Branch country',
example: 'USA',
})
@IsOptional()
@IsString()
country?: string;
@ApiPropertyOptional({ description: 'Branch phone number' })
@ApiPropertyOptional({
description: 'Branch phone number',
example: '+1-555-123-4567',
})
@IsOptional()
@IsString()
phone_number?: string;
@ApiPropertyOptional({ description: 'Branch email' })
@ApiPropertyOptional({
description: 'Branch email',
example: 'branch@example.com',
})
@IsOptional()
@IsEmail()
@IsString()
email?: string;
@ApiPropertyOptional({ description: 'Branch website' })
@ApiPropertyOptional({
description: 'Branch website',
example: 'https://www.example.com/branch',
})
@IsOptional()
@IsUrl()
@IsString()

View File

@@ -17,7 +17,7 @@ import { BrandingTemplateDTOTransformer } from '../../PdfTemplate/BrandingTempla
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { CreditNoteAutoIncrementService } from './CreditNoteAutoIncrement.service';
import { CreditNote } from '../models/CreditNote';
import { CreateCreditNoteDto, EditCreditNoteDto } from '../dtos/CreditNote.dto';
import { CreateCreditNoteDto, CreditNoteEntryDto, EditCreditNoteDto } from '../dtos/CreditNote.dto';
@Injectable()
export class CommandCreditNoteDTOTransform {
@@ -55,7 +55,7 @@ export class CommandCreditNoteDTOTransform {
assocItemEntriesDefaultIndex,
// Associate the reference type to credit note entries.
R.map((entry: ICreditNoteEntryNewDTO) => ({
R.map((entry: CreditNoteEntryDto) => ({
...entry,
referenceType: 'CreditNote',
})),

View File

@@ -19,7 +19,7 @@ enum DiscountType {
Amount = 'amount',
}
class CreditNoteEntryDto extends ItemEntryDto {}
export class CreditNoteEntryDto extends ItemEntryDto {}
class AttachmentDto {
@IsString()

View File

@@ -8,13 +8,11 @@ import {
Put,
} from '@nestjs/common';
import { CustomersApplication } from './CustomersApplication.service';
import {
ICustomerEditDTO,
ICustomerNewDTO,
ICustomerOpeningBalanceEditDTO,
} from './types/Customers.types';
import { ICustomerOpeningBalanceEditDTO } from './types/Customers.types';
import { PublicRoute } from '../Auth/Jwt.guard';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
import { EditCustomerDto } from './dtos/EditCustomer.dto';
@Controller('customers')
@ApiTags('customers')
@@ -30,7 +28,7 @@ export class CustomersController {
@Post()
@ApiOperation({ summary: 'Create a new customer.' })
createCustomer(@Body() customerDTO: ICustomerNewDTO) {
createCustomer(@Body() customerDTO: CreateCustomerDto) {
return this.customersApplication.createCustomer(customerDTO);
}
@@ -38,7 +36,7 @@ export class CustomersController {
@ApiOperation({ summary: 'Edit the given customer.' })
editCustomer(
@Param('id') customerId: number,
@Body() customerDTO: ICustomerEditDTO,
@Body() customerDTO: EditCustomerDto,
) {
return this.customersApplication.editCustomer(customerId, customerDTO);
}

View File

@@ -4,12 +4,9 @@ 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 {
ICustomerEditDTO,
ICustomerNewDTO,
ICustomerOpeningBalanceEditDTO,
// ICustomersFilter,
} from './types/Customers.types';
import { ICustomerOpeningBalanceEditDTO } from './types/Customers.types';
import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
import { EditCustomerDto } from './dtos/EditCustomer.dto';
@Injectable()
export class CustomersApplication {
@@ -36,7 +33,7 @@ export class CustomersApplication {
* @param {ICustomerNewDTO} customerDTO
* @returns {Promise<ICustomer>}
*/
public createCustomer = (customerDTO: ICustomerNewDTO) => {
public createCustomer = (customerDTO: CreateCustomerDto) => {
return this.createCustomerService.createCustomer(customerDTO);
};
@@ -46,7 +43,7 @@ export class CustomersApplication {
* @param {ICustomerEditDTO} customerDTO - Customer edit DTO.
* @return {Promise<ICustomer>}
*/
public editCustomer = (customerId: number, customerDTO: ICustomerEditDTO) => {
public editCustomer = (customerId: number, customerDTO: EditCustomerDto) => {
return this.editCustomerService.editCustomer(customerId, customerDTO);
};

View File

@@ -8,9 +8,9 @@ import { events } from '@/common/events/events';
import {
ICustomerEventCreatedPayload,
ICustomerEventCreatingPayload,
ICustomerNewDTO,
} from '../types/Customers.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CreateCustomerDto } from '../dtos/CreateCustomer.dto';
@Injectable()
export class CreateCustomer {
@@ -35,7 +35,7 @@ export class CreateCustomer {
* @return {Promise<ICustomer>}
*/
public async createCustomer(
customerDTO: ICustomerNewDTO,
customerDTO: CreateCustomerDto,
trx?: Knex.Transaction,
): Promise<Customer> {
// Transformes the customer DTO to customer object.

View File

@@ -11,6 +11,7 @@ import { events } from '@/common/events/events';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { EditCustomerDto } from '../dtos/EditCustomer.dto';
@Injectable()
export class EditCustomer {
@@ -37,7 +38,7 @@ export class EditCustomer {
*/
public async editCustomer(
customerId: number,
customerDTO: ICustomerEditDTO,
customerDTO: EditCustomerDto,
): Promise<Customer> {
// Retrieve the customer or throw not found error.
const oldCustomer = await this.customerModel()

View File

@@ -0,0 +1,84 @@
import { IsEmail, IsOptional, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class ContactAddressDto {
@ApiProperty({ required: false, description: 'Billing address line 1' })
@IsOptional()
@IsString()
billingAddress1?: string;
@ApiProperty({ required: false, description: 'Billing address line 2' })
@IsOptional()
@IsString()
billingAddress2?: string;
@ApiProperty({ required: false, description: 'Billing address city' })
@IsOptional()
@IsString()
billingAddressCity?: string;
@ApiProperty({ required: false, description: 'Billing address country' })
@IsOptional()
@IsString()
billingAddressCountry?: string;
@ApiProperty({ required: false, description: 'Billing address email' })
@IsOptional()
@IsEmail()
billingAddressEmail?: string;
@ApiProperty({ required: false, description: 'Billing address zipcode' })
@IsOptional()
@IsString()
billingAddressZipcode?: string;
@ApiProperty({ required: false, description: 'Billing address phone' })
@IsOptional()
@IsString()
billingAddressPhone?: string;
@ApiProperty({ required: false, description: 'Billing address state' })
@IsOptional()
@IsString()
billingAddressState?: string;
@ApiProperty({ required: false, description: 'Shipping address line 1' })
@IsOptional()
@IsString()
shippingAddress1?: string;
@ApiProperty({ required: false, description: 'Shipping address line 2' })
@IsOptional()
@IsString()
shippingAddress2?: string;
@ApiProperty({ required: false, description: 'Shipping address city' })
@IsOptional()
@IsString()
shippingAddressCity?: string;
@ApiProperty({ required: false, description: 'Shipping address country' })
@IsOptional()
@IsString()
shippingAddressCountry?: string;
@ApiProperty({ required: false, description: 'Shipping address email' })
@IsOptional()
@IsEmail()
shippingAddressEmail?: string;
@ApiProperty({ required: false, description: 'Shipping address zipcode' })
@IsOptional()
@IsString()
shippingAddressZipcode?: string;
@ApiProperty({ required: false, description: 'Shipping address phone' })
@IsOptional()
@IsString()
shippingAddressPhone?: string;
@ApiProperty({ required: false, description: 'Shipping address state' })
@IsOptional()
@IsString()
shippingAddressState?: string;
}

View File

@@ -0,0 +1,100 @@
import {
IsBoolean,
IsEmail,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
} from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { ContactAddressDto } from './ContactAddress.dto';
export class CreateCustomerDto extends ContactAddressDto {
@ApiProperty({ required: true, description: 'Customer type' })
@IsString()
@IsNotEmpty()
customerType: string;
@ApiProperty({ required: true, description: 'Currency code' })
@IsString()
@IsNotEmpty()
currencyCode: string;
@ApiProperty({ required: false, description: 'Opening balance' })
@IsOptional()
@IsNumber()
openingBalance?: number;
@ApiProperty({ required: false, description: 'Opening balance date' })
@IsOptional()
@IsString()
openingBalanceAt?: string;
@ApiProperty({
required: false,
description: 'Opening balance exchange rate',
})
@IsOptional()
@IsNumber()
openingBalanceExchangeRate?: number;
@ApiProperty({ required: false, description: 'Opening balance branch ID' })
@IsOptional()
@IsNumber()
openingBalanceBranchId?: number;
@ApiProperty({ required: false, description: 'Salutation' })
@IsOptional()
@IsString()
salutation?: string;
@ApiProperty({ required: false, description: 'First name' })
@IsOptional()
@IsString()
firstName?: string;
@ApiProperty({ required: false, description: 'Last name' })
@IsOptional()
@IsString()
lastName?: string;
@ApiProperty({ required: false, description: 'Company name' })
@IsOptional()
@IsString()
companyName?: string;
@ApiProperty({ required: true, description: 'Display name' })
@IsString()
@IsNotEmpty()
displayName: string;
@ApiProperty({ required: false, description: 'Website' })
@IsOptional()
@IsString()
website?: string;
@ApiProperty({ required: false, description: 'Email' })
@IsOptional()
@IsEmail()
email?: string;
@ApiProperty({ required: false, description: 'Work phone' })
@IsOptional()
@IsString()
workPhone?: string;
@ApiProperty({ required: false, description: 'Personal phone' })
@IsOptional()
@IsString()
personalPhone?: string;
@ApiProperty({ required: false, description: 'Note' })
@IsOptional()
@IsString()
note?: string;
@ApiProperty({ required: false, description: 'Active status', default: true })
@IsOptional()
@IsBoolean()
active?: boolean;
}

View File

@@ -0,0 +1,65 @@
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { ContactAddressDto } from './ContactAddress.dto';
export class EditCustomerDto extends ContactAddressDto {
@ApiProperty({ required: true, description: 'Customer type' })
@IsString()
@IsNotEmpty()
customerType: string;
@ApiProperty({ required: false, description: 'Salutation' })
@IsOptional()
@IsString()
salutation?: string;
@ApiProperty({ required: false, description: 'First name' })
@IsOptional()
@IsString()
firstName?: string;
@ApiProperty({ required: false, description: 'Last name' })
@IsOptional()
@IsString()
lastName?: string;
@ApiProperty({ required: false, description: 'Company name' })
@IsOptional()
@IsString()
companyName?: string;
@ApiProperty({ required: true, description: 'Display name' })
@IsString()
@IsNotEmpty()
displayName: string;
@ApiProperty({ required: false, description: 'Website' })
@IsOptional()
@IsString()
website?: string;
@ApiProperty({ required: false, description: 'Email' })
@IsOptional()
@IsEmail()
email?: string;
@ApiProperty({ required: false, description: 'Work phone' })
@IsOptional()
@IsString()
workPhone?: string;
@ApiProperty({ required: false, description: 'Personal phone' })
@IsOptional()
@IsString()
personalPhone?: string;
@ApiProperty({ required: false, description: 'Note' })
@IsOptional()
@IsString()
note?: string;
@ApiProperty({ required: false, description: 'Active status' })
@IsOptional()
@IsBoolean()
active?: boolean;
}

View File

@@ -1,23 +1,5 @@
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
// import TenantModel from 'models/TenantModel';
// import PaginationQueryBuilder from './Pagination';
// import ModelSetting from './ModelSetting';
// import CustomerSettings from './Customer.Settings';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import { DEFAULT_VIEWS } from '@/services/Contacts/Customers/constants';
// import ModelSearchable from './ModelSearchable';
// class CustomerQueryBuilder extends PaginationQueryBuilder {
// constructor(...args) {
// super(...args);
// this.onBuild((builder) => {
// if (builder.isFind() || builder.isDelete() || builder.isUpdate()) {
// builder.where('contact_service', 'customer');
// }
// });
// }
// }
export class Customer extends TenantBaseModel{
contactService: string;

View File

@@ -3,6 +3,8 @@ import { Customer } from '../models/Customer';
import { IContactAddressDTO } from '@/modules/Contacts/types/Contacts.types';
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
import { CreateCustomerDto } from '../dtos/CreateCustomer.dto';
import { EditCustomerDto } from '../dtos/EditCustomer.dto';
// Customer Interfaces.
// ----------------------------------
@@ -63,46 +65,38 @@ export interface GetCustomersResponse {
// Customer Events.
// ----------------------------------
export interface ICustomerEventCreatedPayload {
// tenantId: number;
customerId: number;
// authorizedUser: ISystemUser;
customer: Customer;
trx: Knex.Transaction;
}
export interface ICustomerEventCreatingPayload {
// tenantId: number;
customerDTO: ICustomerNewDTO;
customerDTO: CreateCustomerDto;
trx: Knex.Transaction;
}
export interface ICustomerEventEditedPayload {
// tenantId: number
customerId: number;
customer: Customer;
trx: Knex.Transaction;
}
export interface ICustomerEventEditingPayload {
// tenantId: number;
customerDTO: ICustomerEditDTO;
customerDTO: EditCustomerDto;
customerId: number;
trx: Knex.Transaction;
}
export interface ICustomerDeletingPayload {
// tenantId: number;
customerId: number;
oldCustomer: Customer;
}
export interface ICustomerEventDeletedPayload {
// tenantId: number;
customerId: number;
oldCustomer: Customer;
trx: Knex.Transaction;
}
export interface ICustomerEventCreatingPayload {
// tenantId: number;
customerDTO: ICustomerNewDTO;
customerDTO: CreateCustomerDto;
trx: Knex.Transaction;
}
export enum CustomerAction {
@@ -141,13 +135,11 @@ export interface ICustomerOpeningBalanceEditedPayload {
export interface ICustomerActivatingPayload {
// tenantId: number;
trx: Knex.Transaction,
oldCustomer: Customer;
}
export interface ICustomerActivatedPayload {
// tenantId: number;
trx?: Knex.Transaction;
oldCustomer: Customer;
customer: Customer;

View File

@@ -9,13 +9,10 @@ import {
Query,
} from '@nestjs/common';
import { ExpensesApplication } from './ExpensesApplication.service';
import {
IExpenseCreateDTO,
IExpenseEditDTO,
} from './interfaces/Expenses.interface';
import { PublicRoute } from '../Auth/Jwt.guard';
import { IExpensesFilter } from './Expenses.types';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateExpenseDto, EditExpenseDto } from './dtos/Expense.dto';
@Controller('expenses')
@ApiTags('expenses')
@@ -29,7 +26,7 @@ export class ExpensesController {
*/
@Post()
@ApiOperation({ summary: 'Create a new expense transaction.' })
public createExpense(@Body() expenseDTO: IExpenseCreateDTO) {
public createExpense(@Body() expenseDTO: CreateExpenseDto) {
return this.expensesApplication.createExpense(expenseDTO);
}
@@ -42,7 +39,7 @@ export class ExpensesController {
@ApiOperation({ summary: 'Edit the given expense transaction.' })
public editExpense(
@Param('id') expenseId: number,
@Body() expenseDTO: IExpenseEditDTO,
@Body() expenseDTO: EditExpenseDto,
) {
return this.expensesApplication.editExpense(expenseId, expenseDTO);
}

View File

@@ -4,12 +4,9 @@ import { EditExpense } from './commands/EditExpense.service';
import { DeleteExpense } from './commands/DeleteExpense.service';
import { PublishExpense } from './commands/PublishExpense.service';
import { GetExpenseService } from './queries/GetExpense.service';
import {
IExpenseCreateDTO,
IExpenseEditDTO,
IExpensesFilter,
} from './interfaces/Expenses.interface';
import { IExpensesFilter } from './interfaces/Expenses.interface';
import { GetExpensesService } from './queries/GetExpenses.service';
import { CreateExpenseDto, EditExpenseDto } from './dtos/Expense.dto';
@Injectable()
export class ExpensesApplication {
@@ -24,55 +21,55 @@ export class ExpensesApplication {
/**
* Create a new expense transaction.
* @param {IExpenseDTO} expenseDTO
* @param {CreateExpenseDto} expenseDTO
* @returns {Promise<Expense>}
*/
public createExpense = (expenseDTO: IExpenseCreateDTO) => {
public createExpense(expenseDTO: CreateExpenseDto) {
return this.createExpenseService.newExpense(expenseDTO);
};
}
/**
* Edits the given expense transaction.
* @param {number} expenseId - Expense id.
* @param {IExpenseEditDTO} expenseDTO
* @param {EditExpenseDto} expenseDTO
* @returns {Promise<Expense>}
*/
public editExpense = (expenseId: number, expenseDTO: IExpenseEditDTO) => {
public editExpense(expenseId: number, expenseDTO: EditExpenseDto) {
return this.editExpenseService.editExpense(expenseId, expenseDTO);
};
}
/**
* Deletes the given expense.
* @param {number} expenseId - Expense id.
* @returns {Promise<void>}
*/
public deleteExpense = (expenseId: number) => {
public deleteExpense(expenseId: number) {
return this.deleteExpenseService.deleteExpense(expenseId);
};
}
/**
* Publishes the given expense.
* @param {number} expenseId - Expense id.
* @returns {Promise<void>}
*/
public publishExpense = (expenseId: number) => {
public publishExpense(expenseId: number) {
return this.publishExpenseService.publishExpense(expenseId);
};
}
/**
* Retrieve the given expense details.
* @param {number} expenseId -Expense id.
* @param {number} expenseId -Expense id.
* @return {Promise<Expense>}
*/
public getExpense = (expenseId: number) => {
public getExpense(expenseId: number) {
return this.getExpenseService.getExpense(expenseId);
};
}
/**
* Retrieve expenses paginated list.
* @param {IExpensesFilter} expensesFilter
*/
public getExpenses = (filterDTO: IExpensesFilter) => {
public getExpenses(filterDTO: IExpensesFilter) {
return this.getExpensesService.getExpensesList(filterDTO);
};
}
}

View File

@@ -2,14 +2,11 @@ import { Injectable } from '@nestjs/common';
import { omit, sumBy } from 'lodash';
import * as moment from 'moment';
import * as R from 'ramda';
import {
IExpenseCreateDTO,
IExpenseEditDTO,
} from '../interfaces/Expenses.interface';
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
import { Expense } from '../models/Expense.model';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { CreateExpenseDto, EditExpenseDto } from '../dtos/Expense.dto';
@Injectable()
export class ExpenseDTOTransformer {
@@ -28,7 +25,7 @@ export class ExpenseDTOTransformer {
* @return {number}
*/
private getExpenseLandedCostAmount = (
expenseDTO: IExpenseCreateDTO | IExpenseEditDTO,
expenseDTO: CreateExpenseDto | EditExpenseDto,
): number => {
const landedCostEntries = expenseDTO.categories.filter((entry) => {
return entry.landedCost === true;
@@ -52,7 +49,7 @@ export class ExpenseDTOTransformer {
* @return {IExpense}
*/
private expenseDTOToModel(
expenseDTO: IExpenseCreateDTO | IExpenseEditDTO,
expenseDTO: CreateExpenseDto | EditExpenseDto,
): Expense {
const landedCostAmount = this.getExpenseLandedCostAmount(expenseDTO);
const totalAmount = this.getExpenseCategoriesTotal(expenseDTO.categories);
@@ -85,7 +82,7 @@ export class ExpenseDTOTransformer {
* @returns {Promise<Expense>}
*/
public expenseCreateDTO = async (
expenseDTO: IExpenseCreateDTO,
expenseDTO: CreateExpenseDto | EditExpenseDto,
): Promise<Partial<Expense>> => {
const initialDTO = this.expenseDTOToModel(expenseDTO);
const tenant = await this.tenancyContext.getTenant(true);
@@ -104,13 +101,11 @@ export class ExpenseDTOTransformer {
/**
* Transformes the expense edit DTO.
* @param {number} tenantId
* @param {IExpenseEditDTO} expenseDTO
* @param {ISystemUser} user
* @returns {IExpense}
* @param {EditExpenseDto} expenseDTO
* @returns {Promise<Expense>}
*/
public expenseEditDTO = async (
expenseDTO: IExpenseEditDTO,
expenseDTO: EditExpenseDto,
): Promise<Expense> => {
return this.expenseDTOToModel(expenseDTO);
};

View File

@@ -1,14 +1,11 @@
import { sumBy, difference } from 'lodash';
import { ERRORS, SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES } from '../constants';
import {
IExpenseCreateDTO,
IExpenseEditDTO,
} from '../interfaces/Expenses.interface';
import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts';
import { Account } from '@/modules/Accounts/models/Account.model';
import { Injectable } from '@nestjs/common';
import { Expense } from '../models/Expense.model';
import { ServiceError } from '@/modules/Items/ServiceError';
import { CreateExpenseDto, EditExpenseDto } from '../dtos/Expense.dto';
@Injectable()
export class CommandExpenseValidator {
@@ -18,7 +15,7 @@ export class CommandExpenseValidator {
* @throws {ServiceError}
*/
public validateCategoriesNotEqualZero = (
expenseDTO: IExpenseCreateDTO | IExpenseEditDTO,
expenseDTO: CreateExpenseDto | EditExpenseDto,
) => {
const totalAmount = sumBy(expenseDTO.categories, 'amount') || 0;
@@ -30,7 +27,6 @@ export class CommandExpenseValidator {
/**
* Retrieve expense accounts or throw error in case one of the given accounts
* not found not the storage.
* @param {Array<Account>} tenantId
* @param {number} expenseAccountsIds
* @throws {ServiceError}
* @returns {Promise<IAccount[]>}
@@ -40,7 +36,6 @@ export class CommandExpenseValidator {
DTOAccountsIds: number[],
) {
const storedExpenseAccountsIds = expenseAccounts.map((a: Account) => a.id);
const notStoredAccountsIds = difference(
DTOAccountsIds,
storedExpenseAccountsIds,
@@ -84,7 +79,6 @@ export class CommandExpenseValidator {
/**
* Validates the expense has not associated landed cost
* references to the given expense.
* @param {number} tenantId
* @param {number} expenseId
*/
public async validateNoAssociatedLandedCost(expenseId: number) {

View File

@@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
IExpenseCreateDTO,
IExpenseCreatedPayload,
IExpenseCreatingPayload,
} from '../interfaces/Expenses.interface';
@@ -13,6 +12,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CreateExpenseDto } from '../dtos/Expense.dto';
@Injectable()
export class CreateExpense {
@@ -41,7 +41,7 @@ export class CreateExpense {
* Authorize before create a new expense transaction.
* @param {IExpenseDTO} expenseDTO
*/
private authorize = async (expenseDTO: IExpenseCreateDTO) => {
private authorize = async (expenseDTO: CreateExpenseDto) => {
// Validate payment account existance on the storage.
const paymentAccount = await this.accountModel()
.query()
@@ -86,7 +86,7 @@ export class CreateExpense {
* @param {IExpenseDTO} expenseDTO
*/
public newExpense = async (
expenseDTO: IExpenseCreateDTO,
expenseDTO: CreateExpenseDto,
trx?: Knex.Transaction,
): Promise<Expense> => {
// Authorize before create a new expense.

View File

@@ -3,17 +3,16 @@ import { Knex } from 'knex';
import {
IExpenseEventEditPayload,
IExpenseEventEditingPayload,
IExpenseEditDTO,
} from '../interfaces/Expenses.interface';
import { CommandExpenseValidator } from './CommandExpenseValidator.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ExpenseDTOTransformer } from './CommandExpenseDTO.transformer';
// import { EntriesService } from '@/services/Entries';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Account } from '@/modules/Accounts/models/Account.model';
import { Expense } from '../models/Expense.model';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { EditExpenseDto } from '../dtos/Expense.dto';
@Injectable()
export class EditExpense {
@@ -30,7 +29,6 @@ export class EditExpense {
private uow: UnitOfWork,
private validator: CommandExpenseValidator,
private transformDTO: ExpenseDTOTransformer,
// private entriesService: EntriesService,
@Inject(Expense.name)
private expenseModel: TenantModelProxy<typeof Expense>,
@@ -40,11 +38,11 @@ export class EditExpense {
/**
* Authorize the DTO before editing expense transaction.
* @param {IExpenseEditDTO} expenseDTO
* @param {EditExpenseDto} expenseDTO
*/
public authorize = async (
oldExpense: Expense,
expenseDTO: IExpenseEditDTO,
expenseDTO: EditExpenseDto,
) => {
// Validate payment account existance on the storage.
const paymentAccount = await this.accountModel()
@@ -102,7 +100,7 @@ export class EditExpense {
*/
public async editExpense(
expenseId: number,
expenseDTO: IExpenseEditDTO,
expenseDTO: EditExpenseDto,
): Promise<Expense> {
// Retrieves the expense model or throw not found error.
const oldExpense = await this.expenseModel()

View File

@@ -1,3 +1,166 @@
export class CreateExpenseDto {}
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsDate,
IsInt,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
MaxLength,
ValidateNested,
} from 'class-validator';
export class EditExpenseDto {}
class AttachmentDto {
@IsString()
key: string;
}
export class ExpenseCategoryDto {
@IsInt()
@IsNotEmpty()
index: number;
@IsInt()
@IsNotEmpty()
expenseAccountId: number;
@IsNumber()
@IsOptional()
amount?: number;
@IsString()
@MaxLength(255)
@IsOptional()
description?: string;
@IsBoolean()
@IsOptional()
landedCost?: boolean;
@IsInt()
@IsOptional()
projectId?: number;
}
export class CommandExpenseDto {
@IsString()
@MaxLength(255)
@IsOptional()
@ApiProperty({
description: 'The reference number of the expense',
example: 'INV-123456',
})
referenceNo?: string;
@IsDate()
@IsNotEmpty()
@ApiProperty({
description: 'The payment date of the expense',
example: '2021-01-01',
})
paymentDate: Date;
@IsInt()
@IsNotEmpty()
@ApiProperty({
description: 'The payment account id of the expense',
example: 1,
})
paymentAccountId: number;
@IsString()
@MaxLength(1000)
@IsOptional()
@ApiProperty({
description: 'The description of the expense',
example: 'This is a description',
})
description?: string;
@IsNumber()
@IsOptional()
@ApiProperty({
description: 'The exchange rate of the expense',
example: 1,
})
exchangeRate?: number;
@IsString()
@MaxLength(3)
@IsOptional()
@ApiProperty({
description: 'The currency code of the expense',
example: 'USD',
})
currencyCode?: string;
@IsNumber()
@IsOptional()
@ApiProperty({
description: 'The exchange rate of the expense',
example: 1,
})
exchange_rate?: number;
@IsBoolean()
@IsOptional()
@ApiProperty({
description: 'The publish status of the expense',
example: true,
})
publish?: boolean;
@IsInt()
@IsOptional()
@ApiProperty({
description: 'The payee id of the expense',
example: 1,
})
payeeId?: number;
@IsInt()
@IsOptional()
@ApiProperty({
description: 'The branch id of the expense',
example: 1,
})
branchId?: number;
@IsArray()
@ValidateNested({ each: true })
@Type(() => ExpenseCategoryDto)
@ApiProperty({
description: 'The categories of the expense',
example: [
{
index: 1,
expenseAccountId: 1,
amount: 100,
description: 'This is a description',
landedCost: true,
projectId: 1,
},
],
})
categories: ExpenseCategoryDto[];
@IsArray()
@ValidateNested({ each: true })
@Type(() => AttachmentDto)
@IsOptional()
@ApiProperty({
description: 'The attachments of the expense',
example: [
{
key: '123456',
},
],
})
attachments?: AttachmentDto[];
}
export class CreateExpenseDto extends CommandExpenseDto {}
export class EditExpenseDto extends CommandExpenseDto {}

View File

@@ -1,6 +1,7 @@
import { IFilterRole } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { Knex } from 'knex';
import { Expense } from '../models/Expense.model';
import { CreateExpenseDto, EditExpenseDto } from '../dtos/Expense.dto';
// import { AttachmentLinkDTO } from '../Attachments/Attachments';
@@ -20,26 +21,6 @@ export interface IExpensesFilter {
filterQuery?: (query: any) => void;
}
export interface IExpenseCommonDTO {
currencyCode: string;
exchangeRate?: number;
description?: string;
paymentAccountId: number;
peyeeId?: number;
referenceNo?: string;
publish: boolean;
userId: number;
paymentDate: Date;
payeeId: number;
categories: IExpenseCategoryDTO[];
branchId?: number;
// attachments?: AttachmentLinkDTO[];
}
export interface IExpenseCreateDTO extends IExpenseCommonDTO {}
export interface IExpenseEditDTO extends IExpenseCommonDTO {}
export interface IExpenseCategoryDTO {
id?: number;
expenseAccountId: number;
@@ -52,55 +33,44 @@ export interface IExpenseCategoryDTO {
}
export interface IExpenseCreatingPayload {
expenseDTO: CreateExpenseDto;
trx: Knex.Transaction;
tenantId: number;
expenseDTO: IExpenseCreateDTO;
}
export interface IExpenseEventEditingPayload {
tenantId: number;
oldExpense: Expense;
expenseDTO: IExpenseEditDTO;
expenseDTO: EditExpenseDto;
trx: Knex.Transaction;
}
export interface IExpenseCreatedPayload {
tenantId: number;
expenseId: number;
// authorizedUser: ISystemUser;
expense: Expense;
expenseDTO: IExpenseCreateDTO;
expenseDTO: CreateExpenseDto;
trx: Knex.Transaction;
}
export interface IExpenseEventEditPayload {
tenantId: number;
expenseId: number;
expense: Expense;
expenseDTO: IExpenseEditDTO;
// authorizedUser: ISystemUser;
expenseDTO: EditExpenseDto;
oldExpense: Expense;
trx: Knex.Transaction;
}
export interface IExpenseEventDeletePayload {
tenantId: number;
expenseId: number;
// authorizedUser: ISystemUser;
oldExpense: Expense;
trx: Knex.Transaction;
}
export interface IExpenseDeletingPayload {
trx: Knex.Transaction;
tenantId: number;
oldExpense: Expense;
}
export interface IExpenseEventPublishedPayload {
tenantId: number;
expenseId: number;
oldExpense: Expense;
expense: Expense;
// authorizedUser: ISystemUser;
trx: Knex.Transaction;
}

View File

@@ -13,95 +13,190 @@ import {
IsNotEmpty,
} from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
export class CommandItemDto {
@IsString()
@IsNotEmpty()
@MaxLength(255)
@ApiProperty({ description: 'Item name', example: 'Office Chair' })
name: string;
@IsString()
@IsIn(['service', 'non-inventory', 'inventory'])
@ApiProperty({
description: 'Item type',
enum: ['service', 'non-inventory', 'inventory'],
example: 'inventory',
})
type: 'service' | 'non-inventory' | 'inventory';
@IsOptional()
@IsString()
@MaxLength(255)
@ApiProperty({
description: 'Item code/SKU',
required: false,
example: 'ITEM-001',
})
code?: string;
// Purchase attributes
@IsOptional()
@IsBoolean()
@ApiProperty({
description: 'Whether the item can be purchased',
required: false,
example: true,
})
purchasable?: boolean;
@IsOptional()
@IsNumber({ maxDecimalPlaces: 3 })
@Min(0)
@ValidateIf((o) => o.purchasable === true)
@ApiProperty({
description: 'Cost price of the item',
required: false,
minimum: 0,
example: 100.5,
})
costPrice?: number;
@IsOptional()
@IsInt()
@Min(0)
@ValidateIf((o) => o.purchasable === true)
@ApiProperty({
description: 'ID of the cost account',
required: false,
minimum: 0,
example: 1,
})
costAccountId?: number;
// Sell attributes
@IsOptional()
@IsBoolean()
@ApiProperty({
description: 'Whether the item can be sold',
required: false,
example: true,
})
sellable?: boolean;
@IsOptional()
@IsNumber({ maxDecimalPlaces: 3 })
@Min(0)
@ValidateIf((o) => o.sellable === true)
@ApiProperty({
description: 'Selling price of the item',
required: false,
minimum: 0,
example: 150.75,
})
sellPrice?: number;
@IsOptional()
@IsInt()
@Min(0)
@ValidateIf((o) => o.sellable === true)
@ApiProperty({
description: 'ID of the sell account',
required: false,
minimum: 0,
example: 2,
})
sellAccountId?: number;
@IsOptional()
@IsInt()
@Min(0)
@ValidateIf((o) => o.type === 'inventory')
@ApiProperty({
description: 'ID of the inventory account (required for inventory items)',
required: false,
minimum: 0,
example: 3,
})
inventoryAccountId?: number;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Description shown on sales documents',
required: false,
example: 'High-quality ergonomic office chair',
})
sellDescription?: string;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Description shown on purchase documents',
required: false,
example: 'Ergonomic office chair with adjustable height',
})
purchaseDescription?: string;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'ID of the tax rate applied to sales',
required: false,
example: 1,
})
sellTaxRateId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'ID of the tax rate applied to purchases',
required: false,
example: 1,
})
purchaseTaxRateId?: number;
@IsOptional()
@IsInt()
@Min(0)
@ApiProperty({
description: 'ID of the item category',
required: false,
minimum: 0,
example: 5,
})
categoryId?: number;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Additional notes about the item',
required: false,
example: 'Available in multiple colors',
})
note?: string;
@IsOptional()
@IsBoolean()
@ApiProperty({
description: 'Whether the item is active',
required: false,
default: true,
example: true,
})
active?: boolean;
@IsOptional()
@IsArray()
@Type(() => Number)
@IsInt({ each: true })
@ApiProperty({
description: 'IDs of media files associated with the item',
required: false,
type: [Number],
example: [1, 2, 3],
})
mediaIds?: number[];
}

View File

@@ -1,10 +1,12 @@
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsDate,
IsEnum,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
@@ -26,18 +28,35 @@ class AttachmentDto {
}
export class CommandSaleEstimateDto {
@IsNumber()
@IsNotEmpty()
@ApiProperty({
description: 'The id of the customer',
example: 1,
})
customerId: number;
@IsDate()
@Type(() => Date)
@ApiProperty({
description: 'The date of the estimate',
example: '2021-01-01',
})
estimateDate: Date;
@IsDate()
@Type(() => Date)
@ApiProperty({
description: 'The expiration date of the estimate',
example: '2021-01-01',
})
expirationDate: Date;
@IsString()
@IsOptional()
@ApiProperty({
description: 'The reference of the estimate',
example: '123456',
})
reference?: string;
@IsString()
@@ -50,53 +69,111 @@ export class CommandSaleEstimateDto {
@IsNumber()
@Min(0.01)
@IsOptional()
@ApiProperty({
description: 'The exchange rate of the estimate',
example: 1,
})
exchangeRate?: number;
@IsNumber()
@IsOptional()
@ApiProperty({
description: 'The id of the warehouse',
example: 1,
})
warehouseId?: number;
@IsNumber()
@IsOptional()
@ApiProperty({
description: 'The id of the branch',
example: 1,
})
branchId?: number;
@IsArray()
@MinLength(1)
@ValidateNested({ each: true })
@Type(() => SaleEstimateEntryDto)
@ApiProperty({
description: 'The entries of the estimate',
example: [
{
index: 1,
itemId: 1,
description: 'This is a description',
quantity: 100,
cost: 100,
},
],
})
entries: SaleEstimateEntryDto[];
@IsString()
@IsOptional()
@ApiProperty({
description: 'The note of the estimate',
example: 'This is a note',
})
note?: string;
@IsString()
@IsOptional()
@ApiProperty({
description: 'The terms and conditions of the estimate',
example: 'This is a terms and conditions',
})
termsConditions?: string;
@IsString()
@IsOptional()
@ApiProperty({
description: 'The email to send the estimate to',
example: 'test@test.com',
})
sendToEmail?: string;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => AttachmentDto)
attachments?: AttachmentDto[];
@ApiProperty({
description: 'The attachments of the estimate',
example: [
{
key: '123456',
},
],
})
@IsNumber()
@IsOptional()
@ApiProperty({
description: 'The id of the pdf template',
example: 1,
})
pdfTemplateId?: number;
@IsNumber()
@IsOptional()
@ApiProperty({
description: 'The discount of the estimate',
example: 1,
})
discount?: number;
@IsEnum(DiscountType)
@ApiProperty({
description: 'The type of the discount',
example: DiscountType.Amount,
})
discountType: DiscountType = DiscountType.Amount;
@IsNumber()
@IsOptional()
@ApiProperty({
description: 'The adjustment of the estimate',
example: 1,
})
adjustment?: number;
}

View File

@@ -1,4 +1,5 @@
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
@@ -31,89 +32,159 @@ class PaymentMethodDto {
class CommandSaleInvoiceDto {
@IsInt()
@IsNotEmpty()
@ApiProperty({ description: 'Customer ID', example: 1 })
customerId: number;
@IsDate()
@Type(() => Date)
@IsNotEmpty()
@ApiProperty({ description: 'Invoice date', example: '2023-01-01T00:00:00Z' })
invoiceDate: Date;
@IsDate()
@Type(() => Date)
@IsNotEmpty()
@ApiProperty({ description: 'Due date', example: '2023-01-15T00:00:00Z' })
dueDate: Date;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Invoice number',
required: false,
example: 'INV-001',
})
invoiceNo?: string;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Reference number',
required: false,
example: 'REF-001',
})
referenceNo?: string;
@IsOptional()
@IsBoolean()
@ApiProperty({
description: 'Whether the invoice is delivered',
default: false,
required: false,
})
delivered: boolean = false;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Invoice message',
required: false,
example: 'Thank you for your business',
})
invoiceMessage?: string;
@IsOptional()
@IsString()
@ApiProperty({
description: 'Terms and conditions',
required: false,
example: 'Payment due within 14 days',
})
termsConditions?: string;
@IsOptional()
@IsNumber()
@Min(0)
@ApiProperty({
description: 'Exchange rate',
required: false,
minimum: 0,
example: 1.0,
})
exchangeRate?: number;
@IsOptional()
@IsInt()
@ApiProperty({ description: 'Warehouse ID', required: false, example: 1 })
warehouseId?: number;
@IsOptional()
@IsInt()
@ApiProperty({ description: 'Branch ID', required: false, example: 1 })
branchId?: number;
@IsOptional()
@IsInt()
@ApiProperty({ description: 'Project ID', required: false, example: 1 })
projectId?: number;
@IsOptional()
@IsBoolean()
@ApiProperty({
description: 'Whether tax is inclusive',
required: false,
example: false,
})
isInclusiveTax?: boolean;
@IsArray()
@ValidateNested({ each: true })
@Type(() => ItemEntryDto)
@MinLength(1)
@ApiProperty({
description: 'Invoice line items',
type: [ItemEntryDto],
minItems: 1,
})
entries: ItemEntryDto[];
@IsOptional()
@IsInt()
@ApiProperty({ description: 'PDF template ID', required: false, example: 1 })
pdfTemplateId?: number;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => PaymentMethodDto)
@ApiProperty({
description: 'Payment methods',
type: [PaymentMethodDto],
required: false,
})
paymentMethods?: PaymentMethodDto[];
@IsOptional()
@IsNumber()
@ApiProperty({ description: 'Discount value', required: false, example: 10 })
discount?: number;
@IsOptional()
@IsEnum(DiscountType)
@ApiProperty({
description: 'Discount type',
enum: DiscountType,
required: false,
example: DiscountType.Percentage,
})
discountType?: DiscountType;
@IsOptional()
@IsNumber()
@ApiProperty({
description: 'Adjustment amount',
required: false,
example: 5,
})
adjustment?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'ID of the estimate this invoice is created from',
required: false,
example: 1,
})
fromEstimateId?: number;
}

View File

@@ -1,4 +1,5 @@
import { DiscountType } from '@/common/types/Discount';
import { ApiProperty } from '@nestjs/swagger';
import {
IsEnum,
IsIn,
@@ -11,66 +12,130 @@ import {
export class ItemEntryDto {
@IsInt()
@ApiProperty({
description: 'The index of the item entry',
example: 1,
})
index: number;
@IsInt()
@IsNotEmpty()
@ApiProperty({
description: 'The id of the item',
example: 1,
})
itemId: number;
@IsNumber()
@IsNotEmpty()
@ApiProperty({
description: 'The rate of the item entry',
example: 1,
})
rate: number;
@IsNumber()
@IsNotEmpty()
@ApiProperty({
description: 'The quantity of the item entry',
example: 1,
})
quantity: number;
@IsOptional()
@IsNumber()
@ApiProperty({
description: 'The discount of the item entry',
example: 1,
})
discount?: number;
@IsOptional()
@IsEnum(DiscountType)
@ApiProperty({
description: 'The type of the discount',
example: DiscountType.Percentage,
})
discountType?: DiscountType = DiscountType.Percentage;
@IsOptional()
@IsString()
@ApiProperty({
description: 'The description of the item entry',
example: 'This is a description',
})
description?: string;
@IsOptional()
@IsString()
@ApiProperty({
description: 'The tax code of the item entry',
example: '123456',
})
taxCode?: string;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The tax rate id of the item entry',
example: 1,
})
taxRateId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The warehouse id of the item entry',
example: 1,
})
warehouseId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The project id of the item entry',
example: 1,
})
projectId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The project ref id of the item entry',
example: 1,
})
projectRefId?: number;
@IsOptional()
@IsString()
@IsIn(['TASK', 'BILL', 'EXPENSE'])
@ApiProperty({
description: 'The project ref type of the item entry',
example: 'TASK',
})
projectRefType?: string;
@IsOptional()
@IsNumber()
@ApiProperty({
description: 'The project ref invoiced amount of the item entry',
example: 100,
})
projectRefInvoicedAmount?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The sell account id of the item entry',
example: 1020,
})
sellAccountId?: number;
@IsOptional()
@IsInt()
@ApiProperty({
description: 'The cost account id of the item entry',
example: 1021,
})
costAccountId?: number;
}

View File

@@ -56,7 +56,7 @@ export class TransactionsLockingController {
@ApiOperation({ summary: 'Partial unlock all transactions locking for a module or all modules' })
async unlockTransactionsLockingBetweenPeriod(
@Body('module') module: TransactionsLockingGroup,
@Body() unlockDTO: UnlockTransactionsLockingDto,
@Body() unlockDTO: ITransactionLockingPartiallyDTO,
) {
const transactionMeta =
await this.transactionsLockingService.unlockTransactionsLockingPartially(

View File

@@ -1,6 +1,6 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import {
IVendorCreditEditDTO,
IVendorCreditEditedPayload,
IVendorCreditEditingPayload,
} from '../types/VendorCredit.types';
@@ -10,7 +10,6 @@ import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { VendorCredit } from '../models/VendorCredit';
import { Contact } from '@/modules/Contacts/models/Contact';
import { events } from '@/common/events/events';
import { Knex } from 'knex';
import { VendorCreditDTOTransformService } from './VendorCreditDTOTransform.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { EditVendorCreditDto } from '../dtos/VendorCredit.dto';
@@ -40,7 +39,7 @@ export class EditVendorCreditService {
/**
* Deletes the given vendor credit.
* @param {number} vendorCreditId - Vendor credit id.
* @param {EditVendorCreditDto} vendorCreditDto -
* @param {EditVendorCreditDto} vendorCreditDto -
*/
public editVendorCredit = async (
vendorCreditId: number,

View File

@@ -2,7 +2,6 @@ import * as moment from 'moment';
import { omit } from 'lodash';
import * as R from 'ramda';
import { ERRORS } from '../constants';
import { IVendorCreditEntryDTO } from '../types/VendorCredit.types';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform';
@@ -14,6 +13,7 @@ import { Injectable } from '@nestjs/common';
import {
CreateVendorCreditDto,
EditVendorCreditDto,
VendorCreditEntryDto,
} from '../dtos/VendorCredit.dto';
@Injectable()
@@ -52,7 +52,7 @@ export class VendorCreditDTOTransformService {
assocItemEntriesDefaultIndex,
// Associate the reference type to item entries.
R.map((entry: IVendorCreditEntryDTO) => ({
R.map((entry: VendorCreditEntryDto) => ({
referenceType: 'VendorCredit',
...entry,
})),

View File

@@ -14,7 +14,7 @@ enum DiscountType {
Amount = 'amount',
}
class VendorCreditEntryDto extends ItemEntryDto {}
export class VendorCreditEntryDto extends ItemEntryDto {}
class AttachmentDto {
@IsString()

View File

@@ -10,13 +10,13 @@ import {
} from '@nestjs/common';
import { VendorsApplication } from './VendorsApplication.service';
import {
IVendorEditDTO,
IVendorNewDTO,
IVendorOpeningBalanceEditDTO,
IVendorsFilter,
} from './types/Vendors.types';
import { PublicRoute } from '../Auth/Jwt.guard';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateVendorDto } from './dtos/CreateVendor.dto';
import { EditVendorDto } from './dtos/EditVendor.dto';
@Controller('vendors')
@ApiTags('vendors')
@@ -38,13 +38,13 @@ export class VendorsController {
@Post()
@ApiOperation({ summary: 'Create a new vendor.' })
createVendor(@Body() vendorDTO: IVendorNewDTO) {
createVendor(@Body() vendorDTO: CreateVendorDto) {
return this.vendorsApplication.createVendor(vendorDTO);
}
@Put(':id')
@ApiOperation({ summary: 'Edit the given vendor.' })
editVendor(@Param('id') vendorId: number, @Body() vendorDTO: IVendorEditDTO) {
editVendor(@Param('id') vendorId: number, @Body() vendorDTO: EditVendorDto) {
return this.vendorsApplication.editVendor(vendorId, vendorDTO);
}

View File

@@ -6,12 +6,12 @@ import { DeleteVendorService } from './commands/DeleteVendor.service';
import { EditOpeningBalanceVendorService } from './commands/EditOpeningBalanceVendor.service';
import { GetVendorService } from './queries/GetVendor';
import {
IVendorEditDTO,
IVendorNewDTO,
IVendorOpeningBalanceEditDTO,
IVendorsFilter,
} from './types/Vendors.types';
import { GetVendorsService } from './queries/GetVendors.service';
import { CreateVendorDto } from './dtos/CreateVendor.dto';
import { EditVendorDto } from './dtos/EditVendor.dto';
@Injectable()
export class VendorsApplication {
@@ -29,7 +29,7 @@ export class VendorsApplication {
* @param {IVendorNewDTO} vendorDTO
* @return {Promise<void>}
*/
public createVendor(vendorDTO: IVendorNewDTO, trx?: Knex.Transaction) {
public createVendor(vendorDTO: CreateVendorDto, trx?: Knex.Transaction) {
return this.createVendorService.createVendor(vendorDTO, trx);
}
@@ -39,7 +39,7 @@ export class VendorsApplication {
* @param {IVendorEditDTO} vendorDTO -
* @returns {Promise<IVendor>}
*/
public editVendor(vendorId: number, vendorDTO: IVendorEditDTO) {
public editVendor(vendorId: number, vendorDTO: EditVendorDto) {
return this.editVendorService.editVendor(vendorId, vendorDTO);
}
@@ -84,5 +84,5 @@ export class VendorsApplication {
*/
public getVendors(filterDTO: IVendorsFilter) {
return this.getVendorsService.getVendorsList(filterDTO);
};
}
}

View File

@@ -5,6 +5,7 @@ import { IVendorEditDTO, IVendorNewDTO } from '../types/Vendors.types';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { ContactService } from '@/modules/Contacts/types/Contacts.types';
import { Vendor } from '../models/Vendor';
import { CreateVendorDto } from '../dtos/CreateVendor.dto';
@Injectable()
export class CreateEditVendorDTOService {
@@ -30,7 +31,7 @@ export class CreateEditVendorDTOService {
* @returns {IVendorNewDTO}
*/
public transformCreateDTO = async (
vendorDTO: IVendorNewDTO,
vendorDTO: CreateVendorDto,
): Promise<Partial<Vendor>> => {
const commonDTO = this.transformCommonDTO(vendorDTO);

View File

@@ -11,6 +11,7 @@ import {
} from '../types/Vendors.types';
import { CreateEditVendorDTOService } from './CreateEditVendorDTO';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CreateVendorDto } from '../dtos/CreateVendor.dto';
@Injectable()
export class CreateVendorService {
@@ -34,7 +35,7 @@ export class CreateVendorService {
* @param {IVendorNewDTO} vendorDTO
* @return {Promise<void>}
*/
public async createVendor(vendorDTO: IVendorNewDTO, trx?: Knex.Transaction) {
public async createVendor(vendorDTO: CreateVendorDto, trx?: Knex.Transaction) {
// Transforms create DTO to customer object.
const vendorObject = await this.transformDTO.transformCreateDTO(vendorDTO);

View File

@@ -1,5 +1,4 @@
import {
IVendorEditDTO,
IVendorEventEditedPayload,
IVendorEventEditingPayload,
} from '../types/Vendors.types';
@@ -11,6 +10,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Vendor } from '../models/Vendor';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { EditVendorDto } from '../dtos/EditVendor.dto';
@Injectable()
export class EditVendorService {
@@ -29,7 +29,7 @@ export class EditVendorService {
* @param {IVendorEditDTO} vendorDTO -
* @returns {Promise<IVendor>}
*/
public async editVendor(vendorId: number, vendorDTO: IVendorEditDTO) {
public async editVendor(vendorId: number, vendorDTO: EditVendorDto) {
// Retrieve the vendor or throw not found error.
const oldVendor = await this.vendorModel()
.query()

View File

@@ -0,0 +1,103 @@
import { ApiProperty } from '@nestjs/swagger';
import { 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';
export class CreateVendorDto extends ContactAddressDto {
@ApiProperty({ required: false, description: 'Vendor opening balance' })
@IsOptional()
@IsInt()
@Min(0)
openingBalance?: number;
@ApiProperty({
required: false,
description: 'Vendor opening balance exchange rate',
default: 1,
})
@IsOptional()
@IsNumber()
@Min(0.01)
openingBalanceExchangeRate?: number;
@ApiProperty({ required: false, description: 'Date of the opening balance' })
@IsOptional()
@IsISO8601()
openingBalanceAt?: Date;
@ApiProperty({
required: false,
description: 'Branch ID for the opening balance',
})
@IsOptional()
@IsInt()
openingBalanceBranchId?: number;
@ApiProperty({ description: 'Currency code for the vendor' })
@IsOptional()
@IsString()
currencyCode: string;
@ApiProperty({ required: false, description: 'Vendor salutation' })
@IsOptional()
@IsString()
salutation?: string;
@ApiProperty({ required: false, description: 'Vendor first name' })
@IsOptional()
@IsString()
firstName?: string;
@ApiProperty({ required: false, description: 'Vendor last name' })
@IsOptional()
@IsString()
lastName?: string;
@ApiProperty({ required: false, description: 'Vendor company name' })
@IsOptional()
@IsString()
companyName?: string;
@ApiProperty({ required: false, description: 'Vendor display name' })
@IsString()
displayName: string;
@ApiProperty({ required: false, description: 'Vendor website' })
@IsOptional()
@IsString()
website?: string;
@ApiProperty({ required: false, description: 'Vendor email address' })
@IsOptional()
@IsEmail()
email?: string;
@ApiProperty({ required: false, description: 'Vendor work phone number' })
@IsOptional()
@IsString()
workPhone?: string;
@ApiProperty({ required: false, description: 'Vendor personal phone number' })
@IsOptional()
@IsString()
personalPhone?: string;
@ApiProperty({
required: false,
description: 'Additional notes about the vendor',
})
@IsOptional()
@IsString()
note?: string;
@ApiProperty({
required: false,
description: 'Whether the vendor is active',
default: true,
})
@IsOptional()
@IsBoolean()
active?: boolean;
}

View File

@@ -0,0 +1,60 @@
import { ContactAddressDto } from '@/modules/Customers/dtos/ContactAddress.dto';
import { IsEmail, IsString, IsBoolean, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class EditVendorDto extends ContactAddressDto {
@ApiProperty({ required: false, description: 'Vendor salutation' })
@IsOptional()
@IsString()
salutation?: string;
@ApiProperty({ required: false, description: 'Vendor first name' })
@IsOptional()
@IsString()
firstName?: string;
@ApiProperty({ required: false, description: 'Vendor last name' })
@IsOptional()
@IsString()
lastName?: string;
@ApiProperty({ required: false, description: 'Vendor company name' })
@IsOptional()
@IsString()
companyName?: string;
@ApiProperty({ required: false, description: 'Vendor display name' })
@IsOptional()
@IsString()
displayName?: string;
@ApiProperty({ required: false, description: 'Vendor website' })
@IsOptional()
@IsString()
website?: string;
@ApiProperty({ required: false, description: 'Vendor email address' })
@IsOptional()
@IsEmail()
email?: string;
@ApiProperty({ required: false, description: 'Vendor work phone number' })
@IsOptional()
@IsString()
workPhone?: string;
@ApiProperty({ required: false, description: 'Vendor personal phone number' })
@IsOptional()
@IsString()
personalPhone?: string;
@ApiProperty({ required: false, description: 'Additional notes about the vendor' })
@IsOptional()
@IsString()
note?: string;
@ApiProperty({ required: false, description: 'Whether the vendor is active' })
@IsOptional()
@IsBoolean()
active?: boolean;
}

View File

@@ -5,6 +5,8 @@ import { Vendor } from '../models/Vendor';
import { IContactAddressDTO } from '@/modules/Contacts/types/Contacts.types';
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
import { CreateVendorDto } from '../dtos/CreateVendor.dto';
import { EditVendorDto } from '../dtos/EditVendor.dto';
// ----------------------------------
export interface IVendorNewDTO extends IContactAddressDTO {
@@ -60,43 +62,33 @@ export interface GetVendorsResponse {
// Vendor Events.
// ----------------------------------
export interface IVendorEventCreatingPayload {
// tenantId: number;
vendorDTO: IVendorNewDTO;
// authorizedUser: ISystemUser;
vendorDTO: CreateVendorDto;
trx: Knex.Transaction;
}
export interface IVendorEventCreatedPayload {
// tenantId: number;
vendorId: number;
vendor: Vendor;
// authorizedUser: ISystemUser;
trx: Knex.Transaction;
}
export interface IVendorEventDeletingPayload {
// tenantId: number;
vendorId: number;
oldVendor: Vendor;
}
export interface IVendorEventDeletedPayload {
// tenantId: number;
vendorId: number;
// authorizedUser: ISystemUser;
oldVendor: Vendor;
trx?: Knex.Transaction;
}
export interface IVendorEventEditingPayload {
// tenantId: number;
vendorDTO: IVendorEditDTO;
vendorDTO: EditVendorDto;
trx?: Knex.Transaction;
}
export interface IVendorEventEditedPayload {
// tenantId: number;
vendorId: number;
vendor: Vendor;
// authorizedUser: ISystemUser;
trx?: Knex.Transaction;
}
@@ -108,14 +100,12 @@ export interface IVendorOpeningBalanceEditDTO {
}
export interface IVendorOpeningBalanceEditingPayload {
// tenantId: number;
oldVendor: Vendor;
openingBalanceEditDTO: IVendorOpeningBalanceEditDTO;
trx?: Knex.Transaction;
}
export interface IVendorOpeningBalanceEditedPayload {
// tenantId: number;
vendor: Vendor;
oldVendor: Vendor;
openingBalanceEditDTO: IVendorOpeningBalanceEditDTO;
@@ -123,13 +113,11 @@ export interface IVendorOpeningBalanceEditedPayload {
}
export interface IVendorActivatingPayload {
// tenantId: number;
oldVendor: Vendor;
trx: Knex.Transaction;
}
export interface IVendorActivatedPayload {
// tenantId: number;
vendor: Vendor;
oldVendor: Vendor;
trx?: Knex.Transaction;

View File

@@ -1,3 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
@@ -39,32 +40,68 @@ export class WarehouseTransferEntryDto {
export class CommandWarehouseTransferDto {
@IsNotEmpty()
@IsInt()
@ApiProperty({
description: 'The id of the warehouse to transfer from',
example: 1,
})
fromWarehouseId: number;
@IsNotEmpty()
@IsInt()
@ApiProperty({
description: 'The id of the warehouse to transfer to',
example: 2,
})
toWarehouseId: number;
@IsNotEmpty()
@IsDate()
@ApiProperty({
description: 'The date of the warehouse transfer',
example: '2021-01-01',
})
date: Date;
@IsOptional()
@IsString()
@ApiProperty({
description: 'The transaction number of the warehouse transfer',
example: '123456',
})
transactionNumber?: string;
@IsBoolean()
@IsOptional()
@ApiProperty({
description: 'Whether the warehouse transfer has been initiated',
example: false,
})
transferInitiated: boolean = false;
@IsBoolean()
@IsOptional()
@ApiProperty({
description: 'Whether the warehouse transfer has been delivered',
example: false,
})
transferDelivered: boolean = false;
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => WarehouseTransferEntryDto)
@ApiProperty({
description: 'The entries of the warehouse transfer',
example: [
{
index: 1,
itemId: 1,
description: 'This is a description',
quantity: 100,
cost: 100,
},
],
})
entries: WarehouseTransferEntryDto[];
}