feat(branch): admin UI for branch working hours and rooms, plus real API docs
Three pages, all on the existing design system: BranchesPage lists the current environment's booking locations with their working-hours and active-room counts, and two subpages edit the week and the rooms. The list page deliberately does not create or rename a branch — clinic and doctor detail pages already do that, and duplicating it would give one physical place two edit surfaces. Route permission reuses `appointment_settings` rather than inventing a new one. Two real bugs fell out of exercising this end to end: `days` was serialising as a JSON *array*, not an object keyed "0".."6" — keys 0..6 are sequential so json_encode collapses them to a list. The client reads days["0"] either way, so nothing looked broken, but the response shape was unstable: one missing day would flip the same field to an object. The controller now casts to stdClass and WorkingHoursTest::testDaysIsAJsonObjectNotAnArray pins it. Found by curling the endpoint for the docs, not by any test. `<input type="time">` caps at 23:59, so it can neither display nor produce the legal end value 1440. An all-day range would have vanished from the form and been corrupted by the first save. Ranges now carry an explicit end-of-day flag, with a round-trip test proving 1440 survives. docs/api/branch.md documents all eight endpoints with responses captured from real curl runs against ddev, including the 422 and 404 bodies. doctor.md records that active/timezone now appear on all nine existing address endpoints (additive), and tenancy.md gains the two lessons this task taught: an aggregate child whose root is itself declared global inherits no environment and needs a real pair, and TenantFilter is not a substitute for an explicit ownership check because hard isolation only applies to a *chosen* context. Verified: phpunit 1067 tests / 2974 assertions green; slot-mode frozen contract green; phpstan 14 errors before and after, none in touched files; tsc clean; vitest 87 files / 612 tests green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -867,3 +867,74 @@ export interface PatientSession {
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
// ── شعبه، ساعت کاری و اتاق ───────────────────────────────────────────────────
|
||||
// «شعبه» جدول تازهای نیست: همان رکورد آدرس محل نوبتدهی است (`doctor_addresses`)،
|
||||
// همان چیزی که `WeeklySchedule.sessions[].location_id` به آن اشاره میکند. پس
|
||||
// Branch شکلِ `DoctorAddress::toArray()` است بهعلاوهٔ دو شمارشِ فهرست.
|
||||
|
||||
export interface Branch {
|
||||
id: string;
|
||||
uuid: string;
|
||||
type: 'personal' | 'clinic';
|
||||
clinic_id: number | null;
|
||||
clinic_name: string | null;
|
||||
name: string | null;
|
||||
map: { latitude: string | null; longitude: string | null };
|
||||
address: string | null;
|
||||
telephone: string | null;
|
||||
active: boolean;
|
||||
timezone: string;
|
||||
city: { id: string; name: string } | null;
|
||||
province: { id: string; name: string } | null;
|
||||
/** فقط در `GET /api/v1/branches` — شعبهٔ بدون ساعت «تعریفنشده» است، نه همیشهباز */
|
||||
working_hours_defined?: boolean;
|
||||
/** فقط در `GET /api/v1/branches` — تعداد اتاقهای فعال */
|
||||
rooms_count?: number;
|
||||
}
|
||||
|
||||
/** دقیقه از نیمهشب، نه رشتهٔ `"09:00"` — مقایسه و تقاطع باید عددی بماند. */
|
||||
export interface WorkingHourRange {
|
||||
sequence: number;
|
||||
start_minute: number;
|
||||
end_minute: number;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export interface BranchWorkingHours {
|
||||
branch_uuid: string;
|
||||
timezone: string;
|
||||
defined: boolean;
|
||||
/** کلیدهای `"0"`..`"6"`؛ ۰ = شنبه، همان قرارداد محاسبهٔ اسلات */
|
||||
days: Record<string, WorkingHourRange[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* بدنهٔ نوشتن ساعت کاری. عمداً شکل خواندن (`WorkingHourRange`) نیست: `sequence` را
|
||||
* سرور از ترتیب بازهها مشتق میکند و `start_time`/`end_time` فقط برای نمایشاند.
|
||||
*/
|
||||
export type WorkingHoursPayload = Record<string, { start_minute: number; end_minute: number }[]>;
|
||||
|
||||
export interface Room {
|
||||
uuid: string;
|
||||
address_uuid: string;
|
||||
address_name: string | null;
|
||||
name: string;
|
||||
room_type: string | null;
|
||||
/** ظرفیت همزمان: اتاق سهتخته یک اتاق با ظرفیت ۳ است، نه سه اتاق */
|
||||
capacity: number;
|
||||
floor: string | null;
|
||||
active: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export interface RoomPayload {
|
||||
name: string;
|
||||
room_type?: string | null;
|
||||
capacity?: number;
|
||||
floor?: string | null;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user