{data.map((p, i) => {
const isForecast = hasForecast && i >= nActual;
+ const colW = 100 / data.length;
+ const xPct = (pts[i][0] / W) * 100;
+ const yPct = (pts[i][1] / H) * 100;
return (
-
+
getId();
$todayStart = strtotime('today midnight');
$todayEnd = strtotime('tomorrow midnight') - 1;
- $monthStart = strtotime('first day of this month midnight');
+ $monthStart = $this->currentJalaliMonthStart();
$from = $request->query->get('from') ? (int) $request->query->get('from') : $monthStart;
$to = $request->query->get('to') ? (int) $request->query->get('to') : time();
@@ -214,7 +214,7 @@ class DashboardController extends BaseController
$todayEnd = strtotime('tomorrow midnight') - 1;
$tmrStart = strtotime('tomorrow midnight');
$tmrEnd = strtotime('tomorrow midnight') + 86399;
- $monthStart = strtotime('first day of this month midnight');
+ $monthStart = $this->currentJalaliMonthStart();
$from = $request->query->get('from') ? (int) $request->query->get('from') : $monthStart;
$to = $request->query->get('to') ? (int) $request->query->get('to') : time();
@@ -404,6 +404,20 @@ class DashboardController extends BaseController
];
}
+ /**
+ * آغاز ماه شمسی جاری.
+ *
+ * «این ماه» در محصولی که تقویمش شمسی است یعنی ماه شمسی، نه گرگوری. با
+ * `strtotime('first day of this month')` آمارِ داشبورد اولِ ژانویه/فوریه/… صفر
+ * میشد — وسط مرداد یا آبان — و هیچکجای پنل چنین مرزی را نشان نمیدهد.
+ */
+ private function currentJalaliMonthStart(): int
+ {
+ [$year, $month] = $this->currentJalaliYearMonth();
+
+ return $this->jalaliMonthRange($year, $month)['start'];
+ }
+
/**
* بازهی یونیکس یک ماه شمسی و تعداد روزهای آن.
* @return array{start:int, end:int, days:int}
diff --git a/tests/Dashboard/DashboardJalaliMonthTest.php b/tests/Dashboard/DashboardJalaliMonthTest.php
new file mode 100644
index 00000000..ceb02415
--- /dev/null
+++ b/tests/Dashboard/DashboardJalaliMonthTest.php
@@ -0,0 +1,93 @@
+set(\IntlCalendar::FIELD_DAY_OF_MONTH, 1);
+ $cal->set(\IntlCalendar::FIELD_HOUR_OF_DAY, 0);
+ $cal->set(\IntlCalendar::FIELD_MINUTE, 0);
+ $cal->set(\IntlCalendar::FIELD_SECOND, 0);
+ $cal->set(\IntlCalendar::FIELD_MILLISECOND, 0);
+
+ return (int) ($cal->getTime() / 1000);
+ }
+
+ /** یک نوبت در لحظهٔ دادهشده. */
+ private function appointmentAt(Doctor $doctor, User $patient, int $start): void
+ {
+ $this->em->persist($this->newAppointment($doctor, $patient, $start, $start + 1_800));
+ $this->em->flush();
+ }
+
+ public function testDoctorMonthCounterResetsOnTheJalaliMonthBoundary(): void
+ {
+ $owner = $this->createUser(['ROLE_DOCTOR']);
+ $doctor = new Doctor($owner, 'حمیدی');
+ $this->em->persist($doctor);
+ $this->em->flush();
+
+ $start = $this->jalaliMonthStart();
+
+ // یکی داخل ماه شمسی جاری، یکی درست پیش از آغازش.
+ $this->appointmentAt($doctor, $this->createUser(['ROLE_USER']), $start + 3_600);
+ $this->appointmentAt($doctor, $this->createUser(['ROLE_USER']), $start - 3_600);
+
+ $res = $this->authJson('GET', '/api/v1/dashboard/doctor', $owner);
+
+ self::assertSame(200, $this->responseCode());
+ self::assertSame(
+ 1,
+ $res['data']['stats']['this_month_appointments'],
+ 'فقط نوبتِ پس از آغاز ماه شمسی باید شمرده شود',
+ );
+ }
+
+ public function testClinicMonthCounterResetsOnTheJalaliMonthBoundary(): void
+ {
+ $clinicOwner = $this->createUser(['ROLE_CLINIC']);
+ $clinic = new Clinic($clinicOwner);
+ $clinic->setName('کلینیک نمونه');
+
+ $docOwner = $this->createUser(['ROLE_DOCTOR']);
+ $doctor = new Doctor($docOwner, 'حمیدی');
+ $clinic->getDoctors()->add($doctor);
+
+ $this->em->persist($doctor);
+ $this->em->persist($clinic);
+ $this->em->flush();
+
+ $start = $this->jalaliMonthStart();
+
+ $this->appointmentAt($doctor, $this->createUser(['ROLE_USER']), $start + 3_600);
+ $this->appointmentAt($doctor, $this->createUser(['ROLE_USER']), $start - 3_600);
+
+ $res = $this->authJson('GET', '/api/v1/dashboard/clinic', $clinicOwner);
+
+ self::assertSame(200, $this->responseCode());
+ self::assertSame(
+ 1,
+ $res['data']['stats']['this_month_appointments'],
+ 'فقط نوبتِ پس از آغاز ماه شمسی باید شمرده شود',
+ );
+ }
+}