mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
refactor: items services to Nestjs
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Item } from './models/Item';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
@Injectable()
|
||||
export class ActivateItemService {
|
||||
constructor(
|
||||
@Inject(Item.name)
|
||||
private readonly itemModel: typeof Item,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Activates the given item on the storage.
|
||||
* @param {number} itemId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async activateItem(
|
||||
itemId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
// Retrieves the given item or throw not found error.
|
||||
const oldItem = await this.itemModel
|
||||
.query()
|
||||
.findById(itemId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Activate the given item with associated transactions under unit-of-work environment.
|
||||
return this.uow.withTransaction(async (trx) => {
|
||||
// Mutate item on the storage.
|
||||
await this.itemModel.query(trx).findById(itemId).patch({ active: true });
|
||||
|
||||
// Triggers `onItemActivated` event.
|
||||
await this.eventEmitter.emitAsync(events.item.onActivated, {});
|
||||
}, trx);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { events } from '@/common/events/events';
|
||||
import { ItemsValidators } from './ItemValidator.service';
|
||||
import { Item } from './models/Item';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class CreateItemService {
|
||||
@@ -90,17 +89,17 @@ export class CreateItemService {
|
||||
/**
|
||||
* Creates a new item.
|
||||
* @param {IItemDTO} itemDTO
|
||||
* @return {Promise<IItem>}
|
||||
* @return {Promise<number>} - The created item id.
|
||||
*/
|
||||
public async createItem(
|
||||
itemDTO: IItemDTO,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<Item> {
|
||||
): Promise<number> {
|
||||
// Authorize the item before creating.
|
||||
await this.authorize(itemDTO);
|
||||
|
||||
// Creates a new item with associated transactions under unit-of-work envirement.
|
||||
return this.uow.withTransaction<Item>(async (trx: Knex.Transaction) => {
|
||||
return this.uow.withTransaction<number>(async (trx: Knex.Transaction) => {
|
||||
const itemInsert = this.transformNewItemDTOToModel(itemDTO);
|
||||
|
||||
// Inserts a new item and fetch the created item.
|
||||
@@ -114,7 +113,7 @@ export class CreateItemService {
|
||||
trx,
|
||||
} as IItemEventCreatedPayload);
|
||||
|
||||
return item;
|
||||
return item.id;
|
||||
}, trx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,14 +100,15 @@ export class EditItemService {
|
||||
|
||||
/**
|
||||
* Edits the item metadata.
|
||||
* @param {number} itemId
|
||||
* @param {IItemDTO} itemDTO
|
||||
* @param {number} itemId - The item id.
|
||||
* @param {IItemDTO} itemDTO - The item DTO.
|
||||
* @return {Promise<number>} - The updated item id.
|
||||
*/
|
||||
public async editItem(
|
||||
itemId: number,
|
||||
itemDTO: IItemDTO,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<Item> {
|
||||
): Promise<number> {
|
||||
// Validates the given item existance on the storage.
|
||||
const oldItem = await this.itemModel
|
||||
.query()
|
||||
@@ -121,7 +122,7 @@ export class EditItemService {
|
||||
const itemModel = this.transformEditItemDTOToModel(itemDTO, oldItem);
|
||||
|
||||
// Edits the item with associated transactions under unit-of-work environment.
|
||||
return this.uow.withTransaction<Item>(async (trx: Knex.Transaction) => {
|
||||
return this.uow.withTransaction<number>(async (trx: Knex.Transaction) => {
|
||||
// Updates the item on the storage and fetches the updated one.
|
||||
const newItem = await this.itemModel
|
||||
.query(trx)
|
||||
@@ -137,7 +138,7 @@ export class EditItemService {
|
||||
// Triggers `onItemEdited` event.
|
||||
await this.eventEmitter.emitAsync(events.item.onEdited, eventPayload);
|
||||
|
||||
return newItem;
|
||||
return newItem.id;
|
||||
}, trx);
|
||||
}
|
||||
}
|
||||
|
||||
46
packages/server-nest/src/modules/Items/GetItem.service.ts
Normal file
46
packages/server-nest/src/modules/Items/GetItem.service.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Item } from './models/Item';
|
||||
import { events } from '@/common/events/events';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { ItemTransformer } from './Item.transformer';
|
||||
|
||||
@Injectable()
|
||||
export class GetItemService {
|
||||
constructor(
|
||||
@Inject(Item)
|
||||
private itemModel: typeof Item,
|
||||
private eventEmitter2: EventEmitter2,
|
||||
private transformerInjectable: TransformerInjectable,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the item details of the given id with associated details.
|
||||
* @param {number} tenantId - The tenant id.
|
||||
* @param {number} itemId - The item id.
|
||||
*/
|
||||
public async getItem(itemId: number): Promise<any> {
|
||||
const item = await this.itemModel
|
||||
.query()
|
||||
.findById(itemId)
|
||||
.withGraphFetched('sellAccount')
|
||||
.withGraphFetched('inventoryAccount')
|
||||
.withGraphFetched('category')
|
||||
.withGraphFetched('costAccount')
|
||||
.withGraphFetched('itemWarehouses.warehouse')
|
||||
.withGraphFetched('sellTaxRate')
|
||||
.withGraphFetched('purchaseTaxRate')
|
||||
.throwIfNotFound();
|
||||
|
||||
const transformed = await this.transformerInjectable.transform(
|
||||
item,
|
||||
new ItemTransformer(),
|
||||
);
|
||||
const eventPayload = { itemId };
|
||||
|
||||
// Triggers the `onItemViewed` event.
|
||||
await this.eventEmitter2.emitAsync(events.item.onViewed, eventPayload);
|
||||
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Item } from './models/Item';
|
||||
import { events } from '@/common/events/events';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
|
||||
@Injectable()
|
||||
export class InactivateItem {
|
||||
constructor(
|
||||
@Inject(Item.name) private itemModel: typeof Item,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Inactivates the given item on the storage.
|
||||
* @param {number} itemId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async inactivateItem(
|
||||
itemId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
// Retrieves the item or throw not found error.
|
||||
const oldItem = await this.itemModel
|
||||
.query()
|
||||
.findById(itemId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Inactivate item under unit-of-work environment.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Inactivate item on the storage.
|
||||
await this.itemModel.query(trx).findById(itemId).patch({ active: false });
|
||||
|
||||
// Triggers `onItemInactivated` event.
|
||||
await this.eventEmitter.emitAsync(events.item.onInactivated, { trx });
|
||||
}, trx);
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,18 @@ import {
|
||||
Post,
|
||||
UsePipes,
|
||||
UseGuards,
|
||||
Patch,
|
||||
Get,
|
||||
} from '@nestjs/common';
|
||||
import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe';
|
||||
import { createItemSchema } from './Item.schema';
|
||||
import { CreateItemService } from './CreateItem.service';
|
||||
import { Item } from './models/Item';
|
||||
import { DeleteItemService } from './DeleteItem.service';
|
||||
import { TenantController } from '../Tenancy/Tenant.controller';
|
||||
import { SubscriptionGuard } from '../Subscription/interceptors/Subscription.guard';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||
import { EditItemService } from './EditItem.service';
|
||||
import { ItemsApplicationService } from './ItemsApplication.service';
|
||||
|
||||
@Controller('/items')
|
||||
@UseGuards(SubscriptionGuard)
|
||||
@@ -24,20 +26,76 @@ export class ItemsController extends TenantController {
|
||||
constructor(
|
||||
private readonly createItemService: CreateItemService,
|
||||
private readonly deleteItemService: DeleteItemService,
|
||||
private readonly cls: ClsService,
|
||||
private readonly editItemService: EditItemService,
|
||||
private readonly itemsApplication: ItemsApplicationService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit item.
|
||||
* @param id - The item id.
|
||||
* @param editItemDto - The item DTO.
|
||||
* @returns The updated item id.
|
||||
*/
|
||||
@Post(':id')
|
||||
@UsePipes(new ZodValidationPipe(createItemSchema))
|
||||
async editItem(
|
||||
@Param('id') id: string,
|
||||
@Body() editItemDto: any,
|
||||
): Promise<number> {
|
||||
const itemId = parseInt(id, 10);
|
||||
return this.editItemService.editItem(itemId, editItemDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create item.
|
||||
* @param createItemDto - The item DTO.
|
||||
* @returns The created item id.
|
||||
*/
|
||||
@Post()
|
||||
@UsePipes(new ZodValidationPipe(createItemSchema))
|
||||
async createItem(@Body() createItemDto: any): Promise<Item> {
|
||||
async createItem(@Body() createItemDto: any): Promise<number> {
|
||||
return this.createItemService.createItem(createItemDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete item.
|
||||
* @param id - The item id.
|
||||
*/
|
||||
@Delete(':id')
|
||||
async deleteItem(@Param('id') id: string): Promise<void> {
|
||||
const itemId = parseInt(id, 10);
|
||||
return this.deleteItemService.deleteItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate item.
|
||||
* @param id - The item id.
|
||||
*/
|
||||
@Patch(':id/inactivate')
|
||||
async inactivateItem(@Param('id') id: string): Promise<void> {
|
||||
const itemId = parseInt(id, 10);
|
||||
return this.itemsApplication.inactivateItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate item.
|
||||
* @param id - The item id.
|
||||
*/
|
||||
@Patch(':id/activate')
|
||||
async activateItem(@Param('id') id: string): Promise<void> {
|
||||
const itemId = parseInt(id, 10);
|
||||
return this.itemsApplication.activateItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item.
|
||||
* @param id - The item id.
|
||||
*/
|
||||
@Get(':id')
|
||||
async getItem(@Param('id') id: string): Promise<any> {
|
||||
const itemId = parseInt(id, 10);
|
||||
return this.itemsApplication.getItem(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,4 +108,5 @@ export const createItemSchema = z
|
||||
);
|
||||
|
||||
|
||||
export type createItemDTO = z.infer<typeof createItemSchema>;
|
||||
export type createItemDTO = z.infer<typeof createItemSchema>;
|
||||
export type editItemDTOSchema = z.infer<typeof createItemSchema>;
|
||||
61
packages/server-nest/src/modules/Items/Item.transformer.ts
Normal file
61
packages/server-nest/src/modules/Items/Item.transformer.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Transformer } from '../Transformer/Transformer';
|
||||
// import { GetItemWarehouseTransformer } from '@/services/Warehouses/Items/GettItemWarehouseTransformer';
|
||||
|
||||
export class ItemTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'typeFormatted',
|
||||
'sellPriceFormatted',
|
||||
'costPriceFormatted',
|
||||
'itemWarehouses',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted item type.
|
||||
* @param {IItem} item
|
||||
* @returns {string}
|
||||
*/
|
||||
public typeFormatted(item): string {
|
||||
return this.context.i18n.t(`item.field.type.${item.type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted sell price.
|
||||
* @param item
|
||||
* @returns {string}
|
||||
*/
|
||||
public sellPriceFormatted(item): string {
|
||||
return this.formatNumber(item.sellPrice, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted cost price.
|
||||
* @param item
|
||||
* @returns {string}
|
||||
*/
|
||||
public costPriceFormatted(item): string {
|
||||
return this.formatNumber(item.costPrice, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the item warehouses quantity.
|
||||
* @param item
|
||||
* @returns
|
||||
*/
|
||||
// public itemWarehouses = (item) => {
|
||||
// return this.item(
|
||||
// item.itemWarehouses,
|
||||
// new GetItemWarehouseTransformer(),
|
||||
// {},
|
||||
// );
|
||||
// };
|
||||
}
|
||||
@@ -5,6 +5,10 @@ import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||
import { ItemsValidators } from './ItemValidator.service';
|
||||
import { DeleteItemService } from './DeleteItem.service';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { EditItemService } from './EditItem.service';
|
||||
import { InactivateItem } from './InactivateItem.service';
|
||||
import { ActivateItemService } from './ActivateItem.service';
|
||||
import { ItemsApplicationService } from './ItemsApplication.service';
|
||||
|
||||
@Module({
|
||||
imports: [TenancyDatabaseModule],
|
||||
@@ -12,7 +16,11 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
providers: [
|
||||
ItemsValidators,
|
||||
CreateItemService,
|
||||
EditItemService,
|
||||
InactivateItem,
|
||||
ActivateItemService,
|
||||
DeleteItemService,
|
||||
ItemsApplicationService,
|
||||
TenancyContext,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Item } from './models/Item';
|
||||
import { CreateItemService } from './CreateItem.service';
|
||||
import { DeleteItemService } from './DeleteItem.service';
|
||||
import { EditItemService } from './EditItem.service';
|
||||
import { IItem, IItemDTO } from '@/interfaces/Item';
|
||||
import { Knex } from 'knex';
|
||||
import { InactivateItem } from './InactivateItem.service';
|
||||
import { ActivateItemService } from './ActivateItem.service';
|
||||
import { GetItemService } from './GetItem.service';
|
||||
|
||||
export class ItemsApplicationService {
|
||||
constructor(
|
||||
private readonly createItemService: CreateItemService,
|
||||
private readonly editItemService: EditItemService,
|
||||
private readonly deleteItemService: DeleteItemService,
|
||||
private readonly activateItemService: ActivateItemService,
|
||||
private readonly inactivateItemService: InactivateItem,
|
||||
private readonly getItemService: GetItemService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new item.
|
||||
* @param {IItemDTO} createItemDto - The item DTO.
|
||||
* @param {Knex.Transaction} [trx] - The transaction.
|
||||
* @return {Promise<number>} - The created item id.
|
||||
*/
|
||||
async createItem(
|
||||
createItemDto: IItemDTO,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<number> {
|
||||
return this.createItemService.createItem(createItemDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing item.
|
||||
* @param {number} itemId - The item id.
|
||||
* @param {IItemDTO} editItemDto - The item DTO.
|
||||
* @param {Knex.Transaction} [trx] - The transaction.
|
||||
* @return {Promise<number>} - The updated item id.
|
||||
*/
|
||||
async editItem(
|
||||
itemId: number,
|
||||
editItemDto: IItemDTO,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<number> {
|
||||
return this.editItemService.editItem(itemId, editItemDto, trx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing item.
|
||||
* @param {number} itemId - The item id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteItem(itemId: number): Promise<void> {
|
||||
return this.deleteItemService.deleteItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates an item.
|
||||
* @param {number} itemId - The item id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async activateItem(itemId: number): Promise<void> {
|
||||
return this.activateItemService.activateItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivates an item.
|
||||
* @param {number} itemId - The item id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async inactivateItem(itemId: number): Promise<void> {
|
||||
return this.inactivateItemService.inactivateItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item details of the given id with associated details.
|
||||
* @param {number} itemId - The item id.
|
||||
* @returns {Promise<IItem>} - The item details.
|
||||
*/
|
||||
async getItem(itemId: number): Promise<any> {
|
||||
return this.getItemService.getItem(itemId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// import {
|
||||
// defaultOrganizationAddressFormat,
|
||||
// organizationAddressTextFormat,
|
||||
// } from '@/utils/address-text-format';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
// import { findByIsoCountryCode } from '@bigcapital/utils';
|
||||
// import { getUploadedObjectUri } from '../../services/Attachments/utils';
|
||||
|
||||
export class TenantMetadata extends BaseModel {
|
||||
baseCurrency!: string;
|
||||
name!: string;
|
||||
tenantId!: number;
|
||||
industry!: string;
|
||||
location!: string;
|
||||
language!: string;
|
||||
timezone!: string;
|
||||
dateFormat!: string;
|
||||
fiscalYear!: string;
|
||||
primaryColor!: string;
|
||||
logoKey!: string;
|
||||
address!: Record<string, any>;
|
||||
|
||||
/**
|
||||
* Json schema.
|
||||
*/
|
||||
static get jsonSchema() {
|
||||
return {
|
||||
type: 'object',
|
||||
required: ['tenantId', 'name', 'baseCurrency'],
|
||||
properties: {
|
||||
tenantId: { type: 'integer' },
|
||||
name: { type: 'string', maxLength: 255 },
|
||||
industry: { type: 'string', maxLength: 255 },
|
||||
location: { type: 'string', maxLength: 255 },
|
||||
baseCurrency: { type: 'string', maxLength: 3 },
|
||||
language: { type: 'string', maxLength: 255 },
|
||||
timezone: { type: 'string', maxLength: 255 },
|
||||
dateFormat: { type: 'string', maxLength: 255 },
|
||||
fiscalYear: { type: 'string', maxLength: 255 },
|
||||
primaryColor: { type: 'string', maxLength: 7 }, // Assuming hex color code
|
||||
logoKey: { type: 'string', maxLength: 255 },
|
||||
address: { type: 'object' },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'tenants_metadata';
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['logoUri'];
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Organization logo url.
|
||||
// * @returns {string | null}
|
||||
// */
|
||||
// public get logoUri() {
|
||||
// return this.logoKey ? getUploadedObjectUri(this.logoKey) : null;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieves the organization address formatted text.
|
||||
// * @returns {string}
|
||||
// */
|
||||
// public get addressTextFormatted() {
|
||||
// const addressCountry = findByIsoCountryCode(this.location);
|
||||
|
||||
// return organizationAddressTextFormat(defaultOrganizationAddressFormat, {
|
||||
// organizationName: this.name,
|
||||
// address1: this.address?.address1,
|
||||
// address2: this.address?.address2,
|
||||
// state: this.address?.stateProvince,
|
||||
// city: this.address?.city,
|
||||
// postalCode: this.address?.postalCode,
|
||||
// phone: this.address?.phone,
|
||||
// country: addressCountry?.name ?? '',
|
||||
// });
|
||||
// }
|
||||
}
|
||||
@@ -1,12 +1,37 @@
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { Model } from 'objection';
|
||||
import { TenantMetadata } from './TenantMetadataModel';
|
||||
|
||||
export class TenantModel extends BaseModel {
|
||||
public readonly organizationId: string;
|
||||
public readonly initializedAt: string;
|
||||
public readonly seededAt: boolean;
|
||||
public readonly builtAt: string;
|
||||
public readonly metadata: TenantMetadata;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'tenants';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relations mappings.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
// const PlanSubscription = require('./Subscriptions/PlanSubscription');
|
||||
const { TenantMetadata } = require('./TenantMetadataModel');
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: TenantMetadata,
|
||||
join: {
|
||||
from: 'tenants.id',
|
||||
to: 'tenants_metadata.tenantId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,22 @@ export class TenancyContext {
|
||||
|
||||
/**
|
||||
* Get the current tenant.
|
||||
* @param {boolean} withMetadata - If true, the tenant metadata will be fetched.
|
||||
* @returns
|
||||
*/
|
||||
getTenant() {
|
||||
getTenant(withMetadata: boolean = false) {
|
||||
// Get the tenant from the request headers.
|
||||
const organizationId = this.cls.get('organizationId');
|
||||
|
||||
return this.systemTenantModel.query().findOne({ organizationId });
|
||||
if (!organizationId) {
|
||||
throw new Error('Tenant not found');
|
||||
}
|
||||
const query = this.systemTenantModel.query().findOne({ organizationId });
|
||||
|
||||
if (withMetadata) {
|
||||
query.withGraphFetched('metadata');
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Global, Module, Scope } from '@nestjs/common';
|
||||
import { TENANCY_DB_CONNECTION } from '../TenancyDB/TenancyDB.constants';
|
||||
|
||||
import { Item } from '../../../modules/Items/models/Item';
|
||||
import { Account } from '@/modules/Accounts/models/Account';
|
||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||
import { TenantMetadata } from '@/modules/System/models/TenantMetadataModel';
|
||||
|
||||
const models = [Item, Account, TenantModel, TenantMetadata];
|
||||
|
||||
const models = [Item, Account];
|
||||
const modelProviders = models.map((model) => {
|
||||
return {
|
||||
provide: model.name,
|
||||
|
||||
247
packages/server-nest/src/modules/Transformer/Transformer.ts
Normal file
247
packages/server-nest/src/modules/Transformer/Transformer.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
||||
// import { EXPORT_DTE_FORMAT } from '@/services/Export/constants';
|
||||
import { formatNumber } from '@/utils/format-number';
|
||||
import { TransformerContext } from './Transformer.types';
|
||||
|
||||
const EXPORT_DTE_FORMAT = 'YYYY-MM-DD';
|
||||
|
||||
export class Transformer {
|
||||
public context: TransformerContext;
|
||||
public options: Record<string, any>;
|
||||
|
||||
/**
|
||||
* Includeded attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether to exclude all attributes except the include attributes.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isExcludeAllAttributes = () => {
|
||||
return includes(this.excludeAttributes(), '*');
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param object
|
||||
*/
|
||||
transform = (object: any) => {
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param object
|
||||
* @returns
|
||||
*/
|
||||
protected preCollectionTransform = (object: any) => {
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param object
|
||||
* @returns
|
||||
*/
|
||||
protected postCollectionTransform = (object: any) => {
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public work = (object: any) => {
|
||||
if (Array.isArray(object)) {
|
||||
const preTransformed = this.preCollectionTransform(object);
|
||||
const transformed = preTransformed.map(this.getTransformation);
|
||||
|
||||
return this.postCollectionTransform(transformed);
|
||||
} else if (isObject(object)) {
|
||||
return this.getTransformation(object);
|
||||
}
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the given item to desired output.
|
||||
* @param item
|
||||
* @returns
|
||||
*/
|
||||
protected getTransformation = (item) => {
|
||||
const normlizedItem = this.normalizeModelItem(item);
|
||||
|
||||
return R.compose(
|
||||
// sortObjectKeysAlphabetically,
|
||||
this.transform,
|
||||
R.when(this.hasExcludeAttributes, this.excludeAttributesTransformed),
|
||||
this.includeAttributesTransformed
|
||||
)(normlizedItem);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param item
|
||||
* @returns
|
||||
*/
|
||||
protected normalizeModelItem = (item) => {
|
||||
return !isUndefined(item.toJSON) ? item.toJSON() : item;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes from the given item.
|
||||
*/
|
||||
protected excludeAttributesTransformed = (item) => {
|
||||
const exclude = this.excludeAttributes();
|
||||
|
||||
return omit(item, exclude);
|
||||
};
|
||||
|
||||
/**
|
||||
* Incldues virtual attributes.
|
||||
*/
|
||||
protected getIncludeAttributesTransformed = (item) => {
|
||||
const attributes = this.includeAttributes();
|
||||
|
||||
return attributes
|
||||
.filter(
|
||||
(attribute) =>
|
||||
isFunction(this[attribute]) || !isUndefined(item[attribute])
|
||||
)
|
||||
.reduce((acc, attribute: string) => {
|
||||
acc[attribute] = isFunction(this[attribute])
|
||||
? this[attribute](item)
|
||||
: item[attribute];
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param item
|
||||
* @returns
|
||||
*/
|
||||
protected includeAttributesTransformed = (item) => {
|
||||
const excludeAll = this.isExcludeAllAttributes();
|
||||
const virtualAttrs = this.getIncludeAttributesTransformed(item);
|
||||
|
||||
return {
|
||||
...(!excludeAll ? item : {}),
|
||||
...virtualAttrs,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
private hasExcludeAttributes = () => {
|
||||
return this.excludeAttributes().length > 0;
|
||||
};
|
||||
|
||||
private dateFormat = 'YYYY MMM DD';
|
||||
|
||||
setDateFormat(format: string) {
|
||||
this.dateFormat = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date.
|
||||
* @param {string} date
|
||||
* @param {string} format
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formatDate(date: string, format?: string) {
|
||||
// Use the export date format if the async operation is in exporting,
|
||||
// otherwise use the given or default format.
|
||||
const _format = this.context.exportAls.isExport
|
||||
? EXPORT_DTE_FORMAT
|
||||
: format || this.dateFormat;
|
||||
|
||||
return date ? moment(date).format(_format) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date from now.
|
||||
* @param {string} date
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formatDateFromNow(date: string) {
|
||||
return date ? moment(date).fromNow(true) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format number.
|
||||
* @param {number | string} number
|
||||
* @param {any} props
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formatNumber(number: number | string, props?) {
|
||||
return formatNumber(number, { money: false, ...props });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param money
|
||||
* @param options
|
||||
* @returns {}
|
||||
*/
|
||||
protected formatMoney(money, options?) {
|
||||
return formatNumber(money, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param obj
|
||||
* @param transformer
|
||||
* @param options
|
||||
*/
|
||||
public item(
|
||||
obj: Record<string, any>,
|
||||
transformer: Transformer,
|
||||
options?: any
|
||||
) {
|
||||
transformer.setOptions(options);
|
||||
transformer.setContext(this.context);
|
||||
transformer.setDateFormat(this.dateFormat);
|
||||
|
||||
return transformer.work(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets custom options to the application.
|
||||
* @param {} options
|
||||
* @returns {Transformer}
|
||||
*/
|
||||
public setOptions(options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the application context to the application.
|
||||
* @param {} context
|
||||
* @returns {Transformer}
|
||||
*/
|
||||
public setContext(context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import { TenantMetadata } from '../System/models/TenantMetadataModel';
|
||||
|
||||
export interface TransformerContext {
|
||||
organization: TenantMetadata;
|
||||
i18n: I18nService;
|
||||
exportAls: Record<string, any>;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import { Transformer } from './Transformer';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { TransformerContext } from './Transformer.types';
|
||||
|
||||
@Injectable()
|
||||
export class TransformerInjectable {
|
||||
constructor(
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
private readonly i18n: I18nService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the application context of all tenant transformers.
|
||||
* @returns {TransformerContext}
|
||||
*/
|
||||
async getApplicationContext(): Promise<TransformerContext> {
|
||||
const tenant = await this.tenancyContext.getTenant(true);
|
||||
const organization = tenant.metadata;
|
||||
|
||||
return {
|
||||
organization,
|
||||
i18n: this.i18n,
|
||||
exportAls: {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given tenatn date format.
|
||||
* @returns {string}
|
||||
*/
|
||||
async getTenantDateFormat() {
|
||||
const tenant = await this.tenancyContext.getTenant(true);
|
||||
return tenant.metadata.dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the given transformer after inject the tenant context.
|
||||
* @param {Record<string, any> | Record<string, any>[]} object
|
||||
* @param {Transformer} transformer
|
||||
* @param {Record<string, any>} options
|
||||
* @returns {Record<string, any>}
|
||||
*/
|
||||
async transform(
|
||||
object: Record<string, any> | Record<string, any>[],
|
||||
transformer: Transformer,
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
const context = await this.getApplicationContext();
|
||||
transformer.setContext(context);
|
||||
|
||||
const dateFormat = await this.getTenantDateFormat();
|
||||
transformer.setDateFormat(dateFormat);
|
||||
transformer.setOptions(options);
|
||||
|
||||
return transformer.work(object);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user