feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,67 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { CustomerValidators } from './CustomerValidators.service';
import {
ICustomerActivatedPayload,
ICustomerActivatingPayload,
} from '../types/Customers.types';
import { Customer } from '@/modules/Customers/models/Customer';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ActivateCustomer {
/**
* @param {UnitOfWork} uow - Unit of work service.
* @param {EventEmitter2} eventPublisher - Event emitter service.
* @param {CustomerValidators} validators - Customer validators service.
* @param {typeof Customer} customerModel - Customer model.
*/
constructor(
private uow: UnitOfWork,
private eventPublisher: EventEmitter2,
private validators: CustomerValidators,
@Inject(Customer.name)
private customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Inactive the given contact.
* @param {number} customerId - Customer id.
* @returns {Promise<void>}
*/
public async activateCustomer(customerId: number): Promise<void> {
// Retrieves the customer or throw not found error.
const oldCustomer = await this.customerModel()
.query()
.findById(customerId)
.throwIfNotFound();
this.validators.validateNotAlreadyPublished(oldCustomer);
// Edits the given customer with associated transactions on unit-of-work environment.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onCustomerActivating` event.
await this.eventPublisher.emitAsync(events.customers.onActivating, {
trx,
oldCustomer,
} as ICustomerActivatingPayload);
// Update the given customer details.
const customer = await this.customerModel()
.query(trx)
.findById(customerId)
.updateAndFetchById(customerId, { active: true });
// Triggers `onCustomerActivated` event.
await this.eventPublisher.emitAsync(events.customers.onActivated, {
trx,
oldCustomer,
customer,
} as ICustomerActivatedPayload);
});
}
}

View File

@@ -0,0 +1,68 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { CreateEditCustomerDTO } from './CreateEditCustomerDTO.service';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Customer } from '../models/Customer';
import { events } from '@/common/events/events';
import {
ICustomerEventCreatedPayload,
ICustomerEventCreatingPayload,
} from '../types/Customers.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CreateCustomerDto } from '../dtos/CreateCustomer.dto';
@Injectable()
export class CreateCustomer {
/**
* @param {UnitOfWork} uow - Unit of work service.
* @param {EventEmitter2} eventPublisher - Event emitter service.
* @param {CreateEditCustomerDTO} customerDTO - Customer DTO.
* @param {typeof Customer} customerModel - Customer model.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
private readonly customerDTO: CreateEditCustomerDTO,
@Inject(Customer.name)
private readonly customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Creates a new customer.
* @param {ICustomerNewDTO} customerDTO
* @return {Promise<ICustomer>}
*/
public async createCustomer(
customerDTO: CreateCustomerDto,
trx?: Knex.Transaction,
): Promise<Customer> {
// Transformes the customer DTO to customer object.
const customerObj = await this.customerDTO.transformCreateDTO(customerDTO);
// Creates a new customer under unit-of-work envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onCustomerCreating` event.
await this.eventPublisher.emitAsync(events.customers.onCreating, {
customerDTO,
trx,
} as ICustomerEventCreatingPayload);
// Creates a new contact as customer.
const customer = await this.customerModel()
.query(trx)
.insertAndFetch({
...customerObj,
});
// Triggers `onCustomerCreated` event.
await this.eventPublisher.emitAsync(events.customers.onCreated, {
customer,
customerId: customer.id,
trx,
} as ICustomerEventCreatedPayload);
return customer;
}, trx);
}
}

View File

@@ -0,0 +1,72 @@
import * as moment from 'moment';
import { defaultTo, omit, isEmpty } from 'lodash';
import { Injectable } from '@nestjs/common';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { ICustomerEditDTO, ICustomerNewDTO } from '../types/Customers.types';
import { ContactService } from '@/modules/Contacts/types/Contacts.types';
@Injectable()
export class CreateEditCustomerDTO {
/**
* @param {TenancyContext} tenancyContext - Tenancy context service.
*/
constructor(private readonly tenancyContext: TenancyContext) {}
/**
* Transformes the create/edit DTO.
* @param {ICustomerNewDTO | ICustomerEditDTO} customerDTO
* @returns
*/
private transformCommonDTO = (
customerDTO: ICustomerNewDTO | ICustomerEditDTO,
) => {
return {
...omit(customerDTO, ['customerType']),
contactType: customerDTO.customerType,
};
};
/**
* Transformes the create DTO.
* @param {ICustomerNewDTO} customerDTO
* @returns {Promise<Partial<Customer>>}
*/
public transformCreateDTO = async (customerDTO: ICustomerNewDTO) => {
const commonDTO = this.transformCommonDTO(customerDTO);
// Retrieves the tenant metadata.
const tenantMeta = await this.tenancyContext.getTenant(true);
return {
...commonDTO,
currencyCode:
commonDTO.currencyCode || tenantMeta?.metadata?.baseCurrency,
active: defaultTo(customerDTO.active, true),
contactService: ContactService.Customer,
...(!isEmpty(customerDTO.openingBalanceAt)
? {
openingBalanceAt: moment(
customerDTO?.openingBalanceAt,
).toMySqlDateTime(),
}
: {}),
openingBalanceExchangeRate: defaultTo(
customerDTO.openingBalanceExchangeRate,
1,
),
};
};
/**
* Transformes the edit DTO.
* @param {ICustomerEditDTO} customerDTO
* @returns
*/
public transformEditDTO = (customerDTO: ICustomerEditDTO) => {
const commonDTO = this.transformCommonDTO(customerDTO);
return {
...commonDTO,
};
};
}

View File

@@ -0,0 +1,17 @@
import { ERRORS } from '../constants';
import { Injectable } from '@nestjs/common';
import { Customer } from '../models/Customer';
import { ServiceError } from '@/modules/Items/ServiceError';
@Injectable()
export class CustomerValidators {
/**
* Validates the given customer is not already published.
* @param {ICustomer} customer
*/
public validateNotAlreadyPublished = (customer: Customer) => {
if (customer.active) {
throw new ServiceError(ERRORS.CUSTOMER_ALREADY_ACTIVE);
}
};
}

View File

@@ -0,0 +1,62 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
ICustomerDeletingPayload,
ICustomerEventDeletedPayload,
} from '../types/Customers.types';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Customer } from '../models/Customer';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteCustomer {
/**
* @param {UnitOfWork} uow - Unit of work service.
* @param {EventEmitter2} eventPublisher - Event emitter service.
* @param {typeof Customer} contactModel - Customer model.
*/
constructor(
private uow: UnitOfWork,
private eventPublisher: EventEmitter2,
@Inject(Customer.name)
private customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Deletes the given customer from the storage.
* @param {number} customerId - Customer ID.
* @return {Promise<void>}
*/
public async deleteCustomer(customerId: number): Promise<void> {
// Retrieve the customer or throw not found service error.
const oldCustomer = await this.customerModel()
.query()
.findById(customerId)
.throwIfNotFound();
// .queryAndThrowIfHasRelations({
// type: ERRORS.CUSTOMER_HAS_TRANSACTIONS,
// });
// Triggers `onCustomerDeleting` event.
await this.eventPublisher.emitAsync(events.customers.onDeleting, {
customerId,
oldCustomer,
} as ICustomerDeletingPayload);
// Deletes the customer and associated entities under UOW transaction.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Delete the customer from the storage.
await this.customerModel().query(trx).findById(customerId).delete();
// Throws `onCustomerDeleted` event.
await this.eventPublisher.emitAsync(events.customers.onDeleted, {
customerId,
oldCustomer,
trx,
} as ICustomerEventDeletedPayload);
});
}
}

View File

@@ -0,0 +1,77 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ICustomerEditDTO,
ICustomerEventEditedPayload,
ICustomerEventEditingPayload,
} from '../types/Customers.types';
import { CreateEditCustomerDTO } from './CreateEditCustomerDTO.service';
import { Customer } from '../models/Customer';
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 {
/**
* @param {UnitOfWork} uow - Unit of work service.
* @param {EventEmitter2} eventPublisher - Event emitter service.
* @param {CreateEditCustomerDTO} customerDTO - Customer DTO.
* @param {TenantModelProxy<typeof Customer>} contactModel - Customer model.
*/
constructor(
private uow: UnitOfWork,
private eventPublisher: EventEmitter2,
private customerDTO: CreateEditCustomerDTO,
@Inject(Customer.name)
private customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Edits details of the given customer.
* @param {number} customerId
* @param {ICustomerEditDTO} customerDTO
* @return {Promise<ICustomer>}
*/
public async editCustomer(
customerId: number,
customerDTO: EditCustomerDto,
): Promise<Customer> {
// Retrieve the customer or throw not found error.
const oldCustomer = await this.customerModel()
.query()
.findById(customerId)
.throwIfNotFound();
// Transforms the given customer DTO to object.
const customerObj = this.customerDTO.transformEditDTO(customerDTO);
// Edits the given customer under unit-of-work environment.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onCustomerEditing` event.
await this.eventPublisher.emitAsync(events.customers.onEditing, {
customerDTO,
customerId,
trx,
} as ICustomerEventEditingPayload);
// Edits the customer details on the storage.
const customer = await this.customerModel()
.query()
.updateAndFetchById(customerId, {
...customerObj,
});
// Triggers `onCustomerEdited` event.
await this.eventPublisher.emitAsync(events.customers.onEdited, {
customerId,
customer,
trx,
} as ICustomerEventEditedPayload);
return customer;
});
}
}

View File

@@ -0,0 +1,74 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ICustomerOpeningBalanceEditDTO,
ICustomerOpeningBalanceEditedPayload,
ICustomerOpeningBalanceEditingPayload,
} from '../types/Customers.types';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Customer } from '../models/Customer';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class EditOpeningBalanceCustomer {
/**
* @param {EventEmitter2} eventPublisher - Event emitter service.
* @param {UnitOfWork} uow - Unit of work service.
* @param {typeof Customer} customerModel - Customer model.
*/
constructor(
private eventPublisher: EventEmitter2,
private uow: UnitOfWork,
@Inject(Customer.name)
private customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Changes the opening balance of the given customer.
* @param {number} customerId - Customer ID.
* @param {ICustomerOpeningBalanceEditDTO} openingBalanceEditDTO
*/
public async changeOpeningBalance(
customerId: number,
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO,
): Promise<Customer> {
// Retrieves the old customer or throw not found error.
const oldCustomer = await this.customerModel()
.query()
.findById(customerId)
.throwIfNotFound();
// Mutates the customer opening balance under unit-of-work.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onCustomerOpeningBalanceChanging` event.
await this.eventPublisher.emitAsync(
events.customers.onOpeningBalanceChanging,
{
oldCustomer,
openingBalanceEditDTO,
trx,
} as ICustomerOpeningBalanceEditingPayload,
);
// Mutates the customer on the storage.
const customer = await this.customerModel()
.query()
.patchAndFetchById(customerId, {
...openingBalanceEditDTO,
});
// Triggers `onCustomerOpeingBalanceChanged` event.
await this.eventPublisher.emitAsync(
events.customers.onOpeningBalanceChanged,
{
customer,
oldCustomer,
openingBalanceEditDTO,
trx,
} as ICustomerOpeningBalanceEditedPayload,
);
return customer;
});
}
}