From 2e397900e845b79ebc6e73e0176e4d4f01fc884d Mon Sep 17 00:00:00 2001 From: david Date: Tue, 4 Mar 2025 18:08:33 +0100 Subject: [PATCH] . --- .vscode/launch.json | 7 +- apps/server/.env.development | 1 - apps/server/package.json | 7 +- .../domain/value-objects/postal-address.ts | 25 +++- .../sequelize/sequelize-mapper.ts | 20 +-- apps/server/src/config/database.ts | 2 +- .../application/create-account.use-case.ts | 88 +++++++++++++ .../application/get-account.use-case.ts | 23 ++++ .../contexts/accounts/application/index.ts | 5 +- .../application/list-accounts.use-case.ts | 22 ++++ .../application/list-accounts/index.ts | 1 - .../list-accounts/list-accounts.use-case.ts | 17 --- .../update-account.use-case.spec.ts | 46 +++++++ .../application/update-account.use-case.ts | 121 ++++++++++++++++++ .../accounts/domain/aggregates/account.ts | 61 ++++++++- .../src/contexts/accounts/domain/index.ts | 3 +- ...ace.ts => account-repository.interface.ts} | 4 + .../services/account-service.interface.ts | 20 +++ .../domain/services/account-service.spec.ts | 41 ++++++ .../domain/services/account.service.ts | 104 +++++++++++++++ .../services/company-service.interface.ts | 8 -- .../domain/services/company.service.ts | 23 ---- .../accounts/domain/services/index.ts | 2 + .../domain/value-objects/account-status.ts | 59 +++++++++ .../accounts/domain/value-objects/index.ts | 1 + .../infraestructure/mappers/account.mapper.ts | 15 +-- .../sequelize/account.model.ts | 4 + .../sequelize/account.repository.ts | 22 +++- .../infraestructure/sequelize/index.ts | 4 +- .../create-account.controller.ts | 45 +++++++ .../create-account.presenter.ts | 37 ++++++ .../controllers/create-account/index.ts | 16 +++ .../get-account/get-account.controller.ts | 44 +++++++ .../get-account/get-account.presenter.ts | 37 ++++++ .../controllers/get-account/index.ts | 16 +++ .../presentation/controllers/index.ts | 3 + .../controllers/list-accounts/index.ts | 6 +- .../list-accounts/list-accounts.controller.ts | 2 +- .../controllers/update-account/index.ts | 16 +++ .../update-account.controller.ts | 46 +++++++ .../update-account.presenter.ts | 37 ++++++ .../presentation/dto/accounts.request.dto.ts | 51 ++++++++ .../presentation/dto/accounts.response.dto.ts | 87 +++++++++++++ .../dto/accounts.validation.dto.ts | 84 ++++++++++++ .../sequelize/contact.model.ts | 3 + .../application/customer-invoices/index.ts | 1 - .../application/customers/index.ts | 1 - .../customers/list-customers/index.ts | 1 - .../list-customers/list-customers.use-case.ts | 17 --- .../get-customer-invoice.use-case.ts | 0 .../customer-billing/application/index.ts | 4 +- .../list-customer-invoices-use-case.ts | 16 +++ .../domain/aggregates/customer-invoice.ts | 8 +- .../domain/repositories/index.ts | 1 - .../customer-billing/domain/services/index.ts | 2 - .../mappers/customer-invoice.mapper.ts | 6 +- .../sequelize/customer-invoice-item.model.ts | 7 + .../sequelize/customer-invoice.model.ts | 13 ++ .../sequelize/customer.model.ts | 3 + .../sequelize/customer.repository.ts | 81 ------------ .../infraestructure/sequelize/index.ts | 14 +- .../customer-invoices/list/index.ts | 1 + .../list/list-customer-invoices.presenter.ts | 10 +- .../controllers/customers/index.ts | 1 - .../controllers/customers/list/index.ts | 16 --- .../list/list-customers.controller.ts | 37 ------ .../list/list-customers.presenter.ts | 38 ------ .../presentation/controllers/index.ts | 1 - .../customer-invoices.response.dto.ts | 4 +- .../customer-billing/presentation/index.ts | 2 +- apps/server/src/routes/accounts.routes.ts | 44 ++++++- pnpm-lock.yaml | 31 +---- 72 files changed, 1298 insertions(+), 348 deletions(-) create mode 100644 apps/server/src/contexts/accounts/application/create-account.use-case.ts create mode 100644 apps/server/src/contexts/accounts/application/get-account.use-case.ts create mode 100644 apps/server/src/contexts/accounts/application/list-accounts.use-case.ts delete mode 100644 apps/server/src/contexts/accounts/application/list-accounts/index.ts delete mode 100644 apps/server/src/contexts/accounts/application/list-accounts/list-accounts.use-case.ts create mode 100644 apps/server/src/contexts/accounts/application/update-account.use-case.spec.ts create mode 100644 apps/server/src/contexts/accounts/application/update-account.use-case.ts rename apps/server/src/contexts/accounts/domain/repositories/{company-repository.interface.ts => account-repository.interface.ts} (67%) create mode 100644 apps/server/src/contexts/accounts/domain/services/account-service.interface.ts create mode 100644 apps/server/src/contexts/accounts/domain/services/account-service.spec.ts create mode 100644 apps/server/src/contexts/accounts/domain/services/account.service.ts delete mode 100644 apps/server/src/contexts/accounts/domain/services/company-service.interface.ts delete mode 100644 apps/server/src/contexts/accounts/domain/services/company.service.ts create mode 100644 apps/server/src/contexts/accounts/domain/services/index.ts create mode 100644 apps/server/src/contexts/accounts/domain/value-objects/account-status.ts create mode 100644 apps/server/src/contexts/accounts/domain/value-objects/index.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/create-account/create-account.controller.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/create-account/create-account.presenter.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/create-account/index.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.controller.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.presenter.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/get-account/index.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/update-account/index.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/update-account/update-account.controller.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/update-account/update-account.presenter.ts delete mode 100644 apps/server/src/contexts/customer-billing/application/customer-invoices/index.ts delete mode 100644 apps/server/src/contexts/customer-billing/application/customers/index.ts delete mode 100644 apps/server/src/contexts/customer-billing/application/customers/list-customers/index.ts delete mode 100644 apps/server/src/contexts/customer-billing/application/customers/list-customers/list-customers.use-case.ts rename apps/server/src/contexts/customer-billing/application/{customer-invoices => }/get-customer-invoice.use-case.ts (100%) create mode 100644 apps/server/src/contexts/customer-billing/application/list-customer-invoices-use-case.ts delete mode 100644 apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer.repository.ts delete mode 100644 apps/server/src/contexts/customer-billing/presentation/controllers/customers/index.ts delete mode 100644 apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/index.ts delete mode 100644 apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/list-customers.controller.ts delete mode 100644 apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/list-customers.presenter.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 95034f85..c9789ea3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,6 @@ "name": "Launch Chrome localhost", "type": "chrome", "request": "launch", - "reAttach": true, "url": "http://localhost:5173", "webRoot": "${workspaceFolder}/client" }, @@ -30,10 +29,12 @@ { "type": "node", "request": "attach", - "name": "SERVER: Attach to dev:debug", + "name": "Attach to ts-node-dev", "port": 4321, "restart": true, - "cwd": "${workspaceRoot}" + "timeout": 10000, + "sourceMaps": true, + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"] }, { diff --git a/apps/server/.env.development b/apps/server/.env.development index 0d869dfa..99ff3209 100644 --- a/apps/server/.env.development +++ b/apps/server/.env.development @@ -2,7 +2,6 @@ DB_HOST=localhost DB_USER=rodax DB_PASSWORD=rodax DB_NAME=uecko_erp -DB_DIALECT=mariadb DB_PORT=3306 PORT=3002 diff --git a/apps/server/package.json b/apps/server/package.json index 2bfe9a72..4611fe99 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "dev": "ts-node-dev -r tsconfig-paths/register ./src/index.ts", - "dev:debug": "ts-node-dev --transpile-only --respawn --inspect=4321 -r tsconfig-paths/register ./src/index.ts", + "dev:nodebug": "ts-node-dev -r tsconfig-paths/register ./src/index.ts", + "dev": "ts-node-dev --transpile-only --respawn --inspect=4321 -r tsconfig-paths/register ./src/index.ts", "clean": "rm -rf dist", "typecheck": "tsc --noEmit", "build": "npm run clean && npm run typecheck && esbuild src/index.ts --platform=node --format=cjs --bundle --sourcemap --minify --outdir=dist", @@ -45,6 +45,7 @@ "nodemon": "^3.1.9", "ts-jest": "^29.2.5", "ts-node-dev": "^2.0.0", + "tsconfig-paths": "^4.2.0", "typescript": "^5.7.3" }, "dependencies": { @@ -63,7 +64,6 @@ "jsonwebtoken": "^9.0.2", "libphonenumber-js": "^1.11.20", "luxon": "^3.5.0", - "mariadb": "^3.4.0", "module-alias": "^2.2.3", "mysql2": "^3.12.0", "passport": "^0.7.0", @@ -75,7 +75,6 @@ "sequelize": "^6.37.5", "shallow-equal-object": "^1.1.1", "ts-node": "^10.9.1", - "tsconfig-paths": "^4.2.0", "uuid": "^11.0.5", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", diff --git a/apps/server/src/common/domain/value-objects/postal-address.ts b/apps/server/src/common/domain/value-objects/postal-address.ts index f47f232e..acea64d6 100644 --- a/apps/server/src/common/domain/value-objects/postal-address.ts +++ b/apps/server/src/common/domain/value-objects/postal-address.ts @@ -11,14 +11,11 @@ const postalCodeSchema = z message: "Invalid postal code format", }); -const countrySchema = z.string().min(2).max(56); - -const provinceSchema = z.string().min(2).max(50); - -const citySchema = z.string().min(2).max(50); - const streetSchema = z.string().min(2).max(255); const street2Schema = z.string().optional(); +const citySchema = z.string().min(2).max(50); +const stateSchema = z.string().min(2).max(50); +const countrySchema = z.string().min(2).max(56); interface IPostalAddressProps { street: string; @@ -37,7 +34,7 @@ export class PostalAddress extends ValueObject { street2: street2Schema, city: citySchema, postalCode: postalCodeSchema, - state: provinceSchema, + state: stateSchema, country: countrySchema, }) .safeParse(values); @@ -60,6 +57,20 @@ export class PostalAddress extends ValueObject { return PostalAddress.create(values!).map((value) => Maybe.some(value)); } + static update( + oldAddress: PostalAddress, + data: Partial + ): Result { + return PostalAddress.create({ + street: data.street ?? oldAddress.street, + street2: data.street2?.getOrUndefined() ?? oldAddress.street2.getOrUndefined(), + city: data.city ?? oldAddress.city, + postalCode: data.postalCode ?? oldAddress.postalCode, + state: data.state ?? oldAddress.state, + country: data.country ?? oldAddress.country, + }).getOrElse(this); + } + get street(): string { return this.props.street; } diff --git a/apps/server/src/common/infrastructure/sequelize/sequelize-mapper.ts b/apps/server/src/common/infrastructure/sequelize/sequelize-mapper.ts index 4bd9267d..3e886d7b 100644 --- a/apps/server/src/common/infrastructure/sequelize/sequelize-mapper.ts +++ b/apps/server/src/common/infrastructure/sequelize/sequelize-mapper.ts @@ -15,11 +15,11 @@ interface IDomainMapper> } interface IPersistenceMapper> { - mapToPersistence(source: TEntity, params?: MapperParamsType): Result; + mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes; mapCollectionToPersistence( source: Collection, params?: MapperParamsType - ): Result; + ): TModelAttributes[]; } export interface ISequelizeMapper< @@ -59,23 +59,13 @@ export abstract class SequelizeMapper< } } - public abstract mapToPersistence( - source: TEntity, - params?: MapperParamsType - ): Result; + public abstract mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes; public mapCollectionToPersistence( source: Collection, params?: MapperParamsType - ): Result { - try { - const result = source.map( - (value, index) => this.mapToPersistence(value, { index, ...params }).data - ); - return Result.ok(result); - } catch (error) { - return Result.fail(error as Error); - } + ): TModelAttributes[] { + return source.map((value, index) => this.mapToPersistence(value, { index, ...params })); } protected safeMap(operation: () => T, key: string): Result { diff --git a/apps/server/src/config/database.ts b/apps/server/src/config/database.ts index c522dc55..8482cd29 100644 --- a/apps/server/src/config/database.ts +++ b/apps/server/src/config/database.ts @@ -11,7 +11,7 @@ export const sequelize = new Sequelize( process.env.DB_PASSWORD as string, // password { host: process.env.DB_HOST as string, - dialect: "mariadb", + dialect: "mysql", port: parseInt(process.env.DB_PORT || "3306", 10), dialectOptions: { multipleStatements: true, diff --git a/apps/server/src/contexts/accounts/application/create-account.use-case.ts b/apps/server/src/contexts/accounts/application/create-account.use-case.ts new file mode 100644 index 00000000..d1d3bb52 --- /dev/null +++ b/apps/server/src/contexts/accounts/application/create-account.use-case.ts @@ -0,0 +1,88 @@ +import { EmailAddress, PhoneNumber, PostalAddress, TINNumber, UniqueID } from "@common/domain"; + +import { Maybe, Result } from "@common/helpers"; +import { ITransactionManager } from "@common/infrastructure/database"; +import { logger } from "@common/infrastructure/logger"; +import { Account, AccountStatus, IAccountProps, IAccountService } from "@contexts/accounts/domain"; +import { ICreateAccountRequestDTO } from "../presentation"; + +export class CreateAccountUseCase { + constructor( + private readonly accountService: IAccountService, + private readonly transactionManager: ITransactionManager + ) {} + + public execute( + accountID: UniqueID, + dto: ICreateAccountRequestDTO + ): Promise> { + return this.transactionManager.complete(async (transaction) => { + try { + const validOrErrors = this.validateAccountData(dto); + if (validOrErrors.isFailure) { + return Result.fail(validOrErrors.error); + } + + const data = validOrErrors.data; + + // Update account with dto + return await this.accountService.createAccount(accountID, data, transaction); + } catch (error: unknown) { + logger.error(error as Error); + return Result.fail(error as Error); + } + }); + } + + private validateAccountData(dto: ICreateAccountRequestDTO): Result { + const errors: Error[] = []; + + const tinOrError = TINNumber.create(dto.tin); + const emailOrError = EmailAddress.create(dto.email); + const phoneOrError = PhoneNumber.create(dto.phone); + const faxOrError = PhoneNumber.createNullable(dto.fax); + const postalAddressOrError = PostalAddress.create({ + street: dto.street, + city: dto.city, + state: dto.state, + postalCode: dto.postal_code, + country: dto.country, + }); + + const result = Result.combine([ + tinOrError, + emailOrError, + phoneOrError, + faxOrError, + postalAddressOrError, + ]); + + if (result.isFailure) { + return Result.fail(result.error); + } + + const validatedData: IAccountProps = { + status: AccountStatus.createInactive(), + isFreelancer: dto.is_freelancer, + name: dto.name, + tradeName: dto.trade_name ? Maybe.some(dto.trade_name) : Maybe.none(), + tin: tinOrError.data, + address: postalAddressOrError.data, + email: emailOrError.data, + phone: phoneOrError.data, + fax: faxOrError.data, + website: dto.website ? Maybe.some(dto.website) : Maybe.none(), + legalRecord: dto.legal_record, + defaultTax: dto.default_tax, + langCode: dto.lang_code, + currencyCode: dto.currency_code, + logo: dto.logo ? Maybe.some(dto.logo) : Maybe.none(), + }; + + if (errors.length > 0) { + const message = errors.map((err) => err.message).toString(); + return Result.fail(new Error(message)); + } + return Result.ok(validatedData); + } +} diff --git a/apps/server/src/contexts/accounts/application/get-account.use-case.ts b/apps/server/src/contexts/accounts/application/get-account.use-case.ts new file mode 100644 index 00000000..c5f001ab --- /dev/null +++ b/apps/server/src/contexts/accounts/application/get-account.use-case.ts @@ -0,0 +1,23 @@ +import { UniqueID } from "@common/domain"; +import { Result } from "@common/helpers"; +import { ITransactionManager } from "@common/infrastructure/database"; +import { logger } from "@common/infrastructure/logger"; +import { Account, IAccountService } from "@contexts/accounts/domain"; + +export class GetAccountsUseCase { + constructor( + private readonly accountService: IAccountService, + private readonly transactionManager: ITransactionManager + ) {} + + public execute(accountID: UniqueID): Promise> { + return this.transactionManager.complete(async (transaction) => { + try { + return await this.accountService.findAccountById(accountID, transaction); + } catch (error: unknown) { + logger.error(error as Error); + return Result.fail(error as Error); + } + }); + } +} diff --git a/apps/server/src/contexts/accounts/application/index.ts b/apps/server/src/contexts/accounts/application/index.ts index a568d0fe..0f17dc7a 100644 --- a/apps/server/src/contexts/accounts/application/index.ts +++ b/apps/server/src/contexts/accounts/application/index.ts @@ -1 +1,4 @@ -export * from "./list-accounts"; +export * from "./create-account.use-case"; +export * from "./get-account.use-case"; +export * from "./list-accounts.use-case"; +export * from "./update-account.use-case"; diff --git a/apps/server/src/contexts/accounts/application/list-accounts.use-case.ts b/apps/server/src/contexts/accounts/application/list-accounts.use-case.ts new file mode 100644 index 00000000..07dac668 --- /dev/null +++ b/apps/server/src/contexts/accounts/application/list-accounts.use-case.ts @@ -0,0 +1,22 @@ +import { Collection, Result } from "@common/helpers"; +import { ITransactionManager } from "@common/infrastructure/database"; +import { logger } from "@common/infrastructure/logger"; +import { Account, IAccountService } from "@contexts/accounts/domain"; + +export class ListAccountsUseCase { + constructor( + private readonly accountService: IAccountService, + private readonly transactionManager: ITransactionManager + ) {} + + public execute(): Promise, Error>> { + return this.transactionManager.complete(async (transaction) => { + try { + return await this.accountService.findAccounts(transaction); + } catch (error: unknown) { + logger.error(error as Error); + return Result.fail(error as Error); + } + }); + } +} diff --git a/apps/server/src/contexts/accounts/application/list-accounts/index.ts b/apps/server/src/contexts/accounts/application/list-accounts/index.ts deleted file mode 100644 index 4a1555cd..00000000 --- a/apps/server/src/contexts/accounts/application/list-accounts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./list-accounts.use-case"; diff --git a/apps/server/src/contexts/accounts/application/list-accounts/list-accounts.use-case.ts b/apps/server/src/contexts/accounts/application/list-accounts/list-accounts.use-case.ts deleted file mode 100644 index fa603160..00000000 --- a/apps/server/src/contexts/accounts/application/list-accounts/list-accounts.use-case.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Collection, Result } from "@common/helpers"; -import { ITransactionManager } from "@common/infrastructure/database"; -import { Account } from "@contexts/accounts/domain"; -import { IAccountService } from "@contexts/accounts/domain/services/account-service.interface"; - -export class ListAccountsUseCase { - constructor( - private readonly accountService: IAccountService, - private readonly transactionManager: ITransactionManager - ) {} - - public execute(): Promise, Error>> { - return this.transactionManager.complete((transaction) => { - return this.accountService.findAccounts(transaction); - }); - } -} diff --git a/apps/server/src/contexts/accounts/application/update-account.use-case.spec.ts b/apps/server/src/contexts/accounts/application/update-account.use-case.spec.ts new file mode 100644 index 00000000..1042a9a7 --- /dev/null +++ b/apps/server/src/contexts/accounts/application/update-account.use-case.spec.ts @@ -0,0 +1,46 @@ +import { UniqueID } from "@common/domain"; +import { Result } from "@common/helpers"; +import { TransactionManager } from "@common/infrastructure/database"; +import { AccountService } from "../domain"; +import { UpdateAccountUseCase } from "./update-account.use-case"; + +const mockAccountService: AccountService = { + updateAccountById: jest.fn(), +} as unknown as AccountService; + +const mockTransactionManager: TransactionManager = { + complete(work: (transaction: any) => Promise): void { + jest.fn(); + }, +} as unknown as TransactionManager; + +const id = UniqueID.create("", true).data; + +describe("UpdateAccountUseCase", () => { + let updateAccountUseCase: UpdateAccountUseCase; + + beforeEach(() => { + updateAccountUseCase = new UpdateAccountUseCase(mockAccountService, mockTransactionManager); + }); + + it("debería actualizar una cuenta y retornar un DTO", async () => { + const mockUpdatedAccount = { id: "123", name: "Nuevo Nombre" }; + (mockAccountService.updateAccountById as jest.Mock).mockResolvedValue( + Result.ok(mockUpdatedAccount) + ); + + const result = await updateAccountUseCase.execute(id, { name: "Nuevo Nombre" }); + expect(result.isSuccess).toBe(true); + expect(result.data.name).toBe("Nuevo Nombre"); + }); + + it("debería retornar error si la actualización falla", async () => { + (mockAccountService.updateAccountById as jest.Mock).mockResolvedValue( + Result.fail(new Error("Account not found")) + ); + + const result = await updateAccountUseCase.execute(id, { name: "Nuevo Nombre" }); + expect(result.isFailure).toBe(true); + expect(result.error.message).toBe("Account not found"); + }); +}); diff --git a/apps/server/src/contexts/accounts/application/update-account.use-case.ts b/apps/server/src/contexts/accounts/application/update-account.use-case.ts new file mode 100644 index 00000000..8a2fd386 --- /dev/null +++ b/apps/server/src/contexts/accounts/application/update-account.use-case.ts @@ -0,0 +1,121 @@ +import { EmailAddress, PhoneNumber, PostalAddress, TINNumber, UniqueID } from "@common/domain"; + +import { Maybe, Result } from "@common/helpers"; +import { ITransactionManager } from "@common/infrastructure/database"; +import { logger } from "@common/infrastructure/logger"; +import { Account, IAccountProps, IAccountService } from "@contexts/accounts/domain"; +import { IUpdateAccountRequestDTO } from "../presentation"; + +export class UpdateAccountUseCase { + constructor( + private readonly accountService: IAccountService, + private readonly transactionManager: ITransactionManager + ) {} + + public execute( + accountID: UniqueID, + dto: Partial + ): Promise> { + return this.transactionManager.complete(async (transaction) => { + try { + const validOrErrors = this.validateAccountData(dto); + if (validOrErrors.isFailure) { + return Result.fail(validOrErrors.error); + } + + const data = validOrErrors.data; + + // Update account with dto + return await this.accountService.updateAccountById(accountID, data, transaction); + } catch (error: unknown) { + logger.error(error as Error); + return Result.fail(error as Error); + } + }); + } + + private validateAccountData( + dto: Partial + ): Result, Error> { + const errors: Error[] = []; + const validatedData: Partial = {}; + + if (dto.is_freelancer) { + validatedData.isFreelancer = dto.is_freelancer; + } + + if (dto.name) { + validatedData.name = dto.name; + } + + if (dto.trade_name) { + validatedData.tradeName = Maybe.some(dto.trade_name); + } + + if (dto.tin) { + const tinOrError = TINNumber.create(dto.tin); + if (tinOrError.isFailure) errors.push(tinOrError.error); + else validatedData.tin = tinOrError.data; + } + + if (dto.email) { + const emailOrError = EmailAddress.create(dto.email); + if (emailOrError.isFailure) errors.push(emailOrError.error); + else validatedData.email = emailOrError.data; + } + + if (dto.phone) { + const phoneOrError = PhoneNumber.create(dto.phone); + if (phoneOrError.isFailure) errors.push(phoneOrError.error); + else validatedData.phone = phoneOrError.data; + } + + if (dto.fax) { + const faxOrError = PhoneNumber.create(dto.fax); + if (faxOrError.isFailure) errors.push(faxOrError.error); + else validatedData.fax = Maybe.some(faxOrError.data); + } + + if (dto.street || dto.city || dto.state || dto.postal_code || dto.country) { + const postalAddressOrError = PostalAddress.create({ + street: dto.street ?? "", + city: dto.city ?? "", + state: dto.state ?? "", + postalCode: dto.postal_code ?? "", + country: dto.country ?? "", + }); + if (postalAddressOrError.isFailure) errors.push(postalAddressOrError.error); + else validatedData.address = postalAddressOrError.data; + } + + if (dto.website) { + validatedData.website = Maybe.some(dto.website); + } + + if (dto.legal_record) { + validatedData.legalRecord = dto.legal_record; + } + + if (dto.default_tax) { + validatedData.defaultTax = dto.default_tax; + } + + if (dto.lang_code) { + validatedData.langCode = dto.lang_code; + } + + if (dto.currency_code) { + validatedData.currencyCode = dto.currency_code; + } + + if (dto.logo) { + validatedData.logo = Maybe.some(dto.logo); + } + + if (errors.length > 0) { + const message = errors.map((err) => err.message).toString(); + return Result.fail(new Error(message)); + } + return Result.ok(validatedData); + } +} diff --git a/apps/server/src/contexts/accounts/domain/aggregates/account.ts b/apps/server/src/contexts/accounts/domain/aggregates/account.ts index 49ab6249..711d8bda 100644 --- a/apps/server/src/contexts/accounts/domain/aggregates/account.ts +++ b/apps/server/src/contexts/accounts/domain/aggregates/account.ts @@ -7,8 +7,11 @@ import { UniqueID, } from "@common/domain"; import { Maybe, Result } from "@common/helpers"; +import { AccountStatus } from "../value-objects"; export interface IAccountProps { + status: AccountStatus; + isFreelancer: boolean; name: string; tin: TINNumber; @@ -17,7 +20,7 @@ export interface IAccountProps { phone: PhoneNumber; legalRecord: string; defaultTax: number; - status: string; + langCode: string; currencyCode: string; @@ -29,6 +32,7 @@ export interface IAccountProps { export interface IAccount { id: UniqueID; + status: AccountStatus; name: string; tin: TINNumber; address: PostalAddress; @@ -36,6 +40,7 @@ export interface IAccount { phone: PhoneNumber; legalRecord: string; defaultTax: number; + langCode: string; currencyCode: string; @@ -47,6 +52,9 @@ export interface IAccount { isAccount: boolean; isFreelancer: boolean; isActive: boolean; + + activate(): boolean; + deactivate(): boolean; } export class Account extends AggregateRoot implements IAccount { @@ -64,6 +72,55 @@ export class Account extends AggregateRoot implements IAccount { return Result.ok(account); } + static update(oldAccount: Account, data: Partial): Result { + const updatedPostalAddress = PostalAddress.update(oldAccount.address, data.address ?? {}).data; + + return Account.create( + { + isFreelancer: data.isFreelancer ?? oldAccount.isFreelancer, + + name: data.name ?? oldAccount.name, + tin: data.tin ?? oldAccount.tin, + + address: updatedPostalAddress, + email: data.email ?? oldAccount.email, + phone: data.phone ?? oldAccount.phone, + + legalRecord: data.legalRecord ?? oldAccount.legalRecord, + defaultTax: data.defaultTax ?? oldAccount.defaultTax, + status: oldAccount.props.status, + langCode: data.langCode ?? oldAccount.langCode, + currencyCode: data.currencyCode ?? oldAccount.currencyCode, + + tradeName: data.tradeName ?? oldAccount.tradeName, + website: data.website ?? oldAccount.website, + fax: data.fax ?? oldAccount.fax, + logo: data.logo ?? oldAccount.logo, + }, + oldAccount.id + ).getOrElse(this); + } + + activate() { + if (!this.props.status.canTransitionTo("active")) { + return false; + } + this.props.status = AccountStatus.createActive(); + return true; + } + + deactivate() { + if (!this.props.status.canTransitionTo("inactive")) { + return false; + } + this.props.status = AccountStatus.createInactive(); + return true; + } + + get status() { + return this.props.status; + } + get name() { return this.props.name; } @@ -125,6 +182,6 @@ export class Account extends AggregateRoot implements IAccount { } get isActive(): boolean { - return this.props.status === "active"; + return this.props.status.equals(AccountStatus.createActive()); } } diff --git a/apps/server/src/contexts/accounts/domain/index.ts b/apps/server/src/contexts/accounts/domain/index.ts index 160d6093..5dcd597d 100644 --- a/apps/server/src/contexts/accounts/domain/index.ts +++ b/apps/server/src/contexts/accounts/domain/index.ts @@ -1,3 +1,4 @@ export * from "./aggregates"; - export * from "./repositories"; +export * from "./services"; +export * from "./value-objects"; diff --git a/apps/server/src/contexts/accounts/domain/repositories/company-repository.interface.ts b/apps/server/src/contexts/accounts/domain/repositories/account-repository.interface.ts similarity index 67% rename from apps/server/src/contexts/accounts/domain/repositories/company-repository.interface.ts rename to apps/server/src/contexts/accounts/domain/repositories/account-repository.interface.ts index 0ff7f330..df3a1349 100644 --- a/apps/server/src/contexts/accounts/domain/repositories/company-repository.interface.ts +++ b/apps/server/src/contexts/accounts/domain/repositories/account-repository.interface.ts @@ -3,7 +3,11 @@ import { Collection, Result } from "@common/helpers"; import { Account } from "../aggregates"; export interface IAccountRepository { + accountExists(id: UniqueID, transaction?: any): Promise>; findAll(transaction?: any): Promise, Error>>; findById(id: UniqueID, transaction?: any): Promise>; findByEmail(email: EmailAddress, transaction?: any): Promise>; + + create(account: Account, transaction?: any): Promise; + update(account: Account, transaction?: any): Promise; } diff --git a/apps/server/src/contexts/accounts/domain/services/account-service.interface.ts b/apps/server/src/contexts/accounts/domain/services/account-service.interface.ts new file mode 100644 index 00000000..9d426218 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/services/account-service.interface.ts @@ -0,0 +1,20 @@ +import { UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { Account, IAccountProps } from "../aggregates"; + +export interface IAccountService { + findAccounts(transaction?: any): Promise, Error>>; + findAccountById(accountId: UniqueID, transaction?: any): Promise>; + + updateAccountById( + accountId: UniqueID, + data: Partial, + transaction?: any + ): Promise>; + + createAccount( + accountId: UniqueID, + data: IAccountProps, + transaction?: any + ): Promise>; +} diff --git a/apps/server/src/contexts/accounts/domain/services/account-service.spec.ts b/apps/server/src/contexts/accounts/domain/services/account-service.spec.ts new file mode 100644 index 00000000..c8ba48e9 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/services/account-service.spec.ts @@ -0,0 +1,41 @@ +// Pruebas unitarias: AccountService.test.ts +import { Account } from "../domain/Account"; +import { IAccountRepository } from "../repositories/AccountRepository"; +import { AccountService } from "../services/AccountService"; + +const mockAccountRepository: IAccountRepository = { + findById: jest.fn(), + save: jest.fn(), +}; + +describe("AccountService", () => { + let accountService: AccountService; + + beforeEach(() => { + accountService = new AccountService(mockAccountRepository); + }); + + it("debería actualizar una cuenta existente", async () => { + const existingAccount = new Account( + { + /* datos simulados */ + }, + "123" + ); + (mockAccountRepository.findById as jest.Mock).mockResolvedValue(existingAccount); + (mockAccountRepository.save as jest.Mock).mockResolvedValue(undefined); + + const result = await accountService.updateAccountById("123", { name: "Nuevo Nombre" }); + expect(result.isSuccess).toBe(true); + expect(result.data.name).toBe("Nuevo Nombre"); + expect(mockAccountRepository.save).toHaveBeenCalled(); + }); + + it("debería retornar error si la cuenta no existe", async () => { + (mockAccountRepository.findById as jest.Mock).mockResolvedValue(null); + + const result = await accountService.updateAccountById("123", { name: "Nuevo Nombre" }); + expect(result.isFailure).toBe(true); + expect(result.error.message).toBe("Account not found"); + }); +}); diff --git a/apps/server/src/contexts/accounts/domain/services/account.service.ts b/apps/server/src/contexts/accounts/domain/services/account.service.ts new file mode 100644 index 00000000..90df8084 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/services/account.service.ts @@ -0,0 +1,104 @@ +import { UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { Transaction } from "sequelize"; +import { Account, IAccountProps } from "../aggregates"; +import { IAccountRepository } from "../repositories"; +import { IAccountService } from "./account-service.interface"; + +export class AccountService implements IAccountService { + constructor(private readonly repo: IAccountRepository) {} + + async findAccounts(transaction?: Transaction): Promise, Error>> { + const accountsOrError = await this.repo.findAll(transaction); + if (accountsOrError.isFailure) { + return Result.fail(accountsOrError.error); + } + + // Solo devolver usuarios activos + //const allAccounts = accountsOrError.data.filter((account) => account.isActive); + //return Result.ok(new Collection(allAccounts)); + + return accountsOrError; + } + + async findAccountById(accountId: UniqueID, transaction?: Transaction): Promise> { + return await this.repo.findById(accountId, transaction); + } + + async updateAccountById( + accountId: UniqueID, + data: Partial, + transaction?: Transaction + ): Promise> { + // Verificar si la cuenta existe + const accountOrError = await this.repo.findById(accountId, transaction); + if (accountOrError.isFailure) { + return Result.fail(new Error("Account not found")); + } + + const updatedAccountOrError = Account.update(accountOrError.data, data); + if (updatedAccountOrError.isFailure) { + return Result.fail( + new Error(`Error updating account: ${updatedAccountOrError.error.message}`) + ); + } + + const updateAccount = updatedAccountOrError.data; + + await this.repo.update(updateAccount, transaction); + return Result.ok(updateAccount); + } + + async createAccount( + accountId: UniqueID, + data: IAccountProps, + transaction?: Transaction + ): Promise> { + // Verificar si la cuenta existe + const accountOrError = await this.repo.findById(accountId, transaction); + if (accountOrError.isSuccess) { + return Result.fail(new Error("Account exists")); + } + + const newAccountOrError = Account.create(data, accountId); + if (newAccountOrError.isFailure) { + return Result.fail(new Error(`Error creating account: ${newAccountOrError.error.message}`)); + } + + const newAccount = newAccountOrError.data; + + await this.repo.create(newAccount, transaction); + return Result.ok(newAccount); + } + + async activateAccount(id: UniqueID, transaction?: Transaction): Promise> { + const accountOrError = await this.repo.findById(id, transaction); + if (accountOrError.isFailure) { + return Result.fail(new Error("Account not found")); + } + + const account = accountOrError.data; + if (account.activate()) { + await this.repo.update(account, transaction); + return Result.ok(); + } + return Result.fail(new Error("Error activating account")); + } + + async deactivateAccount( + id: UniqueID, + transaction?: Transaction + ): Promise> { + const accountOrError = await this.repo.findById(id, transaction); + if (accountOrError.isFailure) { + return Result.fail(new Error("Account not found")); + } + + const account = accountOrError.data; + if (account.deactivate()) { + await this.repo.update(account, transaction); + return Result.ok(); + } + return Result.fail(new Error("Error deactivating account")); + } +} diff --git a/apps/server/src/contexts/accounts/domain/services/company-service.interface.ts b/apps/server/src/contexts/accounts/domain/services/company-service.interface.ts deleted file mode 100644 index a27c487a..00000000 --- a/apps/server/src/contexts/accounts/domain/services/company-service.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { Account } from "../aggregates"; - -export interface IAccountService { - findAccounts(transaction?: any): Promise, Error>>; - findAccountById(userId: UniqueID, transaction?: any): Promise>; -} diff --git a/apps/server/src/contexts/accounts/domain/services/company.service.ts b/apps/server/src/contexts/accounts/domain/services/company.service.ts deleted file mode 100644 index 4f006785..00000000 --- a/apps/server/src/contexts/accounts/domain/services/company.service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { Account, IAccountRepository } from ".."; -import { IAccountService } from "./account-service.interface"; - -export class AccountService implements IAccountService { - constructor(private readonly accountRepository: IAccountRepository) {} - - async findAccounts(transaction?: any): Promise, Error>> { - const accountsOrError = await this.accountRepository.findAll(transaction); - if (accountsOrError.isFailure) { - return Result.fail(accountsOrError.error); - } - - // Solo devolver usuarios activos - const activeAccounts = accountsOrError.data.filter((account) => account.isActive); - return Result.ok(new Collection(activeAccounts)); - } - - async findAccountById(accountId: UniqueID, transaction?: any): Promise> { - return await this.accountRepository.findById(accountId, transaction); - } -} diff --git a/apps/server/src/contexts/accounts/domain/services/index.ts b/apps/server/src/contexts/accounts/domain/services/index.ts new file mode 100644 index 00000000..207020c4 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/services/index.ts @@ -0,0 +1,2 @@ +export * from "./account-service.interface"; +export * from "./account.service"; diff --git a/apps/server/src/contexts/accounts/domain/value-objects/account-status.ts b/apps/server/src/contexts/accounts/domain/value-objects/account-status.ts new file mode 100644 index 00000000..cea3d6d5 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/value-objects/account-status.ts @@ -0,0 +1,59 @@ +import { ValueObject } from "@common/domain"; +import { Result } from "@common/helpers"; + +interface IAccountStatusProps { + value: string; +} + +export enum ACCOUNT_STATUS { + INACTIVE = "inactive", + ACTIVE = "active", +} + +export class AccountStatus extends ValueObject { + private static readonly ALLOWED_STATUSES = ["inactive", "active"]; + + private static readonly TRANSITIONS: Record = { + inactive: [ACCOUNT_STATUS.ACTIVE], + active: [ACCOUNT_STATUS.INACTIVE], + }; + + static create(value: string): Result { + if (!this.ALLOWED_STATUSES.includes(value)) { + return Result.fail(new Error(`Estado de la cuenta no válido: ${value}`)); + } + + return Result.ok( + value === "active" ? AccountStatus.createActive() : AccountStatus.createInactive() + ); + } + + public static createInactive(): AccountStatus { + return new AccountStatus({ value: ACCOUNT_STATUS.INACTIVE }); + } + + public static createActive(): AccountStatus { + return new AccountStatus({ value: ACCOUNT_STATUS.ACTIVE }); + } + + getValue(): string { + return this.props.value; + } + + canTransitionTo(nextStatus: string): boolean { + return AccountStatus.TRANSITIONS[this.props.value].includes(nextStatus); + } + + transitionTo(nextStatus: string): Result { + if (!this.canTransitionTo(nextStatus)) { + return Result.fail( + new Error(`Transición no permitida de ${this.props.value} a ${nextStatus}`) + ); + } + return AccountStatus.create(nextStatus); + } + + toString(): string { + return this.getValue(); + } +} diff --git a/apps/server/src/contexts/accounts/domain/value-objects/index.ts b/apps/server/src/contexts/accounts/domain/value-objects/index.ts new file mode 100644 index 00000000..88c6ddf6 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/value-objects/index.ts @@ -0,0 +1 @@ +export * from "./account-status"; diff --git a/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts b/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts index 03b64de1..1f349242 100644 --- a/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts +++ b/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts @@ -5,7 +5,7 @@ import { MapperParamsType, SequelizeMapper, } from "@common/infrastructure/sequelize/sequelize-mapper"; -import { Account } from "@contexts/accounts/domain/"; +import { Account, AccountStatus } from "@contexts/accounts/domain/"; import { AccountCreationAttributes, AccountModel } from "../sequelize/account.model"; export interface IAccountMapper @@ -17,6 +17,7 @@ export class AccountMapper { public mapToDomain(source: AccountModel, params?: MapperParamsType): Result { const idOrError = UniqueID.create(source.id); + const statusOrError = AccountStatus.create(source.status); const tinOrError = TINNumber.create(source.tin); const emailOrError = EmailAddress.create(source.email); const phoneOrError = PhoneNumber.create(source.phone); @@ -31,6 +32,7 @@ export class AccountMapper const result = Result.combine([ idOrError, + statusOrError, tinOrError, emailOrError, phoneOrError, @@ -44,6 +46,7 @@ export class AccountMapper return Account.create( { + status: statusOrError.data, isFreelancer: source.is_freelancer, name: source.name, tradeName: source.trade_name ? Maybe.some(source.trade_name) : Maybe.none(), @@ -55,7 +58,6 @@ export class AccountMapper website: source.website ? Maybe.some(source.website) : Maybe.none(), legalRecord: source.legal_record, defaultTax: source.default_tax, - status: source.status, langCode: source.lang_code, currencyCode: source.currency_code, logo: source.logo ? Maybe.some(source.logo) : Maybe.none(), @@ -64,11 +66,8 @@ export class AccountMapper ); } - public mapToPersistence( - source: Account, - params?: MapperParamsType - ): Result { - return Result.ok({ + public mapToPersistence(source: Account, params?: MapperParamsType): AccountCreationAttributes { + return { id: source.id.toString(), is_freelancer: source.isFreelancer, name: source.name, @@ -92,7 +91,7 @@ export class AccountMapper lang_code: source.langCode, currency_code: source.currencyCode, logo: source.logo.getOrUndefined(), - }); + }; } } diff --git a/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts index ea5d4e42..0f6476d1 100644 --- a/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts +++ b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts @@ -63,6 +63,7 @@ export default (sequelize: Sequelize) => { trade_name: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, }, tin: { type: DataTypes.STRING, @@ -104,10 +105,12 @@ export default (sequelize: Sequelize) => { fax: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, }, website: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, validate: { isUrl: true, }, @@ -126,6 +129,7 @@ export default (sequelize: Sequelize) => { logo: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, }, lang_code: { diff --git a/apps/server/src/contexts/accounts/infraestructure/sequelize/account.repository.ts b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.repository.ts index 8a608714..9ccf0413 100644 --- a/apps/server/src/contexts/accounts/infraestructure/sequelize/account.repository.ts +++ b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.repository.ts @@ -2,7 +2,7 @@ import { EmailAddress, UniqueID } from "@common/domain"; import { Collection, Result } from "@common/helpers"; import { SequelizeRepository } from "@common/infrastructure"; import { Account } from "@contexts/accounts/domain"; -import { IAccountRepository } from "@contexts/accounts/domain/repositories/company-repository.interface"; +import { IAccountRepository } from "@contexts/accounts/domain/repositories/account-repository.interface"; import { Transaction } from "sequelize"; import { accountMapper, IAccountMapper } from "../mappers/account.mapper"; import { AccountModel } from "./account.model"; @@ -26,6 +26,16 @@ class AccountRepository extends SequelizeRepository implements IAccount this._mapper = mapper; } + async accountExists(id: UniqueID, transaction?: Transaction): Promise> { + try { + const _account = await this._getById(AccountModel, id, {}, transaction); + + return Result.ok(Boolean(id.equals(_account.id))); + } catch (error: any) { + return this._handleDatabaseError(error, this._customErrorMapper); + } + } + async findAll(transaction?: Transaction): Promise, Error>> { try { const rawAccounts: any = await this._findAll(AccountModel, {}, transaction); @@ -76,6 +86,16 @@ class AccountRepository extends SequelizeRepository implements IAccount return this._handleDatabaseError(error, this._customErrorMapper); } } + + async create(account: Account, transaction?: Transaction): Promise { + const accountData = this._mapper.mapToPersistence(account); + await this._save(AccountModel, account.id, accountData, {}, transaction); + } + + async update(account: Account, transaction?: Transaction): Promise { + const accountData = this._mapper.mapToPersistence(account); + await this._save(AccountModel, account.id, accountData, {}, transaction); + } } const accountRepository = new AccountRepository(accountMapper); diff --git a/apps/server/src/contexts/accounts/infraestructure/sequelize/index.ts b/apps/server/src/contexts/accounts/infraestructure/sequelize/index.ts index 40efc27d..4cee2745 100644 --- a/apps/server/src/contexts/accounts/infraestructure/sequelize/index.ts +++ b/apps/server/src/contexts/accounts/infraestructure/sequelize/index.ts @@ -1,8 +1,10 @@ -import { IAccountRepository } from "@contexts/accounts/domain/repositories/company-repository.interface"; +import { IAccountRepository } from "@contexts/accounts/domain/repositories/account-repository.interface"; import { accountRepository } from "./account.repository"; export * from "./account.model"; +export * from "./account.repository"; + export const createAccountRepository = (): IAccountRepository => { return accountRepository; }; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/create-account/create-account.controller.ts b/apps/server/src/contexts/accounts/presentation/controllers/create-account/create-account.controller.ts new file mode 100644 index 00000000..0a88c280 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/create-account/create-account.controller.ts @@ -0,0 +1,45 @@ +import { UniqueID } from "@common/domain"; +import { ExpressController } from "@common/presentation"; +import { CreateAccountUseCase } from "../../../application"; +import { ICreateAccountRequestDTO } from "../../dto"; +import { ICreateAccountPresenter } from "./create-account.presenter"; + +export class CreateAccountController extends ExpressController { + public constructor( + private readonly createAccount: CreateAccountUseCase, + private readonly presenter: ICreateAccountPresenter + ) { + super(); + } + + protected async executeImpl() { + const createDTO: ICreateAccountRequestDTO = this.req.body; + + // Validar ID + const accountIdOrError = UniqueID.create(createDTO.id); + if (accountIdOrError.isFailure) return this.invalidInputError("Account ID not valid"); + + const accountOrError = await this.createAccount.execute(accountIdOrError.data, createDTO); + + if (accountOrError.isFailure) { + return this.handleError(accountOrError.error); + } + + return this.ok(this.presenter.toDTO(accountOrError.data)); + } + + private handleError(error: Error) { + const message = error.message; + + if ( + message.includes("Database connection lost") || + message.includes("Database request timed out") + ) { + return this.unavailableError( + "Database service is currently unavailable. Please try again later." + ); + } + + return this.conflictError(message); + } +} diff --git a/apps/server/src/contexts/accounts/presentation/controllers/create-account/create-account.presenter.ts b/apps/server/src/contexts/accounts/presentation/controllers/create-account/create-account.presenter.ts new file mode 100644 index 00000000..c957d6af --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/create-account/create-account.presenter.ts @@ -0,0 +1,37 @@ +import { ensureBoolean, ensureNumber, ensureString } from "@common/helpers"; +import { Account } from "@contexts/accounts/domain"; +import { ICreateAccountResponseDTO } from "../../dto"; + +export interface ICreateAccountPresenter { + toDTO: (account: Account) => ICreateAccountResponseDTO; +} + +export const createAccountPresenter: ICreateAccountPresenter = { + toDTO: (account: Account): ICreateAccountResponseDTO => ({ + id: ensureString(account.id.toString()), + + is_freelancer: ensureBoolean(account.isFreelancer), + name: ensureString(account.name), + trade_name: ensureString(account.tradeName.getOrUndefined()), + tin: ensureString(account.tin.toString()), + + street: ensureString(account.address.street), + city: ensureString(account.address.city), + state: ensureString(account.address.state), + postal_code: ensureString(account.address.postalCode), + country: ensureString(account.address.country), + + email: ensureString(account.email.toString()), + phone: ensureString(account.phone.toString()), + fax: ensureString(account.fax.getOrUndefined()?.toString()), + website: ensureString(account.website.getOrUndefined()), + + legal_record: ensureString(account.legalRecord), + + default_tax: ensureNumber(account.defaultTax), + status: ensureString(account.isActive ? "active" : "inactive"), + lang_code: ensureString(account.langCode), + currency_code: ensureString(account.currencyCode), + logo: ensureString(account.logo.getOrUndefined()), + }), +}; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/create-account/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/create-account/index.ts new file mode 100644 index 00000000..ef5d2184 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/create-account/index.ts @@ -0,0 +1,16 @@ +import { SequelizeTransactionManager } from "@common/infrastructure"; +import { CreateAccountUseCase } from "@contexts/accounts/application/create-account.use-case"; +import { AccountService } from "@contexts/accounts/domain"; +import { accountRepository } from "@contexts/accounts/infraestructure"; +import { CreateAccountController } from "./create-account.controller"; +import { createAccountPresenter } from "./create-account.presenter"; + +export const createAccountController = () => { + const transactionManager = new SequelizeTransactionManager(); + const accountService = new AccountService(accountRepository); + + const useCase = new CreateAccountUseCase(accountService, transactionManager); + const presenter = createAccountPresenter; + + return new CreateAccountController(useCase, presenter); +}; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.controller.ts b/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.controller.ts new file mode 100644 index 00000000..0dc89f71 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.controller.ts @@ -0,0 +1,44 @@ +import { UniqueID } from "@common/domain"; +import { ExpressController } from "@common/presentation"; +import { GetAccountsUseCase } from "@contexts/accounts/application"; +import { IGetAccountPresenter } from "./get-account.presenter"; + +export class GetAccountController extends ExpressController { + public constructor( + private readonly getAccount: GetAccountsUseCase, + private readonly presenter: IGetAccountPresenter + ) { + super(); + } + + protected async executeImpl() { + const { accountId } = this.req.params; + + // Validar ID + const accountIdOrError = UniqueID.create(accountId); + if (accountIdOrError.isFailure) return this.invalidInputError("Account ID not valid"); + + const accountOrError = await this.getAccount.execute(accountIdOrError.data); + + if (accountOrError.isFailure) { + return this.handleError(accountOrError.error); + } + + return this.ok(this.presenter.toDTO(accountOrError.data)); + } + + private handleError(error: Error) { + const message = error.message; + + if ( + message.includes("Database connection lost") || + message.includes("Database request timed out") + ) { + return this.unavailableError( + "Database service is currently unavailable. Please try again later." + ); + } + + return this.conflictError(message); + } +} diff --git a/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.presenter.ts b/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.presenter.ts new file mode 100644 index 00000000..6c8adf4c --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.presenter.ts @@ -0,0 +1,37 @@ +import { ensureBoolean, ensureNumber, ensureString } from "@common/helpers"; +import { Account } from "@contexts/accounts/domain"; +import { IGetAccountResponseDTO } from "../../dto"; + +export interface IGetAccountPresenter { + toDTO: (account: Account) => IGetAccountResponseDTO; +} + +export const getAccountPresenter: IGetAccountPresenter = { + toDTO: (account: Account): IGetAccountResponseDTO => ({ + id: ensureString(account.id.toString()), + + is_freelancer: ensureBoolean(account.isFreelancer), + name: ensureString(account.name), + trade_name: ensureString(account.tradeName.getOrUndefined()), + tin: ensureString(account.tin.toString()), + + street: ensureString(account.address.street), + city: ensureString(account.address.city), + state: ensureString(account.address.state), + postal_code: ensureString(account.address.postalCode), + country: ensureString(account.address.country), + + email: ensureString(account.email.toString()), + phone: ensureString(account.phone.toString()), + fax: ensureString(account.fax.getOrUndefined()?.toString()), + website: ensureString(account.website.getOrUndefined()), + + legal_record: ensureString(account.legalRecord), + + default_tax: ensureNumber(account.defaultTax), + status: ensureString(account.isActive ? "active" : "inactive"), + lang_code: ensureString(account.langCode), + currency_code: ensureString(account.currencyCode), + logo: ensureString(account.logo.getOrUndefined()), + }), +}; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/get-account/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/get-account/index.ts new file mode 100644 index 00000000..5cabe3a7 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/get-account/index.ts @@ -0,0 +1,16 @@ +import { SequelizeTransactionManager } from "@common/infrastructure"; +import { GetAccountsUseCase } from "@contexts/accounts/application"; +import { AccountService } from "@contexts/accounts/domain"; +import { accountRepository } from "@contexts/accounts/infraestructure"; +import { GetAccountController } from "./get-account.controller"; +import { getAccountPresenter } from "./get-account.presenter"; + +export const getAccountController = () => { + const transactionManager = new SequelizeTransactionManager(); + const accountService = new AccountService(accountRepository); + + const useCase = new GetAccountsUseCase(accountService, transactionManager); + const presenter = getAccountPresenter; + + return new GetAccountController(useCase, presenter); +}; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/index.ts index a568d0fe..bf0d307c 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/index.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/index.ts @@ -1 +1,4 @@ +export * from "./create-account"; +export * from "./get-account"; export * from "./list-accounts"; +export * from "./update-account"; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts index 149ee8f7..4441e30c 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts @@ -1,7 +1,7 @@ import { SequelizeTransactionManager } from "@common/infrastructure"; -import { ListAccountsUseCase } from "@contexts/accounts/application/list-accounts/list-accounts.use-case"; -import { AccountService } from "@contexts/accounts/domain/services/company.service"; -import { accountRepository } from "@contexts/accounts/infraestructure/sequelize/account.repository"; +import { ListAccountsUseCase } from "@contexts/accounts/application"; +import { AccountService } from "@contexts/accounts/domain"; +import { accountRepository } from "@contexts/accounts/infraestructure"; import { ListAccountsController } from "./list-accounts.controller"; import { listAccountsPresenter } from "./list-accounts.presenter"; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.controller.ts b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.controller.ts index 281a796b..1231352f 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.controller.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.controller.ts @@ -1,5 +1,5 @@ import { ExpressController } from "@common/presentation"; -import { ListAccountsUseCase } from "@contexts/accounts/application/list-accounts/list-accounts.use-case"; +import { ListAccountsUseCase } from "@contexts/accounts/application"; import { IListAccountsPresenter } from "./list-accounts.presenter"; export class ListAccountsController extends ExpressController { diff --git a/apps/server/src/contexts/accounts/presentation/controllers/update-account/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/update-account/index.ts new file mode 100644 index 00000000..9081a2d7 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/update-account/index.ts @@ -0,0 +1,16 @@ +import { SequelizeTransactionManager } from "@common/infrastructure"; +import { UpdateAccountUseCase } from "@contexts/accounts/application"; +import { AccountService } from "@contexts/accounts/domain"; +import { accountRepository } from "@contexts/accounts/infraestructure"; +import { UpdateAccountController } from "./update-account.controller"; +import { updateAccountPresenter } from "./update-account.presenter"; + +export const updateAccountController = () => { + const transactionManager = new SequelizeTransactionManager(); + const accountService = new AccountService(accountRepository); + + const useCase = new UpdateAccountUseCase(accountService, transactionManager); + const presenter = updateAccountPresenter; + + return new UpdateAccountController(useCase, presenter); +}; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/update-account/update-account.controller.ts b/apps/server/src/contexts/accounts/presentation/controllers/update-account/update-account.controller.ts new file mode 100644 index 00000000..f633dd46 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/update-account/update-account.controller.ts @@ -0,0 +1,46 @@ +import { UniqueID } from "@common/domain"; +import { ExpressController } from "@common/presentation"; +import { UpdateAccountUseCase } from "@contexts/accounts/application/update-account.use-case"; +import { IUpdateAccountRequestDTO } from "../../dto"; +import { IUpdateAccountPresenter } from "./update-account.presenter"; + +export class UpdateAccountController extends ExpressController { + public constructor( + private readonly updateAccount: UpdateAccountUseCase, + private readonly presenter: IUpdateAccountPresenter + ) { + super(); + } + + protected async executeImpl() { + const { accountId } = this.req.params; + const updateDTO: IUpdateAccountRequestDTO = this.req.body; + + // Validar ID + const accountIdOrError = UniqueID.create(accountId); + if (accountIdOrError.isFailure) return this.invalidInputError("Account ID not valid"); + + const accountOrError = await this.updateAccount.execute(accountIdOrError.data, updateDTO); + + if (accountOrError.isFailure) { + return this.handleError(accountOrError.error); + } + + return this.ok(this.presenter.toDTO(accountOrError.data)); + } + + private handleError(error: Error) { + const message = error.message; + + if ( + message.includes("Database connection lost") || + message.includes("Database request timed out") + ) { + return this.unavailableError( + "Database service is currently unavailable. Please try again later." + ); + } + + return this.conflictError(message); + } +} diff --git a/apps/server/src/contexts/accounts/presentation/controllers/update-account/update-account.presenter.ts b/apps/server/src/contexts/accounts/presentation/controllers/update-account/update-account.presenter.ts new file mode 100644 index 00000000..b52553c4 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/update-account/update-account.presenter.ts @@ -0,0 +1,37 @@ +import { ensureBoolean, ensureNumber, ensureString } from "@common/helpers"; +import { Account } from "@contexts/accounts/domain"; +import { IUpdateAccountResponseDTO } from "../../dto"; + +export interface IUpdateAccountPresenter { + toDTO: (account: Account) => IUpdateAccountResponseDTO; +} + +export const updateAccountPresenter: IUpdateAccountPresenter = { + toDTO: (account: Account): IUpdateAccountResponseDTO => ({ + id: ensureString(account.id.toString()), + + is_freelancer: ensureBoolean(account.isFreelancer), + name: ensureString(account.name), + trade_name: ensureString(account.tradeName.getOrUndefined()), + tin: ensureString(account.tin.toString()), + + street: ensureString(account.address.street), + city: ensureString(account.address.city), + state: ensureString(account.address.state), + postal_code: ensureString(account.address.postalCode), + country: ensureString(account.address.country), + + email: ensureString(account.email.toString()), + phone: ensureString(account.phone.toString()), + fax: ensureString(account.fax.getOrUndefined()?.toString()), + website: ensureString(account.website.getOrUndefined()), + + legal_record: ensureString(account.legalRecord), + + default_tax: ensureNumber(account.defaultTax), + status: ensureString(account.isActive ? "active" : "inactive"), + lang_code: ensureString(account.langCode), + currency_code: ensureString(account.currencyCode), + logo: ensureString(account.logo.getOrUndefined()), + }), +}; diff --git a/apps/server/src/contexts/accounts/presentation/dto/accounts.request.dto.ts b/apps/server/src/contexts/accounts/presentation/dto/accounts.request.dto.ts index 96c10a0a..4bef8b3f 100644 --- a/apps/server/src/contexts/accounts/presentation/dto/accounts.request.dto.ts +++ b/apps/server/src/contexts/accounts/presentation/dto/accounts.request.dto.ts @@ -1 +1,52 @@ export interface IListAccountsRequestDTO {} + +export interface ICreateAccountRequestDTO { + id: string; + is_freelancer: boolean; + name: string; + trade_name: string; + tin: string; + + street: string; + city: string; + state: string; + postal_code: string; + country: string; + + email: string; + phone: string; + fax: string; + website: string; + + legal_record: string; + + default_tax: number; + lang_code: string; + currency_code: string; + logo: string; +} + +export interface IUpdateAccountRequestDTO { + is_freelancer: boolean; + name: string; + trade_name: string; + tin: string; + + street: string; + city: string; + state: string; + postal_code: string; + country: string; + + email: string; + phone: string; + fax: string; + website: string; + + legal_record: string; + + default_tax: number; + lang_code: string; + currency_code: string; + logo: string; +} diff --git a/apps/server/src/contexts/accounts/presentation/dto/accounts.response.dto.ts b/apps/server/src/contexts/accounts/presentation/dto/accounts.response.dto.ts index aa59d914..2ac225bd 100644 --- a/apps/server/src/contexts/accounts/presentation/dto/accounts.response.dto.ts +++ b/apps/server/src/contexts/accounts/presentation/dto/accounts.response.dto.ts @@ -25,3 +25,90 @@ export interface IListAccountsResponseDTO { currency_code: string; logo: string; } + +export interface IGetAccountResponseDTO { + id: string; + + is_freelancer: boolean; + name: string; + trade_name: string; + tin: string; + + street: string; + city: string; + state: string; + postal_code: string; + country: string; + + email: string; + phone: string; + fax: string; + website: string; + + legal_record: string; + + default_tax: number; + status: string; + lang_code: string; + currency_code: string; + logo: string; +} + +export interface ICreateAccountResponseDTO { + id: string; + + is_freelancer: boolean; + name: string; + trade_name: string; + tin: string; + + street: string; + city: string; + state: string; + postal_code: string; + country: string; + + email: string; + phone: string; + fax: string; + website: string; + + legal_record: string; + + default_tax: number; + status: string; + lang_code: string; + currency_code: string; + logo: string; +} + +// Inferir el tipo en TypeScript desde el esquema Zod +//export type IUpdateAcccountResponseDTO = z.infer; + +export interface IUpdateAccountResponseDTO { + id: string; + + is_freelancer: boolean; + name: string; + trade_name: string; + tin: string; + + street: string; + city: string; + state: string; + postal_code: string; + country: string; + + email: string; + phone: string; + fax: string; + website: string; + + legal_record: string; + + default_tax: number; + status: string; + lang_code: string; + currency_code: string; + logo: string; +} diff --git a/apps/server/src/contexts/accounts/presentation/dto/accounts.validation.dto.ts b/apps/server/src/contexts/accounts/presentation/dto/accounts.validation.dto.ts index 44c98e72..7612fe8c 100644 --- a/apps/server/src/contexts/accounts/presentation/dto/accounts.validation.dto.ts +++ b/apps/server/src/contexts/accounts/presentation/dto/accounts.validation.dto.ts @@ -1,3 +1,87 @@ import { z } from "zod"; export const ListAccountsSchema = z.object({}); + +export const IGetAcccountResponseDTOSchema = z.object({ + id: z.string(), + + is_freelancer: z.boolean(), + name: z.string(), + trade_name: z.string(), + tin: z.string(), + + street: z.string(), + city: z.string(), + state: z.string(), + postal_code: z.string(), + country: z.string(), + + email: z.string().email(), // Validación específica para email + phone: z.string(), + fax: z.string(), + website: z.string().url(), // Validación específica para URL + + legal_record: z.string(), + + default_tax: z.number(), + status: z.string(), + lang_code: z.string(), + currency_code: z.string(), + logo: z.string(), +}); + +export const ICreateAcccountResponseDTOSchema = z.object({ + id: z.string(), + + is_freelancer: z.boolean(), + name: z.string(), + trade_name: z.string(), + tin: z.string(), + + street: z.string(), + city: z.string(), + state: z.string(), + postal_code: z.string(), + country: z.string(), + + email: z.string().email(), // Validación específica para email + phone: z.string(), + fax: z.string(), + website: z.string().url(), // Validación específica para URL + + legal_record: z.string(), + + default_tax: z.number(), + status: z.string(), + lang_code: z.string(), + currency_code: z.string(), + logo: z.string(), +}); + +export const IUpdateAcccountResponseDTOSchema = z.object({ + id: z.string(), + + is_freelancer: z.boolean(), + name: z.string(), + trade_name: z.string(), + tin: z.string(), + + street: z.string(), + city: z.string(), + state: z.string(), + postal_code: z.string(), + country: z.string(), + + email: z.string().email(), // Validación específica para email + phone: z.string(), + fax: z.string(), + website: z.string().url(), // Validación específica para URL + + legal_record: z.string(), + + default_tax: z.number(), + status: z.string(), + lang_code: z.string(), + currency_code: z.string(), + logo: z.string(), +}); diff --git a/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.model.ts b/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.model.ts index 908a58b8..7dad8e28 100644 --- a/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.model.ts +++ b/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.model.ts @@ -67,6 +67,7 @@ export default (sequelize: Sequelize) => { trade_name: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, }, tin: { type: DataTypes.STRING, @@ -108,10 +109,12 @@ export default (sequelize: Sequelize) => { fax: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, }, website: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, validate: { isUrl: true, }, diff --git a/apps/server/src/contexts/customer-billing/application/customer-invoices/index.ts b/apps/server/src/contexts/customer-billing/application/customer-invoices/index.ts deleted file mode 100644 index 960446a8..00000000 --- a/apps/server/src/contexts/customer-billing/application/customer-invoices/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./get-customer-invoice.use-case"; diff --git a/apps/server/src/contexts/customer-billing/application/customers/index.ts b/apps/server/src/contexts/customer-billing/application/customers/index.ts deleted file mode 100644 index 1bcf6e04..00000000 --- a/apps/server/src/contexts/customer-billing/application/customers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./list-customers"; diff --git a/apps/server/src/contexts/customer-billing/application/customers/list-customers/index.ts b/apps/server/src/contexts/customer-billing/application/customers/list-customers/index.ts deleted file mode 100644 index 052600c9..00000000 --- a/apps/server/src/contexts/customer-billing/application/customers/list-customers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./list-customers.use-case"; diff --git a/apps/server/src/contexts/customer-billing/application/customers/list-customers/list-customers.use-case.ts b/apps/server/src/contexts/customer-billing/application/customers/list-customers/list-customers.use-case.ts deleted file mode 100644 index e15a4bf2..00000000 --- a/apps/server/src/contexts/customer-billing/application/customers/list-customers/list-customers.use-case.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Collection, Result } from "@common/helpers"; -import { ITransactionManager } from "@common/infrastructure/database"; -import { Customer } from "@contexts/customer-billing/domain/aggregates"; -import { ICustomerService } from "@contexts/customer-billing/domain/services"; - -export class ListCustomersUseCase { - constructor( - private readonly customerService: ICustomerService, - private readonly transactionManager: ITransactionManager - ) {} - - public execute(): Promise, Error>> { - return this.transactionManager.complete((transaction) => { - return this.customerService.findCustomer(transaction); - }); - } -} diff --git a/apps/server/src/contexts/customer-billing/application/customer-invoices/get-customer-invoice.use-case.ts b/apps/server/src/contexts/customer-billing/application/get-customer-invoice.use-case.ts similarity index 100% rename from apps/server/src/contexts/customer-billing/application/customer-invoices/get-customer-invoice.use-case.ts rename to apps/server/src/contexts/customer-billing/application/get-customer-invoice.use-case.ts diff --git a/apps/server/src/contexts/customer-billing/application/index.ts b/apps/server/src/contexts/customer-billing/application/index.ts index e48a5e9f..c57663bd 100644 --- a/apps/server/src/contexts/customer-billing/application/index.ts +++ b/apps/server/src/contexts/customer-billing/application/index.ts @@ -1,2 +1,2 @@ -export * from "./customer-invoices"; -export * from "./customers"; +export * from "./get-customer-invoice.use-case"; +export * from "./list-customer-invoices-use-case"; diff --git a/apps/server/src/contexts/customer-billing/application/list-customer-invoices-use-case.ts b/apps/server/src/contexts/customer-billing/application/list-customer-invoices-use-case.ts new file mode 100644 index 00000000..3d6c9d7c --- /dev/null +++ b/apps/server/src/contexts/customer-billing/application/list-customer-invoices-use-case.ts @@ -0,0 +1,16 @@ +import { Collection, Result } from "@common/helpers"; +import { ITransactionManager } from "@common/infrastructure/database"; +import { CustomerInvoice, ICustomerInvoiceService } from "../domain"; + +export class ListCustomerInvoicesUseCase { + constructor( + private readonly invoiceService: ICustomerInvoiceService, + private readonly transactionManager: ITransactionManager + ) {} + + public execute(): Promise, Error>> { + return this.transactionManager.complete((transaction) => { + return this.invoiceService.findCustomerInvoices(transaction); + }); + } +} diff --git a/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice.ts b/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice.ts index 9b45f932..b4fb844c 100644 --- a/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice.ts +++ b/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice.ts @@ -1,5 +1,5 @@ import { AggregateRoot, UniqueID, UtcDate } from "@common/domain"; -import { Result } from "@common/helpers"; +import { Maybe, Result } from "@common/helpers"; import { Customer, CustomerInvoiceItem } from "../entities"; import { InvoiceStatus } from "../value-objetcs"; @@ -8,7 +8,7 @@ export interface ICustomerInvoiceProps { issueDate: UtcDate; invoiceNumber: string; invoiceType: string; - invoiceCustomerReference: string; + invoiceCustomerReference: Maybe; customer: Customer; items: CustomerInvoiceItem[]; @@ -20,7 +20,7 @@ export interface ICustomerInvoice { issueDate: UtcDate; invoiceNumber: string; invoiceType: string; - invoiceCustomerReference: string; + invoiceCustomerReference: Maybe; customer: Customer; items: CustomerInvoiceItem[]; @@ -63,7 +63,7 @@ export class CustomerInvoice return this.props.invoiceType; } - get invoiceCustomerReference(): string { + get invoiceCustomerReference(): Maybe { return this.props.invoiceCustomerReference; } diff --git a/apps/server/src/contexts/customer-billing/domain/repositories/index.ts b/apps/server/src/contexts/customer-billing/domain/repositories/index.ts index f7ab9f55..d149dde6 100644 --- a/apps/server/src/contexts/customer-billing/domain/repositories/index.ts +++ b/apps/server/src/contexts/customer-billing/domain/repositories/index.ts @@ -1,2 +1 @@ export * from "./customer-invoice-repository.interface"; -export * from "./customer-repository.interface"; diff --git a/apps/server/src/contexts/customer-billing/domain/services/index.ts b/apps/server/src/contexts/customer-billing/domain/services/index.ts index 04045c87..9927fb2f 100644 --- a/apps/server/src/contexts/customer-billing/domain/services/index.ts +++ b/apps/server/src/contexts/customer-billing/domain/services/index.ts @@ -1,4 +1,2 @@ export * from "./customer-invoice-service.interface"; export * from "./customer-invoice.service"; -export * from "./customer-service.interface"; -export * from "./customer.service"; diff --git a/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer-invoice.mapper.ts b/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer-invoice.mapper.ts index 43e1921c..8e72d990 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer-invoice.mapper.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer-invoice.mapper.ts @@ -1,5 +1,5 @@ import { PostalAddress, TINNumber, UniqueID, UtcDate } from "@common/domain"; -import { Result } from "@common/helpers"; +import { Maybe, Result } from "@common/helpers"; import { ISequelizeMapper, MapperParamsType, @@ -70,7 +70,7 @@ export class CustomerInvoiceMapper issueDate: issueDateOrError.data, invoiceNumber: source.invoice_number, invoiceType: source.invoice_type, - invoiceCustomerReference: source.invoice_customer_reference, + invoiceCustomerReference: Maybe.fromNullable(source.invoice_customer_reference), customer: customerOrError.data, items: [], }, @@ -89,7 +89,7 @@ export class CustomerInvoiceMapper issue_date: source.issueDate.toDateString(), invoice_number: source.invoiceNumber, invoice_type: source.invoiceType, - invoice_customer_reference: source.invoiceCustomerReference, + invoice_customer_reference: source.invoiceCustomerReference.getOrUndefined(), lang_code: "es", currency_code: "EUR", diff --git a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice-item.model.ts b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice-item.model.ts index f05ae494..8f52b00f 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice-item.model.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice-item.model.ts @@ -56,6 +56,7 @@ export default (sequelize: Sequelize) => { id_article: { type: DataTypes.BIGINT().UNSIGNED, allowNull: true, + defaultValue: null, }, position: { type: new DataTypes.MEDIUMINT(), @@ -65,26 +66,32 @@ export default (sequelize: Sequelize) => { description: { type: new DataTypes.TEXT(), allowNull: true, + defaultValue: null, }, quantity: { type: DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, unit_price: { type: new DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, subtotal_price: { type: new DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, discount: { type: new DataTypes.SMALLINT(), allowNull: true, + defaultValue: null, }, total_price: { type: new DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, }, { diff --git a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice.model.ts b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice.model.ts index 62a98346..39aa7ecf 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice.model.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice.model.ts @@ -126,6 +126,7 @@ export default (sequelize: Sequelize) => { customer_id: { type: new DataTypes.UUID(), + allowNull: false, }, customer_name: { @@ -146,6 +147,7 @@ export default (sequelize: Sequelize) => { customer_street2: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, }, customer_city: { @@ -168,56 +170,67 @@ export default (sequelize: Sequelize) => { subtotal_price: { type: new DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, discount: { type: new DataTypes.SMALLINT(), allowNull: true, + defaultValue: null, }, discount_price: { type: new DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, before_tax_price: { type: new DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, tax: { type: new DataTypes.SMALLINT(), allowNull: true, + defaultValue: null, }, tax_price: { type: new DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, total_price: { type: new DataTypes.BIGINT(), allowNull: true, + defaultValue: null, }, notes: { type: DataTypes.TEXT, allowNull: true, + defaultValue: null, }, integrity_hash: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, comment: "Hash criptográfico para asegurar integridad", }, previous_invoice_id: { type: DataTypes.UUID, allowNull: true, + defaultValue: null, comment: "Referencia a la factura anterior (si aplica)", }, signed_at: { type: DataTypes.DATE, allowNull: true, + defaultValue: null, comment: "Fecha en que la factura fue firmada digitalmente", }, }, diff --git a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer.model.ts b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer.model.ts index bc63a03b..a3f673a6 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer.model.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer.model.ts @@ -67,6 +67,7 @@ export default (sequelize: Sequelize) => { trade_name: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, }, tin: { type: DataTypes.STRING, @@ -108,10 +109,12 @@ export default (sequelize: Sequelize) => { fax: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, }, website: { type: DataTypes.STRING, allowNull: true, + defaultValue: null, validate: { isUrl: true, }, diff --git a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer.repository.ts b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer.repository.ts deleted file mode 100644 index db511f73..00000000 --- a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer.repository.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { EmailAddress, UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { SequelizeRepository } from "@common/infrastructure"; -import { Customer, ICustomerRepository } from "@contexts/customer-billing/domain"; -import { Transaction } from "sequelize"; -import { customerMapper, ICustomerMapper } from "../mappers"; -import { CustomerModel } from "./customer.model"; - -class CustomerRepository extends SequelizeRepository implements ICustomerRepository { - private readonly _mapper!: ICustomerMapper; - - /** - * 🔹 Función personalizada para mapear errores de unicidad en autenticación - */ - private _customErrorMapper(error: Error): string | null { - if (error.name === "SequelizeUniqueConstraintError") { - return "Customer with this email already exists"; - } - - return null; - } - - constructor(mapper: ICustomerMapper) { - super(); - this._mapper = mapper; - } - - async findAll(transaction?: Transaction): Promise, Error>> { - try { - const rawCustomers: any = await this._findAll(CustomerModel, {}, transaction); - - if (!rawCustomers === true) { - return Result.fail(new Error("Customer with email not exists")); - } - - return this._mapper.mapArrayToDomain(rawCustomers); - } catch (error: any) { - return this._handleDatabaseError(error, this._customErrorMapper); - } - } - - async findById(id: UniqueID, transaction?: Transaction): Promise> { - try { - const rawCustomer: any = await this._getById(CustomerModel, id, {}, transaction); - - if (!rawCustomer === true) { - return Result.fail(new Error(`Customer with id ${id.toString()} not exists`)); - } - - return this._mapper.mapToDomain(rawCustomer); - } catch (error: any) { - return this._handleDatabaseError(error, this._customErrorMapper); - } - } - - async findByEmail( - email: EmailAddress, - transaction?: Transaction - ): Promise> { - try { - const rawCustomer: any = await this._getBy( - CustomerModel, - "email", - email.toString(), - {}, - transaction - ); - - if (!rawCustomer === true) { - return Result.fail(new Error(`Customer with email ${email.toString()} not exists`)); - } - - return this._mapper.mapToDomain(rawCustomer); - } catch (error: any) { - return this._handleDatabaseError(error, this._customErrorMapper); - } - } -} - -const customerRepository = new CustomerRepository(customerMapper); -export { customerRepository }; diff --git a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/index.ts b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/index.ts index 8092c8b0..81d27fd3 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/index.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/index.ts @@ -1,17 +1,15 @@ -import { ICustomerRepository } from "@contexts/customer-billing/domain"; import { ICustomerInvoiceRepository } from "@contexts/customer-billing/domain/"; -import { customerRepository } from "./customer.repository"; - -export * from "./customer.model"; -export * from "./customer.repository"; +import { customerInvoiceRepository } from "./customer-invoice.repository"; export * from "./customer-invoice.model"; +export * from "./customer.model"; + export * from "./customer-invoice.repository"; -export const createCustomerRepository = (): ICustomerRepository => { +/*export const createCustomerRepository = (): ICustomerRepository => { return customerRepository; -}; +};*/ export const createCustomerInvoiceRepository = (): ICustomerInvoiceRepository => { - return customerRepository; + return customerInvoiceRepository; }; diff --git a/apps/server/src/contexts/customer-billing/presentation/controllers/customer-invoices/list/index.ts b/apps/server/src/contexts/customer-billing/presentation/controllers/customer-invoices/list/index.ts index 98857d6a..72c4bcac 100644 --- a/apps/server/src/contexts/customer-billing/presentation/controllers/customer-invoices/list/index.ts +++ b/apps/server/src/contexts/customer-billing/presentation/controllers/customer-invoices/list/index.ts @@ -1,6 +1,7 @@ import { SequelizeTransactionManager } from "@common/infrastructure"; import { CustomerInvoiceService } from "@contexts/customer-billing/domain"; import { customerInvoiceRepository } from "@contexts/customer-billing/infraestructure"; +import { ListCustomerInvoicesUseCase } from "../../../../application"; import { ListCustomerInvoicesController } from "./list-customer-invoices.controller"; import { listCustomerInvoicesPresenter } from "./list-customer-invoices.presenter"; diff --git a/apps/server/src/contexts/customer-billing/presentation/controllers/customer-invoices/list/list-customer-invoices.presenter.ts b/apps/server/src/contexts/customer-billing/presentation/controllers/customer-invoices/list/list-customer-invoices.presenter.ts index a67bb67d..aa752ba0 100644 --- a/apps/server/src/contexts/customer-billing/presentation/controllers/customer-invoices/list/list-customer-invoices.presenter.ts +++ b/apps/server/src/contexts/customer-billing/presentation/controllers/customer-invoices/list/list-customer-invoices.presenter.ts @@ -1,4 +1,4 @@ -import { Collection, ensureBoolean, ensureNumber, ensureString } from "@common/helpers"; +import { Collection, ensureString } from "@common/helpers"; import { CustomerInvoice } from "@contexts/customer-billing/domain"; import { IListCustomerInvoicesResponseDTO } from "../../../dto"; @@ -8,10 +8,10 @@ export interface IListCustomerInvoicesPresenter { } export const listCustomerInvoicesPresenter: IListCustomerInvoicesPresenter = { - toDTO: (customers: Collection): IListCustomerInvoicesResponseDTO[] => - customers.map((customer) => ({ + toDTO: (invoice: Collection): IListCustomerInvoicesResponseDTO[] => + invoice.map((customer) => ({ id: ensureString(customer.id.toString()), - reference: ensureString(customer.reference), + /*reference: ensureString(customer.), is_freelancer: ensureBoolean(customer.isFreelancer), name: ensureString(customer.name), @@ -34,6 +34,6 @@ export const listCustomerInvoicesPresenter: IListCustomerInvoicesPresenter = { default_tax: ensureNumber(customer.defaultTax), status: ensureString(customer.isActive ? "active" : "inactive"), lang_code: ensureString(customer.langCode), - currency_code: ensureString(customer.currencyCode), + currency_code: ensureString(customer.currencyCode),*/ })), }; diff --git a/apps/server/src/contexts/customer-billing/presentation/controllers/customers/index.ts b/apps/server/src/contexts/customer-billing/presentation/controllers/customers/index.ts deleted file mode 100644 index 491ccf0c..00000000 --- a/apps/server/src/contexts/customer-billing/presentation/controllers/customers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./list"; diff --git a/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/index.ts b/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/index.ts deleted file mode 100644 index 33c7e19f..00000000 --- a/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { SequelizeTransactionManager } from "@common/infrastructure"; -import { ListCustomersUseCase } from "@contexts/customer-billing/application/customers/list-customers"; -import { CustomerService } from "@contexts/customer-billing/domain"; -import { customerRepository } from "@contexts/customer-billing/infraestructure"; -import { ListCustomersController } from "./list-customers.controller"; -import { listCustomersPresenter } from "./list-customers.presenter"; - -export const listCustomersController = () => { - const transactionManager = new SequelizeTransactionManager(); - const customerService = new CustomerService(customerRepository); - - const useCase = new ListCustomersUseCase(customerService, transactionManager); - const presenter = listCustomersPresenter; - - return new ListCustomersController(useCase, presenter); -}; diff --git a/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/list-customers.controller.ts b/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/list-customers.controller.ts deleted file mode 100644 index 8dc424fd..00000000 --- a/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/list-customers.controller.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ExpressController } from "@common/presentation"; -import { ListCustomersUseCase } from "@contexts/customer-billing/application"; -import { IListCustomersPresenter } from "./list-customers.presenter"; - -export class ListCustomersController extends ExpressController { - public constructor( - private readonly listCustomers: ListCustomersUseCase, - private readonly presenter: IListCustomersPresenter - ) { - super(); - } - - protected async executeImpl() { - const customersOrError = await this.listCustomers.execute(); - - if (customersOrError.isFailure) { - return this.handleError(customersOrError.error); - } - - return this.ok(this.presenter.toDTO(customersOrError.data)); - } - - private handleError(error: Error) { - const message = error.message; - - if ( - message.includes("Database connection lost") || - message.includes("Database request timed out") - ) { - return this.unavailableError( - "Database service is currently unavailable. Please try again later." - ); - } - - return this.conflictError(message); - } -} diff --git a/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/list-customers.presenter.ts b/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/list-customers.presenter.ts deleted file mode 100644 index dc3e7934..00000000 --- a/apps/server/src/contexts/customer-billing/presentation/controllers/customers/list/list-customers.presenter.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Collection, ensureBoolean, ensureNumber, ensureString } from "@common/helpers"; -import { Customer } from "@contexts/customer-billing/domain"; -import { IListCustomersResponseDTO } from "../../../dto"; - -export interface IListCustomersPresenter { - toDTO: (customers: Collection) => IListCustomersResponseDTO[]; -} - -export const listCustomersPresenter: IListCustomersPresenter = { - toDTO: (customers: Collection): IListCustomersResponseDTO[] => - customers.map((customer) => ({ - id: ensureString(customer.id.toString()), - reference: ensureString(customer.reference), - - is_freelancer: ensureBoolean(customer.isFreelancer), - name: ensureString(customer.name), - trade_name: ensureString(customer.tradeName.getValue()), - tin: ensureString(customer.tin.toString()), - - street: ensureString(customer.address.street), - city: ensureString(customer.address.city), - state: ensureString(customer.address.state), - postal_code: ensureString(customer.address.postalCode), - country: ensureString(customer.address.country), - - email: ensureString(customer.email.toString()), - phone: ensureString(customer.phone.toString()), - fax: ensureString(customer.fax.getValue()?.toString()), - website: ensureString(customer.website.getValue()), - - legal_record: ensureString(customer.legalRecord), - - default_tax: ensureNumber(customer.defaultTax), - status: ensureString(customer.isActive ? "active" : "inactive"), - lang_code: ensureString(customer.langCode), - currency_code: ensureString(customer.currencyCode), - })), -}; diff --git a/apps/server/src/contexts/customer-billing/presentation/controllers/index.ts b/apps/server/src/contexts/customer-billing/presentation/controllers/index.ts index e48a5e9f..80965b66 100644 --- a/apps/server/src/contexts/customer-billing/presentation/controllers/index.ts +++ b/apps/server/src/contexts/customer-billing/presentation/controllers/index.ts @@ -1,2 +1 @@ export * from "./customer-invoices"; -export * from "./customers"; diff --git a/apps/server/src/contexts/customer-billing/presentation/dto/customer-invoices/customer-invoices.response.dto.ts b/apps/server/src/contexts/customer-billing/presentation/dto/customer-invoices/customer-invoices.response.dto.ts index 1f27e65b..53d54391 100644 --- a/apps/server/src/contexts/customer-billing/presentation/dto/customer-invoices/customer-invoices.response.dto.ts +++ b/apps/server/src/contexts/customer-billing/presentation/dto/customer-invoices/customer-invoices.response.dto.ts @@ -1,6 +1,6 @@ export interface IListCustomerInvoicesResponseDTO { id: string; - reference: string; + /*reference: string; is_freelancer: boolean; name: string; @@ -23,7 +23,7 @@ export interface IListCustomerInvoicesResponseDTO { default_tax: number; status: string; lang_code: string; - currency_code: string; + currency_code: string;*/ } export interface IGetCustomerInvoiceResponseDTO {} diff --git a/apps/server/src/contexts/customer-billing/presentation/index.ts b/apps/server/src/contexts/customer-billing/presentation/index.ts index 77c53837..a123289d 100644 --- a/apps/server/src/contexts/customer-billing/presentation/index.ts +++ b/apps/server/src/contexts/customer-billing/presentation/index.ts @@ -1,2 +1,2 @@ -export * from "./controllers/customers"; +export * from "./controllers"; export * from "./dto"; diff --git a/apps/server/src/routes/accounts.routes.ts b/apps/server/src/routes/accounts.routes.ts index f98dfc79..35fbd1b3 100644 --- a/apps/server/src/routes/accounts.routes.ts +++ b/apps/server/src/routes/accounts.routes.ts @@ -1,6 +1,16 @@ import { validateRequestDTO } from "@common/presentation"; -import { ListAccountsSchema } from "@contexts/accounts/presentation"; -import { listAccountsController } from "@contexts/accounts/presentation/controllers/list-accounts"; +import { + ICreateAcccountResponseDTOSchema, + IGetAcccountResponseDTOSchema, + IUpdateAcccountResponseDTOSchema, + ListAccountsSchema, +} from "@contexts/accounts/presentation"; +import { + createAccountController, + getAccountController, + listAccountsController, + updateAccountController, +} from "@contexts/accounts/presentation/controllers"; import { checkTabContext } from "@contexts/auth/infraestructure"; import { NextFunction, Request, Response, Router } from "express"; @@ -17,5 +27,35 @@ export const accountsRouter = (appRouter: Router) => { } ); + routes.get( + "/:accountId", + validateRequestDTO(IGetAcccountResponseDTOSchema), + checkTabContext, + //checkUser, + (req: Request, res: Response, next: NextFunction) => { + getAccountController().execute(req, res, next); + } + ); + + routes.post( + "/", + validateRequestDTO(ICreateAcccountResponseDTOSchema), + checkTabContext, + //checkUser, + (req: Request, res: Response, next: NextFunction) => { + createAccountController().execute(req, res, next); + } + ); + + routes.put( + "/:accountId", + validateRequestDTO(IUpdateAcccountResponseDTOSchema), + checkTabContext, + //checkUser, + (req: Request, res: Response, next: NextFunction) => { + updateAccountController().execute(req, res, next); + } + ); + appRouter.use("/accounts", routes); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fde587c..1eec8378 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,9 +65,6 @@ importers: luxon: specifier: ^3.5.0 version: 3.5.0 - mariadb: - specifier: ^3.4.0 - version: 3.4.0 module-alias: specifier: ^2.2.3 version: 2.2.3 @@ -94,16 +91,13 @@ importers: version: 2.3.3 sequelize: specifier: ^6.37.5 - version: 6.37.5(mariadb@3.4.0)(mysql2@3.12.0) + version: 6.37.5(mysql2@3.12.0) shallow-equal-object: specifier: ^1.1.1 version: 1.1.1 ts-node: specifier: ^10.9.1 version: 10.9.2(@types/node@22.12.0)(typescript@5.7.3) - tsconfig-paths: - specifier: ^4.2.0 - version: 4.2.0 uuid: specifier: ^11.0.5 version: 11.0.5 @@ -201,6 +195,9 @@ importers: ts-node-dev: specifier: ^2.0.0 version: 2.0.0(@types/node@22.12.0)(typescript@5.7.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 typescript: specifier: ^5.7.3 version: 5.7.3 @@ -1289,9 +1286,6 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} - '@types/geojson@7946.0.16': - resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - '@types/glob@8.1.0': resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} @@ -3215,10 +3209,6 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - mariadb@3.4.0: - resolution: {integrity: sha512-hdRPcAzs+MTxK5VG1thBW18gGTlw6yWBe9YnLB65GLo7q0fO5DWsgomIevV/pXSaWRmD3qi6ka4oSFRTExRiEQ==} - engines: {node: '>= 14'} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5538,8 +5528,6 @@ snapshots: '@types/qs': 6.9.18 '@types/serve-static': 1.15.7 - '@types/geojson@7946.0.16': {} - '@types/glob@8.1.0': dependencies: '@types/minimatch': 5.1.2 @@ -8128,14 +8116,6 @@ snapshots: dependencies: tmpl: 1.0.5 - mariadb@3.4.0: - dependencies: - '@types/geojson': 7946.0.16 - '@types/node': 22.12.0 - denque: 2.1.0 - iconv-lite: 0.6.3 - lru-cache: 10.4.3 - math-intrinsics@1.1.0: {} media-typer@0.3.0: {} @@ -8746,7 +8726,7 @@ snapshots: sequelize-pool@7.1.0: {} - sequelize@6.37.5(mariadb@3.4.0)(mysql2@3.12.0): + sequelize@6.37.5(mysql2@3.12.0): dependencies: '@types/debug': 4.1.12 '@types/validator': 13.12.2 @@ -8765,7 +8745,6 @@ snapshots: validator: 13.12.0 wkx: 0.5.0 optionalDependencies: - mariadb: 3.4.0 mysql2: 3.12.0 transitivePeerDependencies: - supports-color