refactor(resource): drop the branch domain from resources

Resources never needed a branch: devices and rooms belong to the clinic
itself, and the picker always had exactly one option — a mandatory click
that decided nothing.

- `address_uuid` is now optional on resource and pool creation; when it is
  missing the environment's own address is used. Clients still sending it
  keep working.
- The panel no longer asks for or displays a branch anywhere: resource
  form, list column and filter, pool form and column, detail row, and the
  resource-first booking page.
- Availability no longer gates on `doctor_addresses.active`. That gate shut
  down every device of a clinic whose address row happened to be inactive,
  with a message no page in the panel could act on — no endpoint writes
  that column at all.

`address_id` stays on the resource: the timezone and the tenant pair are
derived from it. It is simply no longer the user's decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-03 14:57:47 +03:30
co-authored by Claude Opus 5
parent 05bb4a4ade
commit 581a553516
19 changed files with 246 additions and 117 deletions
+5 -4
View File
@@ -81,7 +81,7 @@ describe('ResourcesPage', () => {
it('passes the URL filters straight to the server', async () => {
mockApi();
renderWithProviders(<ResourcesPage />, {
route: '/admin/resources?address=b1&type=t1&skill=s1&status=1',
route: '/admin/resources?type=t1&skill=s1&status=1',
});
await waitFor(() => expect(get).toHaveBeenCalled());
@@ -89,13 +89,14 @@ describe('ResourcesPage', () => {
const called = get.mock.calls.map((c) => String(c[0]));
const listCall = called.find((p) => p.startsWith('/api/v1/resources'));
expect(listCall).toContain('address_uuid=b1');
// شعبه فیلتر نیست: منابع دامنهٔ شعبه ندارند.
expect(listCall).not.toContain('address_uuid');
expect(listCall).toContain('type_uuid=t1');
expect(listCall).toContain('skill_uuid=s1');
expect(listCall).toContain('active=1');
});
it('sends address and type only when creating, never when editing', async () => {
it('sends the type only when creating, never when editing', async () => {
mockApi();
renderWithProviders(<ResourcesPage />, { route: '/admin/resources' });
@@ -104,7 +105,7 @@ describe('ResourcesPage', () => {
fireEvent.click(screen.getByText('افزودن منبع'));
fireEvent.change(screen.getByPlaceholderText('لیزر آلکساندرایت ۱'), { target: { value: 'لیزر تازه' } });
// شعبه و نوع هنوز انتخاب نشده‌اند → ذخیره غیرفعال است.
// نوع هنوز انتخاب نشده است → ذخیره غیرفعال است.
expect(screen.getByText('ذخیره').closest('button')).toBeDisabled();
});