import { OrderNotFoundError, OrderStateTransitionError } from '../domain/errors.js'; import type { OrderEventPublisher, OrderRepository, OrderServicePort, CreateOrderCommand, } from '../domain/ports.js'; import { isTransitionAllowed, type OrderState, type OrderView, type OrderItemInput } from '../domain/order.js'; export class OrderService implements OrderServicePort { constructor( private readonly repo: OrderRepository, private readonly events: OrderEventPublisher, ) {} async create(input: CreateOrderCommand): Promise { const order = await this.repo.create({ ...input, idempotencyKey: input.idempotencyKey ?? null, }); await this.events.emit({ type: 'OrderCreated', orderId: order.id, userId: order.userId }); return order; } async listOrders(): Promise { return this.repo.findAll(); } async searchOrders(filters: { state?: string; q?: string; limit: number; offset: number; }): Promise<{ items: OrderView[]; total: number }> { if (!this.repo.search) { const all = await this.repo.findAll(); return { items: all.slice(filters.offset, filters.offset + filters.limit), total: all.length }; } return this.repo.search(filters); } /** Edición admin: reemplaza artículos y totales del pedido. */ async editItems( id: string, items: OrderItemInput[], totals: { subtotalCents: number; discountCents: number; taxCents: number; totalCents: number }, ): Promise { const existing = await this.repo.findById(id); if (!existing) throw new OrderNotFoundError(); if (!this.repo.replaceItems) throw new Error('Order repository does not support item editing'); const updated = await this.repo.replaceItems(id, items, totals); if (!updated) throw new OrderNotFoundError(); return updated; } async transition(id: string, next: OrderState, userId: string): Promise { const existing = await this.repo.findByIdAndUserId(id, userId); if (!existing) throw new OrderNotFoundError(); if (!isTransitionAllowed(existing.state, next)) throw new OrderStateTransitionError(existing.state, next); const updated = await this.repo.updateState(id, next); if (!updated) throw new OrderNotFoundError(); if (next === 'PAID') await this.events.emit({ type: 'OrderPaid', orderId: id, userId }); if (next === 'CANCELLED') await this.events.emit({ type: 'OrderCancelled', orderId: id, userId }); return updated; } async getOrder(id: string, userId: string): Promise { return this.repo.findByIdAndUserId(id, userId); } async getOrderAdmin(id: string): Promise { return this.repo.findById(id); } async transitionAdmin(id: string, next: OrderState, trackingNumber?: string, courier?: string): Promise { const existing = await this.repo.findById(id); if (!existing) throw new OrderNotFoundError(); if (!isTransitionAllowed(existing.state, next)) throw new OrderStateTransitionError(existing.state, next); const updated = await this.repo.updateState(id, next, trackingNumber, courier); if (!updated) throw new OrderNotFoundError(); if (next === 'PAID') await this.events.emit({ type: 'OrderPaid', orderId: id, userId: existing.userId }); if (next === 'CANCELLED') await this.events.emit({ type: 'OrderCancelled', orderId: id, userId: existing.userId }); return updated; } }