feat(patient): soft-archive sessions with active/all/archived filter
Add archived + archived_at columns to PatientSession (setArchived stamps the
time). findByRecord/countByRecord accept an archived filter (default all to
keep existing callers, incl. the discount visit-count, unchanged); the
sessions GET reads ?filter=active|all|archived, defaulting to active so
archived visits are hidden. updateSession accepts { archived }. Records are
kept, only hidden. Verified end-to-end; docs updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+2
-1
@@ -280,7 +280,7 @@ GET /api/v1/patient/{uuid}/sessions
|
||||
|
||||
Returns paginated sessions for a patient record.
|
||||
|
||||
**Query params:** `page`, `limit` (same as list)
|
||||
**Query params:** `page`, `limit`, و `filter` = `active` (پیشفرض — آرشیوها مخفی) | `all` | `archived`. مقادیر نامعتبر به `active` برمیگردند. هر session کلیدهای `archived` (bool) و `archived_at` (unix|null) را هم دارد.
|
||||
|
||||
**Response 200:**
|
||||
|
||||
@@ -488,6 +488,7 @@ Updates mutable fields on a session.
|
||||
- **تخفیف تسویه (دستی):** `discount_type` = `percent` (۰..۱۰۰) یا `fixed` (ریال، حداکثر برابر مبلغ نهایی) یا `null` (حذف تخفیف). مبلغ محاسبهشده در `discount_rials` برمیگردد. تخفیف نمیتواند از «مبلغ نهایی منهای پرداختهای ثبتشده» بیشتر شود. تخفیفی که مانده را صفر کند مراجعه را تسویهشده میکند (`is_paid`, `paid_at`).
|
||||
- **تخفیف بر اساس قانون:** `discount_rule_uuid` (رشته) → قانون تخفیف (owner-scoped) اعمال میشود؛ مقدار ریالی از خود قانون توسط موتور محاسبه میگردد (نوع/مبنا بر اساس قانون). `''`/`null` → حذف تخفیف. اولویت بر `discount_type` دستی. قانونِ نامعتبر → `404`. منبع اعمالشده در پاسخ بهصورت `applied_discount_rule_id` و `applied_discount_rule_label` (audit) برمیگردد. قوانین قابلاعمال از `GET /api/v1/session/{uuid}/discount-suggestions` (نگاه کنید به `discount.md`).
|
||||
- `paid_at`: unix timestamp زمان تسویه.
|
||||
- **آرشیو:** `archived` (bool) → آرشیو نرم مراجعه؛ `true` آن را از لیست پیشفرض (`filter=active`) مخفی میکند و `archived_at` را ست میکند، `false` بازمیگرداند. سابقه (فاکتور/پرداختها) حذف نمیشود.
|
||||
|
||||
**Response 200:**
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260717111223 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add archived + archived_at columns to patient_sessions (soft-archive)';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE patient_sessions ADD archived TINYINT DEFAULT 0 NOT NULL, ADD archived_at INT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE patient_sessions DROP archived, DROP archived_at');
|
||||
}
|
||||
}
|
||||
@@ -922,8 +922,10 @@ class PatientController extends BaseController
|
||||
|
||||
$page = max(1, (int) $request->query->get('page', 1));
|
||||
$limit = min(50, max(10, (int) $request->query->get('limit', 20)));
|
||||
$sessions = $this->sessionRepo->findByRecord($record, $page, $limit);
|
||||
$total = $this->sessionRepo->countByRecord($record);
|
||||
// فیلتر آرشیو: active (پیشفرض — آرشیوها مخفی) | all | archived
|
||||
$filter = in_array($request->query->get('filter'), ['all', 'archived'], true) ? $request->query->get('filter') : 'active';
|
||||
$sessions = $this->sessionRepo->findByRecord($record, $page, $limit, $filter);
|
||||
$total = $this->sessionRepo->countByRecord($record, $filter);
|
||||
|
||||
return $this->paginated(
|
||||
array_map(fn($s) => $this->sessionWithBilling($s), $sessions),
|
||||
@@ -1047,6 +1049,9 @@ class PatientController extends BaseController
|
||||
|
||||
if (isset($data['notes'])) { $session->setNotes($data['notes']); }
|
||||
|
||||
// آرشیو نرم: مخفیسازی مراجعهی اشتباه بدون حذف سابقه.
|
||||
if (array_key_exists('archived', $data)) { $session->setArchived((bool) $data['archived']); }
|
||||
|
||||
// تخفیف بر اساس قانون (discount_rule_uuid): مقدار از خود قانون، با ثبت منبع.
|
||||
// '' یا null → حذف تخفیف. اولویت بر تخفیف دستی.
|
||||
if (array_key_exists('discount_rule_uuid', $data)) {
|
||||
|
||||
@@ -91,6 +91,13 @@ class PatientSession
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $notes = null;
|
||||
|
||||
/** آرشیو نرم: مراجعهی اشتباه از لیست پیشفرض مخفی میشود ولی سابقه حفظ میگردد. */
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
private bool $archived = false;
|
||||
|
||||
#[ORM\Column(name: 'archived_at', type: 'integer', nullable: true)]
|
||||
private ?int $archivedAt = null;
|
||||
|
||||
#[ORM\Column(name: 'created_at', type: 'integer')]
|
||||
private int $createdAt;
|
||||
|
||||
@@ -217,6 +224,9 @@ class PatientSession
|
||||
public function setInventoryPackage(?InventoryPackage $v): self { $this->inventoryPackage = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setPaidAt(?int $v): self { $this->paidAt = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setNotes(?string $v): self { $this->notes = $v; $this->updatedAt = time(); return $this; }
|
||||
public function isArchived(): bool { return $this->archived; }
|
||||
public function setArchived(bool $v): self { $this->archived = $v; $this->archivedAt = $v ? time() : null; $this->updatedAt = time(); return $this; }
|
||||
public function getArchivedAt(): ?int { return $this->archivedAt; }
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -259,6 +269,8 @@ class PatientSession
|
||||
),
|
||||
'consumables_total_rials' => $this->getConsumablesTotalRials(),
|
||||
'notes' => $this->notes,
|
||||
'archived' => $this->archived,
|
||||
'archived_at' => $this->archivedAt,
|
||||
'created_at' => $this->createdAt,
|
||||
'updated_at' => $this->updatedAt,
|
||||
];
|
||||
|
||||
@@ -19,26 +19,36 @@ class PatientSessionRepository extends ServiceEntityRepository
|
||||
return $this->findOneBy(['uuid' => $uuid]);
|
||||
}
|
||||
|
||||
public function findByRecord(PatientRecord $record, int $page = 1, int $limit = 20): array
|
||||
/** $filter: all | active | archived. پیشفرض all برای حفظ رفتار callerهای موجود. */
|
||||
public function findByRecord(PatientRecord $record, int $page = 1, int $limit = 20, string $filter = 'all'): array
|
||||
{
|
||||
return $this->createQueryBuilder('s')
|
||||
$qb = $this->createQueryBuilder('s')
|
||||
->where('s.record = :record')
|
||||
->setParameter('record', $record)
|
||||
->orderBy('s.id', 'DESC')
|
||||
->setFirstResult(($page - 1) * $limit)
|
||||
->setMaxResults($limit)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
->setMaxResults($limit);
|
||||
$this->applyArchivedFilter($qb, $filter);
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function countByRecord(PatientRecord $record): int
|
||||
public function countByRecord(PatientRecord $record, string $filter = 'all'): int
|
||||
{
|
||||
return (int) $this->createQueryBuilder('s')
|
||||
$qb = $this->createQueryBuilder('s')
|
||||
->select('COUNT(s.id)')
|
||||
->where('s.record = :record')
|
||||
->setParameter('record', $record)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
->setParameter('record', $record);
|
||||
$this->applyArchivedFilter($qb, $filter);
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
private function applyArchivedFilter(\Doctrine\ORM\QueryBuilder $qb, string $filter): void
|
||||
{
|
||||
if ($filter === 'active') {
|
||||
$qb->andWhere('s.archived = false');
|
||||
} elseif ($filter === 'archived') {
|
||||
$qb->andWhere('s.archived = true');
|
||||
}
|
||||
}
|
||||
|
||||
public function sumRevenue(string $entityType, int $entityId, int $from, int $to): int
|
||||
|
||||
Reference in New Issue
Block a user