add server to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 11:57:50 +02:00
parent 28e309981b
commit 80b97b5fdc
1303 changed files with 137049 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
import { Knex } from 'knex';
import { Service, Inject } from 'typedi';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import { CustomerValidators } from './CustomerValidators';
import {
ICustomerActivatingPayload,
ICustomerActivatedPayload,
} from '@/interfaces';
@Service()
export class ActivateCustomer {
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private tenancy: HasTenancyService;
@Inject()
private validators: CustomerValidators;
/**
* Inactive the given contact.
* @param {number} tenantId - Tenant id.
* @param {number} contactId - Contact id.
* @returns {Promise<void>}
*/
public async activateCustomer(
tenantId: number,
customerId: number
): Promise<void> {
const { Contact } = this.tenancy.models(tenantId);
// Retrieves the customer or throw not found error.
const oldCustomer = await Contact.query()
.findById(customerId)
.modify('customer')
.throwIfNotFound();
this.validators.validateNotAlreadyPublished(oldCustomer);
// Edits the given customer with associated transactions on unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onCustomerActivating` event.
await this.eventPublisher.emitAsync(events.customers.onActivating, {
tenantId,
trx,
oldCustomer,
} as ICustomerActivatingPayload);
// Update the given customer details.
const customer = await Contact.query(trx)
.findById(customerId)
.update({ active: true });
// Triggers `onCustomerActivated` event.
await this.eventPublisher.emitAsync(events.customers.onActivated, {
tenantId,
trx,
oldCustomer,
customer,
} as ICustomerActivatedPayload);
});
}
}

View File

@@ -0,0 +1,73 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import {
ICustomer,
ICustomerEventCreatedPayload,
ICustomerEventCreatingPayload,
ICustomerNewDTO,
ISystemUser,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import { CreateEditCustomerDTO } from './CreateEditCustomerDTO';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class CreateCustomer {
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private customerDTO: CreateEditCustomerDTO;
@Inject()
private tenancy: HasTenancyService;
/**
* Creates a new customer.
* @param {number} tenantId
* @param {ICustomerNewDTO} customerDTO
* @return {Promise<ICustomer>}
*/
public async createCustomer(
tenantId: number,
customerDTO: ICustomerNewDTO,
authorizedUser: ISystemUser
): Promise<ICustomer> {
const { Contact } = this.tenancy.models(tenantId);
// Transformes the customer DTO to customer object.
const customerObj = await this.customerDTO.transformCreateDTO(
tenantId,
customerDTO
);
// Creates a new customer under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onCustomerCreating` event.
await this.eventPublisher.emitAsync(events.customers.onCreating, {
tenantId,
customerDTO,
trx,
} as ICustomerEventCreatingPayload);
// Creates a new contact as customer.
const customer = await Contact.query().insertAndFetch({
...customerObj,
});
// Triggers `onCustomerCreated` event.
await this.eventPublisher.emitAsync(events.customers.onCreated, {
customer,
tenantId,
customerId: customer.id,
authorizedUser,
trx,
} as ICustomerEventCreatedPayload);
return customer;
});
}
}

View File

@@ -0,0 +1,69 @@
import moment from 'moment';
import { defaultTo, omit, isEmpty } from 'lodash';
import { Service, Inject } from 'typedi';
import {
ContactService,
ICustomer,
ICustomerEditDTO,
ICustomerNewDTO,
} from '@/interfaces';
import { TenantMetadata } from '@/system/models';
@Service()
export class CreateEditCustomerDTO {
/**
* Transformes the create/edit DTO.
* @param {ICustomerNewDTO | ICustomerEditDTO} customerDTO
* @returns
*/
private transformCommonDTO = (
customerDTO: ICustomerNewDTO | ICustomerEditDTO
): Partial<ICustomer> => {
return {
...omit(customerDTO, ['customerType']),
contactType: customerDTO.customerType,
};
};
/**
* Transformes the create DTO.
* @param {ICustomerNewDTO} customerDTO
* @returns {}
*/
public transformCreateDTO = async (
tenantId: number,
customerDTO: ICustomerNewDTO
) => {
const commonDTO = this.transformCommonDTO(customerDTO);
// Retrieves the tenant metadata.
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
return {
...commonDTO,
currencyCode: commonDTO.currencyCode || tenantMeta?.baseCurrency,
active: defaultTo(customerDTO.active, true),
contactService: ContactService.Customer,
...(!isEmpty(customerDTO.openingBalanceAt)
? {
openingBalanceAt: moment(
customerDTO?.openingBalanceAt
).toMySqlDateTime(),
}
: {}),
};
};
/**
* Transformes the edit DTO.
* @param {ICustomerEditDTO} customerDTO
* @returns
*/
public transformEditDTO = (customerDTO: ICustomerEditDTO) => {
const commonDTO = this.transformCommonDTO(customerDTO);
return {
...commonDTO,
};
};
}

View File

@@ -0,0 +1,16 @@
import { ServiceError } from '@/exceptions';
import { Service, Inject } from 'typedi';
import { ERRORS } from '../constants';
@Service()
export class CustomerValidators {
/**
* Validates the given customer is not already published.
* @param {ICustomer} customer
*/
public validateNotAlreadyPublished = (customer) => {
if (customer.active) {
throw new ServiceError(ERRORS.CUSTOMER_ALREADY_ACTIVE);
}
};
}

View File

@@ -0,0 +1,69 @@
import { Knex } from 'knex';
import { Service, Inject } from 'typedi';
import {
ICustomerDeletingPayload,
ICustomerEventDeletedPayload,
ISystemUser,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ERRORS } from '../constants';
@Service()
export class DeleteCustomer {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
/**
* Deletes the given customer from the storage.
* @param {number} tenantId
* @param {number} customerId
* @return {Promise<void>}
*/
public async deleteCustomer(
tenantId: number,
customerId: number,
authorizedUser: ISystemUser
): Promise<void> {
const { Contact } = this.tenancy.models(tenantId);
// Retrieve the customer of throw not found service error.
const oldCustomer = await Contact.query()
.findById(customerId)
.modify('customer')
.throwIfNotFound()
.queryAndThrowIfHasRelations({
type: ERRORS.CUSTOMER_HAS_TRANSACTIONS,
});
// Triggers `onCustomerDeleting` event.
await this.eventPublisher.emitAsync(events.customers.onDeleting, {
tenantId,
customerId,
oldCustomer,
} as ICustomerDeletingPayload);
// Deletes the customer and associated entities under UOW transaction.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Delete the customer from the storage.
await Contact.query(trx).findById(customerId).delete();
// Throws `onCustomerDeleted` event.
await this.eventPublisher.emitAsync(events.customers.onDeleted, {
tenantId,
customerId,
oldCustomer,
authorizedUser,
trx,
} as ICustomerEventDeletedPayload);
});
}
}

View File

@@ -0,0 +1,77 @@
import { Knex } from 'knex';
import {
ICustomer,
ICustomerEditDTO,
ICustomerEventEditedPayload,
ICustomerEventEditingPayload,
ISystemUser,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { CreateEditCustomerDTO } from './CreateEditCustomerDTO';
@Service()
export class EditCustomer {
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private tenancy: HasTenancyService;
@Inject()
private customerDTO: CreateEditCustomerDTO;
/**
* Edits details of the given customer.
* @param {number} tenantId
* @param {number} customerId
* @param {ICustomerEditDTO} customerDTO
* @return {Promise<ICustomer>}
*/
public async editCustomer(
tenantId: number,
customerId: number,
customerDTO: ICustomerEditDTO
): Promise<ICustomer> {
const { Contact } = this.tenancy.models(tenantId);
// Retrieve the vendor or throw not found error.
const oldCustomer = await Contact.query()
.findById(customerId)
.modify('customer')
.throwIfNotFound();
// Transformes the given customer DTO to object.
const customerObj = this.customerDTO.transformEditDTO(customerDTO);
// Edits the given customer under unit-of-work evnirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onCustomerEditing` event.
await this.eventPublisher.emitAsync(events.customers.onEditing, {
tenantId,
customerDTO,
customerId,
trx,
} as ICustomerEventEditingPayload);
// Edits the customer details on the storage.
const customer = await Contact.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, Service } from 'typedi';
import { Knex } from 'knex';
import {
ICustomer,
ICustomerOpeningBalanceEditDTO,
ICustomerOpeningBalanceEditedPayload,
ICustomerOpeningBalanceEditingPayload,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
@Service()
export class EditOpeningBalanceCustomer {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private uow: UnitOfWork;
/**
* Changes the opening balance of the given customer.
* @param {number} tenantId
* @param {number} customerId
* @param {number} openingBalance
* @param {string|Date} openingBalanceAt
*/
public async changeOpeningBalance(
tenantId: number,
customerId: number,
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO
): Promise<ICustomer> {
const { Customer } = this.tenancy.models(tenantId);
// Retrieves the old customer or throw not found error.
const oldCustomer = await Customer.query()
.findById(customerId)
.throwIfNotFound();
// Mutates the customer opening balance under unit-of-work.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onCustomerOpeningBalanceChanging` event.
await this.eventPublisher.emitAsync(
events.customers.onOpeningBalanceChanging,
{
tenantId,
oldCustomer,
openingBalanceEditDTO,
trx,
} as ICustomerOpeningBalanceEditingPayload
);
// Mutates the customer on the storage.
const customer = await Customer.query().patchAndFetchById(customerId, {
...openingBalanceEditDTO,
});
// Triggers `onCustomerOpeingBalanceChanged` event.
await this.eventPublisher.emitAsync(
events.customers.onOpeningBalanceChanged,
{
tenantId,
customer,
oldCustomer,
openingBalanceEditDTO,
trx,
} as ICustomerOpeningBalanceEditedPayload
);
return customer;
});
}
}

View File

@@ -0,0 +1,36 @@
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import I18nService from '@/services/I18n/I18nService';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Service, Inject } from 'typedi';
import CustomerTransfromer from '../CustomerTransformer';
@Service()
export class GetCustomer {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieve the given customer details.
* @param {number} tenantId
* @param {number} customerId
*/
public async getCustomer(tenantId: number, customerId: number) {
const { Contact } = this.tenancy.models(tenantId);
// Retrieve the customer model or throw not found error.
const customer = await Contact.query()
.modify('customer')
.findById(customerId)
.throwIfNotFound();
// Retrieves the transformered customers.
return this.transformer.transform(
tenantId,
customer,
new CustomerTransfromer()
);
}
}

View File

@@ -0,0 +1,77 @@
import { Inject, Service } from 'typedi';
import * as R from 'ramda';
import {
ICustomer,
ICustomersFilter,
IFilterMeta,
IPaginationMeta,
} from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import CustomerTransfromer from '../CustomerTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export class GetCustomers {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private dynamicListService: DynamicListingService;
@Inject()
private transformer: TransformerInjectable;
/**
* Parses customers list filter DTO.
* @param filterDTO -
*/
private parseCustomersListFilterDTO(filterDTO) {
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
}
/**
* Retrieve customers paginated list.
* @param {number} tenantId - Tenant id.
* @param {ICustomersFilter} filter - Cusotmers filter.
*/
public async getCustomersList(
tenantId: number,
filterDTO: ICustomersFilter
): Promise<{
customers: ICustomer[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { Customer } = this.tenancy.models(tenantId);
// Parses customers list filter DTO.
const filter = this.parseCustomersListFilterDTO(filterDTO);
// Dynamic list.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
Customer,
filter
);
// Customers.
const { results, pagination } = await Customer.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(
tenantId,
results,
new CustomerTransfromer()
);
return {
customers,
pagination,
filterMeta: dynamicList.getResponseMeta(),
};
}
}