feat(patient): implement record number pattern management
- Add RecordNumberSettingsController for managing patient record number patterns. - Create RecordNumberPattern entity to represent the pattern configuration. - Implement RecordNumberPatternRepository for database interactions. - Develop RecordNumberGenerator service for generating and validating record numbers. - Add tests for record number generation, backfilling, and API interactions. - Ensure proper access control for viewing and updating patterns based on user roles.
This commit is contained in:
+80
-1
@@ -117,6 +117,69 @@ restriction.
|
||||
|
||||
---
|
||||
|
||||
### الگوی شمارهٔ پرونده (2026-08)
|
||||
|
||||
شمارهٔ پرونده میتواند بهجای ورود دستی، از یک الگوی per-tenant تولید شود. الگو و
|
||||
شمارندهاش روی `record_number_patterns` مینشینند — یک ردیف به ازای هر محیط.
|
||||
|
||||
#### `GET /api/v1/patient-record-number-settings`
|
||||
|
||||
**دسترسی:** `patients.view` (منشی/پزشکِ عضو با همان مجوز). محیط از `UserActiveContext`
|
||||
حل میشود؛ محیطِ حلنشده → `403 ERR_FORBIDDEN_001`.
|
||||
|
||||
خروجی واقعی (محیطی که هنوز چیزی ذخیره نکرده — همهچیز پیشفرض):
|
||||
|
||||
```json
|
||||
{"success":true,"data":{"enabled":false,"pattern":"{YY}-{SEQ:4}","reset_policy":"none","counter":0,"next_preview":"05-0001","can_edit":true}}
|
||||
```
|
||||
|
||||
| فیلد | معنی |
|
||||
|---|---|
|
||||
| `enabled` | تولید خودکار روشن است یا نه. خاموش = رفتار قدیمی (ورود دستی برای همه) |
|
||||
| `pattern` | الگو با توکنهای `{YYYY}` `{YY}` `{MM}` `{SEQ}` `{SEQ:n}` |
|
||||
| `reset_policy` | `none` \| `yearly` \| `monthly` — بر مبنای تقویم **شمسی** |
|
||||
| `counter` | شمارندهٔ فعلیِ همین دوره |
|
||||
| `next_preview` | شمارهٔ بعدی، بدون مصرفکردن شمارنده |
|
||||
| `can_edit` | آیا همین کاربر اجازهٔ `PUT` دارد (صاحب محیط یا ادمین) |
|
||||
|
||||
#### `PUT /api/v1/patient-record-number-settings`
|
||||
|
||||
**دسترسی:** فقط **صاحب محیط** (مالک کلینیک، یا پزشک در مطب شخصی خودش) و `ROLE_ADMIN`.
|
||||
پزشکِ مهمانِ کلینیک و منشی — حتی با `patients.update` — رد میشوند: شمارهٔ پرونده
|
||||
قرارداد ثبتِ کل مجموعه است.
|
||||
|
||||
| فیلد | نوع | الزامی | قاعده |
|
||||
|---|---|---|---|
|
||||
| `enabled` | bool | — | پیشفرض `false` |
|
||||
| `pattern` | string | ✅ | حداکثر ۶۰ نویسه، فقط توکنهای مجاز، حتماً شامل `{SEQ...}` |
|
||||
| `reset_policy` | string | — | `none` \| `yearly` \| `monthly`، پیشفرض `none` |
|
||||
|
||||
خروجی واقعی:
|
||||
|
||||
```json
|
||||
{"success":true,"data":{"enabled":true,"pattern":"MD-{YY}-{SEQ:4}","reset_policy":"yearly","counter":0,"next_preview":"MD-05-0001","can_edit":true}}
|
||||
```
|
||||
|
||||
**Errors** (پیامها واقعیاند — از اجرای همین اندپوینت):
|
||||
|
||||
| Code | HTTP | field | شرط |
|
||||
|------|------|-------|-----|
|
||||
| `ERR_FORBIDDEN_001` | 403 | — | «تغییر الگوی شماره پرونده فقط با حساب صاحب مجموعه ممکن است» |
|
||||
| `ERR_VALIDATION_002` | 422 | `pattern` | «توکن ناشناخته: {FOO} — مجاز: {YYYY}، {YY}، {MM}، {SEQ} یا {SEQ:n}» |
|
||||
| `ERR_VALIDATION_002` | 422 | `pattern` | «الگو باید {SEQ} یا {SEQ:n} داشته باشد، وگرنه همهٔ پروندهها یک شماره میگیرند» |
|
||||
| `ERR_VALIDATION_002` | 422 | `pattern` | «وقتی سال در الگو هست، ریست شمارنده باید سالانه یا ماهانه باشد» |
|
||||
| `ERR_VALIDATION_002` | 422 | `pattern` | «وقتی {MM} در الگو هست، ریست شمارنده باید ماهانه باشد» |
|
||||
| `ERR_VALIDATION_002` | 422 | `reset_policy` | «سیاست ریست شمارنده نامعتبر است» |
|
||||
| `ERR_VALIDATION_001` | 422 | — | بدنهٔ غیر-JSON |
|
||||
|
||||
- **تغییر الگو شمارنده را صفر نمیکند.** شمارههای صادرشده وجود دارند و شروع دوبارهٔ
|
||||
دنباله مستقیم به `uniq_patient_record_number` میخورد. صفرشدن فقط با ورود به دورهٔ
|
||||
تازه (سال/ماه شمسی) رخ میدهد.
|
||||
- **الگوی خاموش هم اعتبارسنجی میشود** تا خطای الگو سرِ فرمِ تنظیمات دیده شود، نه
|
||||
روزی که کاربر روشنش میکند و ساختِ پرونده میشکند.
|
||||
|
||||
---
|
||||
|
||||
### Create Patient Record
|
||||
|
||||
```
|
||||
@@ -138,13 +201,24 @@ Creates a patient record for a user under the current entity. If the record alre
|
||||
"mobile": "09xxxxxxxxx (اختیاری — برای جستجو یا ساخت بیمار جدید)",
|
||||
"name": "string (الزامی فقط هنگام ساخت بیمار جدید)",
|
||||
"national_code": "string (اختیاری، ۱۰ رقم)",
|
||||
"record_number": "string (اختیاری) — شماره پرونده، مخصوص رکورد",
|
||||
"record_number": "string (اختیاری) — شماره پرونده، مخصوص رکورد. با الگوی فعال فقط از صاحب مجموعه پذیرفته میشود",
|
||||
"tags": ["uuid برچسبهای TenantTag (اختیاری) — باید متعلق به همین tenant باشند"]
|
||||
}
|
||||
```
|
||||
|
||||
- اگر `user_uuid` و `mobile` هر دو خالی باشند → خطا.
|
||||
- `record_number` و `tags` روی خودِ رکورد ذخیره میشوند (نه پروفایل کاربر). سایر مشخصات دموگرافیک (`gender`, `date_of_birth`, `referral_source`, `description`, بیمهها) روی `UserProfile` هستند و از طریق `PATCH /patient/{uuid}` ست میشوند. پاسخ همیشه `record_number` و `tags: [{uuid,name,color}]` را برمیگرداند.
|
||||
- **شمارهٔ پرونده با الگوی فعال (2026-08):** اگر محیط الگوی فعال داشته باشد و `record_number`
|
||||
فرستاده **نشود**، سرور شمارهٔ بعدیِ همان الگو را میسازد و در پاسخ برمیگرداند
|
||||
(مثلاً `"record_number": "MD-0001"`). فرستادنِ `record_number` توسط کسی جز صاحب
|
||||
مجموعه → `403 ERR_FORBIDDEN_001` با `field: record_number` و پیام «ثبت دستی شماره
|
||||
پرونده مجاز نیست؛ شماره از الگوی مجموعه ساخته میشود». بدون الگوی فعال، رفتار قبلی
|
||||
برقرار است: شماره دستی است و همه میتوانند بفرستند. همین قاعده روی
|
||||
`PATCH /patient/{uuid}` هم اعمال میشود. تنظیم الگو:
|
||||
[الگوی شمارهٔ پرونده](#الگوی-شمارهٔ-پرونده-2026-08).
|
||||
- **پروندهٔ خودکارِ نوبت هم شماره میگیرد:** پروندهای که هنگام قطعیشدن نوبت ساخته
|
||||
میشود (`PatientService::autoCreateOnAppointmentConfirm`) از همان دنباله شماره
|
||||
میگیرد؛ پیش از این همیشه `null` بود.
|
||||
- برچسب متعلق به tenant دیگر → `422 ERR_VALIDATION_001` (`field: tags`).
|
||||
- `national_code` فقط وقتی روی کاربر ست میشود که کاربر کد ملی نداشته باشد.
|
||||
- موبایل تکراری duplicate نمیسازد؛ همان کاربر استفاده میشود.
|
||||
@@ -185,6 +259,11 @@ GET /api/v1/patient/{uuid}
|
||||
|
||||
Returns a single patient record, enriched with the patient's full profile (`profile`) درونخطی از `UserProfile`. اگر پروفایل وجود نداشت، فیلدها `null` برمیگردند (نه خطا). نام بیمهها از روی id resolve میشوند.
|
||||
|
||||
> **اثر جانبیِ عمدی (2026-08):** اگر این پرونده `record_number = null` باشد و محیط الگوی
|
||||
> فعال داشته باشد، همین `GET` شماره را تخصیص میدهد و ذخیره میکند — یعنی پروندههای
|
||||
> ساختهشده پیش از الگو، اولین بار که باز میشوند شمارهدار میشوند. فراخوانی دوم چیزی
|
||||
> نمینویسد و همان شماره را برمیگرداند. بدون الگوی فعال، این `GET` فقط میخواند.
|
||||
|
||||
**Response 200:**
|
||||
|
||||
```json
|
||||
|
||||
Reference in New Issue
Block a user