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,117 @@
// import { Service, Inject } from 'typedi';
// import { AccountNormal, ICustomer, ILedgerEntry } from '@/interfaces';
// import Ledger from '@/services/Accounting/Ledger';
// @Service()
// export class CustomerGLEntries {
// /**
// * Retrieves the customer opening balance common entry attributes.
// * @param {ICustomer} customer
// */
// private getCustomerOpeningGLCommonEntry = (customer: ICustomer) => {
// return {
// exchangeRate: customer.openingBalanceExchangeRate,
// currencyCode: customer.currencyCode,
// transactionType: 'CustomerOpeningBalance',
// transactionId: customer.id,
// date: customer.openingBalanceAt,
// userId: customer.userId,
// contactId: customer.id,
// credit: 0,
// debit: 0,
// branchId: customer.openingBalanceBranchId,
// };
// };
// /**
// * Retrieves the customer opening GL credit entry.
// * @param {number} ARAccountId
// * @param {ICustomer} customer
// * @returns {ILedgerEntry}
// */
// private getCustomerOpeningGLCreditEntry = (
// ARAccountId: number,
// customer: ICustomer
// ): ILedgerEntry => {
// const commonEntry = this.getCustomerOpeningGLCommonEntry(customer);
// return {
// ...commonEntry,
// credit: 0,
// debit: customer.localOpeningBalance,
// accountId: ARAccountId,
// accountNormal: AccountNormal.DEBIT,
// index: 1,
// };
// };
// /**
// * Retrieves the customer opening GL debit entry.
// * @param {number} incomeAccountId
// * @param {ICustomer} customer
// * @returns {ILedgerEntry}
// */
// private getCustomerOpeningGLDebitEntry = (
// incomeAccountId: number,
// customer: ICustomer
// ): ILedgerEntry => {
// const commonEntry = this.getCustomerOpeningGLCommonEntry(customer);
// return {
// ...commonEntry,
// credit: customer.localOpeningBalance,
// debit: 0,
// accountId: incomeAccountId,
// accountNormal: AccountNormal.CREDIT,
// index: 2,
// };
// };
// /**
// * Retrieves the customer opening GL entries.
// * @param {number} ARAccountId
// * @param {number} incomeAccountId
// * @param {ICustomer} customer
// * @returns {ILedgerEntry[]}
// */
// public getCustomerOpeningGLEntries = (
// ARAccountId: number,
// incomeAccountId: number,
// customer: ICustomer
// ) => {
// const debitEntry = this.getCustomerOpeningGLDebitEntry(
// incomeAccountId,
// customer
// );
// const creditEntry = this.getCustomerOpeningGLCreditEntry(
// ARAccountId,
// customer
// );
// return [debitEntry, creditEntry];
// };
// /**
// * Retrieves the customer opening balance ledger.
// * @param {number} ARAccountId
// * @param {number} incomeAccountId
// * @param {ICustomer} customer
// * @returns {ILedger}
// */
// public getCustomerOpeningLedger = (
// ARAccountId: number,
// incomeAccountId: number,
// customer: ICustomer
// ) => {
// const entries = this.getCustomerOpeningGLEntries(
// ARAccountId,
// incomeAccountId,
// customer
// );
// return new Ledger(entries);
// };
// }

View File

@@ -0,0 +1,90 @@
// import { Knex } from 'knex';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import { Service, Inject } from 'typedi';
// import { CustomerGLEntries } from './CustomerGLEntries';
// @Service()
// export class CustomerGLEntriesStorage {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private ledegrRepository: LedgerStorageService;
// @Inject()
// private customerGLEntries: CustomerGLEntries;
// /**
// * Customer opening balance journals.
// * @param {number} tenantId
// * @param {number} customerId
// * @param {Knex.Transaction} trx
// */
// public writeCustomerOpeningBalance = async (
// tenantId: number,
// customerId: number,
// trx?: Knex.Transaction
// ) => {
// const { Customer } = this.tenancy.models(tenantId);
// const { accountRepository } = this.tenancy.repositories(tenantId);
// const customer = await Customer.query(trx).findById(customerId);
// // Finds the income account.
// const incomeAccount = await accountRepository.findOne({
// slug: 'other-income',
// });
// // Find or create the A/R account.
// const ARAccount = await accountRepository.findOrCreateAccountReceivable(
// customer.currencyCode,
// {},
// trx
// );
// // Retrieves the customer opening balance ledger.
// const ledger = this.customerGLEntries.getCustomerOpeningLedger(
// ARAccount.id,
// incomeAccount.id,
// customer
// );
// // Commits the ledger entries to the storage.
// await this.ledegrRepository.commit(tenantId, ledger, trx);
// };
// /**
// * Reverts the customer opening balance GL entries.
// * @param {number} tenantId
// * @param {number} customerId
// * @param {Knex.Transaction} trx
// */
// public revertCustomerOpeningBalance = async (
// tenantId: number,
// customerId: number,
// trx?: Knex.Transaction
// ) => {
// await this.ledegrRepository.deleteByReference(
// tenantId,
// customerId,
// 'CustomerOpeningBalance',
// trx
// );
// };
// /**
// * Writes the customer opening balance GL entries.
// * @param {number} tenantId
// * @param {number} customerId
// * @param {Knex.Transaction} trx
// */
// public rewriteCustomerOpeningBalance = async (
// tenantId: number,
// customerId: number,
// trx?: Knex.Transaction
// ) => {
// // Reverts the customer opening balance entries.
// await this.revertCustomerOpeningBalance(tenantId, customerId, trx);
// // Write the customer opening balance entries.
// await this.writeCustomerOpeningBalance(tenantId, customerId, trx);
// };
// }

View File

@@ -0,0 +1,59 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common';
import { CustomersApplication } from './CustomersApplication.service';
import { ICustomerOpeningBalanceEditDTO } from './types/Customers.types';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
import { EditCustomerDto } from './dtos/EditCustomer.dto';
@Controller('customers')
@ApiTags('customers')
export class CustomersController {
constructor(private customersApplication: CustomersApplication) {}
@Get(':id')
@ApiOperation({ summary: 'Retrieves the customer details.' })
getCustomer(@Param('id') customerId: number) {
return this.customersApplication.getCustomer(customerId);
}
@Post()
@ApiOperation({ summary: 'Create a new customer.' })
createCustomer(@Body() customerDTO: CreateCustomerDto) {
return this.customersApplication.createCustomer(customerDTO);
}
@Put(':id')
@ApiOperation({ summary: 'Edit the given customer.' })
editCustomer(
@Param('id') customerId: number,
@Body() customerDTO: EditCustomerDto,
) {
return this.customersApplication.editCustomer(customerId, customerDTO);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete the given customer.' })
deleteCustomer(@Param('id') customerId: number) {
return this.customersApplication.deleteCustomer(customerId);
}
@Put(':id/opening-balance')
@ApiOperation({ summary: 'Edit the opening balance of the given customer.' })
editOpeningBalance(
@Param('id') customerId: number,
@Body() openingBalanceDTO: ICustomerOpeningBalanceEditDTO,
) {
return this.customersApplication.editOpeningBalance(
customerId,
openingBalanceDTO,
);
}
}

View File

@@ -0,0 +1,35 @@
import { Module } from '@nestjs/common';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { ActivateCustomer } from './commands/ActivateCustomer.service';
import { CreateCustomer } from './commands/CreateCustomer.service';
import { CustomerValidators } from './commands/CustomerValidators.service';
import { EditCustomer } from './commands/EditCustomer.service';
import { EditOpeningBalanceCustomer } from './commands/EditOpeningBalanceCustomer.service';
import { GetCustomerService } from './queries/GetCustomer.service';
import { CreateEditCustomerDTO } from './commands/CreateEditCustomerDTO.service';
import { CustomersController } from './Customers.controller';
import { CustomersApplication } from './CustomersApplication.service';
import { DeleteCustomer } from './commands/DeleteCustomer.service';
@Module({
imports: [TenancyDatabaseModule],
controllers: [CustomersController],
providers: [
ActivateCustomer,
CreateCustomer,
CustomerValidators,
EditCustomer,
EditOpeningBalanceCustomer,
CustomerValidators,
CreateEditCustomerDTO,
GetCustomerService,
CustomersApplication,
DeleteCustomer,
TenancyContext,
TransformerInjectable,
GetCustomerService
],
})
export class CustomersModule {}

View File

@@ -0,0 +1,86 @@
import { Injectable } from '@nestjs/common';
import { GetCustomerService } from './queries/GetCustomer.service';
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 } from './types/Customers.types';
import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
import { EditCustomerDto } from './dtos/EditCustomer.dto';
@Injectable()
export class CustomersApplication {
constructor(
private getCustomerService: GetCustomerService,
private createCustomerService: CreateCustomer,
private editCustomerService: EditCustomer,
private deleteCustomerService: DeleteCustomer,
private editOpeningBalanceService: EditOpeningBalanceCustomer,
// private getCustomersService: GetCustomers,
) {}
/**
* Retrieves the given customer details.
* @param {number} tenantId
* @param {number} customerId
*/
public getCustomer = (customerId: number) => {
return this.getCustomerService.getCustomer(customerId);
};
/**
* Creates a new customer.
* @param {ICustomerNewDTO} customerDTO
* @returns {Promise<ICustomer>}
*/
public createCustomer = (customerDTO: CreateCustomerDto) => {
return this.createCustomerService.createCustomer(customerDTO);
};
/**
* Edits details of the given customer.
* @param {number} customerId - Customer id.
* @param {ICustomerEditDTO} customerDTO - Customer edit DTO.
* @return {Promise<ICustomer>}
*/
public editCustomer = (customerId: number, customerDTO: EditCustomerDto) => {
return this.editCustomerService.editCustomer(customerId, customerDTO);
};
/**
* Deletes the given customer and associated transactions.
* @param {number} tenantId
* @param {number} customerId
* @param {ISystemUser} authorizedUser
* @returns {Promise<void>}
*/
public deleteCustomer = (customerId: number) => {
return this.deleteCustomerService.deleteCustomer(customerId);
};
/**
* Changes the opening balance of the given customer.
* @param {number} tenantId
* @param {number} customerId
* @param {Date|string} openingBalanceEditDTO
* @returns {Promise<ICustomer>}
*/
public editOpeningBalance = (
customerId: number,
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO,
) => {
return this.editOpeningBalanceService.changeOpeningBalance(
customerId,
openingBalanceEditDTO,
);
};
/**
* Retrieve customers paginated list.
* @param {number} tenantId - Tenant id.
* @param {ICustomersFilter} filter - Cusotmers filter.
*/
// public getCustomers = (filterDTO: ICustomersFilter) => {
// return this.getCustomersService.getCustomersList(filterDTO);
// };
}

View File

@@ -0,0 +1,30 @@
// import { Inject, Service } from 'typedi';
// import { IItemsFilter } from '@/interfaces';
// import { CustomersApplication } from './CustomersApplication';
// import { Exportable } from '@/services/Export/Exportable';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
// @Service()
// export class CustomersExportable extends Exportable {
// @Inject()
// private customersApplication: CustomersApplication;
// /**
// * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId
// * @returns
// */
// public exportable(tenantId: number, query: IItemsFilter) {
// const parsedQuery = {
// sortOrder: 'DESC',
// columnSortBy: 'created_at',
// ...query,
// page: 1,
// pageSize: EXPORT_SIZE_LIMIT,
// } as IItemsFilter;
// return this.customersApplication
// .getCustomers(tenantId, parsedQuery)
// .then((output) => output.customers);
// }
// }

View File

@@ -0,0 +1,34 @@
// import { Inject, Service } from 'typedi';
// import { Importable } from '@/services/Import/Importable';
// import { CreateCustomer } from './CRUD/CreateCustomer';
// import { Knex } from 'knex';
// import { ICustomer, ICustomerNewDTO } from '@/interfaces';
// import { CustomersSampleData } from './_SampleData';
// @Service()
// export class CustomersImportable extends Importable {
// @Inject()
// private createCustomerService: CreateCustomer;
// /**
// * Mapps the imported data to create a new customer service.
// * @param {number} tenantId
// * @param {ICustomerNewDTO} createDTO
// * @param {Knex.Transaction} trx
// * @returns {Promise<void>}
// */
// public async importable(
// tenantId: number,
// createDTO: ICustomerNewDTO,
// trx?: Knex.Transaction<any, any[]>
// ): Promise<void> {
// await this.createCustomerService.createCustomer(tenantId, createDTO, trx);
// }
// /**
// * Retrieves the sample data of customers used to download sample sheet.
// */
// public sampleData(): any[] {
// return CustomersSampleData;
// }
// }

View File

@@ -0,0 +1,158 @@
export const CustomersSampleData = [
{
"Customer Type": "Business",
"First Name": "Nicolette",
"Last Name": "Schamberger",
"Company Name": "Homenick - Hane",
"Display Name": "Rowland Rowe",
"Email": "cicero86@yahoo.com",
"Personal Phone Number": "811-603-2235",
"Work Phone Number": "906-993-5190",
"Website": "http://google.com",
"Opening Balance": 54302.23,
"Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2,
"Currency": "LYD",
"Active": "F",
"Note": "Doloribus autem optio temporibus dolores mollitia sit.",
"Billing Address 1": "862 Jessika Well",
"Billing Address 2": "1091 Dorthy Mount",
"Billing Address City": "Deckowfort",
"Billing Address Country": "Ghana",
"Billing Address Phone": "825-011-5207",
"Billing Address Postcode": "38228",
"Billing Address State": "Oregon",
"Shipping Address 1": "37626 Thiel Villages",
"Shipping Address 2": "132 Batz Avenue",
"Shipping Address City": "Pagacburgh",
"Shipping Address Country": "Albania",
"Shipping Address Phone": "171-546-3701",
"Shipping Address Postcode": "13709",
"Shipping Address State": "Georgia"
},
{
"Customer Type": "Business",
"First Name": "Hermann",
"Last Name": "Crooks",
"Company Name": "Veum - Schaefer",
"Display Name": "Harley Veum",
"Email": "immanuel56@hotmail.com",
"Personal Phone Number": "449-780-9999",
"Work Phone Number": "970-473-5785",
"Website": "http://google.com",
"Opening Balance": 54302.23,
"Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2,
"Currency": "LYD",
"Active": "T",
"Note": "Doloribus dolore dolor dicta vitae in fugit nisi quibusdam.",
"Billing Address 1": "532 Simonis Spring",
"Billing Address 2": "3122 Nicolas Inlet",
"Billing Address City": "East Matteofort",
"Billing Address Country": "Holy See (Vatican City State)",
"Billing Address Phone": "366-084-8629",
"Billing Address Postcode": "41607",
"Billing Address State": "Montana",
"Shipping Address 1": "2889 Tremblay Plaza",
"Shipping Address 2": "71355 Kutch Isle",
"Shipping Address City": "D'Amorehaven",
"Shipping Address Country": "Monaco",
"Shipping Address Phone": "614-189-3328",
"Shipping Address Postcode": "09634-0435",
"Shipping Address State": "Nevada"
},
{
"Customer Type": "Business",
"First Name": "Nellie",
"Last Name": "Gulgowski",
"Company Name": "Boyle, Heller and Jones",
"Display Name": "Randall Kohler",
"Email": "anibal_frami@yahoo.com",
"Personal Phone Number": "498-578-0740",
"Work Phone Number": "394-550-6827",
"Website": "http://google.com",
"Opening Balance": 54302.23,
"Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2,
"Currency": "LYD",
"Active": "T",
"Note": "Vero quibusdam rem fugit aperiam est modi.",
"Billing Address 1": "214 Sauer Villages",
"Billing Address 2": "30687 Kacey Square",
"Billing Address City": "Jayceborough",
"Billing Address Country": "Benin",
"Billing Address Phone": "332-820-1127",
"Billing Address Postcode": "16425-3887",
"Billing Address State": "Mississippi",
"Shipping Address 1": "562 Diamond Loaf",
"Shipping Address 2": "9595 Satterfield Trafficway",
"Shipping Address City": "Alexandrinefort",
"Shipping Address Country": "Puerto Rico",
"Shipping Address Phone": "776-500-8456",
"Shipping Address Postcode": "30258",
"Shipping Address State": "South Dakota"
},
{
"Customer Type": "Business",
"First Name": "Stone",
"Last Name": "Jerde",
"Company Name": "Cassin, Casper and Maggio",
"Display Name": "Clint McLaughlin",
"Email": "nathanael22@yahoo.com",
"Personal Phone Number": "562-790-6059",
"Work Phone Number": "686-838-0027",
"Website": "http://google.com",
"Opening Balance": 54302.23,
"Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2,
"Currency": "LYD",
"Active": "F",
"Note": "Quis cumque molestias rerum.",
"Billing Address 1": "22590 Cathy Harbor",
"Billing Address 2": "24493 Brycen Brooks",
"Billing Address City": "Elnorashire",
"Billing Address Country": "Andorra",
"Billing Address Phone": "701-852-8005",
"Billing Address Postcode": "5680",
"Billing Address State": "Nevada",
"Shipping Address 1": "5355 Erdman Bridge",
"Shipping Address 2": "421 Jeanette Camp",
"Shipping Address City": "East Philip",
"Shipping Address Country": "Venezuela",
"Shipping Address Phone": "426-119-0858",
"Shipping Address Postcode": "34929-0501",
"Shipping Address State": "Tennessee"
},
{
"Customer Type": "Individual",
"First Name": "Lempi",
"Last Name": "Kling",
"Company Name": "Schamberger, O'Connell and Bechtelar",
"Display Name": "Alexie Barton",
"Email": "eulah.kreiger@hotmail.com",
"Personal Phone Number": "745-756-1063",
"Work Phone Number": "965-150-1945",
"Website": "http://google.com",
"Opening Balance": 54302.23,
"Opening Balance At": "2022-02-02",
"Opening Balance Ex. Rate": 2,
"Currency": "LYD",
"Active": "F",
"Note": "Maxime laboriosam hic voluptate maiores est officia.",
"Billing Address 1": "0851 Jones Flat",
"Billing Address 2": "845 Bailee Drives",
"Billing Address City": "Kamrenport",
"Billing Address Country": "Niger",
"Billing Address Phone": "220-125-0608",
"Billing Address Postcode": "30311",
"Billing Address State": "Delaware",
"Shipping Address 1": "929 Ferry Row",
"Shipping Address 2": "020 Adam Plaza",
"Shipping Address City": "West Carmellaside",
"Shipping Address Country": "Ghana",
"Shipping Address Phone": "053-333-6679",
"Shipping Address Postcode": "79221-4681",
"Shipping Address State": "Illinois"
}
]

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;
});
}
}

View File

@@ -0,0 +1,27 @@
export const DEFAULT_VIEW_COLUMNS = [];
export const DEFAULT_VIEWS = [
{
name: 'Overdue',
slug: 'overdue',
rolesLogicExpression: '1',
roles: [
{ index: 1, fieldKey: 'status', comparator: 'equals', value: 'overdue' },
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'Unpaid',
slug: 'unpaid',
rolesLogicExpression: '1',
roles: [
{ index: 1, fieldKey: 'status', comparator: 'equals', value: 'unpaid' },
],
columns: DEFAULT_VIEW_COLUMNS,
},
];
export const ERRORS = {
CUSTOMER_HAS_TRANSACTIONS: 'CUSTOMER_HAS_TRANSACTIONS',
CUSTOMER_ALREADY_ACTIVE: 'CUSTOMER_ALREADY_ACTIVE',
};

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

@@ -0,0 +1,216 @@
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class Customer extends TenantBaseModel{
contactService: string;
contactType: string;
balance: number;
currencyCode: string;
openingBalance: number;
openingBalanceAt: Date | string;
openingBalanceExchangeRate: number;
openingBalanceBranchId?: number;
salutation?: string;
firstName?: string;
lastName?: string;
companyName?: string;
displayName: string;
email?: string;
workPhone?: string;
personalPhone?: string;
website?: string;
billingAddress1?: string;
billingAddress2?: string;
billingAddressCity?: string;
billingAddressCountry?: string;
billingAddressEmail?: string;
billingAddressPostcode?: string;
billingAddressPhone?: string;
billingAddressState?: string;
shippingAddress1?: string;
shippingAddress2?: string;
shippingAddressCity?: string;
shippingAddressCountry?: string;
shippingAddressEmail?: string;
shippingAddressPostcode?: string;
shippingAddressPhone?: string;
shippingAddressState?: string;
note: string;
active: boolean;
/**
* Query builder.
*/
// static get QueryBuilder() {
// return CustomerQueryBuilder;
// }
/**
* Table name
*/
static get tableName() {
return 'contacts';
}
/**
* Model timestamps.
*/
get timestamps() {
return ['createdAt', 'updatedAt'];
}
/**
* Defined virtual attributes.
*/
static get virtualAttributes() {
return ['localOpeningBalance', 'closingBalance', 'contactNormal'];
}
/**
* Closing balance attribute.
*/
get closingBalance() {
return this.balance;
}
/**
* Retrieves the local opening balance.
* @returns {number}
*/
get localOpeningBalance() {
return this.openingBalance
? this.openingBalance * this.openingBalanceExchangeRate
: 0;
}
/**
* Retrieve the contact noraml;
*/
get contactNormal() {
return 'debit';
}
/**
*
*/
get contactAddresses() {
return [
{
mail: this.email,
label: this.displayName,
primary: true
},
].filter((c) => c.mail);
}
/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Inactive/Active mode.
*/
inactiveMode(query, active = false) {
query.where('active', !active);
},
/**
* Filters the active customers.
*/
active(query) {
query.where('active', 1);
},
/**
* Filters the inactive customers.
*/
inactive(query) {
query.where('active', 0);
},
/**
* Filters the customers that have overdue invoices.
*/
overdue(query) {
query.select(
'*',
Customer.relatedQuery('overDueInvoices', query.knex())
.count()
.as('countOverdue')
);
query.having('countOverdue', '>', 0);
},
/**
* Filters the unpaid customers.
*/
unpaid(query) {
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
},
};
}
/**
* Relationship mapping.
*/
// static get relationMappings() {
// const SaleInvoice = require('models/SaleInvoice');
// return {
// salesInvoices: {
// relation: Model.HasManyRelation,
// modelClass: SaleInvoice.default,
// join: {
// from: 'contacts.id',
// to: 'sales_invoices.customerId',
// },
// },
// overDueInvoices: {
// relation: Model.HasManyRelation,
// modelClass: SaleInvoice.default,
// join: {
// from: 'contacts.id',
// to: 'sales_invoices.customerId',
// },
// filter: (query) => {
// query.modify('overdue');
// },
// },
// };
// }
// static get meta() {
// return CustomerSettings;
// }
// /**
// * Retrieve the default custom views, roles and columns.
// */
// static get defaultViews() {
// return DEFAULT_VIEWS;
// }
/**
* Model search attributes.
*/
static get searchRoles() {
return [
{ fieldKey: 'display_name', comparator: 'contains' },
{ condition: 'or', fieldKey: 'first_name', comparator: 'contains' },
{ condition: 'or', fieldKey: 'last_name', comparator: 'equals' },
{ condition: 'or', fieldKey: 'company_name', comparator: 'equals' },
{ condition: 'or', fieldKey: 'email', comparator: 'equals' },
{ condition: 'or', fieldKey: 'work_phone', comparator: 'equals' },
{ condition: 'or', fieldKey: 'personal_phone', comparator: 'equals' },
{ condition: 'or', fieldKey: 'website', comparator: 'equals' },
];
}
}

View File

@@ -0,0 +1,38 @@
import { ContactTransfromer } from "../../Contacts/Contact.transformer";
export class CustomerTransfromer extends ContactTransfromer {
/**
* Include these attributes to expense object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'formattedBalance',
'formattedOpeningBalance',
'formattedOpeningBalanceAt',
'customerType',
'formattedCustomerType',
];
};
/**
* Retrieve customer type.
* @returns {string}
*/
protected customerType = (customer): string => {
return customer.contactType;
};
/**
* Retrieve the formatted customer type.
* @param customer
* @returns {string}
*/
protected formattedCustomerType = (customer): string => {
const keywords = {
individual: 'customer.type.individual',
business: 'customer.type.business',
};
return this.context.i18n.t(keywords[customer.contactType] || '');
};
}

View File

@@ -0,0 +1,30 @@
import { Inject, Injectable } from '@nestjs/common';
import { CustomerTransfromer } from './CustomerTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { Customer } from '../models/Customer';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetCustomerService {
constructor(
private transformer: TransformerInjectable,
@Inject(Customer.name)
private customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Retrieve the given customer details.
* @param {number} customerId
*/
public async getCustomer(customerId: number) {
// Retrieve the customer model or throw not found error.
const customer = await this.customerModel()
.query()
.findById(customerId)
.throwIfNotFound();
// Retrieves the transformered customers.
return this.transformer.transform(customer, new CustomerTransfromer());
}
}

View File

@@ -0,0 +1,65 @@
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { Inject, Injectable } from '@nestjs/common';
import * as R from 'ramda';
import { Customer } from '../models/Customer';
import { CustomerTransfromer } from './CustomerTransformer';
import {
GetCustomersResponse,
ICustomersFilter,
} from '../types/Customers.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetCustomers {
constructor(
private dynamicListService: DynamicListService,
private transformer: TransformerInjectable,
@Inject(Customer.name)
private customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Parses customers list filter DTO.
* @param filterDTO -
*/
private parseCustomersListFilterDTO(filterDTO) {
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
}
/**
* Retrieve customers paginated list.
* @param {ICustomersFilter} filter - Cusotmers filter.
* @returns {Promise<GetCustomersResponse>}
*/
public async getCustomersList(
filterDTO: ICustomersFilter,
): Promise<GetCustomersResponse> {
// Parses customers list filter DTO.
const filter = this.parseCustomersListFilterDTO(filterDTO);
const dynamicList = await this.dynamicListService.dynamicList(
this.customerModel(),
filter,
);
const { results, pagination } = await this.customerModel()
.query()
.onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.modify('inactiveMode', filter.inactiveMode);
})
.pagination(filter.page - 1, filter.pageSize);
// Retrieves the transformed customers.
const customers = await this.transformer.transform(
results,
new CustomerTransfromer(),
);
return {
customers,
pagination,
filterMeta: dynamicList.getResponseMeta(),
};
}
}

View File

@@ -0,0 +1,91 @@
// import { Service, Inject } from 'typedi';
// import {
// ICustomerEventCreatedPayload,
// ICustomerEventDeletedPayload,
// ICustomerOpeningBalanceEditedPayload,
// } from '@/interfaces';
// import events from '@/subscribers/events';
// import { CustomerGLEntriesStorage } from '../CustomerGLEntriesStorage';
// @Service()
// export class CustomerWriteGLOpeningBalanceSubscriber {
// @Inject()
// private customerGLEntries: CustomerGLEntriesStorage;
// /**
// * Attaches events with handlers.
// */
// public attach(bus) {
// bus.subscribe(
// events.customers.onCreated,
// this.handleWriteOpenBalanceEntries
// );
// bus.subscribe(
// events.customers.onDeleted,
// this.handleRevertOpeningBalanceEntries
// );
// bus.subscribe(
// events.customers.onOpeningBalanceChanged,
// this.handleRewriteOpeningEntriesOnChanged
// );
// }
// /**
// * Handles the writing opening balance journal entries once the customer created.
// * @param {ICustomerEventCreatedPayload} payload -
// */
// private handleWriteOpenBalanceEntries = async ({
// tenantId,
// customer,
// trx,
// }: ICustomerEventCreatedPayload) => {
// // Writes the customer opening balance journal entries.
// if (customer.openingBalance) {
// await this.customerGLEntries.writeCustomerOpeningBalance(
// tenantId,
// customer.id,
// trx
// );
// }
// };
// /**
// * Handles the deleting opeing balance journal entrise once the customer deleted.
// * @param {ICustomerEventDeletedPayload} payload -
// */
// private handleRevertOpeningBalanceEntries = async ({
// tenantId,
// customerId,
// trx,
// }: ICustomerEventDeletedPayload) => {
// await this.customerGLEntries.revertCustomerOpeningBalance(
// tenantId,
// customerId,
// trx
// );
// };
// /**
// * Handles the rewrite opening balance entries once opening balnace changed.
// * @param {ICustomerOpeningBalanceEditedPayload} payload -
// */
// private handleRewriteOpeningEntriesOnChanged = async ({
// tenantId,
// customer,
// trx,
// }: ICustomerOpeningBalanceEditedPayload) => {
// if (customer.openingBalance) {
// await this.customerGLEntries.rewriteCustomerOpeningBalance(
// tenantId,
// customer.id,
// trx
// );
// } else {
// await this.customerGLEntries.revertCustomerOpeningBalance(
// tenantId,
// customer.id,
// trx
// );
// }
// };
// }

View File

@@ -0,0 +1,146 @@
import { Knex } from 'knex';
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.
// ----------------------------------
export interface ICustomerNewDTO extends IContactAddressDTO {
customerType: string;
currencyCode: string;
openingBalance?: number;
openingBalanceAt?: string;
openingBalanceExchangeRate?: number;
openingBalanceBranchId?: number;
salutation?: string;
firstName?: string;
lastName?: string;
companyName?: string;
displayName: string;
website?: string;
email?: string;
workPhone?: string;
personalPhone?: string;
note?: string;
active?: boolean;
}
export interface ICustomerEditDTO extends IContactAddressDTO {
customerType: string;
salutation?: string;
firstName?: string;
lastName?: string;
companyName?: string;
displayName: string;
website?: string;
email?: string;
workPhone?: string;
personalPhone?: string;
note?: string;
active?: boolean;
}
export interface ICustomersFilter extends IDynamicListFilter {
stringifiedFilterRoles?: string;
page?: number;
pageSize?: number;
}
export interface GetCustomersResponse {
customers: Customer[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}
// Customer Events.
// ----------------------------------
export interface ICustomerEventCreatedPayload {
customerId: number;
customer: Customer;
trx: Knex.Transaction;
}
export interface ICustomerEventCreatingPayload {
customerDTO: CreateCustomerDto;
trx: Knex.Transaction;
}
export interface ICustomerEventEditedPayload {
customerId: number;
customer: Customer;
trx: Knex.Transaction;
}
export interface ICustomerEventEditingPayload {
customerDTO: EditCustomerDto;
customerId: number;
trx: Knex.Transaction;
}
export interface ICustomerDeletingPayload {
customerId: number;
oldCustomer: Customer;
}
export interface ICustomerEventDeletedPayload {
customerId: number;
oldCustomer: Customer;
trx: Knex.Transaction;
}
export interface ICustomerEventCreatingPayload {
customerDTO: CreateCustomerDto;
trx: Knex.Transaction;
}
export enum CustomerAction {
Create = 'Create',
Edit = 'Edit',
Delete = 'Delete',
View = 'View',
}
export enum VendorAction {
Create = 'Create',
Edit = 'Edit',
Delete = 'Delete',
View = 'View',
}
export interface ICustomerOpeningBalanceEditDTO {
openingBalance: number;
openingBalanceAt: Date | string;
openingBalanceExchangeRate: number;
openingBalanceBranchId?: number;
}
export interface ICustomerOpeningBalanceEditingPayload {
oldCustomer: Customer;
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO;
trx?: Knex.Transaction;
}
export interface ICustomerOpeningBalanceEditedPayload {
customer: Customer;
oldCustomer: Customer;
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO;
trx: Knex.Transaction;
}
export interface ICustomerActivatingPayload {
trx: Knex.Transaction,
oldCustomer: Customer;
}
export interface ICustomerActivatedPayload {
trx?: Knex.Transaction;
oldCustomer: Customer;
customer: Customer;
}