feat(GRO-106): portal Communication tab — real backend
- Added GET /portal/conversation and GET /portal/conversation/messages endpoints - Created Communication.api.ts with typed fetchers and React hooks - Rewired Communication.tsx to use real API, removed mock data - Added composer-disabled bar with "Reply from your phone" tooltip - Added conversation route tests to portal.test.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { eq } from "@groombook/db";
|
||||
import { bufferTimeRules, services, type Db } from "@groombook/db";
|
||||
|
||||
export async function resolveBufferMinutes({
|
||||
serviceId,
|
||||
sizeCategory,
|
||||
coatType,
|
||||
db,
|
||||
}: {
|
||||
serviceId: string;
|
||||
sizeCategory: string | null;
|
||||
coatType: string | null;
|
||||
db: Db;
|
||||
}): Promise<number> {
|
||||
// Query all rules for this service in one DB call
|
||||
const allRules = await db
|
||||
.select()
|
||||
.from(bufferTimeRules)
|
||||
.where(eq(bufferTimeRules.serviceId, serviceId));
|
||||
|
||||
// Priority 1: exact match (serviceId + sizeCategory + coatType all match)
|
||||
const exact = allRules.find(
|
||||
(r) =>
|
||||
r.sizeCategory === sizeCategory &&
|
||||
r.coatType === coatType
|
||||
);
|
||||
if (exact) return exact.bufferMinutes;
|
||||
|
||||
// Priority 2: service + size, null coatType
|
||||
const serviceSize = allRules.find(
|
||||
(r) =>
|
||||
r.sizeCategory === sizeCategory &&
|
||||
r.coatType === null
|
||||
);
|
||||
if (serviceSize) return serviceSize.bufferMinutes;
|
||||
|
||||
// Priority 3: service + coat, null sizeCategory
|
||||
const serviceCoat = allRules.find(
|
||||
(r) =>
|
||||
r.sizeCategory === null &&
|
||||
r.coatType === coatType
|
||||
);
|
||||
if (serviceCoat) return serviceCoat.bufferMinutes;
|
||||
|
||||
// Priority 4: service only (null sizeCategory, null coatType)
|
||||
const serviceOnly = allRules.find(
|
||||
(r) =>
|
||||
r.sizeCategory === null &&
|
||||
r.coatType === null
|
||||
);
|
||||
if (serviceOnly) return serviceOnly.bufferMinutes;
|
||||
|
||||
// Priority 5: fallback to service.defaultBufferMinutes
|
||||
const [service] = await db
|
||||
.select({ defaultBufferMinutes: services.defaultBufferMinutes })
|
||||
.from(services)
|
||||
.where(eq(services.id, serviceId))
|
||||
.limit(1);
|
||||
|
||||
if (service?.defaultBufferMinutes != null) {
|
||||
return service.defaultBufferMinutes;
|
||||
}
|
||||
|
||||
// Priority 6: final fallback to 0
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user