)}
@@ -151,7 +197,9 @@ function CasesTab({ status, onStatus, search, onSearch }: {
{search
? `برای «${search}» پروندهای پیدا نشد.`
- : 'پروندهای یافت نشد. پرونده وقتی ساخته میشود که نوبتِ سرویسی با «طول درمان» قطعی شود.'}
+ : (from !== '' || to !== '')
+ ? 'در این بازهٔ تاریخ پروندهای باز نشده است.'
+ : 'پروندهای یافت نشد. پرونده وقتی ساخته میشود که نوبتِ سرویسی با «طول درمان» قطعی شود.'}
@@ -191,7 +239,8 @@ function CaseCard({ item: c, onEdit }: { item: TreatmentCaseSummary; onEdit: ()
{c.service.name}
{c.patient.mobile}
- شروع: {formatDate(c.opened_at)}
+ {/* ساعت هم لازم است: چند پروندهٔ یک روز فقط با ساعت از هم جدا میشوند. */}
+ شروع: {formatDateTime(c.opened_at)}
{c.supervisor && پزشک ناظر: {c.supervisor.name}}
{c.areas.length > 0 && نواحی: {c.areas.map((a) => a.name).join('، ')}}
diff --git a/docs/api/treatment.md b/docs/api/treatment.md
index 56ff4f5b..3407acff 100644
--- a/docs/api/treatment.md
+++ b/docs/api/treatment.md
@@ -210,6 +210,11 @@ single-session again. Idempotent: deleting a service that has no protocol still
|---|---|
| `status` | `active` \| `completed` \| `abandoned` — نبودش یعنی همه |
| `q` | جستجو روی نام بیمار، موبایل، کد ملی، شمارهٔ پرونده و نام سرویس |
+| `from` | `YYYY-MM-DD` میلادی — پروندههایی که از ابتدای این روز به بعد باز شدهاند |
+| `to` | `YYYY-MM-DD` میلادی — تا انتهای این روز |
+
+بازه روی `opened_at` است نه سررسید جلسه. تایمزون تهران (`config/bootstrap_tz.php`).
+مقدارِ بدفرم بیصدا نادیده گرفته میشود، نه خطا — فیلتر است نه ورودی فرم.
```json
{
diff --git a/src/Treatment/Controller/TreatmentCaseController.php b/src/Treatment/Controller/TreatmentCaseController.php
index 58dc879c..2065182f 100644
--- a/src/Treatment/Controller/TreatmentCaseController.php
+++ b/src/Treatment/Controller/TreatmentCaseController.php
@@ -52,10 +52,34 @@ class TreatmentCaseController extends BaseController
return $this->success(array_map(
static fn (TreatmentCase $c): array => $c->toArray(),
- $this->cases->findForTenant($entityType, $entityId, $status, $q !== '' ? $q : null),
+ $this->cases->findForTenant(
+ $entityType,
+ $entityId,
+ $status,
+ $q !== '' ? $q : null,
+ $this->dayBoundary($request->query->get('from'), '00:00:00'),
+ $this->dayBoundary($request->query->get('to'), '23:59:59'),
+ ),
));
}
+ /**
+ * `YYYY-MM-DD` میلادی → ثانیهٔ ابتدای/انتهای همان روز.
+ *
+ * تایمزون سراسری اپلیکیشن تهران است (`config/bootstrap_tz.php`)، پس همان
+ * `strtotime` که بقیهٔ فیلترهای تاریخِ نوبتها استفاده میکنند اینجا هم درست است.
+ */
+ private function dayBoundary(mixed $value, string $time): ?int
+ {
+ if (!is_string($value) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
+ return null;
+ }
+
+ $ts = strtotime($value . ' ' . $time);
+
+ return $ts === false ? null : $ts;
+ }
+
#[Route('/api/v1/treatment-case/{uuid}', name: 'treatment_case_show', methods: ['GET'])]
public function show(#[CurrentUser] User $user, string $uuid): JsonResponse
{
diff --git a/src/Treatment/Repository/TreatmentCaseRepository.php b/src/Treatment/Repository/TreatmentCaseRepository.php
index 3f6cb2c7..224be829 100644
--- a/src/Treatment/Repository/TreatmentCaseRepository.php
+++ b/src/Treatment/Repository/TreatmentCaseRepository.php
@@ -49,6 +49,8 @@ class TreatmentCaseRepository extends ServiceEntityRepository
int $entityId,
?string $status = null,
?string $q = null,
+ ?int $openedFrom = null,
+ ?int $openedTo = null,
): array {
$qb = $this->createQueryBuilder('c')
->where('c.entityType = :type')
@@ -61,6 +63,16 @@ class TreatmentCaseRepository extends ServiceEntityRepository
$qb->andWhere('c.status = :status')->setParameter('status', $status);
}
+ // بازه روی تاریخِ باز شدن پرونده است، نه سررسید جلسه: سؤالِ این فهرست
+ // «چه پروندههایی در این بازه شروع شدند» است.
+ if ($openedFrom !== null) {
+ $qb->andWhere('c.openedAt >= :from')->setParameter('from', $openedFrom);
+ }
+
+ if ($openedTo !== null) {
+ $qb->andWhere('c.openedAt <= :to')->setParameter('to', $openedTo);
+ }
+
if ($q !== null && $q !== '') {
$qb->join('c.patientRecord', 'pr')
->join('pr.user', 'u')
diff --git a/tests/Treatment/TreatmentCaseEditTest.php b/tests/Treatment/TreatmentCaseEditTest.php
index 6363b634..ba9a0037 100644
--- a/tests/Treatment/TreatmentCaseEditTest.php
+++ b/tests/Treatment/TreatmentCaseEditTest.php
@@ -218,4 +218,27 @@ class TreatmentCaseEditTest extends ApiTestCase
self::assertSame($case->getId(), $this->cases()->findForTenant($type, $id, null, $name)[0]->getId());
}
+
+ /** فیلتر بازه روی تاریخِ باز شدن پرونده است، نه سررسید جلسه. */
+ public function testDateRangeFiltersByOpenedAt(): void
+ {
+ [$case, $clinic] = $this->scenario('بازه ' . uniqid());
+
+ $type = 'clinic';
+ $id = (int) $clinic->getId();
+ $openedAt = $case->getOpenedAt();
+
+ // بازهای که همان لحظه را در بر میگیرد
+ self::assertCount(1, $this->cases()->findForTenant($type, $id, null, null, $openedAt - 60, $openedAt + 60));
+
+ // کرانِ پایین بعد از پرونده
+ self::assertSame([], $this->cases()->findForTenant($type, $id, null, null, $openedAt + 60, null));
+
+ // کرانِ بالا قبل از پرونده
+ self::assertSame([], $this->cases()->findForTenant($type, $id, null, null, null, $openedAt - 60));
+
+ // یکطرفه هم باید کار کند
+ self::assertCount(1, $this->cases()->findForTenant($type, $id, null, null, $openedAt - 60, null));
+ self::assertCount(1, $this->cases()->findForTenant($type, $id, null, null, null, $openedAt + 60));
+ }
}