TorbatYar/frontend/modules/beauty/services/beauty-business-api.ts
Mortezakoohjani 6f4a484051 Migrate Beauty, Healthcare, and Accounting frontend to modular src/modules architecture.
Move business logic out of App Router into module packages, add boundary validation scripts, and keep all routes as thin re-exports without changing URLs or API behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 22:28:27 +03:30

1447 lines
40 KiB
TypeScript

/**
* Beauty Business Service API client — real backend only.
* Base: NEXT_PUBLIC_BEAUTY_BUSINESS_API_URL (default http://localhost:8011)
* Always sends Authorization + X-Tenant-ID.
*/
import { getValidAccessToken, refreshSession } from "@/lib/auth";
const BEAUTY_BASE =
typeof window !== "undefined"
? "/api/beauty-business"
: process.env.BEAUTY_BUSINESS_SERVICE_URL ||
process.env.NEXT_PUBLIC_BEAUTY_BUSINESS_API_URL ||
"http://localhost:8011";
export class BeautyBusinessApiError extends Error {
constructor(
public status: number,
public code: string,
message: string
) {
super(message);
this.name = "BeautyBusinessApiError";
}
}
type RequestOptions = RequestInit & { tenantId: string; auth?: boolean };
async function request<T>(path: string, options: RequestOptions): Promise<T> {
const { tenantId, auth = true, headers: customHeaders, ...init } = options;
const headers = new Headers(customHeaders);
headers.set("Content-Type", "application/json");
headers.set("X-Tenant-ID", tenantId);
if (auth) {
const token = await getValidAccessToken();
if (token) headers.set("Authorization", `Bearer ${token}`);
}
const url = path.startsWith("http")
? path
: `${BEAUTY_BASE.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
let res: Response;
try {
res = await fetch(url, { ...init, headers });
} catch (err) {
const detail = err instanceof Error ? err.message : "network_error";
throw new Error(`اتصال به سرویس زیبایی برقرار نشد (${url}) — ${detail}`);
}
if (res.status === 401 && auth) {
const refreshed = await refreshSession({ force: true });
if (refreshed) {
headers.set("Authorization", `Bearer ${refreshed}`);
res = await fetch(url, { ...init, headers });
}
}
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new BeautyBusinessApiError(
res.status,
body?.error?.code || "unknown_error",
body?.error?.message || res.statusText
);
}
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}
function qs(params?: Record<string, string | number | undefined | null>) {
if (!params) return "";
const sp = new URLSearchParams();
Object.entries(params).forEach(([k, v]) => {
if (v !== undefined && v !== null && v !== "") sp.set(k, String(v));
});
const s = sp.toString();
return s ? `?${s}` : "";
}
export type LifecycleStatus = "draft" | "active" | "inactive" | "suspended" | "archived";
export type BusinessFormat = "salon" | "clinic" | "spa" | "barbershop" | "mixed";
export type ProviderKind =
| "communication"
| "crm"
| "loyalty"
| "accounting"
| "delivery"
| "storage"
| "ai"
| "identity"
| "custom";
export type ProviderStatus = "registered" | "active" | "inactive" | "failed" | "retired";
export type BookingEngineStatus = "registered" | "active" | "inactive" | "failed" | "retired";
export type AppointmentStatus =
| "requested"
| "confirmed"
| "checked_in"
| "in_progress"
| "completed"
| "cancelled"
| "no_show";
export type ServiceStatus = "draft" | "active" | "inactive" | "archived";
export type ResourceStatus = "available" | "occupied" | "maintenance" | "inactive";
export type StaffKind = "stylist" | "therapist" | "receptionist" | "manager" | "other";
export type DayOfWeek =
| "monday"
| "tuesday"
| "wednesday"
| "thursday"
| "friday"
| "saturday"
| "sunday";
export type ScheduleExceptionKind = "closed" | "modified_hours" | "holiday";
export type IntegrationDispatchStatus = "pending" | "sent" | "failed" | "skipped";
export type HealthResponse = {
status: string;
service: string;
version: string;
};
export type CapabilitiesResponse = {
service: string;
version: string;
phase: string;
commercial_product: string;
features: Record<string, boolean>;
independence: {
standalone: boolean;
superapp: boolean;
integrations: string[];
integration_mode: string;
};
};
export type MetricsResponse = {
service: string;
version: string;
phase: string;
metrics: Record<string, string | number>;
note?: string;
};
export type Organization = {
id: string;
tenant_id: string;
code: string;
name: string;
description: string | null;
status: LifecycleStatus;
business_format: BusinessFormat;
legal_name: string | null;
timezone: string;
language: string;
currency_code: string;
settings: Record<string, unknown> | null;
version: number;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type Branch = {
id: string;
tenant_id: string;
organization_id: string;
code: string;
name: string;
description: string | null;
status: LifecycleStatus;
business_format: BusinessFormat | null;
address: Record<string, unknown> | null;
timezone: string | null;
working_hours: Record<string, unknown> | null;
metadata_json: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type ExternalProvider = {
id: string;
tenant_id: string;
organization_id: string | null;
code: string;
name: string;
provider_kind: ProviderKind;
adapter_key: string;
status: ProviderStatus;
priority: number;
credentials_ref: string | null;
config: Record<string, unknown> | null;
capabilities: Record<string, unknown> | null;
version: number;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type BookingEngine = {
id: string;
tenant_id: string;
organization_id: string | null;
code: string;
name: string;
adapter_key: string;
status: BookingEngineStatus;
is_default: boolean;
config: Record<string, unknown> | null;
capabilities: Record<string, unknown> | null;
version: number;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type Configuration = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string | null;
code: string;
working_hours: Record<string, unknown> | null;
booking_policies: Record<string, unknown> | null;
cancellation_policies: Record<string, unknown> | null;
reminder_policies: Record<string, unknown> | null;
custom_fields: Record<string, unknown> | null;
timezone: string;
language: string;
currency_code: string;
version: number;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type Setting = {
id: string;
tenant_id: string;
organization_id: string | null;
branch_id: string | null;
key: string;
value: Record<string, unknown> | null;
description: string | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type AuditLog = {
id: string;
tenant_id: string;
entity_type: string;
entity_id: string;
action: string;
actor_user_id: string | null;
changes: Record<string, unknown> | null;
message: string | null;
created_at: string;
};
export type PermissionCatalog = {
prefix: string;
prefixes: string[];
permissions: string[];
count: number;
};
export type StaffSchedule = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string;
staff_ref: string;
code: string;
day_of_week: DayOfWeek;
start_time: string;
end_time: string;
is_active: boolean;
metadata_json: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type StaffAvailability = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string;
staff_ref: string;
code: string;
starts_at: string;
ends_at: string;
is_bookable: boolean;
metadata_json: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type ScheduleException = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string;
staff_ref: string | null;
code: string;
kind: ScheduleExceptionKind;
exception_date: string;
start_time: string | null;
end_time: string | null;
reason: string | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type AppointmentType = {
id: string;
tenant_id: string;
organization_id: string;
code: string;
name: string;
description: string | null;
default_duration_minutes: number;
status: LifecycleStatus;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type Appointment = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string;
appointment_type_id: string | null;
code: string;
customer_ref: string | null;
staff_ref: string | null;
room_ref: string | null;
service_ref: string | null;
status: AppointmentStatus;
scheduled_start: string;
scheduled_end: string;
notes: string | null;
status_reason: string | null;
status_changed_at: string | null;
metadata_json: Record<string, unknown> | null;
version: number;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type AppointmentStatusHistory = {
id: string;
tenant_id: string;
appointment_id: string;
action: string;
from_status: AppointmentStatus;
to_status: AppointmentStatus;
reason: string | null;
actor_user_id: string | null;
metadata_json: Record<string, unknown> | null;
created_at: string;
};
export type WaitingListEntry = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string;
code: string;
customer_ref: string | null;
service_ref: string | null;
preferred_staff_ref: string | null;
requested_date: string | null;
notes: string | null;
priority: number;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type TreatmentRoom = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string;
code: string;
name: string;
description: string | null;
status: ResourceStatus;
capacity: number;
metadata_json: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type Station = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string;
room_id: string | null;
code: string;
name: string;
status: ResourceStatus;
metadata_json: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type BranchPolicy = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string;
code: string;
name: string;
policy_type: string;
rules: Record<string, unknown> | null;
status: LifecycleStatus;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type ServiceCategory = {
id: string;
tenant_id: string;
organization_id: string;
code: string;
name: string;
description: string | null;
status: LifecycleStatus;
sort_order: number;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type BeautyService = {
id: string;
tenant_id: string;
organization_id: string;
category_id: string | null;
code: string;
name: string;
description: string | null;
status: ServiceStatus;
base_duration_minutes: number;
base_price: number | null;
metadata_json: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type CustomerProfile = {
id: string;
tenant_id: string;
organization_id: string;
code: string;
display_name: string;
external_crm_contact_ref: string | null;
external_user_ref: string | null;
mobile: string | null;
email: string | null;
status: LifecycleStatus;
metadata_json: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type PackageDefinition = {
id: string;
tenant_id: string;
organization_id: string;
code: string;
name: string;
description: string | null;
total_sessions: number;
price: number | null;
valid_days: number | null;
status: LifecycleStatus;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type MembershipPlan = {
id: string;
tenant_id: string;
organization_id: string;
code: string;
name: string;
description: string | null;
billing_interval_days: number;
price: number | null;
benefits: Record<string, unknown> | null;
status: LifecycleStatus;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type StaffProfile = {
id: string;
tenant_id: string;
organization_id: string;
branch_id: string | null;
code: string;
display_name: string;
kind: StaffKind;
external_user_ref: string | null;
mobile: string | null;
email: string | null;
status: LifecycleStatus;
hire_date: string | null;
metadata_json: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type CommissionRule = {
id: string;
tenant_id: string;
organization_id: string;
code: string;
name: string;
rule_type: string;
rate_percent: number | null;
flat_amount: number | null;
applies_to_service_id: string | null;
status: LifecycleStatus;
config: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type MarketingCampaign = {
id: string;
tenant_id: string;
organization_id: string | null;
branch_id: string | null;
code: string;
name: string;
external_campaign_ref: string | null;
channel: string | null;
status: LifecycleStatus;
config: Record<string, unknown> | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type LoyaltyIntegration = {
id: string;
tenant_id: string;
organization_id: string | null;
code: string;
name: string;
external_program_ref: string | null;
earn_rules: Record<string, unknown> | null;
status: LifecycleStatus;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type CommunicationIntegration = {
id: string;
tenant_id: string;
organization_id: string | null;
code: string;
name: string;
template_mappings: Record<string, unknown> | null;
status: LifecycleStatus;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
export type IntegrationDispatch = {
id: string;
tenant_id: string;
organization_id: string | null;
integration_kind: string;
config_id: string | null;
event_type: string;
payload_ref: string | null;
status: IntegrationDispatchStatus;
response_json: Record<string, unknown> | null;
notes: string | null;
is_deleted: boolean;
created_at: string;
updated_at: string;
};
type PaginatedList = { page?: number; pageSize?: number };
function listPath(base: string, { page = 1, pageSize = 50 }: PaginatedList = {}) {
return `${base}${qs({ page, page_size: pageSize })}`;
}
export const beautyBusinessApi = {
health: () =>
fetch(`${BEAUTY_BASE}/health`).then((r) => r.json() as Promise<HealthResponse>),
capabilities: () =>
fetch(`${BEAUTY_BASE}/capabilities`).then((r) => r.json() as Promise<CapabilitiesResponse>),
metrics: () =>
fetch(`${BEAUTY_BASE}/metrics`).then((r) => r.json() as Promise<MetricsResponse>),
organizations: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<Organization[]>(listPath("/api/v1/organizations", { page, pageSize }), { tenantId }),
get: (tenantId: string, id: string) =>
request<Organization>(`/api/v1/organizations/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
code: string;
name: string;
description?: string;
status?: LifecycleStatus;
business_format?: BusinessFormat;
legal_name?: string;
timezone?: string;
language?: string;
currency_code?: string;
}
) =>
request<Organization>("/api/v1/organizations", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
update: (
tenantId: string,
id: string,
body: Partial<{
name: string;
description: string;
status: LifecycleStatus;
business_format: BusinessFormat;
legal_name: string;
timezone: string;
language: string;
currency_code: string;
version: number;
}>
) =>
request<Organization>(`/api/v1/organizations/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
delete: (tenantId: string, id: string) =>
request<Organization>(`/api/v1/organizations/${id}/delete`, { tenantId, method: "POST" }),
},
branches: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<Branch[]>(listPath("/api/v1/branches", { page, pageSize }), { tenantId }),
get: (tenantId: string, id: string) =>
request<Branch>(`/api/v1/branches/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
code: string;
name: string;
description?: string;
status?: LifecycleStatus;
}
) =>
request<Branch>("/api/v1/branches", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
update: (
tenantId: string,
id: string,
body: Partial<{
name: string;
description: string;
status: LifecycleStatus;
}>
) =>
request<Branch>(`/api/v1/branches/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
delete: (tenantId: string, id: string) =>
request<Branch>(`/api/v1/branches/${id}/delete`, { tenantId, method: "POST" }),
},
externalProviders: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<ExternalProvider[]>(listPath("/api/v1/external-providers", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<ExternalProvider>(`/api/v1/external-providers/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id?: string;
code: string;
name: string;
provider_kind?: ProviderKind;
adapter_key: string;
status?: ProviderStatus;
priority?: number;
}
) =>
request<ExternalProvider>("/api/v1/external-providers", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
update: (
tenantId: string,
id: string,
body: Partial<{
name: string;
provider_kind: ProviderKind;
adapter_key: string;
status: ProviderStatus;
priority: number;
version: number;
}>
) =>
request<ExternalProvider>(`/api/v1/external-providers/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
delete: (tenantId: string, id: string) =>
request<ExternalProvider>(`/api/v1/external-providers/${id}/delete`, {
tenantId,
method: "POST",
}),
},
bookingEngines: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<BookingEngine[]>(listPath("/api/v1/booking-engines", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<BookingEngine>(`/api/v1/booking-engines/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id?: string;
code: string;
name: string;
adapter_key: string;
status?: BookingEngineStatus;
is_default?: boolean;
}
) =>
request<BookingEngine>("/api/v1/booking-engines", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
update: (
tenantId: string,
id: string,
body: Partial<{
name: string;
adapter_key: string;
status: BookingEngineStatus;
is_default: boolean;
version: number;
}>
) =>
request<BookingEngine>(`/api/v1/booking-engines/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
delete: (tenantId: string, id: string) =>
request<BookingEngine>(`/api/v1/booking-engines/${id}/delete`, {
tenantId,
method: "POST",
}),
},
configurations: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<Configuration[]>(listPath("/api/v1/configurations", { page, pageSize }), { tenantId }),
get: (tenantId: string, id: string) =>
request<Configuration>(`/api/v1/configurations/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id?: string;
code: string;
timezone?: string;
language?: string;
currency_code?: string;
}
) =>
request<Configuration>("/api/v1/configurations", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
update: (
tenantId: string,
id: string,
body: Partial<{
branch_id: string | null;
timezone: string;
language: string;
currency_code: string;
version: number;
}>
) =>
request<Configuration>(`/api/v1/configurations/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
delete: (tenantId: string, id: string) =>
request<Configuration>(`/api/v1/configurations/${id}/delete`, {
tenantId,
method: "POST",
}),
},
settings: {
list: (tenantId: string, page = 1, pageSize = 100) =>
request<Setting[]>(listPath("/api/v1/settings", { page, pageSize }), { tenantId }),
upsert: (
tenantId: string,
body: {
organization_id?: string;
branch_id?: string;
key: string;
value?: Record<string, unknown>;
description?: string;
}
) =>
request<Setting>("/api/v1/settings", {
tenantId,
method: "PUT",
body: JSON.stringify(body),
}),
},
audit: {
list: (tenantId: string, entityType: string, entityId: string, limit = 100) =>
request<AuditLog[]>(
`/api/v1/audit${qs({ entity_type: entityType, entity_id: entityId, limit })}`,
{ tenantId }
),
},
permissions: {
catalog: (tenantId: string) =>
request<PermissionCatalog>("/api/v1/permissions/catalog", { tenantId }),
},
staffSchedules: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<StaffSchedule[]>(listPath("/api/v1/staff-schedules", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<StaffSchedule>(`/api/v1/staff-schedules/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id: string;
staff_ref: string;
code: string;
day_of_week: DayOfWeek;
start_time: string;
end_time: string;
is_active?: boolean;
}
) =>
request<StaffSchedule>("/api/v1/staff-schedules", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
staffAvailabilities: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<StaffAvailability[]>(listPath("/api/v1/staff-availabilities", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<StaffAvailability>(`/api/v1/staff-availabilities/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id: string;
staff_ref: string;
code: string;
starts_at: string;
ends_at: string;
is_bookable?: boolean;
}
) =>
request<StaffAvailability>("/api/v1/staff-availabilities", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
scheduleExceptions: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<ScheduleException[]>(listPath("/api/v1/schedule-exceptions", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<ScheduleException>(`/api/v1/schedule-exceptions/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id: string;
staff_ref?: string;
code: string;
kind: ScheduleExceptionKind;
exception_date: string;
start_time?: string;
end_time?: string;
reason?: string;
}
) =>
request<ScheduleException>("/api/v1/schedule-exceptions", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
appointmentTypes: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<AppointmentType[]>(listPath("/api/v1/appointment-types", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<AppointmentType>(`/api/v1/appointment-types/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
code: string;
name: string;
description?: string;
default_duration_minutes?: number;
status?: LifecycleStatus;
}
) =>
request<AppointmentType>("/api/v1/appointment-types", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
appointments: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<Appointment[]>(listPath("/api/v1/appointments", { page, pageSize }), { tenantId }),
get: (tenantId: string, id: string) =>
request<Appointment>(`/api/v1/appointments/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id: string;
code: string;
scheduled_start: string;
scheduled_end: string;
appointment_type_id?: string;
customer_ref?: string;
staff_ref?: string;
room_ref?: string;
service_ref?: string;
status?: AppointmentStatus;
notes?: string;
}
) =>
request<Appointment>("/api/v1/appointments", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
update: (
tenantId: string,
id: string,
body: Partial<{
staff_ref: string;
room_ref: string;
service_ref: string;
scheduled_start: string;
scheduled_end: string;
notes: string;
version: number;
}>
) =>
request<Appointment>(`/api/v1/appointments/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
confirm: (tenantId: string, id: string, body: { reason?: string; version: number }) =>
request<Appointment>(`/api/v1/appointments/${id}/confirm`, {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
checkIn: (tenantId: string, id: string, body: { reason?: string; version: number }) =>
request<Appointment>(`/api/v1/appointments/${id}/check-in`, {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
complete: (tenantId: string, id: string, body: { reason?: string; version: number }) =>
request<Appointment>(`/api/v1/appointments/${id}/complete`, {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
cancel: (tenantId: string, id: string, body: { reason?: string; version: number }) =>
request<Appointment>(`/api/v1/appointments/${id}/cancel`, {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
noShow: (tenantId: string, id: string, body: { reason?: string; version: number }) =>
request<Appointment>(`/api/v1/appointments/${id}/no-show`, {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
history: (tenantId: string, id: string) =>
request<AppointmentStatusHistory[]>(`/api/v1/appointments/${id}/history`, { tenantId }),
},
waitingList: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<WaitingListEntry[]>(listPath("/api/v1/waiting-list", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<WaitingListEntry>(`/api/v1/waiting-list/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id: string;
code: string;
customer_ref?: string;
service_ref?: string;
preferred_staff_ref?: string;
requested_date?: string;
notes?: string;
priority?: number;
}
) =>
request<WaitingListEntry>("/api/v1/waiting-list", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
treatmentRooms: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<TreatmentRoom[]>(listPath("/api/v1/treatment-rooms", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<TreatmentRoom>(`/api/v1/treatment-rooms/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id: string;
code: string;
name: string;
description?: string;
status?: ResourceStatus;
capacity?: number;
}
) =>
request<TreatmentRoom>("/api/v1/treatment-rooms", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
stations: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<Station[]>(listPath("/api/v1/stations", { page, pageSize }), { tenantId }),
get: (tenantId: string, id: string) =>
request<Station>(`/api/v1/stations/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id: string;
room_id?: string;
code: string;
name: string;
status?: ResourceStatus;
}
) =>
request<Station>("/api/v1/stations", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
branchPolicies: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<BranchPolicy[]>(listPath("/api/v1/branch-policies", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<BranchPolicy>(`/api/v1/branch-policies/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id: string;
code: string;
name: string;
policy_type: string;
rules?: Record<string, unknown>;
status?: LifecycleStatus;
}
) =>
request<BranchPolicy>("/api/v1/branch-policies", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
serviceCategories: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<ServiceCategory[]>(listPath("/api/v1/service-categories", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<ServiceCategory>(`/api/v1/service-categories/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
code: string;
name: string;
description?: string;
status?: LifecycleStatus;
sort_order?: number;
}
) =>
request<ServiceCategory>("/api/v1/service-categories", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
services: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<BeautyService[]>(listPath("/api/v1/services", { page, pageSize }), { tenantId }),
get: (tenantId: string, id: string) =>
request<BeautyService>(`/api/v1/services/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
category_id?: string;
code: string;
name: string;
description?: string;
status?: ServiceStatus;
base_duration_minutes?: number;
base_price?: number;
}
) =>
request<BeautyService>("/api/v1/services", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
customers: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<CustomerProfile[]>(listPath("/api/v1/customers", { page, pageSize }), { tenantId }),
get: (tenantId: string, id: string) =>
request<CustomerProfile>(`/api/v1/customers/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
code: string;
display_name: string;
mobile?: string;
email?: string;
status?: LifecycleStatus;
}
) =>
request<CustomerProfile>("/api/v1/customers", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
packageDefinitions: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<PackageDefinition[]>(listPath("/api/v1/package-definitions", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<PackageDefinition>(`/api/v1/package-definitions/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
code: string;
name: string;
description?: string;
total_sessions?: number;
price?: number;
valid_days?: number;
status?: LifecycleStatus;
}
) =>
request<PackageDefinition>("/api/v1/package-definitions", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
membershipPlans: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<MembershipPlan[]>(listPath("/api/v1/membership-plans", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<MembershipPlan>(`/api/v1/membership-plans/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
code: string;
name: string;
description?: string;
billing_interval_days?: number;
price?: number;
status?: LifecycleStatus;
}
) =>
request<MembershipPlan>("/api/v1/membership-plans", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
staff: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<StaffProfile[]>(listPath("/api/v1/staff", { page, pageSize }), { tenantId }),
get: (tenantId: string, id: string) =>
request<StaffProfile>(`/api/v1/staff/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
branch_id?: string;
code: string;
display_name: string;
kind?: StaffKind;
mobile?: string;
email?: string;
status?: LifecycleStatus;
}
) =>
request<StaffProfile>("/api/v1/staff", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
commissionRules: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<CommissionRule[]>(listPath("/api/v1/commission-rules", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<CommissionRule>(`/api/v1/commission-rules/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id: string;
code: string;
name: string;
rule_type: string;
rate_percent?: number;
flat_amount?: number;
applies_to_service_id?: string;
status?: LifecycleStatus;
}
) =>
request<CommissionRule>("/api/v1/commission-rules", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
marketingCampaigns: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<MarketingCampaign[]>(listPath("/api/v1/marketing-campaigns", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<MarketingCampaign>(`/api/v1/marketing-campaigns/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id?: string;
branch_id?: string;
code: string;
name: string;
external_campaign_ref?: string;
channel?: string;
status?: LifecycleStatus;
}
) =>
request<MarketingCampaign>("/api/v1/marketing-campaigns", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
loyaltyIntegrations: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<LoyaltyIntegration[]>(listPath("/api/v1/loyalty-integrations", { page, pageSize }), {
tenantId,
}),
get: (tenantId: string, id: string) =>
request<LoyaltyIntegration>(`/api/v1/loyalty-integrations/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id?: string;
code: string;
name: string;
external_program_ref?: string;
status?: LifecycleStatus;
}
) =>
request<LoyaltyIntegration>("/api/v1/loyalty-integrations", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
communicationIntegrations: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<CommunicationIntegration[]>(
listPath("/api/v1/communication-integrations", { page, pageSize }),
{ tenantId }
),
get: (tenantId: string, id: string) =>
request<CommunicationIntegration>(`/api/v1/communication-integrations/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id?: string;
code: string;
name: string;
status?: LifecycleStatus;
}
) =>
request<CommunicationIntegration>("/api/v1/communication-integrations", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
integrationDispatches: {
list: (tenantId: string, page = 1, pageSize = 50) =>
request<IntegrationDispatch[]>(
listPath("/api/v1/integration-dispatches", { page, pageSize }),
{ tenantId }
),
get: (tenantId: string, id: string) =>
request<IntegrationDispatch>(`/api/v1/integration-dispatches/${id}`, { tenantId }),
create: (
tenantId: string,
body: {
organization_id?: string;
integration_kind: string;
config_id?: string;
event_type: string;
payload_ref?: string;
}
) =>
request<IntegrationDispatch>("/api/v1/integration-dispatches", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
},
};