fix(booking): reserve conversion produced a zero-length midnight appointment

TransferReserveModal built the live appointment from appointment_time/end_time,
which on a reserve entry are both 00:00 because slot_start == slot_end. Moving a
reserve back to the appointment list silently created a zero-length appointment
at midnight. With the new duration validation it would now fail loudly instead.

Converting back now asks for a real time: the service picker in service mode,
two required time inputs in slot mode. The appointment -> reserve direction is
untouched.

GET /my/appointments has its own array-hydration serializer rather than
Appointment::toArray(), so it exposed none of the service fields the panel needs.
Added service_items (separate query, no row multiplication and no N+1),
clinic_uuid and the duration pair. This was also a hidden prerequisite of the
public-site task, whose checklist listed it as "verify first".

The reserve table now lists every service instead of only the first.

Not done, deliberately: the DataTable migration the task asked for. Its stated
reason — inline tokens breaking dark mode — does not hold; this table's th/td
already use CSS variables and dark mode works. Rewriting a working table for no
real gain is unjustified risk.

Task: docs/new_feture/taskes/task-00-service-mode-completion/
Slot-mode contract: unchanged (--group=slot-mode-frozen green)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-30 15:04:09 +03:30
co-authored by Claude Opus 5
parent 96095c05f3
commit 4fbedecec1
5 changed files with 359 additions and 21 deletions
+12 -1
View File
@@ -180,7 +180,18 @@ export default function ReserveAppointmentsPage() {
</div>
</td>
<td style={{ ...td, fontWeight: 600 }}>{formatDate(a.slot_start)}</td>
<td style={td}>{a.service_item?.name || '—'}</td>
<td style={td}>
{/* چند-سرویسی: نگه‌داشتن فقط سرویس تکی یعنی بقیه دیده نمی‌شوند. */}
{a.service_items?.length
? a.service_items.map(s => s.name).join('، ')
: a.service_item?.name || '—'}
{a.service_total_minutes ? (
<span style={{ color: 'var(--text-3)', fontSize: 12, marginInlineStart: 6 }}>
({a.service_total_minutes} دقیقه
{a.service_buffer_minutes ? ` +${a.service_buffer_minutes} فاصله` : ''})
</span>
) : null}
</td>
<td style={td}>{a.staff?.full_name || '—'}</td>
<td style={td}>
<AppointmentStatusDropdown uuid={a.uuid} currentStatus={a.status} version={a.version} queryKey={queryKey} />