forked from Yara724/api
229 lines
11 KiB
Markdown
229 lines
11 KiB
Markdown
# راهنمای فرانتاند: ثبت پاسخ کارشناس خسارت (V2)
|
||
|
||
این مستند قرارداد API زیر را توضیح میدهد، بهویژه اعتبارسنجی مبلغها و ساختار خطاهایی که باید در فرم نمایش داده شوند.
|
||
|
||
```http
|
||
PUT /v2/expert-claim/reply/submit/:claimRequestId
|
||
Authorization: Bearer <actor-token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
اگر محیط شما API را زیر پیشوند `/api` منتشر میکند، مسیر نهایی بهصورت `/api/v2/expert-claim/reply/submit/:claimRequestId` است.
|
||
|
||
## پیشنیازها
|
||
|
||
- کاربر باید نقش کارشناس مجاز داشته باشد.
|
||
- پرونده باید پیش از ارسال توسط همین کارشناس قفل شده باشد.
|
||
- `partId` هر ردیف باید از `damagedParts[].partId` در جزئیات پرونده انتخاب شود؛ شناسه دلخواه ارسال نکنید.
|
||
- دستکم یک ردیف در `parts` لازم است.
|
||
|
||
## بدنه درخواست
|
||
|
||
تمام مبلغها باید **string** باشند. ارقام فارسی/انگلیسی و جداکننده هزارگان پذیرفته میشوند؛ برای نمونه هر دو مقدار `"100000"` و `"۱۰۰,۰۰۰"` معتبرند.
|
||
|
||
```json
|
||
{
|
||
"description": "تعویض درب جلو لازم است",
|
||
"parts": [
|
||
{
|
||
"partId": 201,
|
||
"typeOfDamage": "تعویض",
|
||
"price": "2500000",
|
||
"salary": "400000",
|
||
"totalPayment": "2900000",
|
||
"factorNeeded": false,
|
||
"daghi": {
|
||
"option": "ارزش لوازم بازیافتی",
|
||
"price": "300000"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### فیلدهای سطح بالا
|
||
|
||
| فیلد | الزامی | توضیح |
|
||
| --- | --- | --- |
|
||
| `description` | خیر | یادداشت متنی کارشناس. میتواند ارسال نشود. |
|
||
| `parts` | بله | آرایهای با حداقل یک ردیف قیمتگذاری. |
|
||
|
||
### فیلدهای هر ردیف `parts[i]`
|
||
|
||
| فیلد | الزامی | مقدار و قاعده |
|
||
| --- | --- | --- |
|
||
| `partId` | بله | عدد صحیح؛ باید متعلق به قطعات آسیبدیده همان پرونده باشد. |
|
||
| `typeOfDamage` | بله | دقیقاً یکی از `"تعمیر"` یا `"تعویض"`. |
|
||
| `price` | برای `"تعویض"` بله؛ برای `"تعمیر"` اختیاری | در صورت ارسال، مبلغ بین 100,000 تا 10,000,000,000 تومان. |
|
||
| `salary` | بله | مبلغ بین 100,000 تا 10,000,000,000 تومان. |
|
||
| `totalPayment` | بله | مبلغ بین 1,000,000 تا 100,000,000,000 ریال. فقط در جریان V1، مجموع این فیلدها در کل پرونده نباید از 530,000,000 ریال بیشتر شود. |
|
||
| `factorNeeded` | بله | مقدار Boolean واقعی (`true` یا `false`)؛ رشته ارسال نکنید. |
|
||
| `daghi` | برای `"تعویض"` بله | برای `"تعمیر"` لازم نیست و در ثبت نهایی حذف میشود. |
|
||
| `daghi.option` | در صورت وجود `daghi` بله | یکی از `"ارزش لوازم بازیافتی"`، `"تحویل داغی"`، `"فاقد ارزش"` یا `"با احتساب داغی"`. |
|
||
| `daghi.price` | فقط وقتی `option` برابر `"ارزش لوازم بازیافتی"` است | مبلغ بین 100,000 تا 10,000,000,000 تومان. |
|
||
| `daghi.branchId` | فقط وقتی `option` برابر `"تحویل داغی"` است | Mongo ObjectId معتبرِ شعبه. |
|
||
|
||
نکتهها:
|
||
|
||
- مقدار `0` برای هیچ مبلغ ارسالی این endpoint معتبر نیست؛ حداقل مبلغ 100,000 تومان است.
|
||
- محدودیت هر فیلد مبلغ مستقل است؛ سقف 530 میلیون ریال فقط برای **مجموع `totalPayment` تمام ردیفهای پرونده V1** اعمال میشود. جریانهای V2 تا V6 سقف مجموع ندارند.
|
||
- فیلدهای ناشناخته در body حذف میشوند. فرانتاند نباید برای انتقال داده به آنها تکیه کند.
|
||
|
||
## پاسخ موفق
|
||
|
||
پاسخ `200` شامل مسیر بعدی workflow است. نمونه:
|
||
|
||
```json
|
||
{
|
||
"claimRequestId": "66c...",
|
||
"status": "COMPLETED",
|
||
"claimStatus": "APPROVED",
|
||
"currentStep": "CLAIM_COMPLETED",
|
||
"workflowNextStep": "CLAIM_COMPLETED",
|
||
"factorNeeded": false,
|
||
"mixedPricingAndFactors": false,
|
||
"allPartsFactorNeeded": false,
|
||
"isFinalReplyAfterObjection": false
|
||
}
|
||
```
|
||
|
||
مقادیر `status` و step به این وابستهاند که ردیف factor داشته باشید یا نه؛ فرانتاند باید از مقادیر پاسخ استفاده کند، نه اینکه مسیر بعدی را فقط از payload حدس بزند.
|
||
|
||
## قرارداد خطا
|
||
|
||
همه خطاهای این endpoint JSON هستند و NestJS مقدار `statusCode` را نیز به پاسخ اضافه میکند. برای نمایش پیام به کاربر از `message` استفاده کنید و برای منطق برنامه از `code` استفاده کنید؛ متن فارسی را با متن ثابت در فرانتاند جایگزین نکنید.
|
||
|
||
### 1. خطای ساختار DTO — `400`
|
||
|
||
برای نبودن فیلد الزامی، نوع اشتباه، enum نامعتبر، آرایه خالی، یا فرمت مبلغ نامعتبر، پاسخ زیر برمیگردد:
|
||
|
||
```json
|
||
{
|
||
"statusCode": 400,
|
||
"message": "اطلاعات ارسالی پاسخ کارشناسی معتبر نیست.",
|
||
"error": "EXPERT_REPLY_VALIDATION_ERROR",
|
||
"code": "EXPERT_REPLY_VALIDATION_ERROR",
|
||
"validationErrors": [
|
||
{
|
||
"field": "parts[0].salary",
|
||
"message": "باید مبلغ صحیح و غیرمنفی به تومان باشد."
|
||
},
|
||
{
|
||
"field": "parts[0].factorNeeded",
|
||
"message": "باید درست یا نادرست باشد."
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
`validationErrors` ممکن است چند خطا داشته باشد. کلید `field` دقیقاً برای اتصال به کنترل فرم است؛ مانند `parts[0].daghi.price` یا `parts[2].partId`.
|
||
|
||
### 2. خطای قواعد مبلغ و قیمتگذاری — `400`
|
||
|
||
این خطاها پس از اعتبارسنجی ساختار و پیش از تغییر وضعیت پرونده بررسی میشوند. برای مثال، مبلغ کمتر از حداقل:
|
||
|
||
```json
|
||
{
|
||
"statusCode": 400,
|
||
"message": "قیمت قطعه 201 باید حداقل ۱۰۰٬۰۰۰ و حداکثر ۱۰٬۰۰۰٬۰۰۰٬۰۰۰ تومان باشد.",
|
||
"error": "EXPERT_REPLY_VALIDATION_ERROR",
|
||
"code": "EXPERT_REPLY_VALIDATION_ERROR",
|
||
"field": "parts[0].price",
|
||
"partId": "201",
|
||
"rule": "amount_out_of_range",
|
||
"minAmount": 100000,
|
||
"maxAmount": 10000000000
|
||
}
|
||
```
|
||
|
||
`rule` یکی از این مقادیر است:
|
||
|
||
| مقدار | معنی |
|
||
| --- | --- |
|
||
| `required` | فیلد لازم ارسال نشده است. |
|
||
| `invalid_value` | مقدار enum یا شناسه معتبر نیست. |
|
||
| `invalid_amount` | مبلغ، عدد صحیح غیرمنفی به تومان نیست. |
|
||
| `amount_out_of_range` | مبلغ خارج از بازه `minAmount` و `maxAmount` است. |
|
||
|
||
در صورت `amount_out_of_range`، برای نمایش محدوده از اعداد `minAmount` و `maxAmount` استفاده کنید، نه از parse کردن متن `message`.
|
||
|
||
### 3. سقف مجموع مبلغها — `400`
|
||
|
||
```json
|
||
{
|
||
"statusCode": 400,
|
||
"message": "مجموع مبلغ قطعات (۵۴۰٬۰۰۰٬۰۰۰) از سقف مجاز (۵۳۰٬۰۰۰٬۰۰۰) ریال بیشتر است.",
|
||
"error": "PRICE_CAP_ERROR",
|
||
"code": "PRICE_CAP_ERROR",
|
||
"totalPrice": 540000000,
|
||
"priceCap": 530000000
|
||
}
|
||
```
|
||
|
||
این خطا فقط برای پروندههای V1 رخ میدهد و به یک ردیف مشخص وصل نیست. آن را در بالای جدول قیمتها نمایش دهید و از `priceCap` جزئیات پرونده/خطا برای پیام UI استفاده کنید. مقدار `priceCap: null` یعنی پرونده سقف مجموع ندارد.
|
||
|
||
### 4. خطاهای قواعد پرونده و workflow
|
||
|
||
این خطاها ساختار مشترک زیر را دارند:
|
||
|
||
```json
|
||
{
|
||
"statusCode": 400,
|
||
"message": "قطعه با شناسه 201 در فهرست قطعات آسیبدیده پرونده وجود ندارد.",
|
||
"error": "EXPERT_REPLY_SUBMISSION_ERROR",
|
||
"code": "PART_NOT_ON_CLAIM",
|
||
"field": "partId",
|
||
"partId": 201
|
||
}
|
||
```
|
||
|
||
کدهای مهم:
|
||
|
||
| HTTP | `code` | رفتار پیشنهادی فرانتاند |
|
||
| --- | --- | --- |
|
||
| 404 | `CLAIM_NOT_FOUND` | پیام خطا و بازگشت به فهرست پروندهها. |
|
||
| 400 | `CLAIM_NOT_REVIEWABLE` | جزئیات پرونده را refresh کنید؛ ثبت در وضعیت فعلی مجاز نیست. |
|
||
| 403 | `CLAIM_NOT_LOCKED` | کاربر باید ابتدا پرونده را قفل کند. |
|
||
| 403 | `CLAIM_LOCKED_BY_ANOTHER_EXPERT` | فرم را read-only کنید و پیام مناسب نمایش دهید. |
|
||
| 400 | `PART_NOT_ON_CLAIM` | داده قطعات را refresh کنید و ردیف خطادار را اصلاح/حذف کنید. |
|
||
| 400 | `PART_INVALID` / `PART_ID_INVALID` | ردیف دارای `partId` را اصلاح کنید. |
|
||
| 400 | `DUPLICATE_PART` | ردیف تکراری را حذف کنید. |
|
||
| 400 | `DAGHI_OPTION_REQUIRED` | فیلد `daghi.option` همان ردیف را نشاندار کنید. |
|
||
| 400 | `DAGHI_PRICE_REQUIRED` | فیلد `daghi.price` همان ردیف را نشاندار کنید. |
|
||
| 400 | `DAGHI_BRANCH_REQUIRED` / `DAGHI_BRANCH_INVALID` | فیلد `daghi.branchId` همان ردیف را نشاندار کنید. |
|
||
| 409 | `FINAL_REPLY_ALREADY_SUBMITTED` | ثبت مجدد ممنوع است؛ داده پرونده را refresh کنید. |
|
||
|
||
برای خطاهای داغی که از سرویس برمیگردند، `field` به شکل `parts[<index>].daghi.<property>` است.
|
||
|
||
## الگوی پیشنهادی هندل کردن خطا در فرانتاند
|
||
|
||
```ts
|
||
type ApiErrorBody = {
|
||
message?: string;
|
||
code?: string;
|
||
field?: string;
|
||
validationErrors?: Array<{ field: string; message: string }>;
|
||
minAmount?: number;
|
||
maxAmount?: number;
|
||
priceCap?: number;
|
||
};
|
||
|
||
function applySubmitError(body: ApiErrorBody) {
|
||
if (body.validationErrors?.length) {
|
||
for (const issue of body.validationErrors) {
|
||
setFieldError(issue.field, issue.message);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (body.field) {
|
||
setFieldError(body.field, body.message ?? "مقدار واردشده معتبر نیست.");
|
||
return;
|
||
}
|
||
|
||
setFormError(body.message ?? "ثبت پاسخ کارشناسی انجام نشد.");
|
||
}
|
||
```
|
||
|
||
قبل از ارسال، فرانتاند میتواند همین بازه مبلغ را برای تجربه کاربری بهتر بررسی کند؛ با این حال اعتبار نهایی همیشه پاسخ API است. جمع `totalPayment` را فقط وقتی `priceCap` جزئیات پرونده عدد است با سقف مقایسه کنید؛ `null` یعنی V2 تا V6 و بدون سقف مجموع.
|