feat(11-02): extend getAllServices with tags join (ServiceWithTags)

- Add ServiceWithTags type = Service & { tags: string[] }
- getAllServices() now left-joins tags scoped to entity_type=services
- Tags aggregated per service, ordered by service name then tag name
This commit is contained in:
2026-06-13 15:38:57 +02:00
parent a447494dde
commit f7434102de
+36 -4
View File
@@ -21,6 +21,7 @@ import {
offer_phase_services,
quotes,
leads,
tags,
} from "@/db/schema";
import { eq, inArray, asc, isNull, sql, and } from "drizzle-orm";
import type {
@@ -356,11 +357,42 @@ export async function getClientFullDetail(id: string): Promise<ClientFullDetail
};
}
export async function getAllServices(): Promise<Service[]> {
return db
.select()
// ── ServiceWithTags — services + assigned tag names (Phase 11 database-view) ──
export type ServiceWithTags = Service & { tags: string[] };
export async function getAllServices(): Promise<ServiceWithTags[]> {
const rows = await db
.select({
id: services.id,
name: services.name,
description: services.description,
unit_price: services.unit_price,
category: services.category,
active: services.active,
migrated_from: services.migrated_from,
migrated_id: services.migrated_id,
created_at: services.created_at,
tag_name: tags.name,
})
.from(services)
.orderBy(asc(services.name));
.leftJoin(
tags,
and(eq(tags.entity_type, "services"), eq(tags.entity_id, services.id))
)
.orderBy(asc(services.name), asc(tags.name));
const serviceMap = new Map<string, ServiceWithTags>();
for (const row of rows) {
const { tag_name, ...serviceFields } = row;
if (!serviceMap.has(row.id)) {
serviceMap.set(row.id, { ...serviceFields, tags: [] });
}
if (tag_name) {
serviceMap.get(row.id)!.tags.push(tag_name);
}
}
return Array.from(serviceMap.values());
}
// ── ProjectWithPayments — used by /admin/projects list ───────────────────────