refactor: migrate item categories to nestjs

This commit is contained in:
Ahmed Bouhuolia
2024-12-20 10:40:35 +02:00
parent 83dfaa00fd
commit 1f32a7c59a
18 changed files with 128 additions and 22 deletions

View File

@@ -1,7 +1,8 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './modules/App/App.module';
import { ClsMiddleware } from 'nestjs-cls'; import { ClsMiddleware } from 'nestjs-cls';
import { AppModule } from './modules/App/App.module';
import './utils/moment-mysql';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);

View File

@@ -33,6 +33,7 @@ import { TransformerInjectable } from '../Transformer/TransformerInjectable.serv
import { TransformerModule } from '../Transformer/Transformer.module'; import { TransformerModule } from '../Transformer/Transformer.module';
import { AccountsModule } from '../Accounts/Accounts.module'; import { AccountsModule } from '../Accounts/Accounts.module';
import { ExpensesModule } from '../Expenses/Expenses.module'; import { ExpensesModule } from '../Expenses/Expenses.module';
import { ItemCategoryModule } from '../ItemCategories/ItemCategory.module';
@Module({ @Module({
imports: [ imports: [
@@ -87,6 +88,7 @@ import { ExpensesModule } from '../Expenses/Expenses.module';
TenancyDatabaseModule, TenancyDatabaseModule,
TenancyModelsModule, TenancyModelsModule,
ItemsModule, ItemsModule,
ItemCategoryModule,
AccountsModule, AccountsModule,
ExpensesModule, ExpensesModule,
], ],
@@ -106,7 +108,6 @@ import { ExpensesModule } from '../Expenses/Expenses.module';
}, },
AppService, AppService,
JwtStrategy, JwtStrategy,
], ],
}) })
export class AppModule { export class AppModule {

View File

@@ -6,17 +6,25 @@ import { PublishExpense } from './commands/PublishExpense.service';
import { ExpensesController } from './Expenses.controller'; import { ExpensesController } from './Expenses.controller';
import { ExpensesApplication } from './ExpensesApplication.service'; import { ExpensesApplication } from './ExpensesApplication.service';
import { GetExpenseService } from './queries/GetExpense.service'; import { GetExpenseService } from './queries/GetExpense.service';
import { ExpenseDTOTransformer } from './commands/CommandExpenseDTO.transformer';
import { CommandExpenseValidator } from './commands/CommandExpenseValidator.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
@Module({ @Module({
imports: [], imports: [],
controllers: [ExpensesController], controllers: [ExpensesController],
providers: [ providers: [
CreateExpense, CreateExpense,
ExpenseDTOTransformer,
CommandExpenseValidator,
EditExpense, EditExpense,
DeleteExpense, DeleteExpense,
PublishExpense, PublishExpense,
GetExpenseService, GetExpenseService,
ExpensesApplication, ExpensesApplication,
TenancyContext,
TransformerInjectable
], ],
}) })
export class ExpensesModule {} export class ExpensesModule {}

View File

@@ -7,7 +7,6 @@ import {
IExpenseEditDTO, IExpenseEditDTO,
} from '../interfaces/Expenses.interface'; } from '../interfaces/Expenses.interface';
// import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform'; // import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
// import { TenantMetadata } from '@/system/models';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Expense } from '../models/Expense.model'; import { Expense } from '../models/Expense.model';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index'; import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
@@ -86,7 +85,7 @@ export class ExpenseDTOTransformer {
*/ */
public expenseCreateDTO = async ( public expenseCreateDTO = async (
expenseDTO: IExpenseCreateDTO, expenseDTO: IExpenseCreateDTO,
): Promise<Expense> => { ): Promise<Partial<Expense>> => {
const initialDTO = this.expenseDTOToModel(expenseDTO); const initialDTO = this.expenseDTOToModel(expenseDTO);
const tenant = await this.tenancyContext.getTenant(true); const tenant = await this.tenancyContext.getTenant(true);

View File

@@ -6,7 +6,7 @@ import {
IExpenseCreatingPayload, IExpenseCreatingPayload,
} from '../interfaces/Expenses.interface'; } from '../interfaces/Expenses.interface';
import { CommandExpenseValidator } from './CommandExpenseValidator.service'; import { CommandExpenseValidator } from './CommandExpenseValidator.service';
import { ExpenseDTOTransformer } from './ExpenseDTOTransformer'; import { ExpenseDTOTransformer } from './CommandExpenseDTO.transformer';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { Expense } from '@/modules/Expenses/models/Expense.model'; import { Expense } from '@/modules/Expenses/models/Expense.model';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';

View File

@@ -7,7 +7,7 @@ import {
} from '../interfaces/Expenses.interface'; } from '../interfaces/Expenses.interface';
import { CommandExpenseValidator } from './CommandExpenseValidator.service'; import { CommandExpenseValidator } from './CommandExpenseValidator.service';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { ExpenseDTOTransformer } from './ExpenseDTOTransformer'; import { ExpenseDTOTransformer } from './CommandExpenseDTO.transformer';
// import { EntriesService } from '@/services/Entries'; // import { EntriesService } from '@/services/Entries';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';

View File

@@ -1,9 +1,11 @@
import { Injectable } from '@nestjs/common';
import { IItemCategoryOTD } from './ItemCategory.interfaces'; import { IItemCategoryOTD } from './ItemCategory.interfaces';
import { CreateItemCategoryService } from './commands/CreateItemCategory.service'; import { CreateItemCategoryService } from './commands/CreateItemCategory.service';
import { DeleteItemCategoryService } from './commands/DeleteItemCategory.service'; import { DeleteItemCategoryService } from './commands/DeleteItemCategory.service';
import { EditItemCategoryService } from './commands/EditItemCategory.service'; import { EditItemCategoryService } from './commands/EditItemCategory.service';
import { GetItemCategoryService } from './queries/GetItemCategory.service'; import { GetItemCategoryService } from './queries/GetItemCategory.service';
@Injectable()
export class ItemCategoryApplication { export class ItemCategoryApplication {
constructor( constructor(
private readonly createItemCategoryService: CreateItemCategoryService, private readonly createItemCategoryService: CreateItemCategoryService,

View File

@@ -9,8 +9,10 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { ItemCategoryApplication } from './ItemCategory.application'; import { ItemCategoryApplication } from './ItemCategory.application';
import { IItemCategoryOTD } from './ItemCategory.interfaces'; import { IItemCategoryOTD } from './ItemCategory.interfaces';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('item-categories') @Controller('item-categories')
@PublicRoute()
export class ItemCategoryController { export class ItemCategoryController {
constructor( constructor(
private readonly itemCategoryApplication: ItemCategoryApplication, private readonly itemCategoryApplication: ItemCategoryApplication,

View File

@@ -6,6 +6,9 @@ import { EditItemCategoryService } from './commands/EditItemCategory.service';
import { GetItemCategoryService } from './queries/GetItemCategory.service'; import { GetItemCategoryService } from './queries/GetItemCategory.service';
import { ItemCategoryApplication } from './ItemCategory.application'; import { ItemCategoryApplication } from './ItemCategory.application';
import { ItemCategoryController } from './ItemCategory.controller'; import { ItemCategoryController } from './ItemCategory.controller';
import { CommandItemCategoryValidatorService } from './commands/CommandItemCategoryValidator.service';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Module({ @Module({
imports: [TenancyDatabaseModule], imports: [TenancyDatabaseModule],
@@ -16,6 +19,9 @@ import { ItemCategoryController } from './ItemCategory.controller';
GetItemCategoryService, GetItemCategoryService,
DeleteItemCategoryService, DeleteItemCategoryService,
ItemCategoryApplication, ItemCategoryApplication,
CommandItemCategoryValidatorService,
TransformerInjectable,
TenancyContext
], ],
}) })
export class ItemCategoryModule {} export class ItemCategoryModule {}

View File

@@ -1,10 +1,11 @@
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { ItemCategory } from '../models/ItemCategory.model'; import { ItemCategory } from '../models/ItemCategory.model';
import { Inject } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { ServiceError } from '@/modules/Items/ServiceError'; import { ServiceError } from '@/modules/Items/ServiceError';
import { ERRORS } from '../constants'; import { ERRORS } from '../constants';
import { ACCOUNT_ROOT_TYPE, ACCOUNT_TYPE } from '@/constants/accounts'; import { ACCOUNT_ROOT_TYPE, ACCOUNT_TYPE } from '@/constants/accounts';
@Injectable()
export class CommandItemCategoryValidatorService { export class CommandItemCategoryValidatorService {
constructor( constructor(
@Inject(ItemCategory.name) @Inject(ItemCategory.name)

View File

@@ -26,12 +26,15 @@ export class CreateItemCategoryService {
* Transforms OTD to model object. * Transforms OTD to model object.
* @param {IItemCategoryOTD} itemCategoryOTD * @param {IItemCategoryOTD} itemCategoryOTD
* @param {ISystemUser} authorizedUser * @param {ISystemUser} authorizedUser
* @returns {ItemCategory}
*/ */
private transformOTDToObject( private transformOTDToObject(
itemCategoryOTD: IItemCategoryOTD, itemCategoryOTD: IItemCategoryOTD,
authorizedUser: SystemUser, ): Partial<ItemCategory> {
): ItemCategory { return {
return { ...itemCategoryOTD, userId: authorizedUser.id }; ...itemCategoryOTD,
// userId: authorizedUser.id
};
} }
/** /**
* Inserts a new item category. * Inserts a new item category.
@@ -58,10 +61,8 @@ export class CreateItemCategoryService {
itemCategoryOTD.inventoryAccountId, itemCategoryOTD.inventoryAccountId,
); );
} }
const itemCategoryObj = this.transformOTDToObject( const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD);
itemCategoryOTD,
authorizedUser,
);
// Creates item category under unit-of-work evnirement. // Creates item category under unit-of-work evnirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Inserts the item category. // Inserts the item category.

View File

@@ -10,12 +10,14 @@ import { SystemUser } from '@/modules/System/models/SystemUser';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { ItemCategory } from '../models/ItemCategory.model'; import { ItemCategory } from '../models/ItemCategory.model';
import { Inject } from '@nestjs/common'; import { Inject } from '@nestjs/common';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
export class EditItemCategoryService { export class EditItemCategoryService {
constructor( constructor(
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
private readonly validator: CommandItemCategoryValidatorService, private readonly validator: CommandItemCategoryValidatorService,
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
private readonly tenancyContext: TenancyContext,
@Inject(ItemCategory.name) @Inject(ItemCategory.name)
private readonly itemCategoryModel: typeof ItemCategory, private readonly itemCategoryModel: typeof ItemCategory,
) {} ) {}
@@ -29,7 +31,7 @@ export class EditItemCategoryService {
public async editItemCategory( public async editItemCategory(
itemCategoryId: number, itemCategoryId: number,
itemCategoryOTD: IItemCategoryOTD, itemCategoryOTD: IItemCategoryOTD,
): Promise<IItemCategory> { ): Promise<ItemCategory> {
// Retrieve the item category from the storage. // Retrieve the item category from the storage.
const oldItemCategory = await this.itemCategoryModel const oldItemCategory = await this.itemCategoryModel
.query() .query()
@@ -52,11 +54,14 @@ export class EditItemCategoryService {
itemCategoryOTD.inventoryAccountId, itemCategoryOTD.inventoryAccountId,
); );
} }
// Retrieves the authorized user.
const authorizedUser = await this.tenancyContext.getSystemUser();
const itemCategoryObj = this.transformOTDToObject( const itemCategoryObj = this.transformOTDToObject(
itemCategoryOTD, itemCategoryOTD,
authorizedUser, authorizedUser,
); );
// // Creates item category under unit-of-work evnirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// //
const itemCategory = await ItemCategory.query().patchAndFetchById( const itemCategory = await ItemCategory.query().patchAndFetchById(

View File

@@ -5,6 +5,15 @@ import { Model, mixin } from 'objection';
// import ItemCategorySettings from './ItemCategory.Settings'; // import ItemCategorySettings from './ItemCategory.Settings';
export class ItemCategory extends BaseModel { export class ItemCategory extends BaseModel {
name!: string;
description!: string;
costAccountId!: number;
sellAccountId!: number;
inventoryAccountId!: number;
userId!: number;
/** /**
* Table name. * Table name.
*/ */
@@ -58,6 +67,6 @@ export class ItemCategory extends BaseModel {
* Model meta. * Model meta.
*/ */
// static get meta() { // static get meta() {
// return ItemCategorySettings; // return ItemCategorySettings;
// } // }
} }

View File

@@ -6,8 +6,19 @@ import { Item } from '../../../modules/Items/models/Item';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { ItemEntry } from '@/modules/Items/models/ItemEntry'; import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
import { Expense } from '@/modules/Expenses/models/Expense.model';
import ExpenseCategory from '@/modules/Expenses/models/ExpenseCategory.model';
import { ItemCategory } from '@/modules/ItemCategories/models/ItemCategory.model';
const models = [Item, Account, ItemEntry, AccountTransaction]; const models = [
Item,
Account,
ItemEntry,
AccountTransaction,
Expense,
ExpenseCategory,
ItemCategory,
];
const modelProviders = models.map((model) => { const modelProviders = models.map((model) => {
return { return {

View File

@@ -0,0 +1,14 @@
import * as moment from 'moment';
// Extends moment prototype to add a new method to format date to MySQL datetime format.
moment.prototype.toMySqlDateTime = function () {
return this.format('YYYY-MM-DD HH:mm:ss');
};
declare global {
namespace moment {
interface Moment {
toMySqlDateTime(): string;
}
}
}

View File

@@ -0,0 +1,49 @@
import * as request from 'supertest';
import { faker } from '@faker-js/faker';
import { app } from './init-app-test';
describe('Item Categories(e2e)', () => {
it('/item-categories (POST)', () => {
return request(app.getHttpServer())
.post('/item-categories')
.set('organization-id', '4064541lv40nhca')
.send({
name: faker.person.fullName(),
description: faker.lorem.sentence(),
})
.expect(201);
});
it('/item-categories/:id (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/item-categories')
.set('organization-id', '4064541lv40nhca')
.send({
name: faker.person.fullName(),
description: faker.lorem.sentence(),
});
const itemCategoryId = response.body.id;
return request(app.getHttpServer())
.get(`/item-categories/${itemCategoryId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/item-categories/:id (DELETE)', async () => {
const response = await request(app.getHttpServer())
.post('/item-categories')
.set('organization-id', '4064541lv40nhca')
.send({
name: faker.person.fullName(),
description: faker.lorem.sentence(),
});
const itemCategoryId = response.body.id;
return request(app.getHttpServer())
.delete(`/item-categories/${itemCategoryId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
});

View File

@@ -1,8 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest'; import * as request from 'supertest';
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import { AppModule } from '../src/modules/App/App.module';
import { app } from './init-app-test'; import { app } from './init-app-test';
describe('Items (e2e)', () => { describe('Items (e2e)', () => {

View File

@@ -20,7 +20,7 @@ export class CreateExpense {
private tenancy: HasTenancyService; private tenancy: HasTenancyService;
@Inject() @Inject()
private eventPublisher: EventPublisher; private eventPublisher: EventPublisher;
@Inject() @Inject()
private uow: UnitOfWork; private uow: UnitOfWork;