import { query, queryOne } from '../../config/database.js'; import { NotFoundError, ConflictError } from '../../shared/errors/index.js'; export interface CustomerGroupMember { id: string; customer_group_id: string; partner_id: string; partner_name?: string; joined_at: Date; } export interface CustomerGroup { id: string; tenant_id: string; name: string; description?: string; discount_percentage: number; members?: CustomerGroupMember[]; member_count?: number; created_at: Date; } export interface CreateCustomerGroupDto { name: string; description?: string; discount_percentage?: number; } export interface UpdateCustomerGroupDto { name?: string; description?: string | null; discount_percentage?: number; } export interface CustomerGroupFilters { search?: string; page?: number; limit?: number; } class CustomerGroupsService { async findAll(tenantId: string, filters: CustomerGroupFilters = {}): Promise<{ data: CustomerGroup[]; total: number }> { const { search, page = 1, limit = 20 } = filters; const offset = (page - 1) * limit; let whereClause = 'WHERE cg.tenant_id = $1'; const params: any[] = [tenantId]; let paramIndex = 2; if (search) { whereClause += ` AND (cg.name ILIKE $${paramIndex} OR cg.description ILIKE $${paramIndex})`; params.push(`%${search}%`); paramIndex++; } const countResult = await queryOne<{ count: string }>( `SELECT COUNT(*) as count FROM sales.customer_groups cg ${whereClause}`, params ); params.push(limit, offset); const data = await query( `SELECT cg.*, (SELECT COUNT(*) FROM sales.customer_group_members cgm WHERE cgm.customer_group_id = cg.id) as member_count FROM sales.customer_groups cg ${whereClause} ORDER BY cg.name LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`, params ); return { data, total: parseInt(countResult?.count || '0', 10), }; } async findById(id: string, tenantId: string): Promise { const group = await queryOne( `SELECT cg.*, (SELECT COUNT(*) FROM sales.customer_group_members cgm WHERE cgm.customer_group_id = cg.id) as member_count FROM sales.customer_groups cg WHERE cg.id = $1 AND cg.tenant_id = $2`, [id, tenantId] ); if (!group) { throw new NotFoundError('Grupo de clientes no encontrado'); } // Get members const members = await query( `SELECT cgm.*, p.name as partner_name FROM sales.customer_group_members cgm LEFT JOIN core.partners p ON cgm.partner_id = p.id WHERE cgm.customer_group_id = $1 ORDER BY p.name`, [id] ); group.members = members; return group; } async create(dto: CreateCustomerGroupDto, tenantId: string, userId: string): Promise { // Check unique name const existing = await queryOne( `SELECT id FROM sales.customer_groups WHERE tenant_id = $1 AND name = $2`, [tenantId, dto.name] ); if (existing) { throw new ConflictError('Ya existe un grupo de clientes con ese nombre'); } const group = await queryOne( `INSERT INTO sales.customer_groups (tenant_id, name, description, discount_percentage, created_by) VALUES ($1, $2, $3, $4, $5) RETURNING *`, [tenantId, dto.name, dto.description, dto.discount_percentage || 0, userId] ); return group!; } async update(id: string, dto: UpdateCustomerGroupDto, tenantId: string): Promise { await this.findById(id, tenantId); const updateFields: string[] = []; const values: any[] = []; let paramIndex = 1; if (dto.name !== undefined) { // Check unique name const existing = await queryOne( `SELECT id FROM sales.customer_groups WHERE tenant_id = $1 AND name = $2 AND id != $3`, [tenantId, dto.name, id] ); if (existing) { throw new ConflictError('Ya existe un grupo de clientes con ese nombre'); } updateFields.push(`name = $${paramIndex++}`); values.push(dto.name); } if (dto.description !== undefined) { updateFields.push(`description = $${paramIndex++}`); values.push(dto.description); } if (dto.discount_percentage !== undefined) { updateFields.push(`discount_percentage = $${paramIndex++}`); values.push(dto.discount_percentage); } values.push(id, tenantId); await query( `UPDATE sales.customer_groups SET ${updateFields.join(', ')} WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`, values ); return this.findById(id, tenantId); } async delete(id: string, tenantId: string): Promise { const group = await this.findById(id, tenantId); if (group.member_count && group.member_count > 0) { throw new ConflictError('No se puede eliminar un grupo con miembros'); } await query(`DELETE FROM sales.customer_groups WHERE id = $1 AND tenant_id = $2`, [id, tenantId]); } async addMember(groupId: string, partnerId: string, tenantId: string): Promise { await this.findById(groupId, tenantId); // Check if already member const existing = await queryOne( `SELECT id FROM sales.customer_group_members WHERE customer_group_id = $1 AND partner_id = $2`, [groupId, partnerId] ); if (existing) { throw new ConflictError('El cliente ya es miembro de este grupo'); } const member = await queryOne( `INSERT INTO sales.customer_group_members (customer_group_id, partner_id) VALUES ($1, $2) RETURNING *`, [groupId, partnerId] ); return member!; } async removeMember(groupId: string, memberId: string, tenantId: string): Promise { await this.findById(groupId, tenantId); await query( `DELETE FROM sales.customer_group_members WHERE id = $1 AND customer_group_id = $2`, [memberId, groupId] ); } } export const customerGroupsService = new CustomerGroupsService();