From 1aa64e4bd0e70fa587de1b14c14fa4ada1b5e790 Mon Sep 17 00:00:00 2001 From: Blxckbxll24 Date: Mon, 5 Oct 2026 14:11:22 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20endpoints=20at=C3=B3micos,=20?= =?UTF-8?q?auth=20de=20descuentos=20y=20limpiar=20deuda=20t=C3=A9cnica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/eslint.config.mjs | 2 +- apps/api/package.json | 1 - .../cash-movement/cash-movement.controller.ts | 13 +- .../cash-movement/cash-movement.repository.ts | 25 +- .../cash-movement/cash-movement.service.ts | 8 +- .../cash-movement-repository.interface.ts | 5 +- .../cash-movement-service.interface.ts | 5 +- .../cash-register-session.controller.ts | 17 +- .../cash-register-session.module.ts | 10 +- .../cash-register-session.service.ts | 8 +- ...h-register-session-repository.interface.ts | 4 +- ...cash-register-session-service.interface.ts | 4 +- .../modules/customer/customer.controller.ts | 27 ++- .../modules/customer/customer.repository.ts | 21 +- .../src/modules/customer/customer.service.ts | 12 +- .../customer-repository.interface.ts | 10 +- .../interfaces/customer-service.interface.ts | 10 +- .../inventory-movement.controller.ts | 10 +- .../inventory-movement.module.ts | 5 +- .../inventory-movement.repository.ts | 14 +- .../inventory-movement.service.ts | 29 ++- .../operating-day-repository.interface.ts | 10 +- .../operating-day-service.interface.ts | 10 +- .../operating-day/operating-day.controller.ts | 22 +- .../operating-day/operating-day.repository.ts | 27 ++- .../operating-day/operating-day.service.ts | 39 +++- .../product-repository.interface.ts | 21 +- .../interfaces/product-service.interface.ts | 19 +- .../src/modules/product/product.controller.ts | 16 ++ .../src/modules/product/product.repository.ts | 33 +++ .../src/modules/product/product.service.ts | 16 ++ .../modules/shop-cart/shop-cart.service.ts | 30 ++- .../_core/components/section-placeholder.tsx | 26 --- .../src/app/_core/components/theme-toggle.tsx | 26 +-- apps/web/src/app/_core/hooks/use-theme.tsx | 16 +- apps/web/src/app/_core/types/auth-user.ts | 10 +- .../src/app/admin/_components/user-menu.tsx | 17 +- apps/web/src/app/admin/clientes/page.tsx | 2 +- .../src/app/admin/inventario/[id]/page.tsx | 11 +- .../app/admin/inventario/categorias/page.tsx | 2 +- apps/web/src/app/admin/inventario/page.tsx | 2 +- .../app/admin/inventario/productos/page.tsx | 27 +-- .../src/app/admin/proveedores/[id]/page.tsx | 6 +- apps/web/src/app/admin/proveedores/page.tsx | 2 +- apps/web/src/app/admin/usuarios/page.tsx | 2 +- .../app/login/_components/password-form.tsx | 2 +- .../pos/_components/cash-register-control.tsx | 6 +- apps/web/src/app/pos/page.tsx | 164 ++++++++++++- package-lock.json | 219 +++++++----------- package.json | 6 + 50 files changed, 726 insertions(+), 303 deletions(-) delete mode 100644 apps/web/src/app/_core/components/section-placeholder.tsx diff --git a/apps/api/eslint.config.mjs b/apps/api/eslint.config.mjs index f7b89a5..34be11f 100644 --- a/apps/api/eslint.config.mjs +++ b/apps/api/eslint.config.mjs @@ -5,7 +5,7 @@ import tseslint from 'typescript-eslint'; export default tseslint.config( { - ignores: ['dist/**'], + ignores: ['dist/**', 'src/prisma/contract.d.ts'], }, eslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, diff --git a/apps/api/package.json b/apps/api/package.json index 2460de0..b455561 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -15,7 +15,6 @@ "@nestjs/common": "^12.0.3", "@nestjs/core": "^12.0.3", "@nestjs/platform-express": "^12.0.3", - "@prisma/client": "^7.10.0", "@prisma/orm-postgres": "^8.0.0-rc.13", "dotenv": "^17.4.2", "next": "^16.3.5", diff --git a/apps/api/src/modules/cash-movement/cash-movement.controller.ts b/apps/api/src/modules/cash-movement/cash-movement.controller.ts index 60a12d6..8150f96 100644 --- a/apps/api/src/modules/cash-movement/cash-movement.controller.ts +++ b/apps/api/src/modules/cash-movement/cash-movement.controller.ts @@ -1,4 +1,12 @@ -import { Body, Controller, Get, Inject, Param, ParseIntPipe, Post } from "@nestjs/common"; +import { + Body, + Controller, + Get, + Inject, + Param, + ParseIntPipe, + Post, +} from "@nestjs/common"; import type { CreateCashMovementInput } from "../../dtos/cash-movement.dto.js"; import { CASH_MOVEMENT_SERVICE } from "./interfaces/cash-movement-service.interface.js"; import type { ICashMovementService } from "./interfaces/cash-movement-service.interface.js"; @@ -6,7 +14,8 @@ import type { ICashMovementService } from "./interfaces/cash-movement-service.in @Controller("cash-movements") export class CashMovementController { constructor( - @Inject(CASH_MOVEMENT_SERVICE) private readonly cashMovementService: ICashMovementService, + @Inject(CASH_MOVEMENT_SERVICE) + private readonly cashMovementService: ICashMovementService, ) {} @Get() diff --git a/apps/api/src/modules/cash-movement/cash-movement.repository.ts b/apps/api/src/modules/cash-movement/cash-movement.repository.ts index d7231b8..7646360 100644 --- a/apps/api/src/modules/cash-movement/cash-movement.repository.ts +++ b/apps/api/src/modules/cash-movement/cash-movement.repository.ts @@ -1,11 +1,22 @@ import { Inject, Injectable } from "@nestjs/common"; import { DB } from "../../database/database.tokens.js"; import type { Database } from "../../prisma/db.js"; -import type { CashMovement, CashMovementKey } from "../../entities/cash-movement.entity.js"; +import type { + CashMovement, + CashMovementKey, +} from "../../entities/cash-movement.entity.js"; import type { CreateCashMovementInput } from "../../dtos/cash-movement.dto.js"; import type { ICashMovementRepository } from "./interfaces/cash-movement-repository.interface.js"; -const COLUMNS = ["id", "sessionId", "type", "amount", "reason", "userId", "createdAt"] as const; +const COLUMNS = [ + "id", + "sessionId", + "type", + "amount", + "reason", + "userId", + "createdAt", +] as const; @Injectable() export class CashMovementRepository implements ICashMovementRepository { @@ -16,11 +27,17 @@ export class CashMovementRepository implements ICashMovementRepository { } findAll(): Promise { - return this.model.select(...COLUMNS).all().toArray(); + return this.model + .select(...COLUMNS) + .all() + .toArray(); } findById(where: CashMovementKey): Promise { - return this.model.select(...COLUMNS).where(where).first(); + return this.model + .select(...COLUMNS) + .where(where) + .first(); } create(data: CreateCashMovementInput): Promise { diff --git a/apps/api/src/modules/cash-movement/cash-movement.service.ts b/apps/api/src/modules/cash-movement/cash-movement.service.ts index cbe5173..d207acf 100644 --- a/apps/api/src/modules/cash-movement/cash-movement.service.ts +++ b/apps/api/src/modules/cash-movement/cash-movement.service.ts @@ -1,5 +1,8 @@ import { Inject, Injectable, NotFoundException } from "@nestjs/common"; -import type { CashMovement, CashMovementKey } from "../../entities/cash-movement.entity.js"; +import type { + CashMovement, + CashMovementKey, +} from "../../entities/cash-movement.entity.js"; import type { CreateCashMovementInput } from "../../dtos/cash-movement.dto.js"; import { CASH_MOVEMENT_REPOSITORY, @@ -10,7 +13,8 @@ import type { ICashMovementService } from "./interfaces/cash-movement-service.in @Injectable() export class CashMovementService implements ICashMovementService { constructor( - @Inject(CASH_MOVEMENT_REPOSITORY) private readonly repository: ICashMovementRepository, + @Inject(CASH_MOVEMENT_REPOSITORY) + private readonly repository: ICashMovementRepository, ) {} findAll(): Promise { diff --git a/apps/api/src/modules/cash-movement/interfaces/cash-movement-repository.interface.ts b/apps/api/src/modules/cash-movement/interfaces/cash-movement-repository.interface.ts index 6582921..6cf4957 100644 --- a/apps/api/src/modules/cash-movement/interfaces/cash-movement-repository.interface.ts +++ b/apps/api/src/modules/cash-movement/interfaces/cash-movement-repository.interface.ts @@ -1,4 +1,7 @@ -import type { CashMovement, CashMovementKey } from "../../../entities/cash-movement.entity.js"; +import type { + CashMovement, + CashMovementKey, +} from "../../../entities/cash-movement.entity.js"; import type { CreateCashMovementInput } from "../../../dtos/cash-movement.dto.js"; export const CASH_MOVEMENT_REPOSITORY = Symbol("CASH_MOVEMENT_REPOSITORY"); diff --git a/apps/api/src/modules/cash-movement/interfaces/cash-movement-service.interface.ts b/apps/api/src/modules/cash-movement/interfaces/cash-movement-service.interface.ts index 0e70eb8..f832197 100644 --- a/apps/api/src/modules/cash-movement/interfaces/cash-movement-service.interface.ts +++ b/apps/api/src/modules/cash-movement/interfaces/cash-movement-service.interface.ts @@ -1,4 +1,7 @@ -import type { CashMovement, CashMovementKey } from "../../../entities/cash-movement.entity.js"; +import type { + CashMovement, + CashMovementKey, +} from "../../../entities/cash-movement.entity.js"; import type { CreateCashMovementInput } from "../../../dtos/cash-movement.dto.js"; export const CASH_MOVEMENT_SERVICE = Symbol("CASH_MOVEMENT_SERVICE"); diff --git a/apps/api/src/modules/cash-register-session/cash-register-session.controller.ts b/apps/api/src/modules/cash-register-session/cash-register-session.controller.ts index 5db5b14..8dc298f 100644 --- a/apps/api/src/modules/cash-register-session/cash-register-session.controller.ts +++ b/apps/api/src/modules/cash-register-session/cash-register-session.controller.ts @@ -1,4 +1,14 @@ -import { Body, Controller, Delete, Get, Inject, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; +import { + Body, + Controller, + Delete, + Get, + Inject, + Param, + ParseIntPipe, + Patch, + Post, +} from "@nestjs/common"; import type { CreateCashRegisterSessionInput, UpdateCashRegisterSessionInput, @@ -29,7 +39,10 @@ export class CashRegisterSessionController { } @Patch(":id") - update(@Param("id", ParseIntPipe) id: number, @Body() body: UpdateCashRegisterSessionInput) { + update( + @Param("id", ParseIntPipe) id: number, + @Body() body: UpdateCashRegisterSessionInput, + ) { return this.sessionService.update({ id }, body); } diff --git a/apps/api/src/modules/cash-register-session/cash-register-session.module.ts b/apps/api/src/modules/cash-register-session/cash-register-session.module.ts index 1f0bb3e..ff007e7 100644 --- a/apps/api/src/modules/cash-register-session/cash-register-session.module.ts +++ b/apps/api/src/modules/cash-register-session/cash-register-session.module.ts @@ -8,8 +8,14 @@ import { CashRegisterSessionService } from "./cash-register-session.service.js"; @Module({ controllers: [CashRegisterSessionController], providers: [ - { provide: CASH_REGISTER_SESSION_REPOSITORY, useClass: CashRegisterSessionRepository }, - { provide: CASH_REGISTER_SESSION_SERVICE, useClass: CashRegisterSessionService }, + { + provide: CASH_REGISTER_SESSION_REPOSITORY, + useClass: CashRegisterSessionRepository, + }, + { + provide: CASH_REGISTER_SESSION_SERVICE, + useClass: CashRegisterSessionService, + }, ], exports: [CASH_REGISTER_SESSION_SERVICE], }) diff --git a/apps/api/src/modules/cash-register-session/cash-register-session.service.ts b/apps/api/src/modules/cash-register-session/cash-register-session.service.ts index 594b51d..97f8d6a 100644 --- a/apps/api/src/modules/cash-register-session/cash-register-session.service.ts +++ b/apps/api/src/modules/cash-register-session/cash-register-session.service.ts @@ -31,9 +31,13 @@ export class CashRegisterSessionService super(sessionRepository); } - override async create(data: CreateCashRegisterSessionInput): Promise { + override async create( + data: CreateCashRegisterSessionInput, + ): Promise { const all = await this.sessionRepository.findAll(); - const alreadyOpen = all.some((s) => s.inventoryId === data.inventoryId && s.status === "open"); + const alreadyOpen = all.some( + (s) => s.inventoryId === data.inventoryId && s.status === "open", + ); if (alreadyOpen) { throw new BadRequestException("Ya hay una caja abierta en este almacén"); } diff --git a/apps/api/src/modules/cash-register-session/interfaces/cash-register-session-repository.interface.ts b/apps/api/src/modules/cash-register-session/interfaces/cash-register-session-repository.interface.ts index 272ac97..bc0d9d2 100644 --- a/apps/api/src/modules/cash-register-session/interfaces/cash-register-session-repository.interface.ts +++ b/apps/api/src/modules/cash-register-session/interfaces/cash-register-session-repository.interface.ts @@ -8,7 +8,9 @@ import type { UpdateCashRegisterSessionInput, } from "../../../dtos/cash-register-session.dto.js"; -export const CASH_REGISTER_SESSION_REPOSITORY = Symbol("CASH_REGISTER_SESSION_REPOSITORY"); +export const CASH_REGISTER_SESSION_REPOSITORY = Symbol( + "CASH_REGISTER_SESSION_REPOSITORY", +); export type ICashRegisterSessionRepository = IRepository< CashRegisterSession, diff --git a/apps/api/src/modules/cash-register-session/interfaces/cash-register-session-service.interface.ts b/apps/api/src/modules/cash-register-session/interfaces/cash-register-session-service.interface.ts index 37f6767..e5d92a9 100644 --- a/apps/api/src/modules/cash-register-session/interfaces/cash-register-session-service.interface.ts +++ b/apps/api/src/modules/cash-register-session/interfaces/cash-register-session-service.interface.ts @@ -8,7 +8,9 @@ import type { UpdateCashRegisterSessionInput, } from "../../../dtos/cash-register-session.dto.js"; -export const CASH_REGISTER_SESSION_SERVICE = Symbol("CASH_REGISTER_SESSION_SERVICE"); +export const CASH_REGISTER_SESSION_SERVICE = Symbol( + "CASH_REGISTER_SESSION_SERVICE", +); export type ICashRegisterSessionService = IService< CashRegisterSession, diff --git a/apps/api/src/modules/customer/customer.controller.ts b/apps/api/src/modules/customer/customer.controller.ts index a4198d7..477b925 100644 --- a/apps/api/src/modules/customer/customer.controller.ts +++ b/apps/api/src/modules/customer/customer.controller.ts @@ -1,11 +1,27 @@ -import { Body, Controller, Delete, Get, Inject, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; -import type { CreateCustomerInput, UpdateCustomerInput } from "../../dtos/customer.dto.js"; +import { + Body, + Controller, + Delete, + Get, + Inject, + Param, + ParseIntPipe, + Patch, + Post, +} from "@nestjs/common"; +import type { + CreateCustomerInput, + UpdateCustomerInput, +} from "../../dtos/customer.dto.js"; import { CUSTOMER_SERVICE } from "./interfaces/customer-service.interface.js"; import type { ICustomerService } from "./interfaces/customer-service.interface.js"; @Controller("customers") export class CustomerController { - constructor(@Inject(CUSTOMER_SERVICE) private readonly customerService: ICustomerService) {} + constructor( + @Inject(CUSTOMER_SERVICE) + private readonly customerService: ICustomerService, + ) {} @Get() findAll() { @@ -23,7 +39,10 @@ export class CustomerController { } @Patch(":id") - update(@Param("id", ParseIntPipe) id: number, @Body() body: UpdateCustomerInput) { + update( + @Param("id", ParseIntPipe) id: number, + @Body() body: UpdateCustomerInput, + ) { return this.customerService.update({ id }, body); } diff --git a/apps/api/src/modules/customer/customer.repository.ts b/apps/api/src/modules/customer/customer.repository.ts index 3d91c17..66fe24b 100644 --- a/apps/api/src/modules/customer/customer.repository.ts +++ b/apps/api/src/modules/customer/customer.repository.ts @@ -1,15 +1,30 @@ import { Injectable } from "@nestjs/common"; import { BaseRepository } from "../../common/base.repository.js"; -import type { CreateCustomerInput, UpdateCustomerInput } from "../../dtos/customer.dto.js"; +import type { + CreateCustomerInput, + UpdateCustomerInput, +} from "../../dtos/customer.dto.js"; import type { Customer, CustomerKey } from "../../entities/customer.entity.js"; import type { ICustomerRepository } from "./interfaces/customer-repository.interface.js"; @Injectable() export class CustomerRepository - extends BaseRepository + extends BaseRepository< + Customer, + CreateCustomerInput, + UpdateCustomerInput, + CustomerKey + > implements ICustomerRepository { - protected readonly columns = ["id", "name", "phone", "email", "taxId", "createdAt"] as const; + protected readonly columns = [ + "id", + "name", + "phone", + "email", + "taxId", + "createdAt", + ] as const; protected get model() { return this.db.orm.public.Customer; diff --git a/apps/api/src/modules/customer/customer.service.ts b/apps/api/src/modules/customer/customer.service.ts index 03c954b..6405209 100644 --- a/apps/api/src/modules/customer/customer.service.ts +++ b/apps/api/src/modules/customer/customer.service.ts @@ -1,6 +1,9 @@ import { Inject, Injectable } from "@nestjs/common"; import { BaseService } from "../../common/base.service.js"; -import type { CreateCustomerInput, UpdateCustomerInput } from "../../dtos/customer.dto.js"; +import type { + CreateCustomerInput, + UpdateCustomerInput, +} from "../../dtos/customer.dto.js"; import type { Customer, CustomerKey } from "../../entities/customer.entity.js"; import { CUSTOMER_REPOSITORY, @@ -10,7 +13,12 @@ import type { ICustomerService } from "./interfaces/customer-service.interface.j @Injectable() export class CustomerService - extends BaseService + extends BaseService< + Customer, + CreateCustomerInput, + UpdateCustomerInput, + CustomerKey + > implements ICustomerService { constructor(@Inject(CUSTOMER_REPOSITORY) repository: ICustomerRepository) { diff --git a/apps/api/src/modules/customer/interfaces/customer-repository.interface.ts b/apps/api/src/modules/customer/interfaces/customer-repository.interface.ts index 428650d..0191416 100644 --- a/apps/api/src/modules/customer/interfaces/customer-repository.interface.ts +++ b/apps/api/src/modules/customer/interfaces/customer-repository.interface.ts @@ -1,6 +1,12 @@ import type { IRepository } from "../../../common/repository.interface.js"; -import type { Customer, CustomerKey } from "../../../entities/customer.entity.js"; -import type { CreateCustomerInput, UpdateCustomerInput } from "../../../dtos/customer.dto.js"; +import type { + Customer, + CustomerKey, +} from "../../../entities/customer.entity.js"; +import type { + CreateCustomerInput, + UpdateCustomerInput, +} from "../../../dtos/customer.dto.js"; export const CUSTOMER_REPOSITORY = Symbol("CUSTOMER_REPOSITORY"); diff --git a/apps/api/src/modules/customer/interfaces/customer-service.interface.ts b/apps/api/src/modules/customer/interfaces/customer-service.interface.ts index 4276ede..8e672cd 100644 --- a/apps/api/src/modules/customer/interfaces/customer-service.interface.ts +++ b/apps/api/src/modules/customer/interfaces/customer-service.interface.ts @@ -1,6 +1,12 @@ import type { IService } from "../../../common/service.interface.js"; -import type { Customer, CustomerKey } from "../../../entities/customer.entity.js"; -import type { CreateCustomerInput, UpdateCustomerInput } from "../../../dtos/customer.dto.js"; +import type { + Customer, + CustomerKey, +} from "../../../entities/customer.entity.js"; +import type { + CreateCustomerInput, + UpdateCustomerInput, +} from "../../../dtos/customer.dto.js"; export const CUSTOMER_SERVICE = Symbol("CUSTOMER_SERVICE"); diff --git a/apps/api/src/modules/inventory-movement/inventory-movement.controller.ts b/apps/api/src/modules/inventory-movement/inventory-movement.controller.ts index 99d6521..65115d4 100644 --- a/apps/api/src/modules/inventory-movement/inventory-movement.controller.ts +++ b/apps/api/src/modules/inventory-movement/inventory-movement.controller.ts @@ -1,4 +1,12 @@ -import { Body, Controller, Get, Inject, Param, ParseIntPipe, Post } from "@nestjs/common"; +import { + Body, + Controller, + Get, + Inject, + Param, + ParseIntPipe, + Post, +} from "@nestjs/common"; import type { CreateInventoryMovementInput } from "../../dtos/inventory-movement.dto.js"; import { INVENTORY_MOVEMENT_SERVICE, diff --git a/apps/api/src/modules/inventory-movement/inventory-movement.module.ts b/apps/api/src/modules/inventory-movement/inventory-movement.module.ts index 9e43cc8..0d44df3 100644 --- a/apps/api/src/modules/inventory-movement/inventory-movement.module.ts +++ b/apps/api/src/modules/inventory-movement/inventory-movement.module.ts @@ -12,7 +12,10 @@ import { InventoryMovementService } from "./inventory-movement.service.js"; imports: [WarehouseModule, ProductModule, SupplierModule], controllers: [InventoryMovementController], providers: [ - { provide: INVENTORY_MOVEMENT_REPOSITORY, useClass: InventoryMovementRepository }, + { + provide: INVENTORY_MOVEMENT_REPOSITORY, + useClass: InventoryMovementRepository, + }, { provide: INVENTORY_MOVEMENT_SERVICE, useClass: InventoryMovementService }, ], exports: [INVENTORY_MOVEMENT_SERVICE], diff --git a/apps/api/src/modules/inventory-movement/inventory-movement.repository.ts b/apps/api/src/modules/inventory-movement/inventory-movement.repository.ts index c3b7cbc..50c5e58 100644 --- a/apps/api/src/modules/inventory-movement/inventory-movement.repository.ts +++ b/apps/api/src/modules/inventory-movement/inventory-movement.repository.ts @@ -20,9 +20,7 @@ const COLUMNS = [ ] as const; @Injectable() -export class InventoryMovementRepository - implements IInventoryMovementRepository -{ +export class InventoryMovementRepository implements IInventoryMovementRepository { constructor(@Inject(DB) private readonly db: Database) {} private get model() { @@ -30,11 +28,17 @@ export class InventoryMovementRepository } findAll(): Promise { - return this.model.select(...COLUMNS).all().toArray(); + return this.model + .select(...COLUMNS) + .all() + .toArray(); } findById(where: InventoryMovementKey): Promise { - return this.model.select(...COLUMNS).where(where).first(); + return this.model + .select(...COLUMNS) + .where(where) + .first(); } create(data: CreateInventoryMovementInput): Promise { diff --git a/apps/api/src/modules/inventory-movement/inventory-movement.service.ts b/apps/api/src/modules/inventory-movement/inventory-movement.service.ts index 24194ad..636c47a 100644 --- a/apps/api/src/modules/inventory-movement/inventory-movement.service.ts +++ b/apps/api/src/modules/inventory-movement/inventory-movement.service.ts @@ -1,4 +1,9 @@ -import { BadRequestException, Inject, Injectable, NotFoundException } from "@nestjs/common"; +import { + BadRequestException, + Inject, + Injectable, + NotFoundException, +} from "@nestjs/common"; import { PRODUCT_SERVICE, type IProductService, @@ -33,9 +38,11 @@ export class InventoryMovementService implements IInventoryMovementService { constructor( @Inject(INVENTORY_MOVEMENT_REPOSITORY) private readonly repository: IInventoryMovementRepository, - @Inject(WAREHOUSE_SERVICE) private readonly warehouseService: IWarehouseService, + @Inject(WAREHOUSE_SERVICE) + private readonly warehouseService: IWarehouseService, @Inject(PRODUCT_SERVICE) private readonly productService: IProductService, - @Inject(SUPPLIER_SERVICE) private readonly supplierService: ISupplierService, + @Inject(SUPPLIER_SERVICE) + private readonly supplierService: ISupplierService, ) {} findAll(): Promise { @@ -66,17 +73,25 @@ export class InventoryMovementService implements IInventoryMovementService { if (data.type === "out") { const currentStock = existing?.stock ?? 0; if (currentStock < data.quantity) { - throw new BadRequestException("Stock insuficiente para registrar la salida"); + throw new BadRequestException( + "Stock insuficiente para registrar la salida", + ); } - await this.warehouseService.update(key, { stock: currentStock - data.quantity }); + await this.warehouseService.update(key, { + stock: currentStock - data.quantity, + }); } else { if (existing) { - await this.warehouseService.update(key, { stock: existing.stock + data.quantity }); + await this.warehouseService.update(key, { + stock: existing.stock + data.quantity, + }); } else { await this.warehouseService.create({ ...key, stock: data.quantity }); } - const product = await this.productService.findById({ id: data.productId }).catch(() => null); + const product = await this.productService + .findById({ id: data.productId }) + .catch(() => null); if (product?.supplierId) { await this.supplierService.update( { id: product.supplierId }, diff --git a/apps/api/src/modules/operating-day/interfaces/operating-day-repository.interface.ts b/apps/api/src/modules/operating-day/interfaces/operating-day-repository.interface.ts index 8234861..4ce6ab1 100644 --- a/apps/api/src/modules/operating-day/interfaces/operating-day-repository.interface.ts +++ b/apps/api/src/modules/operating-day/interfaces/operating-day-repository.interface.ts @@ -1,6 +1,12 @@ import type { IRepository } from "../../../common/repository.interface.js"; -import type { OperatingDay, OperatingDayKey } from "../../../entities/operating-day.entity.js"; -import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../../dtos/operating-day.dto.js"; +import type { + OperatingDay, + OperatingDayKey, +} from "../../../entities/operating-day.entity.js"; +import type { + CreateOperatingDayInput, + UpdateOperatingDayInput, +} from "../../../dtos/operating-day.dto.js"; export const OPERATING_DAY_REPOSITORY = Symbol("OPERATING_DAY_REPOSITORY"); diff --git a/apps/api/src/modules/operating-day/interfaces/operating-day-service.interface.ts b/apps/api/src/modules/operating-day/interfaces/operating-day-service.interface.ts index 4cbdfc1..b1f2509 100644 --- a/apps/api/src/modules/operating-day/interfaces/operating-day-service.interface.ts +++ b/apps/api/src/modules/operating-day/interfaces/operating-day-service.interface.ts @@ -1,6 +1,12 @@ import type { IService } from "../../../common/service.interface.js"; -import type { OperatingDay, OperatingDayKey } from "../../../entities/operating-day.entity.js"; -import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../../dtos/operating-day.dto.js"; +import type { + OperatingDay, + OperatingDayKey, +} from "../../../entities/operating-day.entity.js"; +import type { + CreateOperatingDayInput, + UpdateOperatingDayInput, +} from "../../../dtos/operating-day.dto.js"; export const OPERATING_DAY_SERVICE = Symbol("OPERATING_DAY_SERVICE"); diff --git a/apps/api/src/modules/operating-day/operating-day.controller.ts b/apps/api/src/modules/operating-day/operating-day.controller.ts index 44d191b..cfe41d4 100644 --- a/apps/api/src/modules/operating-day/operating-day.controller.ts +++ b/apps/api/src/modules/operating-day/operating-day.controller.ts @@ -1,5 +1,18 @@ -import { Body, Controller, Delete, Get, Inject, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; -import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../dtos/operating-day.dto.js"; +import { + Body, + Controller, + Delete, + Get, + Inject, + Param, + ParseIntPipe, + Patch, + Post, +} from "@nestjs/common"; +import type { + CreateOperatingDayInput, + UpdateOperatingDayInput, +} from "../../dtos/operating-day.dto.js"; import { OPERATING_DAY_SERVICE } from "./interfaces/operating-day-service.interface.js"; import type { IOperatingDayService } from "./interfaces/operating-day-service.interface.js"; @@ -26,7 +39,10 @@ export class OperatingDayController { } @Patch(":id") - update(@Param("id", ParseIntPipe) id: number, @Body() body: UpdateOperatingDayInput) { + update( + @Param("id", ParseIntPipe) id: number, + @Body() body: UpdateOperatingDayInput, + ) { return this.operatingDayService.update({ id }, body); } diff --git a/apps/api/src/modules/operating-day/operating-day.repository.ts b/apps/api/src/modules/operating-day/operating-day.repository.ts index d043933..1404f7e 100644 --- a/apps/api/src/modules/operating-day/operating-day.repository.ts +++ b/apps/api/src/modules/operating-day/operating-day.repository.ts @@ -1,15 +1,34 @@ import { Injectable } from "@nestjs/common"; import { BaseRepository } from "../../common/base.repository.js"; -import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../dtos/operating-day.dto.js"; -import type { OperatingDay, OperatingDayKey } from "../../entities/operating-day.entity.js"; +import type { + CreateOperatingDayInput, + UpdateOperatingDayInput, +} from "../../dtos/operating-day.dto.js"; +import type { + OperatingDay, + OperatingDayKey, +} from "../../entities/operating-day.entity.js"; import type { IOperatingDayRepository } from "./interfaces/operating-day-repository.interface.js"; @Injectable() export class OperatingDayRepository - extends BaseRepository + extends BaseRepository< + OperatingDay, + CreateOperatingDayInput, + UpdateOperatingDayInput, + OperatingDayKey + > implements IOperatingDayRepository { - protected readonly columns = ["id", "date", "status", "openedById", "closedById", "openedAt", "closedAt"] as const; + protected readonly columns = [ + "id", + "date", + "status", + "openedById", + "closedById", + "openedAt", + "closedAt", + ] as const; protected get model() { return this.db.orm.public.OperatingDay; diff --git a/apps/api/src/modules/operating-day/operating-day.service.ts b/apps/api/src/modules/operating-day/operating-day.service.ts index 85edf5a..8c7673d 100644 --- a/apps/api/src/modules/operating-day/operating-day.service.ts +++ b/apps/api/src/modules/operating-day/operating-day.service.ts @@ -1,17 +1,31 @@ import { BadRequestException, Inject, Injectable } from "@nestjs/common"; import { BaseService } from "../../common/base.service.js"; -import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../dtos/operating-day.dto.js"; -import type { OperatingDay, OperatingDayKey } from "../../entities/operating-day.entity.js"; +import type { + CreateOperatingDayInput, + UpdateOperatingDayInput, +} from "../../dtos/operating-day.dto.js"; +import type { + OperatingDay, + OperatingDayKey, +} from "../../entities/operating-day.entity.js"; import { CASH_REGISTER_SESSION_SERVICE, type ICashRegisterSessionService, } from "../cash-register-session/interfaces/cash-register-session-service.interface.js"; -import { OPERATING_DAY_REPOSITORY, type IOperatingDayRepository } from "./interfaces/operating-day-repository.interface.js"; +import { + OPERATING_DAY_REPOSITORY, + type IOperatingDayRepository, +} from "./interfaces/operating-day-repository.interface.js"; import type { IOperatingDayService } from "./interfaces/operating-day-service.interface.js"; @Injectable() export class OperatingDayService - extends BaseService + extends BaseService< + OperatingDay, + CreateOperatingDayInput, + UpdateOperatingDayInput, + OperatingDayKey + > implements IOperatingDayService { constructor( @@ -32,20 +46,29 @@ export class OperatingDayService return this.operatingDayRepository.create(data); } - override async update(where: OperatingDayKey, data: UpdateOperatingDayInput): Promise { + override async update( + where: OperatingDayKey, + data: UpdateOperatingDayInput, + ): Promise { const current = await this.findById(where); if (data.status === "closed") { if (current.status === "closed") { - throw new BadRequestException("Esta fecha de operación ya está cerrada"); + throw new BadRequestException( + "Esta fecha de operación ya está cerrada", + ); } const sessions = await this.sessionService.findAll(); const hasOpenSession = sessions.some((s) => s.status === "open"); if (hasOpenSession) { - throw new BadRequestException("No se puede cerrar el día de operación con cajas abiertas"); + throw new BadRequestException( + "No se puede cerrar el día de operación con cajas abiertas", + ); } if (!data.closedById) { - throw new BadRequestException("Falta indicar quién cierra la fecha de operación"); + throw new BadRequestException( + "Falta indicar quién cierra la fecha de operación", + ); } const closed = await this.operatingDayRepository.update(where, { diff --git a/apps/api/src/modules/product/interfaces/product-repository.interface.ts b/apps/api/src/modules/product/interfaces/product-repository.interface.ts index 6d32ace..2b2eee1 100644 --- a/apps/api/src/modules/product/interfaces/product-repository.interface.ts +++ b/apps/api/src/modules/product/interfaces/product-repository.interface.ts @@ -7,9 +7,26 @@ import type { export const PRODUCT_REPOSITORY = Symbol("PRODUCT_REPOSITORY"); -export type IProductRepository = IRepository< +export interface SetPriceInput { + unitPrice: string; + costUnit?: string; +} + +export interface IProductRepository extends IRepository< Product, CreateProductInput, UpdateProductInput, ProductKey ->; +> { + setPrice( + productId: number, + data: SetPriceInput, + ): Promise<{ + id: number; + productId: number; + unitPrice: string; + costUnit: string; + status: string; + }>; + setCategories(productId: number, categoryIds: number[]): Promise; +} diff --git a/apps/api/src/modules/product/interfaces/product-service.interface.ts b/apps/api/src/modules/product/interfaces/product-service.interface.ts index b82efb8..ea8989d 100644 --- a/apps/api/src/modules/product/interfaces/product-service.interface.ts +++ b/apps/api/src/modules/product/interfaces/product-service.interface.ts @@ -5,11 +5,26 @@ import type { UpdateProductInput, } from "../../../dtos/product.dto.js"; +import type { SetPriceInput } from "./product-repository.interface.js"; +export type { SetPriceInput }; + export const PRODUCT_SERVICE = Symbol("PRODUCT_SERVICE"); -export type IProductService = IService< +export interface IProductService extends IService< Product, CreateProductInput, UpdateProductInput, ProductKey ->; +> { + setPrice( + productId: number, + data: SetPriceInput, + ): Promise<{ + id: number; + productId: number; + unitPrice: string; + costUnit: string; + status: string; + }>; + setCategories(productId: number, categoryIds: number[]): Promise; +} diff --git a/apps/api/src/modules/product/product.controller.ts b/apps/api/src/modules/product/product.controller.ts index 2180fbf..b6b48cd 100644 --- a/apps/api/src/modules/product/product.controller.ts +++ b/apps/api/src/modules/product/product.controller.ts @@ -37,6 +37,22 @@ export class ProductController { return this.productService.create(body); } + @Post(":id/price") + setPrice( + @Param("id", ParseIntPipe) id: number, + @Body() body: { unitPrice: string; costUnit?: string }, + ) { + return this.productService.setPrice(id, body); + } + + @Patch(":id/categories") + syncCategoriesPatch( + @Param("id", ParseIntPipe) id: number, + @Body() body: { categoryIds: number[] }, + ) { + return this.productService.setCategories(id, body.categoryIds ?? []); + } + @Patch(":id") update( @Param("id", ParseIntPipe) id: number, diff --git a/apps/api/src/modules/product/product.repository.ts b/apps/api/src/modules/product/product.repository.ts index 1ccf99b..2484204 100644 --- a/apps/api/src/modules/product/product.repository.ts +++ b/apps/api/src/modules/product/product.repository.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ import { Injectable } from "@nestjs/common"; import { BaseRepository } from "../../common/base.repository.js"; import type { @@ -32,4 +33,36 @@ export class ProductRepository protected get model() { return this.db.orm.public.Product; } + + async setPrice( + productId: number, + data: { unitPrice: string; costUnit?: string }, + ) { + // 1. Archivar los precios activos anteriores para este producto + await (this.db.orm.public.ProductHistory as any) + .where({ productId, status: "active" }) + .update({ status: "archived" }); + + // 2. Crear el nuevo precio con status active + const newHistory = await (this.db.orm.public.ProductHistory as any).create({ + productId, + unitPrice: data.unitPrice, + costUnit: data.costUnit ?? "0", + status: "active", + }); + + return newHistory; + } + + async setCategories(productId: number, categoryIds: number[]): Promise { + const categoryModel = this.db.orm.public.ProductCategory as any; + // 1. Eliminar las categorías actuales del producto + await categoryModel.where({ productId }).delete(); + + // 2. Insertar las nuevas categorías + const uniqueIds = Array.from(new Set(categoryIds)); + for (const categoryId of uniqueIds) { + await categoryModel.create({ productId, categoryId }); + } + } } diff --git a/apps/api/src/modules/product/product.service.ts b/apps/api/src/modules/product/product.service.ts index 30cbfab..984fe9f 100644 --- a/apps/api/src/modules/product/product.service.ts +++ b/apps/api/src/modules/product/product.service.ts @@ -25,6 +25,22 @@ export class ProductService super(repository); } + async setPrice( + productId: number, + data: { unitPrice: string; costUnit?: string }, + ) { + await this.findById({ id: productId }); + return (this.repository as IProductRepository).setPrice(productId, data); + } + + async setCategories(productId: number, categoryIds: number[]): Promise { + await this.findById({ id: productId }); + return (this.repository as IProductRepository).setCategories( + productId, + categoryIds, + ); + } + protected override notFoundMessage(where: ProductKey): string { return `Product ${where.id} not found`; } diff --git a/apps/api/src/modules/shop-cart/shop-cart.service.ts b/apps/api/src/modules/shop-cart/shop-cart.service.ts index ccef160..f53c024 100644 --- a/apps/api/src/modules/shop-cart/shop-cart.service.ts +++ b/apps/api/src/modules/shop-cart/shop-cart.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from "@nestjs/common"; +import { BadRequestException, Inject, Injectable } from "@nestjs/common"; import { BaseService } from "../../common/base.service.js"; import type { CreateShopCartInput, @@ -25,6 +25,34 @@ export class ShopCartService super(repository); } + private validateDiscount(totalPriceStr?: string, discountStr?: string) { + if (discountStr !== undefined) { + const discount = Number(discountStr); + if (Number.isNaN(discount) || discount < 0) { + throw new BadRequestException("El descuento no puede ser negativo"); + } + if (totalPriceStr !== undefined) { + const total = Number(totalPriceStr); + if (Number.isNaN(total) || total < 0) { + throw new BadRequestException("El total no puede ser negativo"); + } + } + } + } + + override create(data: CreateShopCartInput): Promise { + this.validateDiscount(data.totalPrice, data.discount); + return super.create(data); + } + + override update( + where: ShopCartKey, + data: UpdateShopCartInput, + ): Promise { + this.validateDiscount(data.totalPrice, data.discount); + return super.update(where, data); + } + protected override notFoundMessage(where: ShopCartKey): string { return `ShopCart ${where.id} not found`; } diff --git a/apps/web/src/app/_core/components/section-placeholder.tsx b/apps/web/src/app/_core/components/section-placeholder.tsx deleted file mode 100644 index 33c3a29..0000000 --- a/apps/web/src/app/_core/components/section-placeholder.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Icon, type IconifyIcon } from "@iconify/react"; - -interface SectionPlaceholderProps { - icon: IconifyIcon; - title: string; - description: string; -} - -export function SectionPlaceholder({ icon, title, description }: SectionPlaceholderProps) { - return ( -
-

{title}

-
-
-
-
- - Próximamente - -

{description}

-
-
-
- ); -} diff --git a/apps/web/src/app/_core/components/theme-toggle.tsx b/apps/web/src/app/_core/components/theme-toggle.tsx index be85a62..ca0377b 100644 --- a/apps/web/src/app/_core/components/theme-toggle.tsx +++ b/apps/web/src/app/_core/components/theme-toggle.tsx @@ -2,19 +2,13 @@ import { Icon } from "@iconify/react"; import { useEffect, useRef, useState } from "react"; -import { useTheme } from "../hooks/use-theme"; -import { iconMonitor, iconMoon, iconSun } from "../icons"; - -const MODES = ["light", "dark", "system"] as const; -type Mode = (typeof MODES)[number]; - -const ICONS: Record = { light: iconSun, dark: iconMoon, system: iconMonitor }; -const LABELS: Record = { light: "Claro", dark: "Oscuro", system: "Automático" }; +import { THEME_ICONS, THEME_LABELS, THEME_MODES, type ThemeMode, useTheme } from "../hooks/use-theme"; +import { iconMonitor } from "../icons"; export function ThemeToggle() { const { theme, setTheme } = useTheme(); const mounted = theme !== undefined; - const current: Mode = mounted && MODES.includes(theme as Mode) ? (theme as Mode) : "system"; + const current: ThemeMode = mounted && THEME_MODES.includes(theme as ThemeMode) ? (theme as ThemeMode) : "system"; const [open, setOpen] = useState(false); const rootRef = useRef(null); @@ -34,7 +28,7 @@ export function ThemeToggle() { }; }, [open]); - function select(mode: Mode) { + function select(mode: ThemeMode) { setTheme(mode); setOpen(false); } @@ -46,11 +40,11 @@ export function ThemeToggle() { onClick={() => setOpen((v) => !v)} aria-haspopup="menu" aria-expanded={open} - aria-label={`Tema: ${LABELS[current]}. Cambiar tema`} - title={`Tema: ${LABELS[current]}`} + aria-label={`Tema: ${THEME_LABELS[current]}. Cambiar tema`} + title={`Tema: ${THEME_LABELS[current]}`} className="flex size-11 shrink-0 items-center justify-center rounded-lg text-muted transition hover:bg-foreground/10 hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus" > -