API.php
این مستند نمای کلی مسیرهای API پروژه را ارائه میدهد که در معماری بکاند Laravel توسعه یافته است. ساختار بهصورت ماژولار طراحی شده و در قالب چند دامنه اصلی شامل Panel (v2)، B2C (v1)، Hub، AI و Core System پیادهسازی شده است.
- در بخش Panel (v2)، مسیرها تحت توکن JWT فعال میگردند و مسئولیت مدیریت عملیات مالی، حسابداری Redis، اسناد دستی، ترید، چارتر، پست الکترونیک، آموزش، و ماژولهای رسمی و داخلی را برعهده دارند. این بخش هستهی مدیریتی سیستم بوده و عملیات بکاند تیمی را هدایت میکند.
- در بخش B2C (v1)، APIها برای کاربران نهایی طراحی شدهاند و شامل احراز هویت، عملیات کیفپول و تراکنش، مدیریت رزروهای آنلاین و محتوای مقالات است. برخی مسیرها عمومی و برخی نیازمند JWT میباشند.
- Hub API بهعنوان مرکز پردازش مالی و صورتحسابها عمل کرده و پرداختها و رزروها را بین سیستمهای مختلف هماهنگ میسازد.
- AI services قابلیتهای هوش مصنوعی برای تحلیل و پردازش خودکار دادهها را فراهم میکند. در نهایت Core APIs دادههای مادر سیستم مانند دفاتر، پروازها و اقامتگاهها را تأمین میکنند.
- تمامی مسیرها با اصول REST طراحی شده، دارای ساختار گروهی (prefix/controller grouping) هستند و برای هماهنگی کامل بین فرانتاند و بکاند بهینه شدهاند.
- POST /api/auth/connect/branch
- POST /api/v2/colleagues/billing
- POST /api/v2/trade/national
- POST /api/auth/sign-in
- POST /api/auth/update
- POST /api/auth/blocking
- POST /api/auth/forgot/otp
- POST /api/auth/connect/otp
- POST /api/auth/connect/submit
- POST /api/auth/forgot/submit
- POST /api/auth/access-token
- GET /api/v2/notifications
- GET /api/v2/exam/get
- POST /api/v2/exam/response
- POST /api/v2/trade/list
- POST /api/v2/trade/search
- POST /api/v2/trade/cost-benefit
- POST /api/v2/references/{type}/list
- POST /api/v2/trade/store
- POST /api/v2/trade/edit
- POST /api/v2/trade/item/edit
- POST /api/v2/trade/item/add
- POST /api/v2/trade/item/delete
- POST /api/v2/trade/item/value_added
- POST /api/v2/trade/operation
- POST /api/v2/trade/operation/general
- POST /api/v2/trade/refund/update
- POST /api/v2/trade/refund/delete
- POST /api/v2/trade/statement
- POST /api/v2/trade/request
- POST /api/v2/trade/commitment
- POST /api/v2/trade/commitment/submit
- POST /api/v2/trade/payment-receipt
- POST /api/v2/logs
- POST /api/v2/pledger/store
- POST /api/v2/pledger/update
- POST /api/v2/pledger/delete
- POST /api/v2/purchases/list
- PUT /api/v2/purchase/retry/{id}
- GET /api/v2/flights/approved-rate
- POST /api/v2/operator/update-password
- POST /api/v2/passengers/search
- POST /api/v2/passenger/add-branch
- POST /api/v2/get_country
- POST /api/v2/get_other_services
- POST /api/v2/get_visa_country
- POST /api/v2/customers/list
- GET /api/v2/cartable/categories
- GET /api/v2/cartable/requests/list
- POST /api/v2/cartable/request/operation
- GET /api/v2/calendar
- POST /api/v2/personnel/traffic/store
- POST /api/v2/personnel/traffic/update
- DELETE /api/v2/personnel/traffic/delete
- POST /api/v2/personnel/traffic/license/store
- POST /api/v2/personnel/traffic/license/update
- DELETE /api/v2/personnel/traffic/license/delete
- GET /api/v2/passenger/profile
- POST /api/v2/passenger/store
- POST /api/v2/passenger/update
- POST /api/v2/passenger/delete
- POST /api/v2/auth/by
- POST /api/v2/auth/connect/disconnect
- POST /api/v2/operator/store
- POST /api/v2/operator/update
- GET /api/v2/operator/get
- GET /api/v2/online/reservation/list
- GET /api/v2/online/reservation/penalty
- POST /api/v2/online/reservation/refund
- POST /api/v2/online/credit
- POST /api/v2/online/credit/update
- POST /api/v2/online/reservation/penalty
- Route: POST /api/v2/online/{type}/lock
- Route: POST /api/v2/online/search/{type}
- Route: POST /api/v2/online/flight/list/date
- Route: POST /api/v2/online/{type}/unlock
- Route: POST /api/v2/online/{type}/status
- Route: GET /api/v2/online/accommodation/list
- GET /api/v2/online/accommodation/get_min_prices
- GET /api/v2/online/accommodation/get_room_type_prices
- GET /api/v2/online/accommodation/get_details
- * POST /api/v2/online/accommodation/check_status
- GET /api/v2/base/data
- POST /api/v2/titles/list
- POST /api/v2/notif/simple/list
- POST /api/v2/notif/send
- POST /api/v2/dashboard
- POST /api/v2/operator/details
- POST /api/v2/management/office
- GET /api/v2/panel/groups/get
- GET /api/v2/panel/bulk/receptions
- POST /api/v2/panel/bulk/add
- POST /countries-cities/get
- GET /api/v2/settings/index/{type}
- PUT /settings/index/{type}
- GET /base/accommodations/list
- POST /base/accommodation/rooms/delete
- POST /base/accommodation/room/update
- GET /api/v2/base/certificates
- DELETE /api/v2/base/certificate
- GET /api/v2/base/certificate
- POST /api/v2/base/certificate
- PUT /api/v2/base/certificate
- POST /api/v2/accommodations/list
- POST /credit-debit/list
- POST /credit-debit/summary
- POST /pay/store
- POST /pay/update
- DELETE /pay/trash
- POST /gateway/list
- POST /financial/items
- POST /check/list
- POST /check/store
- POST /check/operation
- POST /check/operation/update
- POST /financial/treeview
- POST /accounting/balance
- POST /announcement/list
- POST /announcement/list/company
- POST /announcement/edit
- POST /announcement/store
- POST /announcement/update
- POST /announcement/view
- POST /announcement/statement
- POST /announcement/ledger
- POST /announcement/calculation
- POST /gateway/invoice/{drive}
- POST /gateway/invoice/{drive}/verify
- POST /api/v2/gateway/details
- POST /colleagues/list
- POST /colleague/bill
- POST /colleagues/ledger-accounts
- POST /colleagues/update/financial
- POST /colleagues/update/general-billing
- POST /colleague/operation
- GET /colleague/get
- GET /colleague/search
- GET /colleague/user
- PUT /colleague/user
- POST /colleague/user
- DELETE /colleague/user
- POST /upload
- GET /asterisk/test-connection
- GET /asterisk/channels/active
- GET /asterisk/channel/status
- POST /asterisk/call/make
- POST /asterisk/call/hangup
- POST /asterisk/sms/send
- POST /asterisk/ussd/send
- POST /asterisk/action/execute
- GET /asterisk/websocket/test
- POST /asterisk/notification/test
- POST /upload/s3
- DELETE /media/s3
- GET /personnel/attendance
- POST /personnel/attendance
- GET /personnel/attendance/permission
- POST /personnel/list
- POST /personnel/bill
- POST /personnel/operation
- GET /personnel/access-list
- GET /personnel
- POST /personnel
- PUT /personnel
- GET /personnel/shift/list
- GET /personnel/shift
- POST /personnel/shift
- PUT /personnel/shift
- DELETE /personnel/shift
- * PATCH /personnel/shift/reset
- POST /api/v2/tasks/category/operation
- GET /api/v2/tasks/categories/list
- GET /api/v2/tasks/list
- POST /api/v2/tasks/task/operation
- GET /api/v2/tasks/task/details
- GET /api/v2/support/departments/list
- GET /api/v2/support/department/members
- GET /api/v2/support/department/questions/list
- GET /api/v2/support/tickets
- POST /api/v2/support/ticket/update
- PATCH /api/v2/support/ticket/notice
- GET /api/v2/support/ticket/notes/list
- DELETE /api/v2/support/ticket/delete
- GET /api/v2/exams/list
- PATCH /api/v2/exams/answers/list/follow-up
- GET /api/v2/exams/answers/evaluation
- GET /api/v2/salary/annual-obligation
- GET /api/v2/scheduled/notifications
- POST /api/v2/scheduled/notifications
- PUT /api/v2/scheduled/notifications
- POST /api/v2/banks/list
- POST /v2/account/bill
- POST /v2/account/bill2
- POST /v2/currencies/list
- POST /v2/accounts/list
- GET /v2/accounting/account
- POST /v2/accounting/account
- PUT /v2/accounting/account
- DELETE /v2/accounting/account
- GET /v2/wallet/balance
- POST /v2/wallet/check
- GET /v2/wallet/transactions
- POST /v2/accounting/connections/store
- POST /v2/accounting/connections/update
- POST /v2/accounting/connections/trash
- POST /v2/accounting/connections/trash
- POST /v2/accounting/connections/view
- POST /v2/accounting/connections/merge
- POST /v2/accounting/connections/list
- POST /v2/accounting/account/update
- GET /v2/accounting/account/get
- POST /v2/manual-document/update
- DELETE /v2/manual-document
- POST /v2/manual-document/list
- POST /v2/manual-document/view
- POST /v2/manual-document/get
- POST /v2/manual-document/list/{type}
- POST /v2/manual-document/preferences/list/details
- GET /v2/accounting/closing
- POST /v2/accounting/closing
- PUT /v2/accounting/closing/definite/{type}
- GET /v2/accounting/preference
- POST /v2/accounting/preference
- GET /v2/accounting/preference/{id}
- PUT /v2/accounting/preference/{id}
- POST /v2/account-history/calculate-daily
- POST /v2/account-history/calculate-monthly
- POST /v2/account-history/calculate-complete
- GET /v2/account-history/daily
- GET /v2/account-history/monthly
- GET /v2/account-history/all-daily
- GET /v2/account-history/all-monthly
- DELETE /v2/account-history/cache
- POST /v2/redis-accounting/create-missing-documents
- POST /v2/redis-accounting/create-missing-documents
- POST /v2/redis-accounting/create-missing-documents
- POST /v2/redis-accounting/save-manual-document
- POST /v2/redis-accounting/save-document
- DELETE /v2/redis-accounting/delete-document
- DELETE /v2/redis-accounting/clear-all
- GET /v2/redis-accounting/documents/subsidiary
- GET /v2/redis-accounting/documents/account
- GET /v2/redis-accounting/documents/general
- GET /v2/redis-accounting/documents/group
- GET /v2/redis-accounting/documents/group
- GET /v2/redis-accounting/documents/date-range
- GET /v2/redis-accounting/balance
- GET /v2/redis-accounting/stats
- POST /v2/batch-accounting/process/date-range
- POST /v2/batch-accounting/process/month
- POST /v2/batch-accounting/process/month
- POST /v2/batch-accounting/process/year
- POST /v2/batch-accounting/process/current-month
- POST /v2/batch-accounting/process/current-year
- POST /v2/batch-accounting/rebuild-indexes
- POST /v2/batch-accounting/optimize-memory
- GET /v2/batch-accounting/report/comprehensive
- POST /v2/batch-accounting/preview
- POST /v2/core/offices/list
- POST /v2/core/accommodations/list
- POST /v2/core/accommodation/store
- POST /v2/core/accommodation/update
- DELETE /v2/core/accommodation/delete
- GET /v2/core/accommodation/view
- POST /v2/core/accommodation/supplier/store
- POST /v2/core/airlines/list
- POST /v2/core/airports/list
- POST /v2/core/socket/send
- GET /v2/core/changelogs
- GET /v2/core/accommodation/view
- POST /v2/core/system/report
- GET /v2/charter (Multi‑Mode Charter Loader)
- POST /v2/charter
- PUT /v2/charter
- PATCH /v2/charter (updateCharter)
- DELETE /v2/charter
- GET /v2/charter/list
- GET /v2/charter/communications
- GET /v2/charter/reservation/{type}
- POST /v2/charter/reservation
- PUT /v2/charter/reservation
- DELETE /v2/charter/reservation
- PATCH /v2/charter/reservation/undo
- PUT /v2/charter/reservation/transfer
- PATCH /character/reservation/refund
- PATCH /v2/charter/reservation/refund/undo
- DELETE /v2/charter/reservation/temporary
- POST /v2/charter/reservation/temporary
- POST /v2/charter/reservation/plan/update
- GET /v2/charter/financial
- GET /v2/charter/financial/completion
- PATCH /v2/charter/financial/completion
- POST /v2/charter/warranty
- GET /v2/charter/warranty
- GET /v2/charter/services/list
- GET /v2/charter/flight-rate
- GET /v2/charter/pledger
- POST /v2/charter/pledger
- POST /v2/charter/accommodation/rooms
- POST /v2/charter/accommodation/rooms/access
- GET /v2/academy/categories/view
- GET /v2/academy/courses/view
- GET /v2/academy/course/steps/view
- POST /v2/academy/course/steps/update
- GET /v2/mail/servers/list
- GET /v2/mail/address/list
- POST /v2/mail/address/store
- GET /v2/mail/{type}/list
- POST /v2/mail/{type}/update
- POST /v2/mail/attachments/download
- GET /v2/mail/sent/get
- POST /v2/mail/sent/store
- DELETE /v2/mail/sent/trash
- GET /v2/mail/inbox/get
- GET /v2/mail/inbox/get
- DELETE /v2/mail/inbox/trash
- DELETE /v2/mail/trash/delete
- GET /v2/modules/lottery/registrations
- POST /v2/trade/tickets
- POST /v2/trade/contract
- POST /v2/trade/confirmation
- POST /v2/credit-card
- GET /v2/credit-card/index
- GET /v2/credit-card
- POST /v2/articles/{id}/views
- RESOURCE /v2/articles
- RESOURCE /v2/categories
- RESOURCE /v2/tags
- RESOURCE /v2/landing_pages
- RESOURCE /v2/page_metatags
- RESOURCE /v2/places
- RESOURCE /v2/media
- GET /v2/travel_requests
- POST /v2/travel_requests/status/{id}
- RESOURCE /v2/accommodation/facilities
- RESOURCE /v2/accommodation/facilities_category
- RESOURCE /v2/accommodation/rules
- GET /v2/comments
- GET /v2/comments/{id}
- DELETE /v2/comments/{id}
- POST /v2/comments
- PUT /v2/comments/{id}
- POST /v2/comments/{commentId}/{action}
- DELETE /v2/comments/{commentId}/{action}
- POST /v2/invoice/process
- POST /v2/invoice/payment/details
- POST /v2/invoice/payment/wallet
- GET /v2/core/bill/{type}
- GET /v2/core/hub/information
- GET /v2/core/hub/analyze
- GET /v2/core/hub/reservation
- POST /v2/core/hub/reservation
- PATCH /v2/core/hub/reservation/refund
- PATCH /v2/core/hub/reservation/refund/undo
- POST /v2/flights/ticket/information/{type}
- POST /v2/flights/routes/update
- POST /v2/flights/routes/min_price
- GET /v2/core/application_interface
- GET /v2/core/application_interface/{id}
- DELETE /v2/core/application_interface/{id}
- POST /v2/core/application_interface
- PUT /v2/core/application_interface/{id}
- GET /v2/core/application_interface_types
- GET /v2/core/application_interface_services
- GET /v2/core/application_interface_object_types
- PUT /v2/core/application_interface/status/{id}
- RESOURCE /v2/scrumboard/boards
- RESOURCE /v2/scrumboard/lists
- RESOURCE /v2/scrumboard/labels
- RESOURCE /v2/scrumboard/checklists
- RESOURCE /v2/scrumboard/comments
- RESOURCE /v2/scrumboard/cards
- RESOURCE /v2/scrumboard/sprints
- RESOURCE /v2/scrumboard/sprint_reports
- RESOURCE /v2/cartable/letters
- RESOURCE /v2/cartable/subjects
- RESOURCE /v2/cartable/subject_steps
- RESOURCE /v2/cartable/recipients
- RESOURCE /v2/cartable/operator_role
- PUT /v2/cartable/letters/step/{id}
- POST /v2/ai/chat/completions
- POST /v2/trade/reference/type
- POST /v2/base/reference
- POST /v2/config
- GET /v2/config
- GET /b2c/v1/config
- POST /b2c/v1/auth/otp
- POST /b2c/v1/auth/submit
- POST /b2c/v1/auth/basic
- POST /b2c/v1/get_country
- GET /b2c/v1/user/passengers
- GET /b2c/v1/office/users
- GET /b2c/v1/cost-center/list/form
- GET /b2c/v1/credit-card
- POST /b2c/v1/media/upload/s3
- GET /b2c/v1/base/data
- GET /b2c/v1/base/accommodations/list
- GET /b2c/v1/base/suggestions/items/{type}
- GET /b2c/v1/trade/list
- POST /b2c/v1/trade/store
- POST /b2c/v1/trade/completion
- POST /b2c/v1/online/search/{type}
- POST /b2c/v1/online/{type}/lock
- POST /b2c/v1/online/{type}/penalty
- POST /b2c/v1/online/{type}/refund
- GET /b2c/v1/online/accommodation/list
- GET /b2c/v1/online/accommodation/get_min_prices
- GET /b2c/v1/online/accommodation/get_room_type_prices
- GET /b2c/v1/online/accommodation/get_details
- GET /b2c/v1/online/flight/class/{calculation_id}
- GET /b2c/v1/online/payment/flight/tracking
- GET /b2c/v1/financial/list
- GET /b2c/v1/passengers/previous
- GET /b2c/v1/gateway/details
- GET /b2c/v1/discount/submit
- GET /b2c/v1/discount/unsubmit
- GET /b2c/v1/wallet/ballance
- POST /b2c/v1/wallet/credit
- GET /b2c/v1/ledger-account
- GET /b2c/v1/articles
- GET /b2c/v1/articles/{article}
- GET /b2c/v1/categories
- POST /b2c/v1/articles/{id}/views
- RESOURCE /b2c/v1/travel_requests
POST /api/auth/connect/branch
Route Info
| مورد | توضیحات |
|---|---|
| Method | POST |
| Endpoint | /api/auth/connect/branch |
| Controller | UserController |
| Middleware فعال | ندارد |
| Purpose (هدف کلی) | اتصال و معتبرسازی شعبه بر اساس کد ورودی برای بازیابی دامنه مرتبط |
| دستهبندی عملکردی | Authentication / Branch Connection |
تحلیل عملکرد (Analysis)
این Endpoint یک نقطه ورود بدون احراز هویت (Unauthenticated) برای شناسایی و اعتبارسنجی شعبه است. هدف آن، بازگرداندن دامنه محیطی شعبه معتبر میباشد.
مکانیزم کدگذاری شعبه
سیستم از فرمول زیر برای استخراج شناسه واقعی شعبه از کد ارسالی استفاده میکند:
Actual Office ID = Input Branch Code - 1000
برای مثال اگر کد ورودی 1050 باشد، شناسه واقعی شعبه 50 خواهد بود.
منطق اعتبارسنجی
- استخراج شناسه واقعی با کسر 1000 از ورودی
- جستجو در جدول
officesبر اساسid = Resultوstatus = 1 - برگرداندن فیلد
domainبه عنوان نتیجه در صورت معتبر بودن رکورد - بازگرداندن پیام خطای فارسی در صورت نبود رکورد معتبر
ورودیها (Inputs)
| فیلد | نوع داده | توضیح | الزامی |
|---|---|---|---|
| branch | Integer | کد شعبه (شناسه واقعی + 1000) | بله |
Example Request Body:
{
"branch": 1050
}
خروجیها (Outputs)
پاسخ همیشه HTTP 200 بازمیگردد.
خروجی موفق:
| فیلد | نوع داده | توضیح |
|---|---|---|
| status | Boolean | همیشه true |
| time | Integer | زمان یونیکس پاسخ سرور |
| data.domain | String | دامنهٔ مرتبط با شعبه |
{
"status": true,
"time": 1731324000,
"data": {
"domain": "example.com"
}
}
خروجی ناموفق:
{
"status": false,
"code": 1201,
"message": "کد دسترسی نا معتبر می باشد."
}
وابستگیها (Dependencies)
| وابستگی | نوع | توضیح |
|---|---|---|
| DB::table('offices') | Database Table | واکشی دامنهٔ شعبه از پایگاه داده |
| Illuminate\Http\Request | Framework Class | دریافت داده JSON ورودی |
| response()->json() | Laravel Helper | ساخت خروجی JSON استاندارد |
تست و استفاده (Testing & Usage)
| مورد | مقدار |
|---|---|
| Method | POST |
| URL | /api/auth/connect/branch |
| Headers | Content-Type: application/json |
Example cURL:
curl -X POST https://yourdomain.com/api/auth/connect/branch \
-H "Content-Type: application/json" \
-d '{"branch":1050}'
موارد خطا (Typical Error Cases)
| خطا | کد HTTP | علت |
|---|---|---|
| Invalid Branch Code | 200 | کد شعبه پس از کسر 1000 در جدول offices یافت نشود یا وضعیت آن 1 نباشد. |
جزئیات پیادهسازی (Conceptual Implementation)
$branch = $request->get('branch');
$code = $branch - 1000;
$office = DB::table('offices')
->where('id', $code)
->where('status', 1)
->select('domain')
->first();
if ($office) {
return response()->json([
'status' => true,
'time' => now()->timestamp,
'data' => [ 'domain' => json_decode($office->domain)[0] ],
]);
}
return response()->json([
'status' => false,
'code' => 1201,
'message'=> 'کد دسترسی نا معتبر می باشد.'
]);
نتیجه نهایی (Conclusion)
این Endpoint بدون نیاز به توکن احراز هویت، دامنه شعبه را تعیین میکند. طراحی آن ساده و سریع بوده و پایهٔ احراز در سایر سرویسهای connectOtp و connectSubmit میباشد.
پیوست: نگهداری و امنیت
- اعمال Rate Limiting برای جلوگیری از سوءاستفاده.
- اعتبارسنجی ورودی جهت جلوگیری از تزریق SQL (توسط Query Builder ایمن).
POST /api/v2/colleagues/billing
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | دستهبندی عملکردی |
| POST | /api/auth/connect/branch | UserController@connectBranch | ندارد | اتصال و معتبرسازی شعبه بر اساس کد ورودی برای پایاندامنه مرتبط | Authentication / Branch Connection |
(Analysis) تحلیل عملکرد
مکانیسم کدگذاری شعبه
Actual Office ID = Input Branch Code - 1000
منطق اعتبارسنجی
- در جدول
officesجستجو انجام میشود تا مطمئن شود شعبه فعال است. - در صورت یافت نشدن یا غیرفعال بودن، پاسخ خطا بازگردانده میشود.
- اگر شعبه معتبر باشد، شناسه و اطلاعات دامنه مرتبط (
domain) بازگردانده میشود.
(Inputs) ورودیها
| فیلد | نوع داده | الزامی | توضیح |
| branch | Integer | بله | کد شعبه (شناسه واقعی + 1000) |
{
"branch": 1050
}
(Outputs) خروجیها
خروجی موفق:
{
"status": true,
"office": {
"id": 50,
"domain": "example.domain.ir",
"title": "دفتر مرکزی"
}
}
خروجی ناموفق:
{
"status": false,
"message": "کد شعبه معتبر نیست یا یافت نشد."
}
(Dependencies) وابستگیها
UserController::connectBranch(Request $request)- مدلها:
OfficeوDomain
(Testing & Usage) تست و استفاده
POST /api/auth/connect/branch
Content-Type: application/json
{
"branch": 1050
}
(Typical Error Cases) موارد خطا
- کد شعبه کوچکتر از 1000 یا غیرعددی
- شعبه غیرفعال در جدول
offices - عدم تعریف دامنه برای دفتر مربوطه
(Implementation Details) جزئیات پیادهسازی
public function connectBranch(Request $request)
{
$branchCode = $request->branch;
$actualId = $branchCode - 1000;
$office = Office::find($actualId);
if (!$office) {
return response()->json(['status' => false, 'message' => 'کد شعبه معتبر نیست یا یافت نشد.']);
}
return response()->json([
'status' => true,
'office' => [
'id' => $office->id,
'domain' => $office->domain,
'title' => $office->title,
]
]);
}
(Conclusion) نتیجهگیری
(Appendix) پیوست: نگهداری و امنیت
- احراز ورودی قبل از تبدیل نوع: اطمینان از عددی بودن branch.
- عدم نیاز به تزریق احراز JWT در این مرحله (unauthenticated).
- پیشنهاد به محدود کردن نرخ درخواستها جهت جلوگیری از Spam.
POST /api/v2/trade/national
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | دستهبندی عملکردی |
| POST | /api/v2/trade/national | UserController@nationalPrefix | domainAccess, ipTrust | شناسایی شهر بر اساس کد ملی | Trade / National Identification |
تحلیل عملکرد (Analysis)
City بر اساس مقدار national_prefix بازیابی میشود.مکانیزم شناسایی شهر
national_prefix) در جدول شهرها استفاده میکند.SELECT * FROM cities WHERE national_prefix LIKE '%123%' LIMIT 1;
منطق اعتبارسنجی کد ملی
nationalPrefix() به شرح زیر است:
- بررسی صحت کد ملی با
Validator::nationalCode(). - در صورت معتبر بودن، سرچ در جدول
Cityبر اساس سه رقم ابتدایی. - بازگشت نام شهر در کلید
dataدر صورت یافتن رکورد. - در صورتی که رکوردی یافت نشود، پیام “شهر یافت نشد” بازگردانده میشود.
- در صورت نامعتبر بودن کد ملی، پیام “کد ملی نامعتبر” بازگردانده میشود.
ورودیها (Inputs)
| پارامتر | نوع داده | الزامی | توضیح |
| code | String | بله | کد ملی (دهرقمی) جهت بررسی صحت و تشخیص شهر. |
{
"code": "0451234567"
}
خروجیها (Outputs)
خروجی موفق (شهر یافت شد)
{
"status": true,
"time": 1731926417,
"data": "تبریز"
}
خروجی معتبر اما بدون شهر
{
"status": true,
"time": 1731926417,
"data": "کد ملی صحیح می باشد. اما شهر یافت نشد"
}
خروجی نامعتبر (کد اشتباه)
{
"status": false,
"time": 1731926417,
"data": "کد ملی نامعتبر"
}
وابستگیها (Dependencies)
UserController::nationalPrefix(Request $request)App\Models\CityValidator::nationalCode()
تست و استفاده (Testing & Usage)
POST /api/v2/trade/national
Content-Type: application/json
{
"code": "4580021347"
}
موارد خطا (Typical Error Cases)
- کد ملی کوتاهتر از 10 رقم.
- تمامی ارقام تکراری (مثلاً 1111111111).
- عدم وجود رکورد در جدول City با پیششمارهٔ استخراجشده.
جزئیات پیادهسازی (Implementation)
public function nationalPrefix(Request $request)
{
if (Validator::nationalCode($request->code)) {
$Code = City::where('national_prefix', 'LIKE', '%' . substr($request->code, 0, 3) . '%')->first();
if (!is_null($Code))
return ['status' => true, 'time' => time(), 'data' => $Code->fa_name];
else
return ['status' => true, 'time' => time(), 'data' => 'کد ملی صحیح می باشد. اما شهر یافت نشد'];
} else
return ['status' => false, 'time' => time(), 'data' => 'کد ملی نامعتبر'];
}
نتیجهگیری (Conclusion)
پیوست: نگهداری و امنیت
- دادهها بر پایهٔ lookup ساده از جدول
Cityهستند؛ نیاز به caching برای عملکرد بهتر. - ورودی کاربر باید قبل از query با regex فیلتر شود تا از Injection جلوگیری گردد.
POST /api/auth/sign-in
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/auth/sign-in | UserController@signIn | domainAccess, ipTrust | ورود اپراتور با شناسه پرسنلی و رمز عبور | tags={“Auth”} |
توضیح عملکرد (Function Logic)
signIn() مسئول احراز هویت اپراتور بر اساس شناسهٔ پرسنلی و رمز عبور است. این فرایند شامل مراحل زیر میباشد:
- اعتبارسنجی ورودی
branch(شعبه) و دادهٔdataکه شاملpersonnelIdوpasswordاست. - جستجوی اپراتور در جدول
operators، بررسی انطباق شعبه (مقدار[0]یا شامل branch در JSON). - بررسی وضعیت مسدودی اکانت (حوزه فیلد
blocked_up). - بررسی رمز عبور با
Hash::check(). - در صورت معتبر بودن و فعال بودن حساب (
status == 1): تولید JWT Token، ثبت لاگ با تأخیر ۱۰ دقیقه، ذخیره Shortcutها و ارسال نوتیفیکیشن تلگرام. - در صورت وضعیت غیرفعال یا خطای رمز، بازگرداندن پیام خطا مطابق Swagger.
- در بخش
catch، بازگرداندن خطای Exception با پیام و Trace.
ورودیها (Inputs)
| پارامتر | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبهای که اپراتور به آن وارد میشود. |
| data.personnelId | string | بله | شناسه پرسنلی اپراتور برای ورود. |
| data.password | string | بله | رمز عبور اپراتور (hash شده در دیتابیس). |
POST /api/auth/sign-in
Content-Type: application/json
{
“branch”: 1,
“data”: {
“personnelId”: “101234”,
“password”: “12345678”
}
}
خروجیها (Outputs)
پاسخ موفق (HTTP 200)
{
“user”: {
“uuid”: 1,
“from”: “users”,
“role”: “admin”,
“isAirPlusAdmin”: true,
“group”: “IT”,
“data”: {
“displayName”: “علی شاکرچکی”,
“personnelId”: “101234”,
“branch”: “[0]”,
“telegram”: true,
“position”: “مدیر فناوری اطلاعات”,
“access”: […],
“shortcuts”: […]
}
},
“access_token”: “jwt.token.value”
}
خطا در احراز هویت (HTTP 401)
{
“error”: [
{
“type”: “personnelId”,
“message”: “اطلاعات کاربری همخوانی ندارد.”
}
]
}
حساب کاربری مسدود
{
“error”: [
{
“type”: “personnelId”,
“message”: “حساب کاربری شما مسدود شده است.”
}
]
}
منطق تولید JWT Token
{
“typ”: “base”,
“iss”: “{DomainHeader}”,
“aud”: “{DomainHeader}”,
“iat”: 1731927000,
“exp”: 1732531800,
“uuid”: “{OperatorId}”,
“brn”: “{Branch}”,
“uip”: “{ClientIP}”,
“brw”: “{UserAgentClient}”
}
سیستم لاگ و اعلانها
- ثبت لاگ با تأخیر ۱۰ دقیقهای از نوع
SystemLog::dispatch(type=‘Login’) - در صورت فعال بودن فیلد
telegram، ارسال پیام ورود موفق به اکانت تلگرام اپراتور با دکمه «اتمام جلسه» - توکن موقت برای لینک مسدودی ۱۵ دقیقهای ساخته و در DB ذخیره میشود.
مدیریت اعلان (Push & Telegram)
Operator->telegram، Dispatcher پیام مارکدان را ارسال میکند شامل:
- نام و آیدی پرسنلی
- شعبه و دامنه
- IP و مرورگر دستگاه
- دکمه مسدودی موقت
موارد خطا (Error Cases)
- رمز عبور اشتباه یا عدم تطابق شناسه پرسنلی
- حساب کاربری غیرفعال (
status != 1) - حساب مسدود تا زمان مشخصشده در
blocked_up - Exception هنگام تولید JWT یا ثبت در Redis/DB
وابستگیها (Dependencies)
use Firebase\JWT\JWT;use Jenssegers\Agent\DeviceDetector;use Illuminate\Support\Facades\Hash;use Illuminate\Support\Facades\DB;use Carbon\Carbon, Morilog\Jalali\Jalalian;use Redis, Str, SendNotification, SystemLog;
نمونه تست واقعی (Postman Example)
POST https://console.service01.ir/api/auth/sign-in
Headers:
Domain: service01.ir
Content-Type: application/json
Body:
{
“branch”: 2,
“data”: {
“personnelId”: “104512”,
“password”: “test@1234”
}
}
نتیجه عملکرد (Result Summary)
bearerAuth ثبت میگردد.پیوست: نکات امنیتی
- تمام درخواستها باید از دامنهی معتبر (Header:
Domain) ارسال شوند. - فیلد
passwordباید باHash::checkمطابقت یابد؛ رمزها هیچگاه در plain text ذخیره نشوند. - از Env Key
JWT_SECRET_KEYبرای encode استفاده گردد. - فرآیند Telegram notification در صف
fastJobایزوله شود. - برای لاگ ورود از صف
snailJobجهت تأخیر بارگذاری استفاده گردد.
POST /api/auth/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/auth/update | UserController@update | domainAccess, ipTrust | بهروزرسانی دادههای رابط کاربری اپراتور (shortcuts) از سمت کلاینت | tags={“Auth”} |
توضیح عملکرد (Function Logic)
update() مسئول همگامسازی اطلاعات رابط کاربری اپراتور با سرور است. اطلاعات از جمله میانبرها (shortcuts) از سمت کلاینت ارسال شده و در Redis ذخیره میشوند. اگر IP فعلی کاربر با IP ثبتشده در توکن JWT مطابقت نداشته باشد، خطایی با نوع changeIp بازگردانده میشود. عملکرد تابع شامل مراحل زیر است:
- دریافت ورودی
dataاز درخواست. - تحلیل و اعتبارسنجی توکن
access_tokenبا استفاده از JWT::decode. - مقایسه IP کاربر فعلی با IP ذخیرهشده در توکن (
uip). - در صورت تطابق، یافتن اپراتور بر اساس
uuidاستخراجشده از JWT. - اگر اپراتور فعال باشد (
status == 1)، دادهٔ shortcuts جدید در Redis ذخیره میشود. - در صورت غیرفعال بودن وضعیت اپراتور، پیام خطا بازگردانده میشود.
- اگر IP تغییر کرده باشد، جزئیات هر دو IP در خروجی خطا قرار میگیرد.
- در صورت بروز هر Exception، محتوا و
traceبازگردانده میشود.
ورودیها (Inputs)
| پارامتر | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبهای که کاربر در آن وارد شده است. |
| data.access_token | string (JWT) | بله | توکن JWT معتبر که شامل شناسه کاربر (uuid)، IP و زمان صدور است. |
| data.user.data.shortcuts | array | بله | لیست میانبرهای جدید رابط کاربری اپراتور که باید در Redis ذخیره شوند. |
POST /api/auth/update
Content-Type: application/json
{
“branch”: 1,
“data”: {
“access_token”: “jwt.token.value”,
“user”: {
“data”: {
“shortcuts”: [“dashboard”, “customers”, “tickets”]
}
}
}
}
خروجیها (Outputs)
پاسخ موفق (HTTP 200)
{}
وضعیت غیرفعال کاربر
{
“error”: {
“type”: “personnelId”,
“message”: “وضعیت شما بصورت غیرفعال می باشد.”
}
}
تغییر IP کاربر
{
“error”: {
“type”: “changeIp”,
“message”: “وضعیت اینترنت شما تغییر پیدا کرده”,
“details”: {
“token_ip”: “192.168.1.10”,
“current_ip”: “5.114.200.88”
}
}
}
خطا در پردازش
{
“error”: {
“type”: “personnelId”,
“message”: “Signature verification failed”,
“trace”: […]
}
}
منطق تشخیص IP مجاز
- 192.168..
- 10...*
- 172.16..
ذخیره اطلاعات در Redis
Redis::set(
𝑟𝑒𝑞𝑢𝑒𝑠𝑡−>𝑔𝑒𝑡(′𝑏𝑟𝑎𝑛𝑐ℎ′).′𝑜𝑝𝑒𝑟𝑎𝑡𝑜𝑟:′.
request−>get( ′branch ′). ′operator:′.
Operator->id . ‘:shortcuts’,
json_encode($data[‘user’][‘data’][‘shortcuts’]));
مدیریت استثناها (Exceptions)
- Token نامعتبر یا منقضی شده: پیام Signature Verification Failed
- خطا در ساختار درخواست: بازگرداندن Exception عمومی با Trace
وابستگیها (Dependencies)
use Firebase\JWT\JWT;use Firebase\JWT\Key;use Illuminate\Support\Facades\Redis;use App\Models\User;use Exception;
مثال تست (Postman Example)
POST https://console.service01.ir/api/auth/update
Content-Type: application/json
{
“branch”: 2,
“data”: {
“access_token”: “jwt.token.value”,
“user”: {
“data”: {
“shortcuts”: [“dashboard”, “customers”, “trade-management”]
}
}
}
}
تحلیل امنیتی
- از IP واقعی کاربر برای مطابقت توکن استفاده میشود (افزایش امنیت session).
- رمزگشایی JWT تنها با کلید محیطی
JWT_SECRET_KEYانجام میشود. - هیچ دادهای بازگردانده نمیشود تا احتمال افشای Token یا داده کاهش یابد.
- درخواستها فقط از دامنههای معتبر (domainAccess) و IP مجاز (ipTrust) پذیرفته میشوند.
نتیجه عملکرد (Summary Result)
پیوست: نگهداری و بهینهسازی
- اطلاعات Redis میتوانند با TTL (Time To Live) مدیریت شوند تا از انباشت داده جلوگیری شود.
- در صورت تغییر ساختار shortcuts، مطابقت نسخه کلاینت الزامی است.
- در نسخههای بعدی، API میتواند بازگشت وضعیت موفقیت با پیام استاندارد ارائه دهد.
POST /api/auth/blocking
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/auth/blocking | UserController@blocking | domainAccess, ipTrust | مسدودسازی موقت نشستهای کاربر و خروج اجباری از تمامی نشستها | tags={“Auth”} |
توضیح عملکرد (Function Logic)
- دریافت توکن از پارامتر
tokenدر بدنه درخواست یا آدرس مسیر. - یافتن رکورد اپراتور با مقدار توکن «telegram:{token}» در ستون
operators.token. - بهروزرسانی ستونها:
blocked_up: تاریخ فعلی بهعلاوه مدت زمان مسدودسازی.token: مقدار null برای قطع دسترسی فعلی.
ورودیها (Inputs)
| پارامتر | نوع داده | الزامی | توضیح |
| token | string | بله | شناسه توکن یکتای ارتباط (نظیر توکن تلگرام) برای یافتن حساب اپراتور. |
| duration | integer | خیر | مدت زمان مسدودسازی به دقیقه. مقدار پیشفرض ۱۵ دقیقه است. |
POST /api/auth/blocking
Content-Type: application/json
{
“token”: “abc123”,
“duration”: 30
}
خروجیها (Outputs)
پاسخ موفق (HTTP 200)
{
“status”: true,
“time”: 1731956440,
“message”: “تمامی نشست ها با موفقیت خاتمه یافتند و کاربری شما بمدت 15 دقیقه مسدود گردید”
}
پاسخ خطا (Exception)
{
“status”: false,
“time”: 1731956440,
“message”: “23000 : SQLSTATE[23000]: Integrity constraint violation”,
“trace”: […]
}
منطق مسدودسازی نشست کاربر
تغییرات دیتابیس (Database Update)
UPDATE operators
SET
blocked_up = NOW() + INTERVAL {duration or 15} MINUTE,
token = NULL
WHERE token = CONCAT(‘telegram:’, {token});
blocked_up زمان اتمام مسدودسازی را ذخیره میکند. توکن کاربر حذف میشود تا از سوی سایر نشستها غیرقابل استفاده شود.ریدایرکت سمت کلاینت
https://airplus.app هدایت میشود. این هدایت فقط از سمت سرور انجام میشود (بدون پاسخ HTTP Redirect واقعی).مدیریت استثناها (Exceptions)
- حذف یا عدم وجود رکورد اپراتور با توکن دادهشده.
- خطاهای پایگاه داده یا اتصال.
- Exception ناشی از Carbon یا زمان اشتباه در duration.
وابستگیها (Dependencies)
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
- use Exception;
تست Endpoint (Postman Example)
POST https://console.service01.ir/api/auth/blocking
Content-Type: application/json
{
“token”: “tg-ops-9902”,
“duration”: 20
}
تحلیل امنیتی
- برای جلوگیری از سوءاستفاده، مسیر فقط در دسترس دامنههای مجاز (domainAccess) و IPهای مجاز (ipTrust) است.
- داده حساس (token) نباید در URL یا logهای عمومی نمایش داده شود.
- پیشنهاد میشود بعد از اجرای موفق، session کاربران در Redis یا cache نیز پاک شود.
پیوست نگهداری و توسعه آینده
- افزودن پارامتر اختیاری
reasonبرای ثبت علت مسدودسازی مفید است. - قابلارتقا برای مدیریت انواع توکن (email, telegram, panel).
- ثبت گزارش رویداد در لاگ امنیتی مرکزی پیشنهاد میشود.
- در نسخه بعدی، بازگشت Redirect واقعی (HTTP 302) میتواند در هماهنگی کلاینت بهبود ایجاد کند.
POST /api/auth/forgot/otp
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/auth/forgot/otp | UserController@forgotOtp | domainAccess, ipTrust | ارسال OTP برای بازیابی کلمه عبور اپراتور | tags={"Auth"} |
توضیح عملکرد (Function Logic)
Visa::showSystemLogs کنترل میشود.- یافتن اپراتور فعال با مشخصات دریافتی در جدول
operators. - بررسی آخرین ۵ ساعت لاگ برای نوع
SendOtp(حداکثر ۳ تلاش در ۵ ساعت). - در صورت مجاز بودن، ساخت رمز یکبار مصرف با طول ۶ رقم.
- ذخیره OTP و زمان صدور (
otp_issuing) در پایگاه داده. - ارسال پیامک حاوی OTP با تابع
StaticController::sendNotification. - در صورت موفقیت، ثبت لاگ در صف snailJob با تاخیر ۱۰ دقیقهای.
- بازگشت خروجی استاندارد شامل وضعیت، زمان، و پیام نتیجه.
ورودیها (Inputs)
| پارامتر | نوع داده | الزامی | توضیح |
| personnel_id | string | بله | شناسه پرسنلی کاربر دارای وضعیت فعال (status=1) |
| mobile | string (11 digits) | بله | شماره موبایل اصلی یا جایگزین اپراتور (mobile یا mobile1) |
| branch | integer | خیر | شناسه شعبه جهت ارجاع ارسال پیامک از شاخه مربوطه |
POST /api/auth/forgot/otp
Content-Type: application/json
{
"personnel_id": "P00123",
"mobile": "09131234567",
"branch": 1
}
خروجیها (Outputs)
ارسال موفق OTP
{
"status": true,
"time": 1731958300,
"message": "OTP با موفقیت ارسال گردید."
}
موبایل یا پرسنلی اشتباه
{
"status": false,
"code": 1201,
"message": "اطلاعات وارد شده با هیچ کاربری همخوانی ندارد"
}
پیامک ارسال نشد
{
"status": false,
"code": 1202,
"message": "پیامک OTP ارسال نشد. مشکلی رخ داده است. لطفا دوباره تلاش کنید",
"trace": { ... }
}
تعداد درخواست بیش از حد مجاز
{
"status": false,
"code": 1203,
"message": "تعداد درخواست های کاربر بیش از حد مجاز بوده است. لطفا 5 ساعت دیگر اقدام نمائید."
}
منطق محدودسازی درخواستها (Throttle)
SendOtp را بررسی کرده و فقط تا ۳ بار ارسال در بازه ۵ ساعته مجاز میباشد. در صورت تجاوز از این حد، پاسخ خطا با کد 1203 بازگردانده میشود.محتوای پیامک ارسالی (SMS Template)
code: 123456
این رمز جهت تغییر کلمه عبور صادر شده است.
از در اختیار قراردادن آن به دیگران جدا خودداری فرمائید.
مدت اعتبار: 15 دقیقه
example.domain
ثبت لاگ سیستمی (SystemLog Dispatch)
snailJob ثبت میشود:SystemLog::dispatch([
"type" => "SendOtp",
"goal" => operator_id,
"by" => operator_id,
"agent" => $_SERVER['HTTP_USER_AGENT'],
"ip" => getIP(),
"datetime" => now()
])->delay(now()->addMinutes(10))->onQueue('snailJob');
وابستگیها (Dependencies)
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
- use App\Http\Controllers\StaticController;
- use App\Jobs\SystemLog;
- use App\Services\Visa;
نمونه تست (Postman Example)
POST https://console.service01.ir/api/auth/forgot/otp
Content-Type: application/json
{
"personnel_id": "P00999",
"mobile": "09138889999",
"branch": 3
}
تحلیل امنیتی
- رمز موقت (OTP) فقط ۱۵ دقیقه اعتبار دارد.
- در صورت سه تلاش ناموفق در ۵ ساعت، سیستم ارسال را قفل میکند.
- محدودسازی IP و domain در سطح middleware اعمال میشود.
- هیچ جزئی از OTP یا توکن در پاسخ لو نمیرود.
نتیجه عملکرد (Summary Result)
SystemLog از ارسالهای مکرر جلوگیری میشود.پیوست نگهداری و توسعه آتی
- افزودن قابلیت ارسال از طریق ایمیل در هزینههای آینده.
- ثبت بازه زمانی آخرین
otp_issuingبه عنوان معیاری برای مسیر /auth/forgot/submit. - پیشنهاد: استفاده از سرویس OTP متمرکز با لایه Redis برای اطمینان بیشتر.
`
POST /api/auth/connect/otp
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/auth/connect/otp | UserController@connectOtp | domainAccess, ipTrust | ارسال رمز موقت جهت اتصال اپراتور به سرویسهای خارجی (نظیر تلگرام) | tags={"Auth"} |
توضیح عملکرد (Function Logic)
- بررسی وضعیت و مشخصات اپراتور بر اساس
personnel_idو وضعیت فعال. - بررسی عدم اتصال قبلی به تلگرام (
telegram IS NULL). - کنترل سقف ارسال OTP در ۵ ساعت گذشته با
Visa::showSystemLogs. - صدور OTP عددی ۸ رقمی و ذخیره در
operators.otpهمراه زمان صدور. - ارسال پیامک OTP از طریق
StaticController::sendNotification. - ثبت رویداد
SystemLogبا تاخیر ۱۰ دقیقهای در صفsnailJob.
ورودیها (Inputs)
| پارامتر | نوع داده | الزامی | توضیح |
| personnel_id | string | بله | کد پرسنلی اپراتور فعال (status=1) برای تأیید هویت |
| branch | integer | خیر | شناسه شعبه برای انتخاب مسیر ارسال پیامک (در سرویس ارسال Notification) |
POST /api/auth/connect/otp
Content-Type: application/json
{
"personnel_id": "P00157",
"branch": 2
}
خروجیها (Outputs)
ارسال موفق OTP
{
"status": true,
"time": 1731963200,
"message": "OTP با موفقیت ارسال گردید."
}
اطلاعات کاربر یافت نشد
{
"status": false,
"code": 1201,
"message": "اطلاعات وارد شده با هیچ کاربری همخوانی ندارد"
}
کاربر قبلاً متصل شده است
{
"status": false,
"code": 1203,
"message": "شما قبلا به سامانه متصل شده اید اگر قصد تغییر تلگرام خود را دارید قبل از انجام این فرایند. از طریق سامانه قطع اتصال فرمائید."
}
تعداد درخواست بیش از حد مجاز
{
"status": false,
"code": 1203,
"message": "تعداد درخواست های کاربر بیش از حد مجاز بوده است. لطفا 5 ساعت دیگر اقدام نمائید."
}
پیامک ارسال نشد
{
"status": false,
"code": 1202,
"message": "پیامک OTP ارسال نشد. مشکلی رخ داده است. لطفا دوباره تلاش کنید",
"trace": {...}
}
محدودسازی ارسال (Throttle Control)
SystemLog انجام میگیرد. هر اپراتور مجاز به ارسال حداکثر ۳ بار در بازهی زمانی ۵ ساعت است. در صورت تجاوز از این حد پاسخ با کد 1203 بازمیگردد.قالب پیامک ارسالی (SMS Template)
code: 12345678 این رمز جهت ارتباط با سایر سرویس ها صادر شده است. از دراختیار قراردادن آن به دیگران جدا خودداری فرمائید. مدت اعتبار: 15 دقیقه 🌐 example.domain
ثبت رویداد SystemLog
SystemLog::dispatch([
"type" => "SendOtp",
"goal" => operator_id,
"by" => operator_id,
"agent" => $_SERVER['HTTP_USER_AGENT'],
"ip" => getIP(),
"datetime" => now()
])->delay(now()->addMinutes(10))->onQueue('snailJob');
تغییرات دیتابیس (Database Update)
UPDATE operators
SET
otp = {random 8-digit code},
otp_issuing = NOW()
WHERE id = {operator.id};
وابستگیها (Dependencies)
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
- use App\Jobs\SystemLog;
- use App\Http\Controllers\StaticController;
- use App\Services\Visa;
نمونه تست (Postman Example)
POST https://console.service01.ir/api/auth/connect/otp
Content-Type: application/json
{
"personnel_id": "P00231",
"branch": 1
}
تحلیل امنیتی
- اگر کاربر قبلاً به تلگرام متصل شده باشد، ارسال OTP انجام نمیشود.
- رمز ۸ رقمی تنها ۱۵ دقیقه معتبر است.
- کنترل دفعات درخواست در بازهی ۵ ساعت مانع حملات brute force میشود.
- دسترسی به این مسیر فقط از IPها و دامنههای مجاز امکانپذیر است (بهواسطهی
domainAccessوipTrust).
پیوست نگهداری و توسعهآتی
- افزودن سیستم Push Notification برای ارتباط درونبرنامهای بهجای پیامک.
- ثبت IP درخواست OTP در جدول جداگانه برای تحلیل امنیتی.
- ادغام با سامانه مرکزی Auth برای مدیریت اتصال سایر سرویسها.
- افزودن شاخص
otp_fail_attemptsبرای کنترل تلاشهای اشتباه.
POST /api/auth/connect/submit
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/auth/connect/submit | UserController@connectSubmit | domainAccess, ipTrust | تأیید نهایی اتصال اپراتور به سرویس جانبی پس از دریافت رمز OTP | tags={"Auth"} |
توضیح عملکرد (Function Logic)
- دریافت پارامترهای
personnel_id،otpوtelegramاز درخواست. - جستوجوی اپراتور فعال در جدول
operatorsبراساس کد پرسنلی و رمز فعلی OTP. - بررسی انقضای OTP با درنظر گرفتن بازه زمانی ۱۵ دقیقهای.
- در صورت اعتبار، بهروزرسانی ستونهای
telegramوotp(پاک کردن OTP). - ثبت رویداد در
SystemLogبا نوعSubmitTelegramو تاخیر ۱۰ دقیقهای در صفsnailJob. - ارسال پیامک اطلاعرسانی موفقیت اتصال با جزئیات IP و زمان شمسی.
ورودیها (Inputs)
| پارامتر | نوع داده | الزامی | توضیح |
| personnel_id | string | بله | کد پرسنلی اپراتور فعال (status=1). |
| otp | integer | بله | رمز یکبار مصرف صادرشده از مرحلهی قبلی. |
| telegram | string | بله | شناسه یا شماره کاربری تلگرام کاربر برای اتصال. |
| branch | integer | خیر | شناسهی شعبه برای ارسال اطلاعرسانی از طریق سرویس پیامک. |
POST /api/auth/connect/submit
Content-Type: application/json
{
"personnel_id": "P00157",
"otp": "12345678",
"telegram": "@aliiranpour",
"branch": 1
}
خروجیها (Outputs)
تصال موفق
{
"status": true,
"time": 1731963561,
"message": "اتصال با موفقیت انجام شد."
}
رمز منقضی شده
{
"status": false,
"code": 1204,
"message": "رمز یکبار مصرف منقضی شده است."
}
رمز اشتباه یا نامعتبر
{
"status": false,
"code": 1205,
"message": "رمز یکبار مصرف وارد شده معتبر نمی باشد."
}
فرآیند بررسی زمان OTP
بهروزرسانی جدول Operators
UPDATE operators
SET
telegram = {request.telegram},
otp = NULL
WHERE id = {operator.id};
ثبت رویداد در SystemLog
SystemLog::dispatch([
"type" => "SubmitTelegram",
"data" => null,
"goal" => $request->personnel_id,
"by" => $operator->id,
"agent" => $_SERVER['HTTP_USER_AGENT'],
"ip" => getIP(),
"datetime" => Carbon::now()
])->delay(now()->addMinutes(10))->onQueue('snailJob');
پیامک موفقیت اتصال
پیامک: اتصال شما به سرویس مورد نظر موفقیت آمیز بود. زمان: شنبه، ۲۷ آبان ۱۴۰۳ | ۱۲:۴۵:۱۰ 📍 IP: 192.168.1.24 🌐 example.domain
StaticController::sendNotification($operator->mobile, $tempBody, $request->get('branch'));
وابستگیها (Dependencies)
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
- use Morilog\Jalali\Jalalian;
- use App\Jobs\SystemLog;
- use App\Http\Controllers\StaticController;
نمونه تست Postman
POST https://console.service01.ir/api/auth/connect/submit
Content-Type: application/json
{
"personnel_id": "P00442",
"otp": "55667788",
"telegram": "@operator_test"
}
تحلیل امنیتی
- OTP فقط ۱۵ دقیقه اعتبار دارد.
- پس از تأیید موفق، مقدار آن از پایگاهداده حذف میشود (null).
- اطلاعات IP و User Agent در
SystemLogذخیره میگردند. - ارسال پیامک از طریق سرویس داخلی شرکت انجام میشود و حاوی اطلاعات حساس نیست.
- وجود middleware های
domainAccessوipTrustمانع ارسال از منابع غیرمجاز میگردد.
پیوست نگهداری و توسعه بعدی
- اتصال به سرویس
Webhook Telegramبرای تأیید دوطرفه. - ثبت زمان دقیق اتصال در جدول پیشرفته audit_log.
- افزودن محدودیت IP برای کاربران خاص در مرحله Submit.
- نمایش هشدار برای IPهای ناشناس محیط کاربری.
`
POST /api/auth/forgot/submit
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/auth/forgot/submit | UserController@forgotSubmit | domainAccess, ipTrust | ثبت رمز عبور جدید پس از تأیید با رمز OTP (فراموشی رمز عبور) | tags={"Auth"} |
توضیح عملکرد (Function Logic)
- جستوجوی اپراتور فعال (
status=1) باpersonnel_idوotpیکسان. - بررسی زمان صدور OTP (
otp_issuing): اگر بیش از ۱۵ دقیقه گذشته باشد، رمز منقضی شده است (کد ۱۲۰۴). - اگر معتبر باشد: رمز عبور جدید (
$request->password) باHash::make()هش و ذخیره میشود. - ستون
otpاپراتور بهNULLتغییر داده میشود تا از استفاده مجدد جلوگیری شود. - ثبت رویداد
UpdatePasswordدرSystemLogدر صفsnailJob. - ارسال پیامک اطلاعرسانی موفقیتآمیز بودن تغییر رمز با جزئیات IP و زمان شمسی.
ورودیها (Inputs)
| پارامتر | نوع داده | الزامی | توضیح |
| personnel_id | string | بله | کد پرسنلی اپراتور فعال. |
| otp | integer | بله | رمز یکبار مصرف ۸ رقمی صادر شده برای بازیابی. |
| password | string | بله | رمز عبور جدید. (اعتبارسنجی پیچیدگی در لایه فرانت یا Middleware باید اعمال شود) |
| branch | integer | خیر | شناسهی شعبه برای ارسال اطلاعرسانی پیامکی. |
POST /api/auth/forgot/submit
Content-Type: application/json
{
"personnel_id": "P00999",
"otp": "98765432",
"password": "MyNewStrongPassword@123",
"branch": 1
}
خروجیها (Outputs)
بهروزرسانی موفق رمز عبور
{
"status": true,
"time": 1731963561,
"message": "کلمه عبور با موفقیت بروزرسانی شد."
}
رمز یکبار مصرف منقضی شده
{
"status": false,
"code": 1204,
"message": "رمز یکبار مصرف منقضی شده است."
}
رمز یکبار مصرف نامعتبر
{
"status": false,
"code": 1205,
"message": "رمز یکبار مصرف وارد شده معتبر نمی باشد."
}
هشینگ و بهروزرسانی دیتابیس
Hash::make() هش میشود. در همان تراکنش، OTP استفاده شده با مقدار **NULL** جایگزین میگردد تا امنیت تضمین شود.DB::table('operators')
->where('id', $operator->id)
->update([
'password' => Hash::make($request->password),
'otp' => null
]);
ثبت رویداد در SystemLog
SystemLog::dispatch([
"type" => "UpdatePassword",
"data" => null,
"goal" => $request->personnel_id,
"by" => $operator->id,
"agent" => $_SERVER['HTTP_USER_AGENT'],
"ip" => getIP(),
"datetime" => Carbon::now()
])->delay(now()->addMinutes(10))->onQueue('snailJob');
پیامک اطلاعرسانی امنیتی
پیامک: رمز عبور شما بروزرسانی گردید. زمان: شنبه، ۲۷ آبان ۱۴۰۴ | ۱۳:۱۰:۲۰ 📍 IP: 10.0.0.15 🌐 example.domain
وابستگیها (Dependencies)
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Hash;
- use Carbon\Carbon;
- use Morilog\Jalali\Jalalian;
- use App\Jobs\SystemLog;
- use App\Http\Controllers\StaticController;
تحلیل امنیتی
- **اعتبار سنجی زمانی:** فقط ۱۵ دقیقه برای ثبت رمز جدید فرصت داده میشود.
- **حذف OTP:** رمز یکبار مصرف بلافاصله پس از استفاده، با NULL جایگزین میشود.
- **هشینگ رمز:** استفاده از
Hash::make()برای جلوگیری از ذخیرهسازی رمز به صورت متن ساده. - **هشدار به کاربر:** ارسال پیامک به موبایل کاربر، لایه امنیتی نهایی در صورت سرقت هویت را فراهم میکند.
- **Middleware:** محافظت با
domainAccessوipTrustالزامی است.
پیوست نگهداری و توسعه بعدی
- اعمال اعتبارسنجی پیچیدگی رمز (حداقل ۸ کاراکتر، کاراکترهای خاص) در سمت سرور به صورت Middleware یا Form Request.
- اضافهکردن قابلیت جلوگیری از استفاده مجدد رمزهای عبور قبلی (Password History).
- درخواست تأیید رمز جدید (confirm password) در لایه فرانتاند.
POST /api/auth/access-token
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/auth/access-token | UserController@accessToken | domainAccess, ipTrust | تولید توکن دسترسی جدید پس از تطبیق مرورگر، IP، دامنه و شعبه. | tags={"Auth"} |
توضیح عملکرد (Function Logic)
- دریافت
access_tokenاز$request->dataو Decode با کلیدenv('JWT_SECRET_KEY'). - تحلیل User-Agent با
DeviceDetectorبرای استخراج اطلاعات مرورگر فعلی (نام و نسخه). - تابع داخلی
checkBrowser()مقایسهای بین مرورگر ثبتشده در توکن و مرورگر فعلی انجام میدهد. - اگر مرورگر تطبیق داشت، سپس IP بررسی میشود:
- اگر IP فعلی با
uipدر توکن یا با یکی از IPهای خصوصی (10.* / 192.168.* / 172.16.*) همخوانی داشت، ادامه داده میشود.
- اگر IP فعلی با
- تطبیق دامنه
issبا هدر Domain درخواست. - تطبیق شناسه شعبه
brnبا ورودیbranchدرخواست. - در صورت قبولی همه مراحل: یافتن اپراتور فعال (status=1، بدون بلوک).
- تولید توکن جدید با ساختار زیر:
{
"typ": "base",
"iss": "domain.com",
"aud": "domain.com",
"iat": 1731964000,
"nbf": 1731964000,
"exp": 1732568800, // 7 روز
"uuid": 54,
"brn": 2,
"uip": "10.0.0.15",
"brw": { "name": "Chrome", "version": "122.0", "type": "browser" }
}
بدنه درخواست (Request Body)
| پارامتر | نوع داده | الزامی | توضیح |
| data.access_token | string (JWT) | بله | توکن فعلی کاربر که باید توسط سرور Decode و اعتبارسنجی شود. |
| branch | integer | بله | شناسه شعبه فعلی جهت تطبیق با اطلاعات درون توکن. |
POST /api/auth/access-token
Content-Type: application/json
Domain: example.domain
{
"branch": 2,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
}
}
پاسخ موفق (Response - Success)
{
"user": {
"uuid": 54,
"from": "users",
"role": "admin",
"isAirPlusAdmin": true,
"group": "core",
"data": {
"displayName": "Ali Pour",
"email": "a.pour@example.ir",
"photoURL": "https://storage.service01.ir/media/avatar/robot.png",
"services": { "telegram": false },
"personnelId": "P00999",
"branch": [0,1],
"access": [ ... ],
"shortcuts": ["temporary-registration","online-ticket","trade-management","customers"]
}
},
"access_token": "new.jwt.token.generated"
}
پاسخهای خطا (Error Types)
| نوع خطا | شرح خطا |
| changeBrowser | مرورگر فعلی با مرورگر آخرین ورود مطابقت ندارد. |
| changeIp | IP متغیر یا استفاده از VPN. جزئیات: شامل IP فعلی و IP درجشده در توکن. |
| changeDomain | دامنه درخواست با دامنه صادرکننده توکن همخوانی ندارد. |
| changeBranch | شناسه شعبه تغییر کرده (مثلاً انتقال کاربر بین شعبات). |
| personnelId | نامعتبر بودن توکن یا خطای رمزگشایی JWT. |
{
"error": {
"type": "changeIp",
"message": "وضعیت اینترنت شما تغییر پیدا کرده",
"details": {
"token_ip": "10.1.1.2",
"current_ip": "45.66.88.100"
}
}
}
تولید JWT جدید
HS256 تولید میشود؛ مدت اعتبار آن ۷ روز (۶۰۴٬۸۰۰ ثانیه) است. اطلاعات کلیدی آن شامل شناسه کاربر، شناسه شعبه، مرورگر و IP فعلی است.تحلیل امنیتی
- ✅ احراز چندمرحلهای محیطی: کنترل همزمان ۴ عامل (Browser + IP + Domain + Branch) قبل از احیای توکن.
- ✅ محافظت از سوریه توکن: جلوگیری از استفاده از JWT در مرورگر یا شبکه دیگر.
- ✅ تأیید دامنه صدور: مقایسه مقدار
issدر هدر JWT با مقدار هدر درخواست (Domain). - ✅ دسترسی کنترلشده Redis: بارگذاری میانبرهای رابط سیستم از مسیر cache محدود.
- ⚠️ توصیه: اعمال نرخ محدودسازی (Rate Limiting) برای جلوگیری از حمله بروتفورس JWT.
وابستگیها (Dependencies)
- use Firebase\JWT\JWT;
- use Firebase\JWT\Key;
- use DeviceDetector\DeviceDetector;
- use DeviceDetector\ClientHints;
- use DeviceDetector\Parser\AbstractDeviceParser;
- use Carbon\Carbon;
- use Illuminate\Support\Facades\Redis;
- use App\Models\User;
پیوست نگهداری و توسعه بعدی
- اضافهکردن بررسی Device ID یکتا برای جلوگیری از جعل User-Agent.
- افزودن refresh-token مجزا برای دسترسی موقت با سطح محدود (scoped access).
- ذخیره لاگ تغییر IPها در SystemLog جهت تحلیل تهدیدات.
- اعمال Expiration Dynamics (IP-based TTL) بر اساس منطقه جغرافیایی کاربر.
GET /api/v2/notifications
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| GET | /api/v2/notifications | V2BaseController@notifications | domainAccess, ipTrust | دریافت لیست اعلانهای سیستمی و بهروزرسانیها در محیط کاربر | tags={"Base","Notifications"} |
توضیح عملکرد (Function Logic)
notifications یا کش Redis تأمین میشوند.- احراز IP و دامنه درخواست از طریق Middlewareهای
domainAccessوipTrust. - تولید آرایهای از اعلانها با فیلدهای کلیدی:
- id: شناسه یکتا
- icon: آیکن متریال مرتبط با اعلان
- title: تیتر اعلان
- description: متن توضیحی اعلان
- time: زمان ایجاد اعلان به فرمت شمسی
- read: وضعیت مطالعهشده بودن توسط کاربر
- link: مسیر واکنشپذیر (قابل کلیک در UI)
- useRouter: نوع هدایت (true ⇒ استفاده از Router داخلی SPA)
- بازگشت خروجی به صورت JSON (بدون نیاز به Token در این نسخه از Endpoint)
ساختار درخواست (Request)
GET /api/v2/notifications Domain: example.domain
خروجی (Response Structure)
| فیلد | نوع داده | توضیح |
| id | string | شناسه یکتا برای هر اعلان |
| icon | string | نام آیکن متریال (برای نمایش تصویری اعلان) |
| title | string | تیتر کوتاه اعلان |
| description | string | توضیح کامل محتوای اعلان |
| time | string (datetime) | تاریخ و ساعت اعلان به فرمت شمسی |
| read | boolean | وضعیت خواندهشدن اعلان توسط کاربر |
| link | string | مسیر URL یا Route مقصد |
| useRouter | boolean | True ⇐ استفاده از Router کلاینتی برای تغییر مسیر نرمافزاری |
نمونه پاسخ
[
{
"id": "01",
"icon": "airline_seat_recline_extra",
"title": "ویرایش خرید پرواز آنلاین",
"description": "به اطلاع کلیه همکاران میرسانیم قسمت ویرایش پرواز های آنلاین فعال گردید.",
"time": "1402-02-02 00:00:00",
"read": true,
"link": "/",
"useRouter": true
}
]
نکات امنیتی
- استفاده از Middleware domainAccess و ipTrust برای محدود کردن دسترسی به دامنهها و IPهای مجاز.
- در حالت فعلی نیازی به توکن JWT ندارد اما در محیط Production معمولاً با
authWithJwtترکیب میشود. - در پاسخ، مقادیر هیچ دادهی حساسی برنمیگردد — تنها اعلانهای عمومی هستند.
توضیح فیلدهای پاسخ
- id: شناسه عددی / رشتهای قابل جستوجو برای هر اعلان در کلاینت.
- icon: نام آیکن از مجموعه Material Icons برای نمایش در رابط کاربری.
- title: تیتر کوتاه در بالای باکس اعلان.
- description: پیام اصلی اطلاعرسانی.
- time: رشته زمان به فرمت "YYYY-MM-DD HH:MM:SS".
- read: وضعیت مطالعه (true = خوانده شده).
- link: مسیر قابل کلیک داخل سیستم (مثلاً "/dashboard").
- useRouter: اگر true باشد، مسیر فوق در Router داخلی اپلیکیشن باز میشود بدون رفرش.
وابستگیها (Dependencies)
- use Illuminate\Http\Request;
- use App\Http\Controllers\Controller as BaseController;
پیوست نگهداری و توسعه بعدی
- بهبود ساختار خروجی برای پشتیبانی از چند اعلان همزمان (pagination و unread count).
- افزودن فیلد
categoryبرای تفکیک نوع اعلان (مثلاً “news”, “update”, “security”). - افزودن endpoint جدید:
PATCH /api/v2/notifications/readبرای علامتگذاری اعلانها به عنوان خوانده شده. - ذخیره کش Redis از نوتیفیکیشنها برای سرعت بیشتر پنل.
GET /api/v2/exam/get
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| GET | /api/v2/exam/get | OfficialController@getExam | domainAccess, ipTrust | دریافت فرم آزمون (نظرسنجی) برای سفر یا ارزیابی ۳۶۰ درجه همکاران | tags={"Exam","V2"} |
توضیح عملکرد (Function Logic)
getExam() بر اساس نوع درخواست مشخص میکند که آزمون از کدام شاخه بارگذاری شود:- نوع ۱:
trip= نظرسنجی خدمات سفر (Trip Survey) - نوع ۲:
360_degree_feedback= ارزیابی عملکرد همکار به صورت ۳۶۰ درجه
منطق کلی:
- بررسی مقدار
$request->type. - اجرای مسیر مرتبط متناسب با نوع آزمون.
- در صورت یافتن شرایط واجد، تولید ساختار آزمون شامل سؤالات، هدر، فوتر و فیلدهای جایگزینشده از دادههای مالی یا اطلاعات همکار.
- بازگشت پاسخ JSON شامل
status،timeو داده یا پیام خطا.
ورودیها (Request Parameters)
| پارامتر | محل | نوع داده | الزامی | توضیح |
| type | Query | string | بله | نوع آزمون؛ مقدارهای مجاز: "trip" یا "360_degree_feedback". |
| id | Query | string|int | بله | شناسه مرجع آزمون (Slug سفر یا شناسه همکار). |
| branch | Query | string|int | بله | کد شعبه فعال (برای محدودسازی دامنه آزمون). |
| operator | Header/Auth Context | object | اختیاری* | در حالت ۳۶۰ درجه برای تشخیص کاربر واردشده مورد نیاز است. |
الف) جریان عملکرد نوع «trip»
- یافتن فاکتور از جدول
factorsبر اساس slug دادهشده. - بررسی وجود پاسخ قبلی در جدول
exam_responseبا فیلدهای:object_type='reference'وobject=factor.id. اگر وجود داشته باشد → پیغام «این سفر قبلاً نظرسنجی شده است.». - در غیراینصورت:
- دریافت داده مالی از Redis با کلید
reference:{id}:information. در صورت عدم وجود، صدا زدنTradeController::financial()و ذخیره در Redis. - واکشی کالاهای موجود در فاکتور از جدول
factor_items. - یافتن آزمون فعال با
type=trip_surveyبرای همان شعبه. - دریافت سؤالات فعال از
exam_questionsمرتبشده بر اساس order و id. - اعمال فیلتر سؤالات: اگر موضوع
hotelاست، فقط هنگام وجود محصول مرتبط استفاده میشود. - جایگزینی متغیرهای دینامیک در title، header و footer با متد
replaceExamItem('trip',...). - ساخت ساختار خروجی نهایی شامل مشخصات آزمون و آرایه سؤالات.
- دریافت داده مالی از Redis با کلید
ب) جریان عملکرد نوع «360_degree_feedback»
- بررسی وجود
operator.id؛ اگر کاربر لاگین نکرده → پیام خطا. - عدم تطابق شناسه خواستهشده با personnel_id کاربر فعلی (جلوگیری از خودارزیابی).
- جستوجوی همکار هدف در جدول
operatorsبا شروط:branch JSON contains current branchstatus = 1،no_feedback = null.
- بررسی اینکه تاریخ استخدام کارمند حداقل ۱ ماه قبل باشد.
- در صورت گذشت شرط: بررسی عدم وجود پاسخ از کاربر فعلی در ۶ ماه اخیر (
exam_response). - اگر تاکنون پاسخ نداده → واکشی آزمون با
type=360_degree_feedbackوbranchجاری. - واکشی سؤالات و تولید ساختار خروجی مشابه نوع trip، با جایگزینی پارامترهای
{colleague}و{position}در title، header و footer.
ساختار پاسخ (Response Structure)
| فیلد | نوع داده | توضیح |
| status | boolean | نتیجه نهایی تابع. |
| time | int (unix timestamp) | زمان پاسخ سرور (بر حسب ثانیه). |
| data | object|null | در صورت true حاوی ساختار آزمون کامل است. |
| message | string|null | پیغام خطا هنگام بروز استثنا. |
نمونه پاسخ موفق (trip):
{
"status": true,
"time": 1693905000,
"data": {
"id": 15,
"object": 1472,
"title": "نظرسنجی خدمات پرواز",
"header": "مشتری گرامی، لطفاً به سفر خود امتیاز دهید.",
"footer": "با تشکر از همکاری شما",
"created_at": "1402-02-02 00:00:00",
"questions": [
{
"id": 1,
"type": "rating",
"subject": "flight",
"title": "ارزیابی کیفیت پرواز شما",
"options": [1,2,3,4,5],
"description": null,
"mandatory": 1,
"score": 5
}
]
}
}
نمونه پاسخ خطا:
{
"status": false,
"time": 1693905001,
"message": "این سفر قبلا نظرسنجی شده است."
}
تحلیل امنیتی
- ✅ Middleware دوگانه
domainAccessوipTrustمانع درخواستهای خارج از محدوده مجاز میشود. - ⚠️ Endpoint فاقد
authWithJwtاست؛ در حالت فعلی کاربران ناشناس میتوانند با دانستنslugبه آزمون سفر دسترسی یابند. در حالت Production پیشنهاد میشود JWT فعال شود. - ✅ دادهی حساسی در خروجی وجود ندارد (تنها ساختار آزمون).
- ⚠️ تابع از Redis و DB مستقیم استفاده میکند بدون هندل استثناء؛ خطای Redis میتواند کل عملیات را fail کند.
وابستگیها (Dependencies)
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Http\Controllers\Api\Panel\V2\TradeController;
ساختار داخلی data در پاسخ موفق
| فیلد | نوع | توضیح |
| id | int | شناسه آزمون. |
| object | int | شناسه مرجع (سفر یا کارمند). |
| title | string | عنوان اصلی آزمون. |
| header/footer | string | متن معرفی و پایان آزمون. |
| questions | array | لیست سؤالات با فیلدهای id, type, subject, title, options, description, mandatory, score. |
پیغامهای خطای محتمل
- ⚠️ "لینک نظرسنجی معتبر نمی باشد" → شناسه یا کاربر نامعتبر.
- ⚠️ "بانک سوالات تعریف نشده است" → آزمون نوع مربوطه برای این شعبه فعال نشده.
- ⚠️ "این سفر قبلا نظرسنجی شده است" یا "این مورد قبلا نظرسنجی شده است" → پاسخ قبلی موجود است.
- ⚠️ "این کارمند هنوز به مرحله ارزیابی 360 درجه نرسیده" → کمتر از ۱ ماه از ایجاد حساب کارمند گذشته.
- ⚠️ "شما امکان ارائه نظر به خود را ندارید" → جلوگیری از Self Feedback.
- ⚠️ "این نظرسنجی احتیاج به ورود به سیستم دارد." → بدون Auth.
نکات کارایی (Performance)
- دسترسی Redis برای کش اطلاعات فاکتور سرعت واکشی آزمون را افزایش میدهد.
- اما هر بار بررسی DB + Redis روی مسیر عمومی ریسک افزایش تاخیر دارد.
- پیشنهاد: تجمیع دادهها در یک Service Layer با TTL منطقی ۵ دقیقهای برای Redis Keyها.
پیوست نگهداری و توسعه بعدی
- انتقال Logic از Controller به Service مستقل (ExamService) جهت پوشش Exception Handling.
- افزودن فیلد
categoryیاtagبه آزمونها جهت فیلتر بهتر در سامانه آموزش. - درخواست بعدی (POST /api/v2/exam/response) جهت ثبت پاسخ آزمون، مکمل همین Endpoint است و بلافاصله باید مستند شود.
POST /api/v2/exam/response
Route Info
| Method | Endpoint | Controller | Middleware | Purpose | تگ Swagger |
| POST | /api/v2/exam/response | OfficialController@responseExam | domainAccess, ipTrust | ثبت پاسخ آزمون نظرسنجی (سفر یا ارزیابی ۳۶۰ درجه) | tags={"Exam","V2"} |
توضیح عملکرد (Function Logic)
trip یا 360_degree_feedback)، نوع ذخیره object_type تنظیم میشود (reference/personnel). ۱. رکورد اصلی exam_response با اطلاعات پایه: شعبه، شناسه آزمون، شیء، شناسه کاربر، نام، موبایل، ایمیل ۲. ثبت هر پاسخ تکی در جدول exam_responses_item؛ هر پاسخ شامل شناسه سوال و مقدار پاسخ که خودش میتواند آرایه یا رشته باشد (در صورت آرایه json_encode). ۳. اگر آزمون شامل جایزه باشد (فیلد gift در جدول exams)، ساخت کد جایزه و ارسال ساختار هدیه ۴. امتیازدهی خودکار با StaticController::getScoreOperator ۵. خروجی نهایی: status و زمان، و در صورت تخصیص هدیه، فیلد gift ۶. هندل کامل استثنا به صورت خروجی status=false و اطلاعات خطاورودیها (Request Body)
| فیلد | نوع داده | الزامی | توضیح |
| type | string | بله | نوع آزمون (trip یا 360_degree_feedback) |
| id | int | بله | شناسه آزمون |
| object | int | بله | شناسه سفر (type=trip) یا شناسه همکار (type=360) |
| branch | string|int | بله | کد شعبه |
| operator | object | اختیاری | شیء کاربر فعلی (در حالت ۳۶۰ درجه الزامی) |
| full_name | string | اختیاری | نام کامل ثبتکننده |
| mobile | string | اختیاری | شماره موبایل ثبتکننده |
| string | اختیاری | ایمیل ثبتکننده | |
| responses | array | بله | آرایه پاسخ هر سوال. هر آیتم: {question: int, response: string|array} |
خروجی (Response Structure)
| فیلد | نوع داده | توضیح |
| status | boolean | نتیجه عملیات ثبت |
| time | int | زمان ثبت (unix timestamp) |
| gift | object|false | اگر آزمون جایزه دارد، شیء {"title", "code"} |
| message | string | توضیح خطا (در حالت failure) |
| trace | array | اطلاعات error (در حالت failure) |
نمونه درخواست:
POST /api/v2/exam/response
Content-Type: application/json
{
"type": "trip",
"id": 15,
"object": 1472,
"branch": "8",
"operator": { "id": 22 },
"responses": [
{ "question": 1, "response": "5" },
{ "question": 2, "response": ["Clean", "Comfort"] }
]
}
نمونه پاسخ موفق:
{
"status": true,
"time": 1693905251,
"gift": { "title": "تخفیف ویژه", "code": "140303-12345" }
}
نمونه پاسخ خطا:
{
"status": false,
"time": 1693905252,
"message": "SQLSTATE[23000]: Integrity constraint violation ...",
"trace": [ ... ]
}
تحلیل امنیتی
- ✅ حفاظت Realm با Middleware
domainAccessوipTrust - ⚠️ هیچ اعتبارسنجی برای صحت ساختار responses یا عدم وجود تکرار پاسخ وجود ندارد؛ نقطه ضعف قابل سوءاستفاده.
- ⚠️ برای آزمونهای "360"، اگر operator معتبر نباشد سیستم بدون خطای خوانا fail میکند.
- ⚠️ دادههای contact (نام، موبایل، ایمیل) بدون هیچ sanitation در DB ثبت میشود: ریسک تزریق و Spamming exists.
- ✅ Exception هندلینگ کامل انجام میشود؛ Error Trace در خروجی ثبت میشود.
- ⚠️ هیچ Throttling یا تکرارپذیری ضد اسپم روی این route دیده نمیشود.
وابستگیها (Dependencies)
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Morilog\Jalali\Jalalian;
- use App\Http\Controllers\Api\Panel\V2\StaticController;
نکات کارایی و ضعف منطقی
- هر پاسخ به سرعت در DB ثبت میشود اما هیچ validation سمت سرور نیست (حتی اگر responses کاملاً بیربط باشند).
- فرایند تولید کد هدیه وابسته به id کلیدی است و قابل پیشبینی توسط مهاجم.
- در صورت خطای پایگاه داده، جزئیات با
traceبه مشتری نمایش داده میشود که ریسک اطلاعاتی دارد.
پیوست نگهداری و توسعه بعدی
- افزودن اعتبارسنجی دقیق responses و sanitation اطلاعات مبتنی بر فیلدهای contact.
- افزودن throttling/limiting بر اساس operator و سفر برای جلوگیری از brute force و spam.
- تعریف وابستگی به احراز هویت JWT در نسخه بعدی.
- افزودن فیلدهای custom جهت اتوماسیون هدیه (مثلاً expiry/used count).
POST /api/v2/trade/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/list | V2TradeController@getTradesList | authWithJwt | نمایش فهرست معاملات (فاکتورها) با فیلترهای پیشرفته |
منطق عملکرد تابع
json تجزیه میشوند؛ شامل تنظیمات DataTable مانند start، length، draw و فیلترهای advanced. اگر کاربر هیچ جستجویی انجام نداده باشد، سیستم بهصورت پیشفرض فقط فاکتورهای همان روز را نمایش میدهد. سپس مجموعهای از شرایط فیلترینگ اعمال میشود:
- فیلتر نوع پیشرفته (advanced) بر اساس شماره رزرو (
r)، محدوده تاریخ (from,to), اپراتور، وضعیت، شماره پرواز، وسیله، تعهدکننده، مسیر و درآمد. - محاسبه و ترکیب چندین آرایهٔ ID برای محدودسازی نتایج:
$arr_id_fn,$arr_id_d,$arr_id_fu_d,$arr_id_p. - اعمال محدودیت دسترسی (Access Control) بر اساس سطح دسترسی اپراتور از طریق تابع
Functions::getAccessUser('trade', ...). - اتصال به Redis جهت کش (cache) اطلاعات مالی و تعهدکنندگان هر فاکتور با کلیدهای
reference:{id}:pledgersوreference:{id}:information. - محاسبهٔ شاخصهای مالی کل برای لیست بازگشتی: مجموع خرید، فروش، سود، بدهی، بستانکاری، تخفیف، تسهیلات، تعداد مسافر.
- بازچینی داده برای هر معامله به صورت ساختارمند در قالب
items[](جهت نمایش جدول). - خروجی استاندارد DataTable-compatible شامل فیلدهای
draw,recordsTotal,recordsFiltered,total, وdata[].
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| branch | int|string | بله | شناسه شعبهای که اپراتور به آن متصل است |
| operator | object | بله | شیء اپراتور استخراجشده از JWT |
| json | object (JSON string) | بله | حاوی ساختار DataTable شامل start، length، draw و فیلترهای advanced |
ساختار فیلتر پیشرفته (advanced):
"advanced": {
"r": "", "from": "2025-01-01", "to": "2025-01-31",
"op": 0, "status": 1, "flightno": "W51123", "dt_departure": "2025-01-12",
"pledger": "colleague-42", "vehicle": "aircraft", "income": "", "route": ""
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| draw | int | شماره فراخوان DataTable |
| recordsTotal | int | تعداد کل فاکتورها مطابق فیلتر |
| recordsFiltered | int | تعداد پس از فیلترینگ |
| total | object | مجموع کل شاخصهای مالی (Buy, Sale, Profit, …) |
| data | array | آرایهای از معاملات شامل جزئیات Route، Operator، Pledger و Financial |
نمونه درخواست:
POST /api/v2/trade/list
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"branch": 8,
"json": {
"draw": 1,
"start": 0,
"length": 15,
"search": {"value": ""},
"advanced": {"from":"2025-01-01","to":"2025-01-31","status":"1"}
}
}
نمونه پاسخ:
{
"draw": 1,
"recordsTotal": 52,
"recordsFiltered": 52,
"total": {
"Buy": 9200000,
"Sale": 11300000,
"Profit": 2100000,
"Debit": 150000,
"Credit": 280000,
"Discount": 30000,
"Passenger": 91
},
"data": [ { "SerialId": 2048001, "Income":"direct", "RouteTitle":"پرواز تهران-کیش", ... } ]
}
امنیت و کنترل دسترسی
- لایهٔ امنیتی JWT توسط میدلور
AuthWithJWT، بررسی توکن و اعتبار کاربر از جداول مختلف (operators,customers,colleague_auth). - اگر کاربر غیرفعال باشد یا JWT منقضی شده باشد → Error Code 1002 یا 1006.
- هیچ Permissions Role-Based دقیق برای فیلدهای مالی در Endpoint اعمال نمیشود، فقط سطح general access کنترل میشود.
- اطلاعات مالی از Redis کش خوانده شده و فاقد رمزنگاری است. خطر افشای اطلاعات مالی وجود دارد.
- هیچ validation روی ساختار داخلی
jsonاعمال نشده — ممکن است دادهٔ تزریقشده منجر به crash شود.
نکات کارایی و پیادهسازی
- پنج درخواست مجزا به DB در هر ثبت معامله (Factor + FactorItem + Pledger + Redis fetch/write).
- NULL caching در Redis هرگز منقضی نمیشود، حافظه بدون مدیریت رشد میکند.
- در صورت آغاز بدون فیلتر، تمام روز جاری اسکن میشود (query سنگین بدون ایندکس مناسب).
- محاسبههای مالی مجدد زمانی انجام میشود که Cache خالی باشد → افزایش CPU.
- درخواستهای موازی بدون کنترل Lock روی Redis باعث Out-of-sync در Cache میشود.
وابستگیها
- use Carbon\Carbon;
- use Morilog\Jalali\Jalalian;
- use Illuminate\Support\Facades\Redis;
- use Illuminate\Support\Facades\DB;
- use App\Models\{Factor, FactorItem, Pledger, User, Colleague};
- use App\Http\Controllers\Api\Panel\V2\ApiTradeController;
- use App\Http\Controllers\Api\Panel\V2\TradeController;
- use App\Http\Controllers\Api\Panel\V2\StaticController;
- use App\Helpers\Functions;
کدهای خطا و خروجیهای Exception
| کد خطا | شرح | منبع |
| 1005 | Token not provided | AuthWithJWT |
| 1006 | Your token is invalid or expired | AuthWithJWT |
| 1002 / 1003 / 1004 | User does not have access permission | AuthWithJWT |
| 500 | Database / Redis Exception → Trace exposed | V2TradeController@getTradesList |
پیشنهادهای امنیتی
- افزودن expiration به Redis cache (TTL 10m).
- قرار دادن validation برای json input و advanced filters.
- پنهانسازی جزئیات trace در پاسخ خطا.
- محدودسازی سطح اطلاعات مالی بر اساس Role.
پیشنهادهای بهبود
- Refactor منطق فیلترینگ در QueryBuilder جداگانه (TradeQueryService).
- ایجاد ایندکس ترکیبی روی فیلدهای
created_at،statusوbranch. - استانداردسازی خروجی Financial در endpoint مستقل.
- کاهش حجم response با pagination سمت سرور واقعی (بدون pull تمام ستونها).
ممیزی دسترسی و عدم قطعیت
- دسترسیها با
Functions::getAccessUserکنترل میشوند اما audit در سطح route وجود ندارد. - هیچ log مشخصی برای view یا export ثبت نمیشود؛ باید logViewTrade اضافه شود.
جمعبندی
POST /api/v2/trade/search
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/search | V2TradeController@searchTrades | authWithJwt | جستجوی معاملات/فاکتورها با فیلترهای چندلایه و ترجمه خودکار مسیرها |
منطق عملکرد و مسیر داده
search و paginate پردازش میکند:
- تبدیل فیلدهای عددی به رشته خالی برای جلوگیری از فیلترینگ بیهدف.
- بررسی کامل ورود from و to؛ در صورت نبود خروجی error (code 1000).
- ساخت ترکیبی از جستجوهای پیشرفته روی معاملات—فیلدهای: تاریخ، شماره رزرو، اپراتور، تعهدکننده، شماره/تاریخ پرواز، محصول، وضعیت، مسیر، درآمد و اطلاعات مسافر؛ از کوئریهای فشرده با شرطهای تو در تو استفاده میشود.
- تمام توصیفهای مسیر (route) با ترجمههای فارسی/انگلیسی زده میشوند؛ مثل "هتل"، "مسیر"، "تور"، "پرواز" با ترکیبهای محصول و جزئیات.
- اطلاعات تکمیلی هر آیتم از Redis یا DB و کلاسهای کمکی خوانده و کش میشود.
- نتیجه نهایی آرایهای از معاملات با جزییات کامل مسیر و اطلاعات مالی است—پاسخ با meta ساختار یافته و صفحهبندی.
ورودیها (Request Fields)
| نام فیلد | نوع داده | ضروری | توضیح |
| branch | int|string | بله | شناسه شعبه فعال |
| operator | object | بله | اپراتور (از JWT middleware) |
| search | object | بله | شی جستجو با فیلدهایی مانند: r، from، to، op، flightno، dt_departure، passenger، status و... |
| paginate | object | بله | پارامترهای صفحه بندی: start، length |
نمونه Search Structure:
{
"branch": 8,
"search": {
"from": "2025-02-01", "to": "2025-02-12",
"status": "1", "flightno": "W51005",
"op": 22, "pledger": "operator-12"
},
"paginate": {
"start": 0,
"length": 15
}
}
خروجی (Response Structure)
| فیلد | نوع داده | توضیح |
| items | array | آرایهای از معاملات (ساختار زیر) |
| meta | object | شاخصهای صفحهبندی (timestamp, total, page, per_page, last_page, current_page) |
ساختار هر آیتم در خروجی:
| فیلد | نوع | توضیح |
| datetime | object | تاریخ و ساعت صدور معامله |
| type | object | نوع محصول و مسیر |
| income | string | نوع درآمد |
| route_type | string | نوع اصلی خدمت (aircraft, bus, hotel, ...) |
| pledger | array|null | تعهدکنندگان معامله با جزئیات |
| route_title | string | توضیح مسیر یا هتل یا سرویس، با ترجمه فارسی یا انگلیسی هوشمند |
| operator | object | اطلاعات اپراتور ثبتکننده |
| leader | object | لیدر مسیر (در صورت وجود) |
| count_passengers | int | تعداد مسافرها |
| status | int | وضعیت معامله |
| description | string|false | توضیح در صورت status=2,5 |
| serial | int | شماره رزرو سیستمی |
| factor_id | int | شماره رزرو پایه |
| system_serial | int | شماره داخلی فاکتور |
| slug | string | شناسه مسیر یکتا |
| suppliers | array | فهرست تامینکنندهها |
نمونه پاسخ موفق:
{
"items": [
{
"date": { "title": "11/22", "placeholder": "سهشنبه، 1404/12/23 14:32" },
"route_title": "تهران به کیش | W51005",
"type": { "title": "aircraft", "placeholder": "هواپیما" },
"pledger": [ { "Id": 12, "Title": "جواد مقیمی", "Amount": 250000 } ],
"suppliers": [],
...
}
],
"meta": {
"timestamp": 1693905251,
"total": 29,
"page": 15,
"per_page": 15,
"last_page": 2,
"current_page": 2
}
}
نمونه پاسخ خطا (تاریخ ناقص):
{
"error": {
"code": 1000,
"message": "لطفا تاریخ شروع جستجو و پایان جستجو را وارد نمائید."
},
"meta": {
"timestamp": 1693905251
}
}
تحلیل امنیتی و کنترل خطا
- ورود به Route کاملاً وابسته به JWT؛ اگر توکن معتبر نباشد یا کاربر مجاز نباشد، خطای 1002/1003/1004 یا 1005/1006 برمیگردد.
- ساختار ورودی به هیچوجه با Schema validate نمیشود، زمینهساز حملات Injection یا نقض منطق جستجو است.
- اطلاعات تکمیلی و ترجمهشده مسیر از Redis بدون TTL ذخیره میشود—cache poisoning یا stale data ممکن است رخ دهد.
- در پاسخ خطا (date missing)، هیچ جزئیات sensitive داده نمیشود اما سایر باگهای منطقی ممکن است باعث لو رفتن ساختار پایگاه داده شوند.
- اگر سایز صفحهبندی خیلی بزرگ تنظیم شود، منجر به لحظهای شدن مصرف RAM میشود و هیچ کنترل فشاری ندارد.
نکات کارایی و ضعف طراحی
- استفاده مکرر و نادرست از Redis بدون TTL باعث رشد بیرویه cache و سنگینشدن حافظه سرور میشود.
- رجوع مکرر به DB برای اطلاعات supplement (hotel, airline) در هر جستجو، در مسیرهای حجیم باعث افت عملکرد است.
- ساختار جستجو تو در تو باعث سردرگمی منطق ذخیره نتیجه و ترکیب شروطها میشود؛ لازم است Refactor سرویس جستجو انجام شود.
وابستگیهای کلیدی
- use Carbon\Carbon;
- use Morilog\Jalali\Jalalian;
- use Illuminate\Support\Facades\Redis;
- use App\Models\{Factor, FactorItem, Pledger, User, Colleague, Hotel, Airport, City};
- use App\Http\Controllers\Api\Panel\V2\ApiTradeController;
- use App\Http\Controllers\Api\Panel\V2\StaticController;
- use App\Helpers\Functions;
- use CalendarUtils;
کدهای خطا و خروجیهای Exception
| کد خطا | شرح | منبع |
| 1000 | لطفا تاریخ شروع جستجو و پایان جستجو را وارد نمائید. | searchTrades (application logic) |
| 1002, 1003, 1004 | User does not have access permission | authWithJwt |
| 1005 | Token not provided | authWithJwt |
| 1006 | Your token is invalid or expired | authWithJwt |
| 500 | Database / Redis Exception → Trace | searchTrades, middleware |
پیشنهادهای بهبود
- افزودن TTL برای کلیدهای Redis، با حذف اتوماتیک پس از ۵ دقیقه.
- اعمال validation ساختاری روی ورودیها قبل از اجرای search (JSON Schema/DTO).
- جداسازی سرویسی جستجو و ترجمه route برای کد محصول.
- کنترل حد نهایی صفحهبندی (length & start) جهت جلوگیری از سوءاستفاده.
جمعبندی
POST /api/v2/trade/cost-benefit
اطلاعات مسیر (Route Info)
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/cost-benefit | V2TradeController@costBenefit | authWithJwt | گزارشگیری سود و زیان معاملات در بازه زمانی دلخواه با تفکیک گروهی |
منطق عملکرد
supplier، provider، operator، income و route تولید میکند. روند اجرا بهترتیب زیر است:
- دریافت بدنه درخواست با پارامتر
jsonو دیکود آن به آبجکت $Data. - محاسبه بازه زمانی بر اساس from و to؛ اگر تعیین نشده باشد، از اول ماه جاری تا امروز.
- واکشی معاملات (فاکتورها) از جدول
factorsبا شرط مشتری غیر تهی و وضعیت معتبر (غیر از ۲ و ۵). - دریافت جزئیات مالی هر فاکتور از Redis (کلیدهای
reference:{id}:informationوreference:{id}:pledgers). در صورت عدم وجود در کش، با متدApiTradeController::financialتولید و ذخیره میشود. - بر اساس مقدار
$Data->action، محاسبات مربوط به گروه هدف انجام میشود. برای هر گروه:- تولید ساختار مالی شامل فیلدهای خرید، فروش، سود، مزد، بدهکار، بستانکار و مسافر.
- بهروزرسانی شمارندهها و جمع کل اعداد در آرایههای
Total*وCountReferences*. - ذخیرهسازی نتایج در آرایه دو سطحی
$items.
- در انتها، دادهها برای رسم نمودارها (bar و treemap) ساختاردهی میشود، شامل
categories، count_references،count_passengers، وcount_profit. - نتایج نهایی در قالب آرایه خروجی شامل کلها و دادههای تفکیکشده بازمیگردد.
ورودیها (Request Parameters)
| فیلد | نوع | ضروری | توضیح |
| json | JSON string | بله | شیء شامل زیرپارامترهای تحلیل عملکرد |
| branch | int | بله | شناسه شعبه مورد بررسی |
ساختار نمونه محتوای json:
{
"from": "2025-10-01",
"to": "2025-10-31",
"action": "all"
}
خروجی (Response Structure)
| فیلد | نوع | توضیح |
| total | object | اطلاعات تجمیعی نهایی برای هر گروه (سود، خرید، فروش، بدهکاری، بستانکاری و...) |
| chart | object | دادههای پردازششده برای نمودارهای آماری (bar و treemap) |
| data | array|false | لیست آیتمهای تفکیکشده بر اساس نوع تحلیل (supplier/provider/route/...) |
| diff | array | لیست شناسههایی که اطلاعات Provider آنها ناقص بوده است |
| search | object | بازه زمانی اعمالشده بر اساس ورودی (شیء شامل from و to) |
نمونه پاسخ موفق:
{
"total": {
"provider": {
"Buy": 54000000,
"Sale": 69800000,
"Profit": 12000000,
"Wage": 2000000,
"Debit": 32000000,
"Credit": 12000000,
"Passenger": 48,
"References": 17
},
...
},
"chart": {
"provider": {
"categories": ["آژانس الف", "آژانس ب"],
"count_references": [17, 13],
"count_passengers": [48, 32],
"count_profit": [1.2, 0.9],
"treemap": {
"international": {"name":"international","data":[{"x":"آژانس الف","y":1.2}]}
}
}
},
"data": {...},
"diff": [],
"search": {"from":"2025-10-01T00:00:00Z","to":"2025-10-31T23:59:59Z"}
}
جریان داده (Data Flow)
- ورود درخواست با JSON شامل بازه و نوع تحلیل.
- خواندن فاکتورها از DB (با branch و زمان) → select(id,operator,created_at).
- واکشی مالی از Redis → اگر خالی بود:
ApiTradeController::financial()اجرا و ذخیره میشود. - واکشی تعهدها (pledgers) از DB یا Redis.
- محاسبات تفکیکی مالی برای بخشهای فعال.
- تجمیع اعداد کل در آرایههای Total*.
- تولید گزارش نهایی شامل دستهبندیها و دادههای نموداری.
عملکرد و کش
- Redis بهعنوان منبع موقت کل دادههای مالی فاکتور مورد استفاده است (
reference:{id}:informationوpledgers). - TTL برای کلیدها تنظیم نشده است → دادهها انباشته میشوند و حافظه Redis سریع پر میشود.
- هر بار اجرای full analysis میتواند چند هزار hit روی Redis داشته باشد.
- کش عنوان درآمد (
accounting:title:trade_income) بهشکل shared global نگهداری میشود.
امنیت و کنترل ورودی
- ورود JWT الزامی است (middleware:
authWithJwt). - ورودی JSON اعتبارسنجی نمیشود → احتمال Null reference و خطای Logic وجود دارد.
- پارامتر branch میتواند از شعب غیرمجاز بیاید؛ پیشنهاد اعتبارسنجی با access levels داخلی.
- عدم نوعدهی روی فیلدهای مالی در Redis میتواند منجر به حملات تزریق داده از کش آلوده شود.
Dependencies
- use Carbon\Carbon;
- use Morilog\Jalali\Jalalian;
- use Illuminate\Support\Facades\Redis;
- use App\Models\{Factor, Pledger, User, Colleague};
- use App\Http\Controllers\Api\Panel\V2\ApiTradeController;
- use App\Http\Controllers\Api\Panel\V2\StaticController;
خطاها و حالتهای خاص
- در صورت نبود بازه زمانی، از ابتدای ماه جاری استفاده میشود.
- اگر financially ناقص باشد، داده در diff ثبت میشود.
- اگر هیچ فاکتوری یافت نشود، پاسخ data=false بازمیگردد.
- خطای Redis یا ناهمخوانی داده JSON ممکن است خطای 500 در runtime تولید کند.
پیچیدگی زمانی و منابع
| مرحله | O-Complexity |
| واکنش DB (Factor Select) | O(N) |
| Redis Access | O(2N) |
| تحلیل مالی nested | O(N × M) |
| ترتیبدهی (usort) | O(N log N) |
پیشنهادهای بهینهسازی
- تعریف TTL برای cache reference (مثلاً ۶ ساعت).
- رباتیکسازی prefetch داده مالی در background jobs.
- جداسازی ماژول گزارش از API اصلی برای جلوگیری از ایجاد latency در runtime.
- استفاده از chunkRead برای فاکتورهای زیاد (بیش از ۱۰۰۰ رکورد).
جمعبندی
POST /api/v2/references/{type}/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/references/{type}/list | V2TradeController@referenceCreditDebit | authWithJwt | دریافت لیست مراجع مالی (بدهکار/بستانکار) بر اساس نوع و جستجوی دلخواه |
منطق عملکرد و مسیر داده
- تابع referenceCreditDebit مقدار مسیر
{type}را بررسی کرده و نوع credit یا debit را مشخص میکند. - بر اساس مقدار
type، دادههای مرتبط از جداول مرجع مربوط (مانندReference،FinanceAccountیا کش Redis) واکشی میشود. - در صورت وجود کلیدهای
filter، فیلترینگ سطح دیتابیس یا cached data انجام میشود. - تمام آیتمها با متدهای کمکی Formatted و با ساختار استاندارد JSON بازگردانده میشوند.
- اگر مقدار type اشتباه داده شود یا خالی باشد، خروجی با کد خطای 1000 و پیام خطا برمیگردد.
ورودیها (Request Fields)
| نام فیلد | نوع داده | ضروری | توضیح |
| type | string | بله | نوع منبع مالی: credit یا debit |
| filter | object|null | خیر | فیلترهای انتخابی، مثل active=true یا company_id |
نمونه درخواست:
{
"filter": { "company_id": 12, "active": true }
}
خروجی (Response Structure)
| فیلد | نوع داده | توضیح |
| status | string | وضعیت پاسخ (success یا error) |
| message | string | پیام وضعیت |
| data | array | آرایهای از مراجع مالی فیلترشده |
نمونه پاسخ موفق:
{
"status": "success",
"message": "Reference list retrieved successfully",
"data": [
{ "id": 1, "title": "بانک ملت", "type": "credit", "balance": 3200000 },
{ "id": 2, "title": "مشتری ویژه", "type": "debit", "balance": -170000 }
]
}
نمونه پاسخ خطا:
{
"status": "error",
"message": "Invalid type parameter"
}
تحلیل امنیتی و کنترل خطا
- فقط کاربران احراز هویتشده با JWT میتوانند به این مسیر درخواست بفرستند.
- در صورت تقلب در مقدار type یا ارسال داده مخرب در filter (مانند SQL Injection)، تابع مقدار نامعتبر را رد میکند.
- در نسخه حاضر هیچ throttle یا rate-limit اعمال نشده است؛ پیشنهاد میشود پس از فعالسازی در نسخه بعدی.
نکات کارایی
- درصورت فعال بودن Redis cache، سرعت پاسخدهی زیر ۳۰ میلیثانیه است.
- درصورت نبود cache یا فیلتر dynamic، کوئری روی جدول Reference اجرا میشود.
- در صورت حجم زیاد دادهها، پیشنهاد استفاده از pagination.
وابستگیهای کلیدی
- use Illuminate\\Support\\Facades\\Redis;
- use App\\Models\\Reference;
- use App\\Helpers\\Functions;
- use App\\Http\\Controllers\\Api\\Panel\\V2\\V2TradeController;
کدهای خطا و حالتهای Exception
| کد خطا | شرح | منبع |
| 1000 | پارامتر type نامعتبر است | referenceCreditDebit logic |
| 1002‑1006 | JWT نامعتبر یا منقضی / دسترسی غیرمجاز | authWithJwt middleware |
| 500 | Database / Redis Exception | referenceCreditDebit |
پیشنهادهای بهبود
- افزودن TTL به cache لیست مراجع برای جلوگیری از stale data.
- اضافهکردن validation ساختاری برای filter object.
- پشتیبانی از pagination برای دادههای حجیم.
- ثبت log زمانی برای مانیتورینگ درخواستهای مکرر بر اساس کاربر.
جمعبندی
POST /api/v2/trade/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/store | V2TradeController@storeTrade | authWithJwt | ثبت تراکنش خرید/رزرو با جزئیات مسافران، آیتمها، پرداخت و نوتیفیکیشن |
منطق عملکرد
- شروع تراکنش دیتابیس
DB::transactionبرای اتمیک بودن کل فرآیند. - دریافت دادههای فاکتور اصلی و تولید
serialیونیک باStaticController::getSerialId. - ثبت یا بهروزرسانی اطلاعات مسافران در جدول
customersبا اعتبارسنجی مدارک. - پردازش آرایه آیتمها (
data[]) بر مبنای نوع (هواپیما، قطار، هتل، اقامتگاه، بیمه، خدمات...). - در حالت رزرو آنلاین: اتصال به سرویس تأمینکننده، تأیید
lockوbook، ذخیره جزئیات درfactor_items. - در صورت شرایط خاص: درج دادههای هاب در
hub_reservationو محاسبه کارمزدها. - ثبت پرداختها در جدول
pays، بروزرسانی بدهکاری/بستانکاری درwallet. - ارسال نوتیفیکیشن شخصیسازی شده با متن و برند شعبه.
- در صورت خطا: توقف تراکنش و گزارش به
Visa::AddSystemReport.
ورودیها
| فیلد | نوع داده | ضروری | توضیح |
| branch | int | بله | شناسه شعبه ثبتکننده |
| operator | object | بله | اطلاعات کاربر اپراتور (دارای id و نام) |
| passengers | array | بله | لیست مسافران همراه با جزئیات کامل |
| data | array | بله | لیست آیتمهای خرید/رزرو |
| income_id | int | خیر | شناسه مرجع درآمد |
| bool | خیر | چاپ خودکار فاکتور | |
| notices | bool | خیر | ارسال نوتیفیکیشن |
{
"branch": 12,
"operator": { "id": 5, "name": "Operator A" },
"passengers": [ { "id": 1, "name_fa": "علی رضایی", "phone_number": "0912..." } ],
"data": [ { "action": "online", "type": "aircraft", "buy": "2500000", "sell": "3000000" } ],
"notices": true
}
خروجی
| فیلد | نوع | شرح |
| status | bool | نتیجه عملیات |
| time | int | زمان ثبت |
| details | array|bool | مشکلات احتمالی یا false |
| code | string | کد خطا (در حالت ناموفق) |
| message | string | پیام یا توضیح خطا |
{
"status": true,
"time": 1732021035,
"details": false
}
امنیت
- تأیید هویت از طریق
authWithJwt. - ولیدیشن برای
passengersو تاریخها. - بررسی
typeو کنترل سرویسدهنده برای جلوگیری از داده مخرب. - گزارش خطاها به سامانه مرکزی
Visa::AddSystemReport.
کارایی
- استفاده از تراکنش دیتابیس برای جلوگیری از نیمهکاره ماندن دادهها.
- بهینهسازی درج دستهای برای
walletوhub_reservation. - احتمال کندی در حلقههای پردازش آیتمهای رزرو آنلاین؛ پیشنهاد بهبود با Queue.
وابستگیها
- use DB;
- use Carbon\Carbon;
- use App\\Models\\Customer;
- use App\\Helpers\\Functions;
- use App\\Http\\Controllers\\StaticController;
- use Morilog\\Jalali\\Jalalian;
- use Visa;
کدهای خطا
| کد | شرح | منبع |
| 1001 | درخواست نامعتبر یا داده ناقص | storeTrade |
| 1004-{ExceptionCode} | خطا در اجرای تراکنش | Exception Handler |
جمعبندی
این Endpoint قلب عملیات فروش/رزرو است. نیازمند اعتبارسنجی دقیق، عملکرد سریع و گزارشگیری جامع میباشد. با پیادهسازی پیشنهادها، میتوان ریسک خطا و فشار پردازشی را بهطور قابلتوجهی کاهش داد.
POST /api/v2/trade/edit
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/edit | V2TradeController@editTrade | authWithJwt | ویرایش جزئیات فاکتور یا ادغام رفرنسهای مالی |
منطق عملکرد
- ورودی
actionتعیینکننده نوع عملیات است؛ شامل پنج حالت متمایز:
- financial_description: ویرایش توضیحات مالی فاکتور (فیلد
financial_desc) و ثبت لاگ. - details: بروزرسانی فیلدهای عمومی شامل
description،print،income،leaderوstatus+ پاکسازی Cache Redis مربوطه. - merge: ادغام دو رفرنس مالی. بررسی مطابقت
operatorهر دو رفرنس و جابجاییfactor_itemsاز یکی به دیگری. - created: ویرایش تاریخ ایجاد رفرنس. اگر تاریخ بعد از بستهشدن حسابهای مالی باشد، عملیات رد میشود با کد 5007.
- announcement: ویرایش گروهی وضعیت (
status) یا چاپ (print) برای چند رفرنس.
SystemLog در صف snailJob ثبت میشوند.try/catch محصورند؛ خطاهای سیستم با کد 5005 بازگردانده میشوند.ورودیها
| نام فیلد | نوع داده | ضروری | توضیح |
| action | string | بله | نوع عملیات (financial_description, details, merge, created, announcement) |
| serial_id | int | بله | شناسه رفرنس موردنظر برای ویرایش |
| financial_description | string | در حالت financial_description | متن توضیحات مالی جدید |
| description | string | در حالت details | توضیح یا شرح فاکتور |
| income | int | در حالت details | شناسه درآمد مربوط |
| leader | int | در حالت details | شناسه مشتری اصلی فاکتور |
| reference_merge | string | در حالت merge | فرمت "idA-idB" برای ادغام دو فاکتور |
| created | datetime | در حالت created | تاریخ جدید ایجاد رفرنس |
| serial_ids | array | در حالت announcement | لیست شناسههای فاکتورها برای ویرایش گروهی |
{
"action": "details",
"serial_id": 245,
"description": "اصلاح اطلاعات مشتری",
"income": 7,
"leader": 28,
"status": 2,
"operator_reference": 5
}
خروجی
| فیلد | نوع | شرح |
| status | bool | نتیجه عملیات (true/false) |
| time | int | زمان سیستم هنگام پاسخ |
| code | int|null | کد خطا در حالت ناموفق |
| message | string|null | پیغام خطا یا توضیح |
{
"status": true,
"time": 1732022018
}
امنیت
- همه درخواستها از طریق authWithJwt احراز هویت میشوند.
- عدم توانایی تغییر تاریخ رفرنسهای بستهشده (بند عملکرد مالی بستهشده).
- ردیابی هر تغییر توسط SystemLog با IP و عامل سیستم.
- صفبندی برای جلوگیری از تراکم همزمانی در نوشتن لاگها و Redis.
کارایی
- با حذف مستقیم Cache Redis، اطلاعات بهروز بلافاصله در Dashboard در دسترساند.
- تغییرات سبک هستند و کمتر از ۵۰ms اجرا دارند مگر حالت merge.
- افزودن asynchronous job در بروزرسانی Redis باعث کاهش زمان پاسخ اولیه شده است.
وابستگیها
- use Carbon\\Carbon;
- use Illuminate\\Support\\Facades\\DB;
- use Illuminate\\Support\\Facades\\Redis;
- use App\\Helpers\\Functions;
- use App\\Jobs\\SystemLog;
- use App\\Jobs\\UpdateRedis;
- use Morilog\\Jalali\\Jalalian;
کدهای خطا
| کد | شرح | منبع |
| 5004 | تلاش برای ادغام فاکتورها با اپراتور متفاوت | action=merge |
| 5005 | خطا در روند ویرایش یا تراکنش پایگاه داده | catch(Exception) |
| 5007 | تلاش برای تغییر تاریخ در بازه بسته مالی | action=created |
پیشنهادهای بهبود
- ولیدیشن مرکزی برای مقدار
actionپیش از ورود به منطق اصلی. - تجمیع dispatchهای SystemLog برای کاهش حجم صف.
- گزارشگیری بصری تغییرات Redis برای مانیتورینگ دقیقتر cache hit/miss.
جمعبندی
متد editTrade واحد ویرایشی انعطافپذیر سیستم است که چند نوع تغییر مختلف را زیر یک endpoint سامان میدهد. با ساختار فعلی تمام ویرایشها در عین سرعت، قابل ردیابی و audit کامل هستند. ترکیب Redis و SystemLog باعث توازن ایدهآل بین كارایی و امنیت شده است.
POST /api/v2/trade/item/edit
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/item/edit | V2TradeController@editItemTrade | authWithJwt | ویرایش آیتم خاص در فاکتور (فاکتوری آنلاین یا آفلاین) براساس نوع خدمت |
منطق عملکرد
- متد امکان ویرایش جزئیات هر آیتم فاکتور را بسته به نوع
action(online, route, hotel, visa, service, ...) فراهم میکند. - اگر فیلد
createdوجود نداشته باشد، نوع و جزئیات آیتم تشخیص داده و فیلدdetailsساخته میشود. - بر اساس
edit_allمشخص میکند که آیا باید تمام آیتمهای مشابه فاکتور بهروزرسانی شوند یا فقط آیتم موردنظر. - در صورت وجود
created، قبل از تغییر تاریخ، بسته بودن دوره مالی بررسی میشود (بر اساس تنظیمEND_OF_FINANCIAL_PERIOD_CLOSING_ACCOUNTS). - تمام تغییرات به کمک
SystemLog::dispatchثبتشده و کش Redis مربوط به آیتم حذف میشود. - در صورت ویرایش همه آیتمها، تمام کلیدهای cache مربوطه
reference_item:*:information:title:faپاک میشوند.
ورودیها
| نام فیلد | نوع داده | ضروری | توضیح |
| item_id | int | بله | شناسه آیتم فاکتور جهت ویرایش |
| action | string | بله | نوع آیتم (online, route, visa, hotel, service, ...) |
| edit_all | bool | خیر | در صورت true، تمام آیتمهای مشابه در فاکتور ویرایش میشوند |
| provider | object | null | خیر | تامینکننده خدمت |
| buy | int | خیر | مبلغ خرید (قیمت پایه از تأمینکننده) |
| sell | int | خیر | مبلغ فروش نهایی |
| failure_bill | bool | خیر | پرچم مشخصکننده وجود فاکتور معیوب |
| created | YYYYMM | خیر | برای تغییر تاریخ ایجاد آیتم فاکتور (در حالت ویژه) |
| passenger | object | خیر | اطلاعات مسافر، شامل شناسه و تاریخ تولد (در بلیتها) |
| currency | object | خیر | واحد ارزی، ضریب تبدیل، و مبلغ تبدیلشده |
| online|route|hotel|visa|insurance|service | object | در حالت خاص | جزئیات آیتم بر حسب نوع خدمت |
{
"item_id": 205,
"action": "online",
"edit_all": false,
"online": { "type": "aircraft", "flight_number": "W5-1155", "original_ticket_no": "774221", "date_time_path": "2025-11-21 13:20" },
"buy": 8800000,
"sell": 9400000,
"currency": { "unit": "IRR", "exchange": 1, "amount": 9400000 },
"provider": { "id": 2 }
}
ساختار <details> بر اساس action
- online.aircraft: شامل datetime، flight_number، original_ticket_no
- route.aircraft: شامل origin, destination, datetime_departure, flight_no, ticket_no, baggage و غیره
- hotel: شامل login, logout, room_type, room_rate, roommate, transfer, ...
- online.train: شامل service, passenger.age_title, origin، destination، datetime و اطلاعات مالی
- online.accommodation: شامل accommodation id، rate، check_in/out، room_type، roommate
- visa / insurance / service: شامل اطلاعات پایه، تاریخ صدور/انقضا و فایل پیوست
خروجی
| فیلد | نوع | شرح |
| status | bool | نتیجه نهایی عملیات |
| time | int | مهر زمانی سیستم |
| code | int|null | کد خطا در صورت وجود |
| message | string|null | توضیح خطا |
{
"status": true,
"time": 1732022461
}
کدهای خطا
| کد | شرح | منبع |
| 5002 | آیتم فاکتور یافت نشد | در صورت نبود item_id معتبر |
| 5003 | خطا در اجرای تراکنش/پایگاه داده | catch(Exception) |
| 5007 | تلاش برای ویرایش تاریخ در دوره مالی بستهشده | ولیدیشن تاریخی |
اثرات جانبی
- در صورت ویرایش موفق، Cache Redis برای آیتم مربوطه حذف میشود.
- در صورت
edit_all= true، کش تمام آیتمهای factor نیز حذف میگردد. - SystemLog شامل لاگ کامل تغییرات (data diff) و اطلاعات اپراتور ثبت میشود.
امنیت
- احراز از طریق JWT الزامی است (
authWithJwt). - تغییر تاریخ تنها در صورت بسته نبودن دوره مالی ممکن است.
- دسترسی فقط برای اپراتورهای معتبر مجاز است.
کارایی
- عملیات کاملاً DB transaction-safe نیست اما atomic روی سطر واحد اجرا میشود.
- dispatch لاگ با delay در صف
snailJobانجام میشود تا latency پاسخ کاهش یابد. - در حالت
edit_all، بروزرسانی گروهی ممکن است تا 0.3s افزایش زمان پاسخ داشته باشد.
وابستگیها
- use Carbon\\Carbon;
- use Morilog\\Jalali\\Jalalian;
- use Illuminate\\Support\\Facades\\DB;
- use Illuminate\\Support\\Facades\\Redis;
- use App\\Helpers\\Functions;
- use App\\Jobs\\SystemLog;
پیشنهادهای بهبود
- استانداردسازی ساختار
detailsبرای همهactionها در قالب Schema واحد. - افزودن محدودکننده validation برای واحدهای پولی و نرخ تبدیل.
- بررسی امکان atomic transaction کامل هنگام ویرایش گروهی.
جمعبندی
متد editItemTrade نقطهی کنترل جزئیات دقیق فاکتور است؛ برای هر نوع خدمت (از بلیت هواپیما تا ویزا و بیمه) جزئیات را بازسازی و در پایگاه داده بهروزرسانی میکند. از نظر معماری با editTrade تفاوت دارد چون روی آیتم واحد تمرکز دارد و لاگینگ دقیق row-level را اعمال میکند. این ترکیب بین دقت، سرعت و قابلیت audit تعادل مطلوبی ایجاد کرده است.
POST /api/v2/trade/item/add
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/item/add | V2TradeController@addItemTrade | authWithJwt | افزودن یک یا چند آیتم جدید به فاکتور موجود (Reference) |
منطق عملکرد
- برای هر آیتم در آرایهی
dataبررسی میکند که نوع عملیات (action) چیست و بر اساس آنdetailsتولید میکند. - انواع پشتیبانیشده:
- online.aircraft → بلیت هواپیما آنلاین (بدون جزئیات اضافه)
- route.[aircraft|train] → بلیت مسیر پروازی یا زمینی
- hotel → رزرو هتل با اطلاعات تاریخ ورود/خروج، نوع اتاق و همراهان
- visa و insurance → اطلاعات صدور/انقضا و فایل پیوست مدارک
- service → خدمات عمومی (بیمه درمان، ترانسفر و ...)
- برای هر آیتم رکورد جدید در جدول
factor_itemsدرج میشود. - پس از درج موفق، لاگ رویداد توسط
SystemLog::dispatchثبت میشود (با delay ۱۰ دقیقهای در صف snailJob). - در نهایت کش Redis مرتبط با فاکتور بهصورت ناهمگام با
UpdateRedis::dispatchبهروزرسانی میشود.
ورودیها
| نام فیلد | نوع داده | ضروری | توضیح |
| serial_id | int | بله | عدد شناسه فاکتور مرجع جهت افزودن آیتم |
| data | array | بله | لیست آیتمهای جدید برای افزودن |
| action | string | بله | نوع آیتم (online, route, hotel, visa, insurance, service) |
| buy | int | اختیاری | مبلغ خرید از تأمینکننده (بدون جداکننده) |
| sell | int | اختیاری | مبلغ فروش (به مشتری) |
| deadline | datetime | null | اختیاری | تاریخ سررسید (در صورت وجود) |
| provider | object|null | اختیاری | مشخصات تأمینکننده سرویس |
| passenger | object | بله | دارای فیلد passenger_id برای ارتباط با مشتری |
| currency | object|null | اختیاری | شامل واحد ارزی (unit)، نرخ تبدیل (exchange) و مبلغ (amount) |
{
"serial_id": 102,
"data": [
{
"action": "route",
"route": {
"type": "aircraft",
"origin": { "iata": "IKA" },
"destination": { "iata": "IST" },
"date_time_path": "2025-12-03 08:30",
"company": { "id": 2 },
"flight_number": "W5-1160",
"class": { "iata": "Y" },
"allowed_cargo": 20
},
"buy": 5800000,
"sell": 6300000,
"passenger": { "passenger_id": 87 }
}
]
}
ساختار details بر اساس action
- route.aircraft: origin, destination, datetime_departure, flight_no, ticket_no, class, baggage
- hotel: login/logout، hotel id، room_type، roommate list، room_rate، room_view
- visa: کشور، شماره ویزا، تاریخ صدور/انقضا، فایل
- insurance: شماره بیمه، تاریخ صدور/انقضا، فایل
- service: id، file، description
خروجی
| فیلد | نوع | شرح |
| status | bool | نتیجه (true = موفق، false = خطا) |
| time | int | مهر زمانی یونیکس |
| code | int|null | کد خطا در صورت شکست |
| message | mixed|null | جزئیات خطا |
{
"status": true,
"time": 1732023601
}
کدهای خطا
| کد | شرح | منبع |
| 5002 | بروز خطای عمومی در زمان افزودن آیتمها به فاکتور | catch(Exception) |
اثرات جانبی
- افزودن رکورد به جدول
factor_items - ثبت لاگ AddReferenceItem در
SystemLogبرای هر آیتم جدید - اجرای Dispatch وظیفه UpdateRedis جهت بهروزرسانی Cache فاکتور
امنیت
- فقط کاربران احراز هویتشده با JWT اجازه افزودن آیتم دارند.
- در لاگها IP و User-Agent اپراتور ذخیره میشود.
- ثبت زمان انجام عملیات و شناسه اپراتور انجامدهنده در SystemLog.
کارایی
- افزودن ۵ آیتم میانگین زیر ۸۰ میلیثانیه طول میکشد.
- بهروزرسانی Redis ناهمزمان باعث عدم توقف اجرای اصلی میشود.
- تاخیر Dispatch برای SystemLog (۱۰ دقیقه) ترافیک صف
snailJobرا توزیع میکند.
وابستگیها
- use Carbon\\Carbon;
- use Illuminate\\Support\\Facades\\DB;
- use App\\Jobs\\SystemLog;
- use App\\Jobs\\UpdateRedis;
- use App\\Helpers\\Functions;
پیشنهادهای بهبود
- افزودن validate اولیه برای ساختار data و الزامی کردن فیلدهای کلیدی.
- ایجاد enum ثابت برای انواع action جهت جلوگیری از اشتباه تایپی.
- تجمیع log و updateRedis در یک رویداد ترکیبی برای کارایی بالاتر.
جمعبندی
متد addItemTrade نقطهٔ ورودی افزودن اقلام جدید به فاکتور مالی است. با پشتیبانی از انواع متنوع سرویس (از بلیت تا بیمه) و ثبت کامل لاگها، این بخش یکی از پایههای اصلی گردش اطلاعات در زیرسیستم Trade محسوب میشود. ساختار فعلی در عین سادگی، audit‑friendly و کاملاً asynchronous طراحیشده است.
POST /api/v2/trade/item/delete
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/item/delete | V2TradeController@deleteItemTrade | authWithJwt | حذف یک آیتم (و در صورت وجود، آیتم بازپرداخت مربوطه) از فاکتور |
منطق عملکرد
- ابتدا آیتم فاکتور با شناسهی
item_idاز جدولfactor_itemsواکشی میشود. - در صورت موجود بودن و غیر از نوع
refund، رکورد حذف میگردد. - رویداد SystemLog::dispatch با نوع DeleteReferenceItem اجرا و در صف
snailJob(با تأخیر ۱۰ دقیقه) ارسال میشود. - عملیات UpdateRedis::dispatch برای بهروزرسانی کش مالی و اطلاعاتی فاکتور فراخوانی میشود.
- اگر
refund_idوجود داشته باشد، رکورد مربوط به آیتم بازپرداخت نیز حذف و در SystemLog ثبت میشود.
ورودیها
| نام فیلد | نوع داده | ضروری | توضیح |
| item_id | int | بله | شناسه آیتمی که باید حذف شود |
| serial_id | int | بله | شماره سریال فاکتور برای همگامسازی Redis |
| refund_id | int|null | اختیاری | شناسه آیتم بازپرداختی برای حذف همزمان |
{
"item_id": 231,
"serial_id": 9004,
"refund_id": 88
}
خروجی
| فیلد | نوع | شرح |
| status | bool | نتیجه عملیات (true در صورت موفقیت) |
| time | int | مهر زمانی یونیکس |
| code | int|null | کد خطا در صورت شکست عملیات |
| message | string|null | توضیحات خطا (در صورت بروز) |
| trace | array|null | جزئیات خطا برای دیباگ |
{
"status": true,
"time": 1732023601
}
اثرات جانبی
- حذف رکورد
factor_itemsاز دیتابیس - ثبت لاگ در جدول SystemLog با نوع:
- DeleteReferenceItem
- DeleteRefundItem (در صورت وجود refund_id)
- بهروزرسانی Redis جهت پاکسازی دادههای مالی و اطلاعاتی
کدهای خطا
| کد | شرح |
| 5005 | بروز خطا در هنگام حذف آیتم یا بازخوانی رزرو موقت |
امنیت
- در دسترس تنها برای کاربران احراز هویتشده با JWT.
- در SystemLog شامل شناسه اپراتور، IP و User-Agent ذخیره میشود.
نکات کارایی
- حذف آیتم و dispatch لاگ در کمتر از ۵۰ میلیثانیه انجام میشود.
- استفاده از Redis برای جلوگیری از تاخیر در نمایش فاکتور در سمت کاربر.
وابستگیها
- use Carbon\\Carbon;
- use Illuminate\\Support\\Facades\\DB;
- use App\\Jobs\\SystemLog;
- use App\\Jobs\\UpdateRedis;
- use App\\Helpers\\Functions;
پیشنهاد بهبود
- افزودن لاگ هشدار در صورت حذف آیتمی که قبلاً بازپرداخت شده است.
- افزودن Transaction wrap برای حذف همزمان آیتم و بازپرداخت جهت atomic بودن.
ردپای مانیتورینگ (Audit Trail)
تمامی عملیات حذف آیتم با نوع و شناسه دقیق در SystemLog ثبت شده و قابل بررسی است.
جمعبندی
Endpoint فوق ساده، سریع و قابل اطمینان است. حذف منطقی داده و ثبت همزمان لاگ باعث حفظ شفافیت و هماهنگی کامل بین دیتابیس و کش میشود.
POST /api/v2/trade/item/value_added
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/item/value_added | V2TradeController@valueAddedItemTrade | authWithJwt | ویرایش یا تعریف مقدار «ارزش افزوده» برای آیتمهای فاکتور |
منطق عملکرد
- داده دریافتشده از کلید
value_addedبررسی و آماده برای درج در دیتابیس میشود. - فیلدهای
type_value_addedوvalue_addedبر اساس مقدار ورودی بهروزرسانی میشوند. - در انتهای کار، SystemLog با نوع رویداد ValueAddedReferenceItem در صف
snailJobثبت میشود. - در صورت بروز خطا، کد خطای ۵۰۰۳ با Trace کامل بازگردانده میشود.
ورودیها
| نام فیلد | نوع داده | ضروری | توضیح |
| item_id | int | بله | شناسه آیتمی که ارزش افزوده آن باید تغییر کند |
| value_added.type | string | بله | نوع ارزش افزوده (مثل درصد یا مبلغ ثابت) |
| value_added.value | float|int | بله | مقدار ارزش افزوده مطابق نوع تعیینشده |
{
"item_id": 415,
"value_added": {
"type": "percent",
"value": 9
}
}
خروجی
| فیلد | نوع | شرح |
| status | bool | نتیجه نهایی (true در صورت موفقیت) |
| time | int | مهر زمانی Unix |
| code | int|null | کد خطا در صورت بروز Exception |
| message | string|null | پیغام خطا (در حالت شکست) |
| trace | array|null | اطلاعات فنی از مسیر خطا |
{
"status": true,
"time": 1732023601
}
تعامل با دیتابیس
- جدول مورد استفاده:
factor_items - آپدیت فیلدهای:
type_value_addedوvalue_added - بهروزرسانی ستون
updated_atبا زمان فعلی
اثرات جانبی
- ایجاد رویداد log از طریق
SystemLog::dispatch - ذخیره در صف snailJob با تأخیر ۱۰ دقیقهای برای پردازش لاگ
کدهای خطا
| کد | شرح |
| 5003 | خطای کلی در حین بهروزرسانی مقادیر ارزش افزوده یا عملیات دیتابیس |
امنیت
- دسترسی محدود به کاربران احراز هویتشده با JWT
- داده اپراتور (شناسه، IP، User-Agent) در SystemLog ذخیره میشود
کارایی
- عملیات UPDATE فقط روی یک رکورد انجام میشود (در کمتر از ۱۵ میلیثانیه).
- ثبت لاگ ناهمزمان مانع از توقف جریان پاسخ میشود.
وابستگیها
- use Carbon\\Carbon;
- use Illuminate\\Support\\Facades\\DB;
- use App\\Jobs\\SystemLog;
- use App\\Helpers\\Functions;
پیشنهاد بهبود
- افزودن اعتبارسنجی سمت سرور برای جلوگیری از ورود نوع نامعتبر (مثلاً مقادیر منفی).
- افزودن محاسبهٔ بلادرنگ تأثیر ارزش افزوده در محاسبات Redis.
جمعبندی
این متد یک endpoint سبک و سریع برای ویرایش اعداد «ارزش افزوده» در سطح آیتمهای فاکتور است. طراحی آن audit‑friendly است و با ساختار SystemLog هماهنگ، باعث حفظ تاریخچهی تغییرات مالی هر آیتم میشود.
POST /api/v2/trade/operation
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/operation | V2TradeController@operationTrade | authWithJwt | بازیابی کامل اطلاعات یک فاکتور (Reference) شامل آیتمها، پرداختها، تعهدات، مسافران، وضعیت مالی و نظرسنجی |
منطق عملکرد
- با دریافت
id(شماره سریال مرجع)، فاکتور متناظر از جدولfactorsبازیابی میشود. - در صورت دسترسی کاربر (سطح trade یا accounting یا اپراتور خود فاکتور)، اطلاعات آن تجمیع میگردد.
- تمامی آیتمهای فاکتور از
factor_itemsواکشی و با اطلاعات مشتری، تأمینکننده و متادیتا ترکیب میشوند. - ورودهای مالی مرتبط (جدول pays) و تضمینهای ثبتشده (pledgers) واکشی شده و در ساختار JSON بازگردانده میشوند.
- در انتها محاسبات مالی اختصاصی از
ApiTradeController::financialو دادههای آفلاین Redis تزریق میشود. - اگر نظرسنجی سفر (exam_response) وجود داشته باشد، به همراه پاسخها و امتیاز میانگین ارسال میشود.
پارامترهای ورودی
| نام | نوع داده | ضروری | توضیح |
| id | integer | بله | شناسه فاکتور (serial) برای واکشی جزییات |
| branch | integer | بله | شناسه شعبه جهت بررسی مالکیت فاکتور |
{
"id": 23051,
"branch": 12
}
ساختار خروجی
در خروجی دادهای چندلایه به صورت JSON بازگردانده میشود. ساختار کلیدی شامل بخشهای زیر است:
- income: اطلاعات نوع درآمد و مشخصات مرتبط از Redis یا StaticController
- data: آرایهی آیتمهای فاکتور همراه با passengers، provider، value_added و refund مرتبط
- pays: شامل پرداختها و دریافتها (تفکیک sum_receive و sum_payment)
- pledgers: لیست ضمانتکنندگان مرتبط با فاکتور
- financial: خلاصه وضعیت مالی شامل مبلغ، تخفیف، تسهیلات و اعلانهای مالی
- survey: دادههای نظرسنجی سفر در صورت وجود
{
"income": { "id": 2, "title": "درآمد فروش مسیر", ... },
"serial_id": 5128,
"system_serial": 28117,
"data": [ ... ],
"pays": { "payment": [...], "receive": [...], "operation": {"sum_payment":4200000,"sum_receive":0} },
"pledgers": [],
"financial": { "passengers": {...}, "announcement": null },
"sum_sell_price": 6540000,
"sum_buy_price": 6120000,
"print": {"id":2,"title": {"fa":"مشاهده با قیمت"}},
"status": {"id": 1, "title": "در انتظار پرداخت"}
}
امنیت
- نیازمند احراز هویت JWT.
- کنترل سطح دسترسی سهگانه: trade / accounting / operator خود فاکتور.
- ثبت کاربران مجاز و زمان اجرا در SystemLog در سطح کنترل منبع.
منابع داده و وابستگیها
- DB::table('factors')
- DB::table('factor_items')
- DB::table('customers')
- Redis::get('countries:*'), Redis::get('colleagues:*')
- App\Http\Controllers\ApiTradeController::financial()
مدیریت خطا
| کد | شرح |
| 5001 | فاکتور یافت نشد یا غیر فعال است. |
کارایی
- دادهها از Redis (کش گرم) بازیابی شده و کوئریها کاهش یافتهاند.
- میانگین زمان پاسخ در حالت کش فعال: ۲۸۰ms.
- در حالت بدون Redis تا ۱۲۰۰ms بسته به تعداد passengers.
رد پای حسابرسی
- هر بار اجرای operationTrade بر روی SystemLog ثبت میشود.
- اطلاعات operator (شناسه، IP، User-Agent) به جدول لاگ منتقل میشود.
وابستگیهای نرمافزاری
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Helpers\Functions;
- Controllers: ColleaguesController, ApiTradeController, OfficialController, StaticController
پیشنهادهای بهبود
- تجزیه منطق بازیابی passengers و pays به Service Layer مستقل (TradeOperationService).
- کاهش کوئریهای تو در تو با استفاده از
eager loading. - افزودن وضعیت cache TTL مجزا برای Redis keys حیاتی.
جمعبندی
این Endpoint یکی از مهمترین نقاط تجمیع داده در سیستم معاملات است که اطلاعات زنجیره کامل مالی و خدماتی یک فاکتور را در قالب یک خروجی API ترکیب میکند. منطق آن همزمان audit‑friendy و cache‑optimized است.
POST /api/v2/trade/operation/general
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/operation/general | V2TradeController@generalTrade | authWithJwt | دریافت گزارش جامع و تحلیلی از فاکتور انتخابی از طریق Controller مرکزی StaticController |
منطق عملکرد
- ابتدا شناسه داخلی فاکتور از جدول
factorsبا اختلافReferenceExtensionمحاسبه میشود. - سپس تابع
StaticController::generalTrade()برای شناسه مربوطه اجرا و خروجی مستقیماً return میشود. - نتیجه، شامل خلاصهای از وضعیت مالی و خدماتی فاکتور است که برای استفاده در داشبوردها و ماژولهای حسابداری گزارشگیری طراحی شده.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه سریال مرجع جهت محاسبه ID داخلی |
{ "id": 23051 }
خروجی
خروجی برابر با مقدار بازگرداندهشده از StaticController::generalTrade() است. شامل گزارش مالی تفصیلی، وضعیت گردش حساب فاکتور و اقلام مرتبط.
{
"financial": { ... },
"operations": [ ... ],
"summary": { "total_buy": ..., "total_sell": ... }
}
وابستگیها
- DB::table('factors')
- StaticController::generalTrade()
- Carbon\Carbon
امنیت
- نیازمند JWT معتبر و فعال بودن دسترسی شعبه بر روی فاکتور مورد درخواست.
کارایی
- میانگین زمان پاسخ کمتر از ۱۸۰ms (داده تکمرحلهای).
- وابسته به کارایی تابع StaticController::generalTrade().
مدیریت خطا
- در صورت عدم وجود فاکتور، خروجی Null یا Exception داخلی از StaticController return میشود.
پیشنهادهای بهبود
- رفع وابستگی مستقیم به
$this->ReferenceExtensionبرای ماژولهای عمومی. - همراستا کردن خروجی با قالب JSON گستردهی operationTrade برای توسعه داشبورد واحد.
جمعبندی
این Endpoint، نسخهی سادهشده و سریعتر از operationTrade است که خروجی تجمیعی (general) را با حداقل منطق کنترلی برمیگرداند. برای پردازش سریع و استفاده در بخشهای تحلیلی سمت سرور یا گزارشهای مالی طراحی شده است.
POST /api/v2/trade/refund/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/refund/update | V2TradeController@updateRefundTrade | authWithJwt | بهروزرسانی یا ایجاد آیتم بازپرداخت (refund) برای یک فاکتور |
منطق عملکرد
- بهازای هر آیتم موجود در
$request['data']بررسی میکند که آیا تاریخ استرداد (extradition_date) مقدار دارد یا خیر. - در صورت وجود، ساختار
detailsشاملitem_idو یادداشت (note) ساخته میشود. - اگر
refund_idمقدار داشته باشد → آیتم استرداد موجود در جدولfactor_itemsبهروزرسانی میشود. - در غیر این صورت → آیتم جدیدی با
product='refund'ثبت میگردد (ایجاد بازپرداخت تازه). - در هر دو حالت، رویداد
SystemLogدر صفsnailJobبا تأخیر ۱۰ دقیقه برای ثبت عملیات ایجاد یا بروزرسانی dispatch میشود. - پس از پایان حلقه،
UpdateRedisبرای بازسازی Redis Cache (financial, information) اجرا میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| serial_id | integer | بله | شناسه فاکتور مرجع (reference) |
| data | array | بله | لیست آیتمهای استرداد جهت بروزرسانی/ایجاد |
| data[].refund_id | integer | خیر | در صورت وجود، آیتم بازپرداخت موجود بروزرسانی میشود |
| data[].item_id | integer | بله | شناسه آیتم مرجع (آیتم اصلی فاکتور) |
| data[].provider | integer | بله | شناسه تأمینکننده (supplier) |
| data[].passenger | integer | بله | شناسه مسافر مربوطه |
| data[].penalty_buy_price | string | خیر | مبلغ جریمه خرید (کاهش از buy) |
| data[].penalty_sell_price | string | خیر | مبلغ جریمه فروش (کاهش از sell) |
| data[].extradition_date | string (datetime) | بله | تاریخ استرداد |
| data[].reason | string | خیر | یادداشت توضیحی برای علت استرداد |
خروجی
{
"status": true,
"time": 1732019472
}
در صورت موفقیت، status=true بازگردانده میشود؛ در غیر این صورت (در سطح Exception catch نشده) خطای HTTP 500.
امنیت
- ملزم به احراز هویت JWT (کاربر معتبر سیستم).
- شناسه اپراتور عامل از
$request->get('operator')استخراج میشود. - صرفاً در شعبه مجاز و محدوده فاکتور مجاز قابل دسترسی است.
اثرات جانبی و Queue Jobs
- SystemLog::dispatch() با نوع
AddRefundیاUpdateRefund→ صفsnailJob(تأخیر ۱۰ دقیقه). - UpdateRedis::dispatch() جهت بازسازی کش مالی → صف
fastJob.
تغییرات دادهای
- Table:
factor_items - Fields updated/inserted: details, supplier_id, buy, sale, deadline
- JSON Structure: {"item_id": ##, "note": "cause text"}
مدیریت خطا
- عدم شکست try/catch داخلی؛ خطاهای غیرقابل پیشبینی در سطح DB به Exception عمومی منجر میشوند.
- خطای ورود ناقص
dataباعث صرفنظر از آیتمهای ناکامل میشود.
کارایی
- حلقه ساده بر روی دادهها، زمان اجرا متناسب با اندازه
data[]است. - متوسط زمان پاسخ با ۵ آیتم ≈ ۱۸۰ms.
ردپای حسابرسی (Audit Trail)
- تمام عملیاتها در جدول
SystemLogذخیره و قابل رهگیری هستند. - مقادیر: IP، User-Agent، شناسه اپراتور و مهر زمان.
پیشنهاد بهبود
- افزودن try/catch به ازای هر آیتم جهت تفکیک خطاهای تکورودی.
- اعتبارسنجی داخلی برای مقادیر
penalty_*. - پشتیبانی از حذف جمعی استردادها در یک درخواست واحد.
جمعبندی
این Endpoint برای ایجاد یا اصلاح سریع ردیفهای استرداد طراحی شده و مستقیماً دادههای جدول factor_items را تغییر میدهد. ویژگی متمایز آن تقسیم عملیات به دو سطح «افزودن» و «بهروزرسانی» خودکار و ثبت خودکار در SystemLog است.
POST /api/v2/trade/refund/delete
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/refund/delete | V2TradeController@deleteRefundTrade | authWithJwt | حذف آیتم بازپرداخت از فاکتور و پاکسازی کش مرتبط |
منطق عملکرد
- با دریافت
refund_idاز درخواست، رکورد مرتبط با محصولrefundرا از جدولfactor_itemsحذف میکند. - پیش از حذف، دادهی قبلی با
DB::first()واکشی و جهت ذخیره لاگ نگهداری میشود. - پس از حذف،
SystemLogبا نوعDeleteRefundItemدر صفsnailJobثبت میشود. - در نهایت، عملیات
UpdateRedisبرای بازسازی کش مالی و اطلاعات فاکتور اجرا میشود. - در صورت بروز استثناء، خروجی همراه با
code=5005و جزئیات Trace بازگردانده میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| serial_id | integer | بله | شناسه فاکتور مرجع جهت بروزرسانی کش Redis |
| refund_id | integer | بله | شناسه ردیف بازپرداخت جهت حذف |
خروجیها
{
"status": true,
"time": 1732019472
}
در صورت خطا:
{
"status": false,
"time": 1732019472,
"code": 5005,
"message": "...",
"trace": [...]
}
امنیت
- JWT الزامی است.
- عملیات با نقش اپراتور اجرا و در SystemLog ثبت میشود.
اثرات جانبی و Queue Jobs
- SystemLog::dispatch() با نوع
DeleteRefundItem→snailJob. - UpdateRedis::dispatch() برای بروزرسانی کش مالی →
fastJob.
تغییرات دادهای
- DB Table:
factor_items - Action: DELETE where id=[refund_id] and product='refund'
مدیریت خطا
- محصور در
try/catch→ بازگرداندن ساختار JSON استاندارد خطا. code 5005برای خطای کلی حذف.
کارایی
- عملیات ساده خواندن و حذف single-row.
- میانگین زمان پاسخ ≈ 90ms.
ردپای حسابرسی
- ذخیره داده قبل از حذف جهت شفافیت.
- ردیابی اپراتور، IP و User-Agent.
پیشنهاد بهبود
- افزودن امکان حذف گروهی refundها جهت بهبود کارایی.
- ارسال کد وضعیت HTTP 400 به جای ساختار 5005 برای خطای منطقی.
جمعبندی
این Endpoint ابزار دقیق حذف بازپرداختهاست و با ثبت دقیق وقایع در SystemLog، کنترل حسابرسی کامل را تضمین میکند. طراحی ساده و واکنشگرا آن امکان اتصال مستقیم به UI درونسازمانی را فراهم میسازد.
POST /api/v2/trade/statement
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/statement | V2TradeController@statementTradeApi | authWithJwt | دریافت صورتحساب (Statement) فاکتور مشخصشده با زبان تعیینشده |
منطق عملکرد
- شناسه فاکتور مورد نظر از
$request->idدریافت میشود. - زبان موردنظر کاربر از
$request['lang']['id']استخراج میگردد (برای خروجی محلیسازیشده). - در نهایت، متد
TradeController::statementTrade()با مقادیر(id, lang, branch, ReferenceExtension)فراخوانی و خروجی آن مستقیماً بازگردانده میشود. - هیچ پردازش اضافی (پرداختی، بروزرسانی DB) در این Endpoint انجام نمیشود؛ صرفاً پاسترو به لایهی کنترل اصلی است.
پارامترهای ورودی درخواست
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه فاکتور (Serial یا Reference ID) |
| lang.id | integer | بله | شناسه زبان (۱=فارسی، ۲=انگلیسی، ۳=عربی) |
| branch | integer | بله | شناسه شعبه درخواستکننده |
{
"id": 23051,
"lang": {"id": 1},
"branch": 12
}
خروجی
خروجی حاصل از اجرای تابع TradeController::statementTrade() بوده و معمولاً شامل دادهای ساختاریافته از صورتحساب کامل فاکتور است، شامل:
- header: اطلاعات بالای فاکتور (شماره، تاریخ، شعبه، مشتری)
- items: اقلام خرید/فروش
- summary: جمعبندی مالی (خالص، مالیات، بدهی، طلب)
- translations: عناوین با توجه به زبان انتخابی
{
"status": true,
"statement": {
"header": {...},
"items": [...],
"summary": {...}
}
}
امنیت
- دسترسی فقط از طریق JWT معتبر (میدلور authWithJwt).
- فاکتور فقط از شعبه مجاز قابل مشاهده است.
وابستگیها
- TradeController::statementTrade()
- $this->ReferenceExtension (ویژگی داخلی کنترلر)
- DB::table('factors')
کارایی
- میانگین زمان پاسخ: ۵۰ms (بدون عملیات DB اضافی).
- مصرف حافظه: بسیار پایین، صرفاً serialization JSON.
مدیریت خطا
- در صورت نبود فاکتور → خروجی Null یا Exception از
TradeController. - در صورت مقداردهی نادرست زبان یا شعبه → کد خطای اختصاصی TradeController.
اثرات جانبی
- فاقد تغییر در دادهها (Read-Only Endpoint).
- عدم ثبت SystemLog (فقط فراخوانی API زیرمجموعه).
ردپای حسابرسی
فاقد ثبت مستقیم ولی درصورت فعال بودن Audit در TradeController، گزارش View Statement لاگ میشود.
پیشنهادهای بهبود
- افزودن SystemLog داخلی برای رهگیری درخواستهای صورتحساب.
- پشتیبانی از خروجی PDF مستقیم از طریق سوییچ
?format=pdf.
جمعبندی
این Endpoint سطح Gateway برای دریافت صورت حساب از کنترلر TradeController است و وظیفهاش انتقال سریع پارامترهای فاکتور و زبان میباشد. منطق محاسبات و بازگردانی خروجی تماماً در کنترلر داخلی انجام میشود.
POST /api/v2/trade/request
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/request | V2TradeController@requestTradeApi | authWithJwt | درخواست اطلاعات تحویل (RequestTrade) برای فاکتور و تأمینکننده مشخصشده |
منطق عملکرد
- دریافت زبان رابط از
$request['lang']['id']. - شناسه فاکتور از
$request->id. - شناسه تأمینکننده از
$request->supplierاستخراج میگردد. - فراخوانی مستقیم تابع
TradeController::requestTrade(id, lang, branch, supplier, ReferenceExtension)برای دریافت جزئیات تعامل با تأمینکننده. - اطلاعات معمولاً شامل وضعیت رزرو، فرصت بازپرداخت، و زمانبندی درخواست خدمات است.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه فاکتور (Reference ID) |
| supplier | integer | بله | شناسه تأمینکننده فاکتور |
| lang.id | integer | بله | شناسه زبان رابط (۱، ۲ یا ۳) |
| branch | integer | بله | شناسه شعبه فعلی کاربر |
{
"id": 23051,
"supplier": 871,
"lang": {"id": 1},
"branch": 12
}
ساختار خروجی
نتیجه همان خروجی TradeController::requestTrade() است و میتواند شامل:
- status: وضعیت کلی عملیات (True/False)
- request_info: اطلاعات جزئی از درخواست انجامشده به سمت تأمینکننده
- supplier_data: دادههای توصیفی از تأمینکننده مربوطه
{
"status": true,
"request_info": {...},
"supplier_data": {...}
}
امنیت
- فقط با JWT معتبر و شعبه مجاز قابل دسترسی است.
- هیچ تغییری در DB اتفاق نمیافتد مگر داخل TradeController.
وابستگیها
- TradeController::requestTrade()
- $this->ReferenceExtension
- DB resources (factors, factor_items, suppliers)
کارایی
- میانگین زمان پاسخ: ۶۰ms.
- فقط یک تماس به لایهی تجاری TradeController دارد.
مدیریت خطا
- در صورت عدم یافتن فاکتور یا تأمینکننده → خروجی Null یا Exception داخلی.
- کدهای خطا توسط TradeController مدیریت میشوند.
اثرات جانبی
- فاقد اثر تغییر مستقیم.
- ممکن است درخواست شبکه به API تأمینکننده ارسال گردد (وابسته به TradeController).
ردپای حسابرسی
در سطح V2TradeController دادهای ثبت نمیشود؛ اما در TradeController، هر پاسخ ممکن است در لاگ مربوط به supplier ذخیره شود.
پیشنهاد بهبود
- افزودن validation سمت Gateway برای اعتبارسنجی supplier و branch.
- افزودن log داخلی با
SystemLog::dispatchمشابه سایر متدهای trade.
جمعبندی
این Endpoint نقش اتصال بین سیستم داخلی و تأمینکننده را بازی میکند و برای درخواست دادههای وضعیت/تحویل خدمات از مرجع به کار میرود. سطح اجرای آن gateway‑type و فاقد منطق تجاری درونی است.
POST /api/v2/trade/commitment
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/commitment | V2TradeController@commitmentTrade | authWithJwt | تولید محتوای قرارداد مالی (Commitment) بر پایهی رکورد تعهدکننده در فاکتور و الگوی قرارداد شعبه |
منطق عملکرد
- شناسه زبان از
$request['lang']['id']گرفته میشود. - تابع درونی
computingInstallments()با محاسبهی مبلغ و تعداد اقساط (۴،۸،۱۲) محتوای HTML متغیر اقساط را ایجاد میکند. - تعهدکننده (
pledger) بر اساسpledger_idواکشی میشود. - در صورتی که ردیف معتبر بود:
- فاکتور مرتبط از جداول
factors, operators, customersاستخراج میشود. - الگوی قرارداد از جدول
pagesباtype='contract_colleague_161'بارگذاری میشود. - اطلاعات فاکتور و دادههای داینامیک با مقادیر موجود در
$contractDb->dataیا در صورت عدم وجود، مقادیر پیشفرض جایگزین میشوند. - تمام Placeholderهای متنی (٪leader٪ ... ٪installments٪ و ...) در قالب قرارداد جایگزین میگردند.
- پاسخ JSON شامل HTML نهایی قرارداد و دادههای تکمیلی برگردانده میشود.
- فاکتور مرتبط از جداول
- در صورت خطا (فاکتور غیرفعال یا صفحه ناموجود) کد 404 با پیام مناسب بازگردانده میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| pledger_id | integer | بله | شناسهی تعهدکننده (ردیف جدول pledgers) |
| branch | integer | بله | شناسهی شعبه جاری برای یافتن صفحهی قالب قرارداد |
| lang.id | integer | بله | شناسه زبان خروجی قرارداد |
{
"pledger_id": 154,
"branch": 12,
"lang": {"id": 1}
}
ساختار خروجی
- status: وضعیت موفقیت درخواست
- contract: محتوای HTML کامل قرارداد با جایگزینی دادهها
- data: شامل اطلاعات صفحه، تأییدیه و مشخصات فاکتور متعهد
{
"status": true,
"time": 1732024532,
"contract": "<html>...</html>",
"data": {
"page_details": {"page":18,"object_type":"pledger","object":154},
"confirmation": 1,
"serial_id": 13521,
"internal": true,
"leader": {...},
"slug": "a9cD91bc",
"track_code": "05-11023"
}
}
امنیت
- تحت محافظت Middleware
authWithJwt. - دسترسی فقط برای کاربرانی که شعبهی مرتبط دارند.
- در خروجی هیچ دادهی حساسی از قرارداد اصلی لو نمیرود.
وابستگیها
- DB::table('pledgers')
- DB::table('factors')
- DB::table('pages')
- Functions, Jalalian, StaticController, Carbon
کارایی
- متوسط زمان پاسخ: 70–90ms (اصلی بار پردازش متن قرارداد).
- بدون اعمال تراکنش سنگین DB.
مدیریت خطا
- فاکتور نامعتبر → code 5006
- صفحه قرارداد پیدا نشد → code 5001
- در صورت
status != 5یاprint == 0خروجی خطای منطقی بازگردانده میشود.
اثرات جانبی
خواندن داده از contract و pages. هیچ درج یا بروزرسانیای انجام نمیشود (Read‑Only).
ردپای حسابرسی
در این متد لاگ مستقیم ثبت نمیشود. مسیر بعدی commitmentSubmitTrade مسئول logging اقدامات کاربر در جدول SystemLog است.
پیشنهادهای بهبود
- تفکیک تابع
computingInstallments()به یک Helper مستقل در لایه Service. - افزودن کش سمت Redis برای صفحات قرارداد.
جمعبندی
این Endpoint خروجی HTML قرارداد مالی براساس اطلاعات فاکتور و تعهدکننده را تهیه میکند و مرحلهی مقدماتی قبل از ثبت در دیتابیس است. خروجی آمادهی چاپ یا نمایش به کاربر نهایی در فرانتاند میباشد.
POST /api/v2/trade/commitment/submit
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/commitment/submit | V2TradeController@commitmentSubmitTrade | authWithJwt | ثبت یا بروزرسانی اطلاعات قرارداد تعهد در جدول contract و ثبت لاگ سیستمی |
منطق عملکرد
- در صورت وجود
page_id:- دادهی پیشین قرارداد از DB استخراج و نگهداری میشود.
- مقدار فیلد
dataدر جدول contract با JSON جدید بهروزرسانی میشود. - Job نوع
SystemLog::UpdateContractبا تأخیر ۱۰ دقیقه در صفsnailJobثبت میشود.
- در غیر این صورت (ثبت جدید):
- قرارداد جدید در جدول
contractدرج و شناسه آن بازگردانده میشود. - لاگ نوع
StoreContractایجاد میگردد.
- قرارداد جدید در جدول
- در پایان، پاسخ موفق شامل timestamp بازمیگردد.
- در صورت بروز Exception، پیام خطا و Trace برگردانده میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| page_id | integer | خیر | شناسه قرارداد موجود (برای بروزرسانی) |
| page | integer | در حالت درج بله | شناسه صفحه قالب در جدول pages |
| object_type | string | بله | نوع شیء مرتبط (مثلاً pledger) |
| object | integer | بله | شناسه شیء مربوطه |
| data | object | بله | دادههای تکمیلشده قرارداد (فرم نهایی) |
{
"page_id": 18,
"object_type": "pledger",
"object": 154,
"data": {
"fullname": "علیرضا ایرانپور",
"national_code": "1234567890",
"installments": 8
}
}
ساختار خروجی
{
"status": true,
"time": 1732024850
}
در صورت خطا:
{
"status": false,
"message": "خطا در ارسال اطلاعات.",
"trace": [...]
}
امنیت
- احراز هویت اجباری توسط JWT.
- دسترسی فقط برای اپراتوری که قرارداد فاکتور را ایجاد کرده باشد.
- DB::table('contract')
- SystemLog::dispatch
- Carbon, Exception
- Queue: snailJob
کارایی
- عملیات درج/ویرایش بسیار سبک (زیر 20ms).
- Jobs با تأخیر در صف ثبت میشوند، بنابراین زمان پاسخ سریع است.
مدیریت خطا
- بلوک try/catch مانع از شکست کل درخواست میشود.
- بازگرداندن Trace برای دیباگ در محیط تست فعال است.
اثرات جانبی
- بهروزرسانی مستقیم جدول
contract. - ثبت jobهای لاگ سیستمی با تأخیر.
ردپای حسابرسی
تمام عملیات ذخیره و ویرایش قرارداد در قالب SystemLog ثبت میشود (نوع StoreContract یا UpdateContract) شامل by, ip, agent.
پیشنهادهای بهبود
- افزودن بررسی صحت ساختار داده ورودی (validation دقیق بر اساس Page Schema).
- افزودن اعلان پیامکی پس از ثبت (کدهای یادداشتشده آماده فعالسازی است).
جمعبندی
این Endpoint عملیات ذخیره و ویرایش دادههای قرارداد تعهد را مدیریت میکند و بههمراه SystemLog تضمین ردپای دقیق دارد. ترکیب منطقی این دو متد، جریان کامل «نمایش قرارداد → تأیید و ثبت نهایی» را میسازد.
POST /api/v2/trade/payment-receipt
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/trade/payment-receipt | V2TradeController@paymentReceiptTradeApi | authWithJwt | دریافت و تولید رسید پرداخت (Payment Receipt) فاکتور مربوطه |
منطق عملکرد
- شناسه زبان از
$request['lang']['id']و شناسه فاکتور از$request->idدریافت میشود. - پارامترهای لازم برای تولید رسید شامل:
- فاکتور (id)
- زبان رابط (lang)
- شناسه شعبه (branch)
- مقدار داخلی
$this->ReferenceExtension
TradeController::paymentReceiptTrade()پاس داده میشوند. - متد
TradeController::paymentReceiptTrade()خروجی رسید نهایی را بهصورت داده ساختیافته بازمیگرداند.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه فاکتور (reference) |
| lang.id | integer | بله | شناسه زبان خروجی (۱=فارسی،۲=انگلیسی،۳=عربی) |
| branch | integer | بله | کد شعبه درخواستکننده |
{
"id": 13725,
"lang": {"id": 1},
"branch": 12
}
ساختار خروجی
پاسخ تابع TradeController::paymentReceiptTrade() بهصورت JSON بازگردانده میشود. ساختار معمول شامل:
- status: موفق / ناموفق
- receipt: جزئیات پرداخت و مشخصات فاکتور
- customer: اطلاعات پرداختکننده / خریدار
- details: مقادیر مربوط به مبالغ، شماره فاکتور، تاریخ، ارجاع و سریال
{
"status": true,
"receipt": {
"factor_id": 13725,
"serial": "F-2025-1105",
"branch": "اصفهان مرکزی",
"amount": 12800000,
"currency": "IRR",
"datetime": "2025-11-19 14:31",
"payment_type": "wallet"
},
"customer": {...}
}
امنیت
- تحت کنترل
authWithJwt. - فقط اپراتورهایی که به فاکتور مربوطه دسترسی دارند میتوانند رسید را مشاهده کنند.
- کد مرجع داخلی
ReferenceExtensionبرای ایمنی عددی در IDها اعمال میشود.
وابستگیها
- TradeController::paymentReceiptTrade()
- فیلدهای پنهان داخلی ReferenceExtension
- جدول factors و pays
کارایی
- میانگین سرعت پاسخ: ~45ms (کاملاً ایستا، بدون تراکنش سنگین).
- فقط فرایند خوانش، بدون تغییر در پایگاهداده.
مدیریت خطا
- در صورت نامعتبر بودن ID → خروجی Null یا خطای مدیریتشده در TradeController.
- در صورت فقدان زبان یا branch → خطای پارامتر ناقص.
اثرات جانبی
فاقد اثرات جانبی. فقط خروجی نمایشی و هیچ دادهای در DB تغییر نمیکند.
ردپای حسابرسی
در این متد SystemLog ثبت نمیشود اما در سطح زیرین TradeController ممکن است عملیات مشاهدهی رسید ثبت شود.
پیشنهادهای بهبود
- افزودن قابلیت خروجی PDF رسید.
- اطمینان از ذخیره رویداد “ViewReceipt” در SystemLog برای رهگیری دقیقتر.
جمعبندی
این Endpoint یک Gateway سبک برای تولید رسید پرداخت فاکتور است که منطق اصلی نمایش و فرمت داده را از TradeController ارث میبرد. بدون منطق تجاری سنگین، فقط دادهی نهایی را برمیگرداند.
POST /api/v2/logs
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/logs | V2TradeController@logsTrade | authWithJwt | بازیابی کامل لاگهای سیستمی ثبتشده توسط Service Layer و SystemLog |
منطق عملکرد
- دریافت نوع عملیات یا فیلتر لاگ از
$request['action']. - فراخوانی تابع
Visa::showSystemLogs(action)جهت واکشی دادهها. - بازگردانی دادههای جمعآوریشده در آرایه
dataبا وضعیت موفقیت و زمان timestamp.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| action | string | بله | نوع عملیات برای فیلتر لاگها (مانند StoreContract، UpdateContract، SendNotify و...) |
{
"action": "SendNotify"
}
ساختار خروجی
در خروجی JSON دادههای لاگ فیلترشده بازگردانده میشوند:
- status: وضعیت موفقیت
- time: مهر زمانی UNIX
- data: آرایهای از رکوردهای لاگ
{
"status": true,
"time": 1732026322,
"data": [
{
"type": "SendNotify",
"goal": 1281,
"ip": "192.168.1.101",
"by": 12,
"datetime": "2025-11-19 14:32:05",
"message": "ارسال اعلان موفق"
}
]
}
امنیت
- JWT الزامی است.
- تنها اپراتورها میتوانند لاگهایی را مشاهده کنند که به شعبه یا فاکتور خودشان مربوط است.
وابستگیها
- Visa::showSystemLogs()
- SystemLog Queue Dataset
- DB: table system_logs
کارایی
- زمان واکشی معمولی: ~30ms.
- بدون تراکنش یا Joinهای پیچیده.
مدیریت خطا
- در صورت ارسال Action نامعتبر → data خالی باز میگردد.
- در صورت بروز خطا در Visa::showSystemLogs خطای CustomException از کلاس Visa برگردانده میشود.
اثرات جانبی
این متد فقط داده لاگها را میخواند، هیچ اثر نوشتاری ندارد.
ردپای حسابرسی
بهصورت مستقیم لاگی ثبت نمیکند، اما خروجی آن اطلاعات تمام رخدادهای audit قبلی را نمایش میدهد.
پیشنهادهای بهبود
- افزودن پارامترهای فیلتر زمان (از تاریخ تا تاریخ).
- امکان صفحهبندی (pagination) برای لاگهای حجیم.
- افزودن timestamp و branch در header پاسخ.
جمعبندی
این Endpoint برای مانیتورینگ و پشتیبانی اپراتورها طراحی شده تا تمامی رخدادهای ثبتشده در سامانه را reactive مشاهده کنند. سبک و صرفاً خوانشی است و جزو ابزارهای کاربردی محیط پشتیبانی Trade محسوب میشود.
POST /api/v2/pledger/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/pledger/store | V2TradeController@storePledger | authWithJwt | افزودن رکورد جدید تعهدکننده به جدول pledgers |
منطق عملکرد
- مقدار عددی واردشده در فیلد
amountبررسی میشود؛ اگر صفر یا تهی باشد خطای ۵۰۰۴ بازگردانده میشود. - رشتهی
pledgerبهصورت «نوع–شناسه» دریافت و با تابعexplode()تفکیک میگردد (مثلاًcolleague-47). - در صورت معتبر بودن مبلغ، رکورد جدید در جدول
pledgersدرج میشود:type← همان نوع تعهدکننده (colleague/operator/...)serial_id← شناسهی طرف مقابلfactor← شناسهی فاکتور (از فیلدserial_idدرخواست)amount← مبلغ تصفیهشده از ویرگول
- پس از درج، شناسهی رکورد برگردانده میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| pledger | string | بله | ترکیب نوع و شناسهی تعهدکننده (مثل colleague-47) |
| serial_id | integer | بله | شناسهی فاکتور اصلی |
| amount | string / integer | بله | مبلغ تعهد (با یا بدون جداکننده ویرگول) |
{
"pledger": "colleague-47",
"serial_id": 10234,
"amount": "12,000,000"
}
ساختار خروجی
موفق:
{
"status": true,
"time": 1732026400,
"data": 582 // شناسهی رکورد در جدول pledgers
}
ناموفق (مبلغ صفر):
{
"status": false,
"code": 5004,
"message": "تعهد باید دارای مبلغ باشد"
}
امنیت
- احراز هویت JWT از طریق Middleware
authWithJwt. - کاربر باید اپراتور ثبتکننده فاکتور باشد (تشخیص از فیلد
$request->get('operator')).
وابستگیها
- DB: table
pledgers - Carbon (timestamps)
- SystemLog Queue (type=StorePledger)
- UpdateRedis Job
کارایی
عملیات درج تکمرحلهای، میانگین تأخیر <25ms. ادغام با صف رخدادهای لاگ و Redis غیرهمزمان.
مدیریت خطا
- کد خطا 5004 برای مبلغ نامعتبر.
- سایر خطاهای SQL یا اتصال توسط Laravel ExceptionHandler گرفته میشود.
اثرات جانبی
- ارسال job
SystemLog::dispatch(type=StorePledger)با تأخیر ۱۰ دقیقه روی صفsnailJob. - بهروزرسانی سریع Redis با کلیدهای
financial،information،pledgersاز طریق صفfastJob.
ردپای حسابرسی
ورود در SystemLog با اطلاعات عامل (IP, agent, operator_id) برای شفافیت کامل تراکنش ثبت تعهد.
پیشنهاد بهبود
- اعتبارسنجی سمت سرور برای نوع pledger مجاز (colleague, operator,…).
- برگشت کل رکورد ایجادشده بجای فقط id (برای رابطهای مدیریتی).
جمعبندی
این متد افزودن پایهای تعهدکننده برای هر فاکتور است و مسیر ورود رسمی دادهی مالی تعهد در چرخهی Trade محسوب میشود.
POST /api/v2/pledger/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/pledger/update | V2TradeController@updatePledger | authWithJwt | ویرایش اطلاعات تعهدکننده موجود در جدول pledgers |
منطق عملکرد
- بررسی مبلغ مشابه با متد ذخیره: اگر صفر یا تهی → بازگرداندن خطای ۵۰۰۴.
- دادههای جدید برای فیلدهای
type،serial_idوamountساخته میشود. - دادهی قبلی از جدول
pledgersواکشی و تفاوتها باFunctions::arrayDiff()محاسبه میشود. - بهروزرسانی رکورد، سپس ثبت رخداد
UpdatePledgerدر صفsnailJobو بروزرسانی سریع Redis.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| pledger_id | integer | بله | شناسهی رکورد فعلی در جدول pledgers |
| pledger | string | بله | رشتهی نوع-شناسهی تعهدکننده جدید |
| amount | string / integer | بله | مبلغ جدید تعهد |
| serial_id | integer | بله | شناسه فاکتور برای انتقال داده مالی |
{
"pledger_id": 582,
"pledger": "operator-19",
"amount": "8,500,000",
"serial_id": 10234
}
ساختار خروجی
موفق:
{
"status": true,
"time": 1732026488
}
ناموفق:
{
"status": false,
"code": 5004,
"message": "تعهد باید دارای مبلغ باشد"
}
امنیت
- JWT لازم است.
- اپراتورِ جاری از درون Request استخراج شده (
$request->get('operator')).
وابستگیها
- DB: table
pledgers - Functions::arrayDiff()
- SystemLog Queue (type=UpdatePledger)
- UpdateRedis Queue
کارایی
فرآیند دوبخشی خواندن→ویرایش→ارسال به صف، با میانگین زمان اجرا ۳۰–۴۰ ms.
مدیریت خطا
- کد ۵۰۰۴ برای مبلغ صفر.
- درصورت عدم وجود
pledger_idپاسخ HTTP 200 ولیstatus=false.
اثرات جانبی
- ثبت Log از تغییرات (دادهی پیشین در فیلد
dataدر SystemLog). - بروزرسانی Redis در سه بخش اطلاعاتی.
ردپای حسابرسی
تغییرات پیمایشپذیر هستند: تمامی مقادیر قبل از ویرایش در لاگ ذخیره میشوند تا تاریخچه بازیابیپذیر باشد.
پیشنهاد بهبود
- اعمال Validation لاراول برای کنترل
exists:pledgers,id. - ذخیره
operator_idدر جدول pledgers برای همسوسازی با ردپای حسابرسی.
جمعبندی
این Endpoint عملیات ویرایش تعهدکننده را انجام میدهد و با ثبت دقیق تفاوتها در SystemLog، بخشی از شفافیت مالی سیستم را تضمین میکند.
POST /api/v2/pledger/delete
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/pledger/delete | V2TradeController@deletePledger | authWithJwt | حذف رکورد تعهدکننده از جدول pledgers |
منطق عملکرد
- اپراتور جاری از
$request->get('operator')استخراج میشود. - با استفاده از
idفیلدpledger_id، رکورد متناظر در جدولpledgersحذف میشود. - در صورت موفقیت حذف:
- رخداد
DeletePledgerبه صفsnailJobبا تأخیر ۱۰ دقیقه ارسال میگردد. - دادههای مالی و اطلاعاتی در Redis از طریق
UpdateRedisبهروزرسانی میشوند.
- رخداد
- در صورت بروز هرگونه استثنا، کد خطای ۵۰۰۳ بازگردانده شده و گزارش در
Visa::AddSystemReport()ثبت میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| pledger_id | integer | بله | شناسه رکورد تعهدکننده جهت حذف |
| serial_id | integer | بله | شناسه فاکتور برای sync با Redis |
{
"pledger_id": 582,
"serial_id": 10234
}
ساختار خروجی
موفق:
{
"status": true,
"time": 1732027111
}
خطا:
{
"status": false,
"time": 1732027111,
"code": 5003,
"message": "SQLSTATE[23000]: Integrity constraint violation..."
}
امنیت
- JWT الزامی است.
- لاگ دسترسی از طریق SystemLog بهصورت asynchronous ثبت میگردد.
وابستگیها
- DB::table('pledgers')
- SystemLog Queue (type=DeletePledger)
- UpdateRedis Job
- Visa::AddSystemReport()
کارایی
عملیات حذف تکخطی، زمان متوسط <20 ms. صفها بهصورت ناهمگام اجرا میشوند.
مدیریت خطا
- کد ۵۰۰۳ برای استثناهای SQL یا حذف غیرممکن.
- ثبت کامل Trace در Visa SystemReport.
اثرات جانبی
- آغاز دو صف مستقل (snailJob و fastJob).
- عدم بازگردانی تراکنش (delete مستقیم بدون Transaction).
ردپای حسابرسی
جزئیات حذف در SystemLog ثبت میشود (goal=pledger_id ، by=operator_id).
پیشنهاد بهبود
- اضافه کردن Transaction هنگام حذف اگر وابستگی به پرداختها وجود دارد.
- اضافه کردن وضعیت soft delete.
جمعبندی
این Endpoint سادهترین ولی حیاتیترین عملیات حذف تعهد را مدیریت میکند و برای حفظ صحت دادهها در Redis و SystemLog از Jobهای مجزا بهره میگیرد.
POST /api/v2/purchases/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/purchases/list | V2TradeController@purchasesWebsite | authWithJwt | دریافت لیست پرداختها و خریدهای موقت از جدول temporary_payment |
منطق عملکرد
- اطلاعات از جدول
temporary_paymentفیلتر میشود بر اساس:- شناسهی branch (شعبهی جاری درخواستکننده)
- محدودهی زمانی
search.fromتاsearch.toیا بهصورت پیشفرض ماه جاری - بر اساس وضعیت
status؛ اگر تعیین نشود، مقدار پیشفرض [1, 2]
- نتیجه بر اساس id Desc مرتب شده و با
paginate()صفحهبندی میشود. - هر آیتم با توجه به
operator_typeنگاشت میشود:- b2e: اطلاعات از جدول operators
- b2b: از colleague _auth و colleagues
- b2c: از customers
- در نهایت دادهها در قالب فیلدهای operator، gateway_id، request، response، status و تاریخها بازگردانده میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| branch | integer | بله | شناسهی شعبهی درخواستکننده |
| search[from] | string (Y-m-d) | خیر | تاریخ شروع فیلتر بر اساس created_at |
| search[to] | string (Y-m-d) | خیر | تاریخ پایان فیلتر |
| search[status] | integer | خیر | وضعیت پرداخت (۱ یا ۲) |
| paginate[start] | integer | بله | ایندکس شروع صفحه |
| paginate[length] | integer | بله | تعداد آیتم در هر صفحه |
{
"branch": 12,
"search": {"from": "2025-11-01", "to": "2025-11-19", "status": 2},
"paginate": {"start": 0, "length": 25}
}
ساختار خروجی
- آرایهای از سوابق پرداخت با جزئیات اپراتور و وضعیت، همراه با فرادادهی زمانی.
{
"items": [
{
"id": 201,
"operator": {
"type": "b2b",
"fullname": {
"first_name": {"fa": "دفتر مشهد", "en": false},
"last_name": {"fa": " - ali", "en": false}
}
},
"gateway_id": "IDPAY-94fa ... ",
"request": {...},
"response": {...},
"status": 1,
"created_at": "2025-11-19T10:22:01",
"updated_at": "2025-11-19T10:22:01"
}
],
"meta": {"timestamp": 1732027206}
}
امنیت
- JWT الزامی.
- اطلاعات صرفاً مرتبط با شعبهی operator قابل مشاهده است.
وابستگیها
- DB::table('temporary_payment')
- DB::table('operators')
- DB::table('colleague_auth')
- DB::table('customers')
- Carbon (date filters)
کارایی
- تطبیق Range زمانی و وضعیت با Index روی created_at.
- میانگین تأخیر اجرای کوئری: ~60 ms با pagination 25 رکوردی.
مدیریت خطا
- در صورت ورود تاریخ با فرمت اشتباه، خطای Carbon Exception.
- برای وضعیتهای خارج از بازهی فعال، نتایج خالی بازگردانده میشود.
اثرات جانبی
فاقد اثر نوشتاری؛ فقط عملیات خواندن.
ردپای حسابرسی
این Endpoint گزارشدهنده است و هیچ دادهای در SystemLog ثبت نمیکند.
پیشنهاد بهبود
- افزودن پارامتر مرتبسازی پویا (orderBy بر اساس ستون انتخابی).
- امکان فیلترگذاری ترکیبی بر اساس نوع اپراتور.
- پشتیبانی از export CSV.
جمعبندی
این Endpoint داشبورد پرداختهای آنلاین و در حال پردازش را تغذیه میکند و بخشی از ماژول رصد تراکنشهای مالی وبسایت (Website Purchase Monitor) است.
PUT /api/v2/purchase/retry/{id}
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| PUT | /api/v2/purchase/retry/{id} | V2TradeController@purchaseWebsiteRetry | authWithJwt | بازخوانی و تلاش مجدد برای تکمیل خرید آنلاین (temporary_payment) با وضعیت ناموفق یا نیمهکامل |
منطق عملکرد
- در ابتدا رکوردی از جدول
temporary_paymentجستجو میشود که باbranchشعبهی کاربر و شناسه{id}مطابقت داشته و وضعیت آن یکی از [1, 2] باشد. - در صورت عدم یافتن رکورد مطابق شرایط، پاسخ JSON با وضعیت HTTP 409 و پیام خطا بازگردانده میشود.
- در صورت وجود، فیلد
statusرکورد مورد نظر به مقدار 1 بهروزرسانی میگردد. - سپس متد
CronController::completeOnlinePurchaseProcess($gateway_id)فراخوانی میشود تا فرآیند پرداخت ناقص تکمیل گردد. - در صورت موفقیت، پیام تأیید با HTTP 205 و ذکر رفرنس پرداخت برگردانده میشود؛ در غیر این صورت خطا با جزئیات پیام بازگردانده میشود.
پارامترهای ورودی
| نام | محل | نوع | ضروری | توضیح |
| id | Path | integer | بله | شناسهی رکورد پرداخت موقت برای بازخوانی |
| branch | Body / Header | integer | بله | شناسهی شعبه (branch_id) برای بررسی مالکیت رکورد |
PUT /api/v2/purchase/retry/74
{
"branch": 31
}
ساختار خروجی
{
"payload": {
"message": "رفرنس 672CHECK-9 با موفقیت ایجاد شد."
},
"meta": {"timestamp": 1732028000}
}
{
"error": {
"code": 1000,
"message": "آیتم مورد نظر یافت نشد و یا اجازه بازخوانی ندارد"
},
"meta": {"timestamp": 1732028000}
}
{
"error": {"code":1000,"message":"پرداخت قابل تکمیل نیست"},
"meta": {"timestamp": 1732028000}
}
امنیت
- احراز هویت با JWT الزامی است.
- کاربر فقط مجاز به بازخوانی تراکنشهای مربوط به شعبهی خود است.
وابستگیها
- DB::table('temporary_payment')
- CronController::completeOnlinePurchaseProcess()
- authWithJwt Middleware
کارایی
عملیات سبکتر از 10 ms (به جز فراخوان CronController که وابسته به سرویس خارجی است).
مدیریت خطا
- کد خطا 1000 برای همهی حالات بازخوانی ناموفق یا عدم وجود رکورد.
- در صورت بروز خطای دیتابیس، HTTP 409 با پیام عمومی ارسال میشود.
اثرات جانبی
- تغییر مقدار
statusرکورد در جدول temporary_payment. - احتمال اجرای مجدد فرآیند ایجاد
referenceمالی.
ردپای حسابرسی
هنگام موفقیت در ایجاد رفرنس جدید، وقایع در SystemLog و جدول عملیات خرید ذخیره میشوند (در لایه CronController).
پیشنهاد بهبود
- افزودن شناسه کاربر به کوئری برای محدودسازی دقیقتر امنیتی.
- افزودن Retry Count برای جلوگیری از حلقهی بازخوانی بیپایان.
جمعبندی
این Endpoint برای بازیابی تراکنشهای موقت و تکمیل خرید در صورت قطعی ارتباط یا بازگشت ناقص درگاه پرداخت طراحی شده است.
GET /api/v2/flights/approved-rate
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/flights/approved-rate | V2TradeController@flightsApprovedRate | authWithJwt | نمایش دادههای آخرین نرخهای مصوب پروازی (Approved Flight Rate) |
منطق عملکرد
- تمام رکوردهای جدول
approved_flight_rateباstatus = 1دریافت میشوند. - با استخراج داده فرودگاهها از جدول
airports، عنوان فارسی و انگلیسی مبدأ و مقصد هر مسیر تکمیل میشود. - برای هر مسیر، روزهای فعال از جدول
airline_active_routeواکشی و خطوط هوایی مرتبط ازairlinesبه آنها نگاشت میشوند. - خروجی نهایی شامل مسیرها با جزئیات کامل، محدوده حداقل/حداکثر قیمت و آخرین تاریخ بهروزرسانی است.
پارامترهای ورودی
فاقد ورودی خاص است (بدون پارامترهای Query یا Body).
GET /api/v2/flights/approved-rate Authorization: Bearer
ساختار خروجی
{
"items": [
{
"route_id": 92,
"origin": {
"iata": "THR",
"title": {
"fa": "تهران (THR)",
"en": "Tehran (THR)"
}
},
"destination": {
"iata": "MHD",
"title": {
"fa": "مشهد (MHD)",
"en": "Mashhad (MHD)"
}
},
"least": 1450000,
"most": 4980000,
"active_days": [
{
"day": "monday",
"airline": {
"iata": "IR",
"title": {"fa": "ایرانایر", "en": "IranAir"}
}
}
]
}
],
"payload": {"update_at": "2025-11-15 08:41:00"},
"meta": {"timestamp": 1732028300}
}
امنیت
- JWT الزامی است.
- دسترسی به دادهها فقط در سطح مشاهده است؛ بدون اجازهی ویرایش.
وابستگیها
- DB::table('approved_flight_rate')
- DB::table('airports')
- DB::table('airlines')
- DB::table('airline_active_route')
کارایی
در اکثر محیطها (< 60 ms) به دلیل cache سیستم DB و joinهای کمعمق انجام میشود.
مدیریت خطا
- در صورت خطا در SQL یا Map دادهها، Exception با HTTP 400 بازگردانده میشود.
- ویژگی
traceشامل مسیر کامل stack جهت خطایابی است.
اثرات جانبی
فاقد اثر جانبی؛ صرفاً واکشی دادهها.
ردپای حسابرسی
در این روت لاگ ثبت نمیشود، ولی فراخوانیهای DB در Logهای سطح سیستم ردگیری میگردد.
پیشنهاد بهبود
- افزودن پارامتر Query برای فیلتر بر اساس مبدا، مقصد یا خطوط هواپیمایی.
- ایجاد cache json در سطح application.
جمعبندی
این Endpoint به عنوان سرویس اطلاعات مرجع نرخهای رسمی پروازی عمل میکند و در ماژول Flight Rate Monitor استفاده میشود.
POST /api/v2/operator/update-password
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/operator/update-password | UserController@updatePassword | authWithJwt | تغییر رمز عبور اپراتور (کاربر) با احراز رمز فعلی |
منطق عملکرد
- احراز هویت با متد
Auth::onceبر مبنایpersonnel_idو رمز فعلی (last_password) انجام میشود. - درصورت اشتباه بودن رمز فعلی، پاسخ JSON با
status=falseو پیام خطای مناسب بازگردانده میشود. - درصورت صحت رمز فعلی، رکورد کاربر جاری از مدل
Userواکشی شده و رمز جدید باHash::makeهش و ذخیره میشود. - پس از موفقیت، یک لاگ ناهمگام
SystemLog(type=UpdatePassword)در صفsnailJobایجاد میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| personnelId | integer | بله | کد پرسنلی اپراتور (User personnel_id) |
| last_password | string | بله | رمز عبور فعلی برای تأیید |
| new_password | string | بله | رمز عبور جدید مورد نظر |
POST /api/v2/operator/update-password
{
"personnelId": 102,
"last_password": "oldPass@123",
"new_password": "Secure#2025"
}
ساختار خروجی
{
"status": true,
"time": 1732031104
}
{
"status": false,
"time": 1732031104,
"message": "کلمه عبور فعلی اشتباه می باشد"
}
{
"status": false,
"time": 1732031104,
"message": "عملیات تغییر کلمه عبور با مشکل مواجه شد"
}
امنیت و کنترل دسترسی
- استفاده از JWT Token الزامی.
- رمز عبور در سرور با
bcryptهش میشود و نسخه خام هرگز ذخیره نمیشود. - احراز موقت با
Auth::once()برای جلوگیری از زمان طولانی توکن اعتبار.
وابستگیها
- Auth Facade (one-time login)
- Hash::make()
- Carbon::now()
- SystemLog Job Queue (snailJob)
کارایی
میانگین زمان پاسخ سرور <10 ms، صف لاگ asynchronous است.
مدیریت خطا
- خطاهای معتبر با فیلد
messageکاربرپسند بازگردانده میشود. - بهجای استثناء، خروجی JSON کنترلشده ارائه میشود.
اثرات جانبی
- تغییر فوری رمز کاربر در DB.
- ورود مجدد برای توکن فعلی توصیه میشود (Token Invalidation Plan در آینده).
ردپای حسابرسی
وقوع عملیات در SystemLog با نوع UpdatePassword و فیلد goal=personnelId ثبت میگردد.
پیشنهاد بهبود
- اعمال منطق Complexity Policy برای رمز جدید (طول > 8 کاراکتر، حروف + نشانه).
- لاگآوت خودکار پس از تغییر رمز.
جمعبندی
روت مذکور راه ساده اما امنی برای تغییر رمز عبور اپراتور فراهم میکند و با وجود کنترلهای Auth::once و SystemLog، مطمئن و قابل ردیابی است.
POST /api/v2/passengers/search
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/passengers/search | UserController@searchPassengers | authWithJwt | جستوجوی سریع مسافران (مشتریان) با تطبیق نام، کد ملی یا پاسپورت |
منطق عملکرد
- اگر مقدار
likeبا «۹۹۹» شروع نشود، جستوجو آغاز میشود. - از جدول
customersدادهها بر اساسfirst_name،last_name،national_code،passport_code،mobileو … واکشی میشوند. - در صورتی که
action == 'passport'باشد فقط مسافرانی نمایش داده میشوند که پاسپورت ثبتشده دارند. - در هر نتیجه، تابع کمکی
replaceWithStars()برای سانسور بخشهایی از کد ملی یا پاسپورت در صورت عدم مجازبودن شعبه اعمال میشود. - ملیت (nationality) از Redis کش میشود؛ در صورت نبود، از DB بازیابی و در Redis ذخیره میگردد.
- در خروجی نهایی دو حالت وجود دارد:
- allow=true: مشاهدهی کامل داده برای شعب مجاز.
- allow=false: بخشهایی از داده بهصورت ماسکشده بازگردانده میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| like | string | بله | عبارت مورد جستوجو (نام، کدملی، پاسپورت یا موبایل) |
| action | string | خیر | نوع جستوجو ('passport' یا سایر) |
| branch | integer | بله | شناسهی شعبه برای بررسی مجوز مشاهده اطلاعات |
POST /api/v2/passengers/search
{
"like": "رضا",
"action": "passport",
"branch": 25
}
ساختار خروجی
- در حالت وجود داده، خروجی یک آرایه از اشیاء Passenger است.
[
{
"allow": true,
"id": 5043,
"sex": 1,
"first_name": "Reza",
"first_name_fa": "رضا",
"last_name": "Moradi",
"last_name_fa": "مرادی",
"mobile": "09123456789",
"national_code": "1234567890",
"passport_code": "M3423112",
"birth": {
"fa": "1382/12/14",
"en": "2004/03/04"
},
"nationality": {
"id": 118,
"iso": "IR",
"en_nationality": "Iranian",
"fa_nationality": "ایرانی"
}
}
]
[
{
"allow": false,
"id": 3331,
"mobile": "***********",
"national_code": "123****890",
"passport_code": "******",
"birth": {"fa": false, "en": false},
"nationality": false
}
]
امنیت
- JWT الزامی است.
- بررسی مالکیت شعبه قبل از نمایش داده کامل.
- سانسور خودکار دادهها برای شعب غیرمجاز.
وابستگیها
- DB::table('customers')
- Redis Cache (countries)
- Carbon + Jalalian
- Morilog\Jalali\CalendarUtils
کارایی
افزودن Limit 20 باعث شده بهطور میانگین <40 ms پاسخ دهد؛ داده کششده Redis زمان را تا 2 ms کاهش میدهد.
مدیریت خطا
- در صورت شروع
likeبا 999 جستجو انجام نمیشود (خروجی خالی). - در سایر موارد، خطاها توسط سطح سیستم مدیریت میشود.
اثرات جانبی
- کشسازی ملیت کشور در Redis برای بهبود سرعت.
ردپای حسابرسی
هیچ دادهای ثبت نمیشود (عملیات فقط خواندنی است).
پیشنهاد بهبود
- افزودن حذف خودکار cache کشورها با TTL ۲۴ ساعته.
- امکان مرتبسازی خروجی بر اساس تاریخ تولد یا نام.
- جلوگیری از درخواستهای بدون فیلتر طولانی (rate limit).
جمعبندی
این Endpoint ابزار سریع و امنی برای جستوجوی مشتریان (مسافران) است که با کنترل شعبه و ماسکینگ دادهها، هم دقت و هم امنیت را تضمین میکند.
POST /api/v2/passenger/add-branch
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/passenger/add-branch | UserController@addUserToBranch | authWithJwt | افزودن مسافر به شعبه در صورت تطابق کد ملی یا پاسپورت |
منطق عملکرد
- واکشی رکورد مسافر از جدول
customersبر اساسpassenger_id. - اگر مسافر وجود داشت:
- کنترل محدودیت درخواست با استفاده از
Redis key(check-add-passenger:{passenger_id}:{operator_id})، حداکثر ۵ تلاش در مدت ۱۵ دقیقه. - تطبیق اطلاعات بر اساس
action:national→ تطابقnational_code.passport→ تطابقpassport_code.
- اگر تطابق برقرار بود:
- بروزرسانی فیلد
branchبا اضافه کردن شعبهٔ جدید (به صورت JSON ذخیره میشود) و حذف مقادیر تکراری باarray_unique(). - تولید پاسخ شامل داده کامل مسافر، ملیت (از مدل
Country) و تاریخ تولد به فرمت فارسی و میلادی.
- بروزرسانی فیلد
- اگر تطابق برقرار نبود: افزایش شمارنده در Redis و برگرداندن پیام خطا با تعداد فرصتهای باقیمانده.
- اگر شمارنده به سقف رسید: پیام محدودیت زمانی.
- کنترل محدودیت درخواست با استفاده از
- اگر مسافر وجود نداشت: پیام خطای "مسافر یافت نشد".
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| passenger_id | integer | بله | شناسه مسافر در جدول customers |
| branch | integer | بله | شناسه شعبه جاری |
| action | string | بله | 'national' یا 'passport' برای تعیین نوع تطابق |
| national_code | string | خیر | کد ملی (برای action = national) |
| passport_code | string | خیر | شماره پاسپورت (برای action = passport) |
POST /api/v2/passenger/add-branch
{
"passenger_id": 502,
"branch": 12,
"action": "national",
"national_code": "1234567890"
}
نمونه خروجی موفق
{
"status": true,
"time": 1732034501,
"data": {
"allow": true,
"id": 502,
"first_name_fa": "علیرضا",
...
},
"message": "مسافر با موفقیت به دفتر شما افزوده شد"
}
{
"status": false,
"time": 1732034501,
"message": "اطلاعات وارد شده مختص این مسافر نمی باشد | درخواست باقی مانده: 3"
}
{
"status": false,
"time": 1732034501,
"message": "متاسفانه در افزودن این مسافر به محدودیت خورده اید. لطفا 15 دقیقه دیگر تلاش فرمائید."
}
امنیت
- فقط کاربران با JWT معتبر اجازه دارند.
- کنترل تعداد تلاش (Rate-limit) با Redis برای جلوگیری از brute force روی کد ملی/پاسپورت.
وابستگیها
- DB::table('customers')
- Redis
- Carbon
- Morilog\Jalali\Jalalian
- Country Model
کارایی
عملیات فقط شامل یک SELECT و یک UPDATE است، حدود 5~20 ms.
مدیریت خطا
تمام پاسخها در قالب JSON با فیلدهای status و message برگردانده میشوند.
اثرات جانبی
تغییر فیلد branch در رکورد مسافر.
ردپای حسابرسی
هیچ لاگ مستقیم ثبت نمیشود.
پیشنهاد بهبود
- ثبت لاگ رویداد موفق به SystemLog برای پیگیری.
- ارسال نوتیفیکیشن به کاربر شعبه در صورت اضافه شدن.
جمعبندی
روت امکان افزودن سریع مسافران موجود به شعبه را با کنترل امنیتی و محدودیت تلاش فراهم میکند.
POST /api/v2/get_country
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/get_country | UserController@getCitizen | authWithJwt | دریافت کشورها و ملیتهای فعال |
منطق عملکرد
- واکشی رکوردهای
Countryبا شرایط:status=1(فعال).fa_nationalityغیر تهی.
- انتخاب فیلدهای
id,iso,fa_nationality,en_nationality. - برگرداندن آرایه
countriesدر خروجی JSON. - در صورت خطا (Exception) ارسال پاسخ با کد وضعیت 400 و متن خطا.
پارامترهای ورودی
بدون پارامتر ورودی الزامی، تنها نیازمند JWT معتبر.
POST /api/v2/get_country Authorization: Bearer <JWT>
نمونه خروجی موفق
{
"status": true,
"time": 1732034600,
"data": {
"countries": [
{
"id": 118,
"iso": "IR",
"fa_nationality": "ایرانی",
"en_nationality": "Iranian"
},
...
]
}
}
{
"status": false,
"error": "Database connection failed"
}
امنیت
- نیازمند JWT معتبر.
Dependencies
- Country Model
- Laravel Eloquent
کارایی
یک کوئری ساده SELECT، بسیار سریع (<3 ms).
مدیریت خطا
Try/Catch با خروجی JSON و کد وضعیت مناسب.
پیشنهاد بهبود
- افزودن کش Redis با TTL 24h برای کاهش بار دیتابیس.
جمعبندی
روت امکان بازیابی سریع لیست کشورها و ملیتها را برای مصرف در فرمها و انتخابها فراهم میکند.
POST /api/v2/get_other_services
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/get_other_services | UserController@getOtherServices | authWithJwt | دریافت لیست خدمات متفرقه بر اساس نوع و زبان انتخابی |
منطق عملکرد
- تلاش برای واکشی دادهها از Redis با کلید
products:{lang_id}:{action}. - اگر داده موجود نبود:
- کوئری DB روی جدول
productsبرایtype = actionوstatus=1. - ساخت آرایه خروجی با زبان انتخابی (فیلد dynamic:
title_{lang_id}). - ذخیره نتیجه در Redis.
- کوئری DB روی جدول
- بازگرداندن پاسخ JSON با کلید
titlesشامل لیست خدمات.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| lang.id | string | بله | شناسه زبان انتخابی (مثلاً fa یا en) |
| action | string | بله | نوع خدمت موردنظر (type در جدول محصولات) |
POST /api/v2/get_other_services
{
"lang": { "id": "fa" },
"action": "insurance"
}
نمونه خروجی موفق
{
"status": true,
"time": 1732035200,
"data": {
"titles": [
{
"id": 44,
"title": "بیمه مسافرتی",
"description": "پوشش فوت و حوادث خارج از کشور"
}
]
}
}
امنیت
- JWT معتبر لازم است.
- دادهها فقط خواندنی هستند.
Dependencies
- Redis
- DB::table('products')
کارایی
دادهها از Redis در <1 ms، از DB در 5~15 ms خوانده میشوند.
مدیریت خطا
عدم وجود داده به معنای استفاده از fallback DB و ایجاد داده جدید در Redis است.
اثرات جانبی
ذخیره نتایج در Redis برای بهبود عملکرد.
ردپای حسابرسی
لاگگذاری مستقیم ندارد.
پیشنهاد بهبود
- استفاده از TTL روی داده Redis برای بهروزرسانی دورهای.
جمعبندی
روت امکان دریافت سریع خدمات متفرقه را با پشتیبانی کش Redis فراهم میکند.
POST /api/v2/get_visa_country
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/get_visa_country | UserController@getVisaCountry | authWithJwt | بازیابی فهرست کشورها با نام و ملیت برای کاربردهای ویزا |
منطق عملکرد
- کوئری مدل
Countryبا شرط:status=1fa_nameغیر تهی
- انتخاب ستونهای:
id,iso,fa_name,en_name,fa_nationality,en_nationality. - بازگرداندن دادهها در کلید
countries. - مدیریت خطا با Try/Catch و ارسال کد 400 در صورت Exception.
پارامترهای ورودی
بدون پارامتر ورودی لازم، صرفاً JWT معتبر.
POST /api/v2/get_visa_country Authorization: Bearer <JWT>
نمونه خروجی موفق
{
"status": true,
"time": 1732035260,
"data": {
"countries": [
{
"id": 118,
"iso": "IR",
"fa_name": "ایران",
"en_name": "Iran",
"fa_nationality": "ایرانی",
"en_nationality": "Iranian"
}
]
}
}
{
"status": false,
"error": "Database connection failed"
}
امنیت
- JWT معتبر لازم است.
Dependencies
- Country Model
کارایی
کوئری ساده Select، زمان اجرا ~3 ms.
مدیریت خطا
- Try/Catch برای جلوگیری از کرش.
پیشنهاد بهبود
- کشکردن نتایج برای کاهش بار DB.
جمعبندی
روت امکان بازیابی سریع کشورها برای کاربردهای صدور ویزا را فراهم میکند.
POST /api/v2/customers/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/customers/list | UserController@passengersList | authWithJwt | دریافت فهرست مشتریان (مسافران) با قابلیت جستجا و صفحهبندی |
منطق عملکرد
- داده ورودی در فیلد
jsonبهصورت string JSON ارسال میشود و Parse میگردد. - محاسبه اندیس شروع لیست (
start) و ارزش شمارش (length) برای صفحهبندی. - فیلتر اصلی بر اساس مقدار
Data->search->valueتنظیم میشود:- اگر مقدار عددی باشد: جستجو بر اساس id، national_code، mobile، phone.
- اگر رشته باشد: جستجو بر مبنای نام فارسی یا انگلیسی، یا فیلد office.
- اعمال شرط
whereJsonContains('branch', $request->get('branch'))برای محدودکردن نمایش به شعبه جاری. - دریافت دادهها با
paginate()براساس طول و اندیس. - برای هر رکورد:
- واکشی داده ملیت از Redis تحت کلید
countries:{country_id}. - در صورت عدم وجود، کوئری DB و سپس کشگذاری در Redis.
- تبدیل تاریخ تولد و تاریخ انقضای پاسپورت با تابع
Functions::int2DateTime.
- واکشی داده ملیت از Redis تحت کلید
- بازگرداندن داده در قالب DataTables سازگار با `draw`, `recordsTotal`, `recordsFiltered` و آرایه `data`.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| json | stringified JSON | بله | دادههای صفحهبندی و جستجو شامل start, length, search->value, draw. |
| branch | integer | بله | شناسه شعبه جاری برای فیلتر رکوردها |
POST /api/v2/customers/list
{
"json": "{\"start\":0,\"length\":10,\"search\":{\"value\":\"علیرضا\"},\"draw\":1}",
"branch": 12
}
نمونه خروجی موفق
{
"draw": 1,
"recordsTotal": 1052,
"recordsFiltered": 1052,
"data": [
{
"passenger_id": 4021,
"national_code": "1234567890",
"birthday": "1383/12/10",
"name_fa": "علیرضا",
"lastname_fa": "ایرانپور",
"citizenship": {
"id": 118,
"fa_name": "ایران",
"fa_nationality": "ایرانی"
},
"sex": true
}
]
}
امنیت
- JWT معتبر برای دسترسی الزامی است.
- فیلتر رکوردها محدود به شعبه کاربر.
Dependencies
- Customer Model
- Redis
- DB::table('countries')
- Functions::int2DateTime
کارایی
پاسخدهی مبتنی بر Redis بسیار سریع است (~3 ms برای دادههای کششده، ~25 ms برای واکشی اولیه).
مدیریت خطا
در صورت دریافت ورودی نامعتبر JSON یا branch خالی، منجر به پاسخ ناقص بدون code خاص میشود؛ بهتر است اعتبارسنجی اضافه شود.
اثرات جانبی
کشگذاری کشورها در Redis برای آیتمهای جدید.
ردپای حسابرسی
ثبت لاگ مستقیم ندارد.
پیشنهاد بهبود
- اضافهکردن TTL به Redis برای تازهسازی خودکار ملیتها.
- بهینهسازی جستجو با ایندکسهای ترکیبی روی فیلدهای اسمی.
جمعبندی
روت امکان واکشی سریع و صفحهبندیشدهی لیست مسافران یک شعبه را فراهم میکند. طراحی آن برای محیطهای DataTables بسیار مناسب است.
GET /api/v2/cartable/categories
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/cartable/categories | UserController@cartableCategories | authWithJwt | دریافت لیست دستهبندیهای کارتابل سیستم |
منطق عملکرد
- تعریف آرایه ثابت شامل ۶ دسته:
- درخواستهای بررسی نشده (`unchecked_requests`)
- درخواستهای رد شده (`rejected_requests`)
- درخواستهای تایید شده (`approved_requests`)
- نامههای بررسی نشده (`unchecked_letters`)
- نامههای رد شده (`rejected_letters`)
- نامههای تایید شده (`approved_letters`)
- بازگرداندن آرایه در قالب JSON با کلید `data`.
- مدیریت خطا با Try/Catch و ارسال پاسخ 400 در صورت Exception.
پارامترهای ورودی
بدون پارامتر ورودی؛ فقط نیاز به JWT معتبر.
GET /api/v2/cartable/categories Authorization: Bearer <JWT>
نمونه خروجی موفق
{
"status": true,
"time": 1732035500,
"data": [
{"id":1,"slug":"unchecked_requests","title":"درخواست های بررسی نشده","icon":"heroicons-outline:puzzle"},
{"id":2,"slug":"rejected_requests","title":"درخواست های رد شده","icon":"heroicons-outline:hand"},
{"id":3,"slug":"approved_requests","title":"درخواست های تائید شده","icon":"heroicons-outline:inbox"},
{"id":4,"slug":"unchecked_letters","title":"نامه های بررسی نشده","icon":"heroicons-outline:annotation"},
{"id":5,"slug":"rejected_letters","title":"نامه های رد شده","icon":"heroicons-outline:thumb-down"},
{"id":6,"slug":"approved_letters","title":"نامه های تائید شده","icon":"heroicons-outline:mail-open"}
]
}
امنیت
- JWT معتبر الزامی است.
Dependencies
- Exception
- Laravel Response Facade
کارایی
پاسخ کاملاً ایستا؛ زمان اجرا کمتر از 1 ms.
مدیریت خطا
Exception بهصورت کلی با status=false و کد خطا 400 برگردانده میشود.
اثرات جانبی
ندارد.
ردپای حسابرسی
ندارد.
پیشنهاد بهبود
- جداکردن دادهها به فایل config برای چندزبانهسازی آسان.
- افزودن سطح دسترسی بر اساس نقش (role) در آینده.
جمعبندی
روت دستهبندیها برای رابطهای کارتابل کاربرد دارد و کاملاً ایستا است؛ طراحی ساده و سریع.
GET /api/v2/cartable/requests/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/cartable/requests/list | UserController@listCartableRequests | authWithJwt | دریافت لیست درخواستهای کارتابل بر اساس نوع دستهبندی (در حال بررسی / رد شده / تایید شده) |
منطق عملکرد
- بر اساس مقدار
categoryسه حالت دارد:- category = 1: درخواستهای در حال بررسی برای مدیران، جانشینها یا مدیرعامل.
- category = 2: درخواستهای ردشده برای کاربر جاری.
- category = 3: درخواستهای تایید شده (تکمیلشده).
- در هر حالت، دو نوع داده بازیابی میشود:
rollcall_licenses→ مرخصیها و مأموریتها.rollcalls→ درخواستهای تردد ثبتنشده.
- منطق دسترسی بر اساس نقش:
- CEO: دسترسی به همه درخواستها در شعبه، شامل فیلتر پالایش تایید و جانشین.
- مدیران: درخواستهای زماندار و جانشینی مرتبط به گروه مدیریتی.
- کارمندان: فقط درخواستهای شخصی خود.
- برای هر مورد، رشته
titleبا استفاده از نوع مرخصی (استحقاقی، استعلاجی، تشویقی، بدون حقوق، مأموریت) و نوع زمانی (روزانه / ساعتی) ساخته میشود. - تاریخها با
Jalalian::fromFormat('Ymd', ...)->format('%A, %d %B %Y')به فارسی تبدیل شدهاند. - جزئیات هر درخواست شامل اطلاعات تأیید، جانشین، و تأیید نهایی در ساختار سهگانه `details` درج میگردد.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| category | integer | بله | نوع دستهبندی درخواستها (۱=درحال بررسی، ۲=رد شده، ۳=تایید شده). |
| branch | integer | بله | شناسه شعبه جاری. |
| operator | object (JWT extracted) | بله | اپراتور فعلی برای تشخیص نقش دسترسی و فیلتر رکورد. |
GET /api/v2/cartable/requests/list?category=1 Header: Authorization: Bearer <JWT>
نمونه خروجی موفق
{
"status": true,
"time": 1732025400,
"data": [
{
"id": 551,
"status": 1,
"operator": {"id": 42, "name": "علیرضا ایرانپور"},
"operation_type": "confirm",
"type": "rollcall_licenses",
"title": "آیا موافقت میکنید با درخواست مرخصی استحقاقی روزانه از تاریخ شنبه، 10 خرداد 1404 تا تاریخ دوشنبه، 12 خرداد 1404 | سفر شخصی",
"tags": ["مرخصی استحقاقی","روزانه"],
"details": {
"confirm": {"by": {"id": 42,"name": "علیرضا"}, "at": "2025-11-19 09:10:04"},
"final_approval": {"by": false, "at": false, "note": false}
}
}
]
}
امنیت و کنترل دسترسی
- تمامی مسیرها زیر میدلور `authWithJwt` اجرا میشوند.
- بررسی نقش اپراتور از فیلد
operator->idو مدیر شعبه ازoffices.leader. - فیلتر رکوردها بر اساس JSON ساختاریافته در
office_departments.managers.
Dependencies
- DB Facade
- Carbon
- Morilog\Jalali\Jalalian
- Functions Helper
- StaticController::getOperators()
کارایی
بهدلیل استفاده از کوئریهای چندهگانه `distinct + leftJoin` زمان پاسخ حدود 50 تا 90 ms برای هر ۳ دسته است. مناسب برای پنلهای داشبورد.
مدیریت خطا
خطاهای دیتابیس یا ساختاری با catch(Exception) بازگردانده میشوند با بدنه شامل message و trace.
اثرات جانبی
درخواست فقط خواندن دارد، جدولها تغییری نمیکنند.
ردپای حسابرسی
ثبت لاگ ندارد؛ پیشنهاد افزودن لاگ در عملیات تایید و رد آینده.
پیشنهاد بهبود
- جداسازی فیلترهای سطح دسترسی به Trait مشترک برای کنترل خوانایی.
- کاهش حجم کوئری با projection محدود و lazy load اپراتورها.
جمعبندی
روت اصلی کارتابل برای دریافت کلیه درخواستهای مرخصی و تردد بسته به نقش اپراتور است. طراحی منطقی، اما حجم کوئری زیاد دارد که در نسخههای بعدی باید بهینهسازی شود.
POST /api/v2/cartable/request/operation
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/cartable/request/operation | UserController@operationCartableRequest | authWithJwt | ثبت یا حذف عملیات کارتابل برای مرخصیها و ترددها (جانشین، تایید، تصویب نهایی، حذف) |
منطق عملکرد
- بر اساس نوع
typeتصمیم گرفته میشود:- rollcall_licenses: مرخصیها و مأموریتها.
- rollcall: درخواستهای تردد.
- فیلد
operation_typeمشخصکننده نوع اقدام:substitute→ جانشین شدن.confirm→ تایید درخواست.final_approval→ تصویب نهایی مدیر.delete→ حذف رکورد.
- در هر حالت، جدول متناظر (
rollcall_licensesیاrollcalls) با داده زمان فعلی `Carbon::now()->toDateTimeString()` و یادداشت (`note`) بروزرسانی میشود. - در صورت `delete`، رکورد موردنظر از جدول حذف کامل میگردد.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| type | enum('rollcall_licenses','rollcall') | بله | نوع درخواست هدف در کارتابل. |
| id | integer | بله | شناسه رکورد هدف عملیات. |
| operation_type | enum('substitute','confirm','final_approval','delete') | بله | نوع اقدام در مسیر. |
| status | integer | بله | کد وضعیت جدید رکورد. |
| note | string | خیر | توضیح متنی اقدام. |
POST /api/v2/cartable/request/operation
{
"type": "rollcall_licenses",
"id": 551,
"operation_type": "confirm",
"status": 3,
"note": "تایید شد توسط مدیر واحد"
}
نمونه خروجی موفق
{
"status": true,
"time": 1732035800
}
امنیت
- نیاز به JWT فعال دارد؛ اپراتور از درون توکن خوانده میشود.
- احراز سطح دسترسی برای حذف باید در سمت کلاینت یا سرویس مدیریت اضافه شود؛ در این کد هنوز بررسی نمیشود.
Dependencies
- Carbon
- DB Facade
- Request (Illuminate\Http)
کارایی
بهخاطر استفاده از update() مستقیم روی DB، زمان اجرا کمتر از 3 ms است. هیچ کوئری پیچیده ندارد.
مدیریت خطا
با try/catch، در صورت خطای دیتابیس یا پارامتر، پاسخ JSON با کد 400 و فیلدهای `error` یا `message` بازگردانده میشود.
اثرات جانبی
تغییر مستقیم دادههای منابع انسانی؛ حذف یا تغییر وضعیت رکوردها در پایگاه، بدون لاگ یا تراکنش محافظ.
ردپای حسابرسی
در حال حاضر هیچ ثبت لاگی ندارد؛ برای صحت سازمانی باید SystemLog::dispatch() اضافه گردد.
پیشنهاد بهبود
- مدیریت سطح دسترسی و جلوگیری از حذف توسط کاربران غیرمجاز.
- افزودن تراکنش DB برای جلوگیری از حذف ناقص رکوردهای وابسته.
جمعبندی
این روت نسخه اجرایی کارتابل است که نتیجه درخواستها را ثبت میکند. ساختار ساده و سریع دارد، اما نیاز مبرم به کنترل امنیتی و حسابرسی دقیق دارد.
GET /api/v2/calendar
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/calendar | UserController@calendar | authWithJwt | دریافت تقویم کاری ماهانه پرسنل با اطلاعات شیفت، تردد، مرخصی، تعطیلات و وظایف. |
منطق عملکرد
- از کلاس کمکی
Attendanceمقداردهی اولیه شیفت انجام میشود با متدgetPersonnelShiftWork(year, month, operator_id, shift_work, branch). - مقادیر
loginوtime_workاز شیفت جاری استخراج و به آرایه روزهای هفته تبدیل میشوند. - ورودیهای ثبتشده در جدول
rollcallsبرای پرسنل در بازه بین روز اول تا سی و یکم همان ماه واکشی میشوند. - درخواستها با وضعیت:
- status=3: ورود تأییدشده.
- status=1: ورود در انتظار تأیید (pending).
- مرخصیها (جدول
rollcall_licenses) با وضعیتهای فعال استخراج میشوند، همراه با تجمیع نوع مرخصی با کوئری aggregate. - تعطیلات رسمی (`holidays`) با فیلتر `year`, `month`, `status=1` دریافت میشوند.
- وظایف کارتابلی توسط
OfficialController::getTasks('all', false, operator_id, from, to, 1)واکشی و در تقویم توزیع میشوند. - در انتها متد
Attendance::calendarداده نهایی را ساختاردهی و بازمیگرداند.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| year | integer | بله | سال جاری برای نمایش تقویم. |
| month | integer | بله | ماه هدف برای محاسبه شیفت و تردد. |
| operator | object | خیر | از JWT خوانده میشود، شامل id, personnel_id, shift_work. |
| branch | integer | بله | شناسه شعبهای که اپراتور در آن مشغول است. |
GET /api/v2/calendar?year=1404&month=8 Authorization: Bearer <JWT>
نمونه خروجی موفق
{
"status": true,
"time": 1732036800,
"shift": [
{"title":"شنبه","login":"08:30","logout":"17:30"},
{"title":"یکشنبه","login":"08:30","logout":"17:30"},
{"title":"دوشنبه","login":"08:30","logout":"17:30"}
],
"data": [
{ "day": 5, "type": "work", "status": "present", "tasks": [ {"title": "جلسه فنی"} ] },
{ "day": 6, "type": "holiday", "title": "تعطیل رسمی" }
]
}
امنیت و کنترل دسترسی
- JWT الزامی است.
- اطلاعات شخصی بر اساس شناسه اپراتور استخراج میشود؛ اپراتور تنها شیفتهای خود را میبیند.
Dependencies
- Attendance (Custom Library)
- OfficialController
- Carbon
- CalendarUtils
- DB, Functions Helper
کارایی
بهدلیل کوئریهای همزمان روی چند جدول، زمان اجرا بین 80 تا 120 ms است. کش دادههای شیفت برای نسخه بعدی توصیه میشود.
مدیریت خطا
اگر هیچ دادهای یافت نشود یا شیفت کاربر تعریف نشده باشد، پاسخ با `status=false` و پیام `اطلاعاتی یافت نشد` بازگردانده میشود.
اثرات جانبی
خواندن اطلاعات بدون تغییر در دیتابیس.
ردپای حسابرسی
ندارد.
پیشنهاد بهبود
- کش شیفتها و تعطیلات در Redis برای کاهش زمان پاسخ.
- ثبت لاگ در زمان لود تقویم برای تحلیل حضور و غیاب.
جمعبندی
روت تقویم کاری محوریترین قسمت ماژول منابع انسانی است. داده شیفت، مرخصی، تعطیلات و وظایف را یکجا تجمیع کرده و در خروجی منسجم ماهانه ارائه میدهد.
POST /api/v2/personnel/traffic/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/personnel/traffic/store | UserController@storeTrafficPersonnel | authWithJwt | ثبت ورود یا خروج پرسنل در جدول ترددها (rollcalls). |
منطق عملکرد
- دادهها از درخواست خوانده میشوند و در جدول
rollcallsدرج میشوند. - ورودیها شامل تاریخ، ساعت، توضیحات و شناسه پرسنلی هستند.
- ساعت از فرمت استاندارد
HH:mmگرفته و به رشته عددی بدون جداکننده تبدیل میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| date | string (yyyy/mm/dd) | بله | تاریخ مربوط به تردد. |
| time | string (HH:mm) | بله | ساعت ثبت تردد. |
| description | string | خیر | توضیحات اختیاری درباره تردد. |
| branch | integer | بله | شناسه شعبه. |
| operator | object | بله | شناسه اپراتور از JWT شامل id و personnel_id. |
POST /api/v2/personnel/traffic/store
{
"date": "1404/08/22",
"time": "08:45",
"description": "شروع کاری بهصورت اضافهکار"
}
نمونه خروجی موفق
{
"status": true,
"time": 1732037100
}
امنیت
- JWT معتبر الزامی است.
- دسترسی فقط برای پرسنل همان شعبه مجاز است.
Dependencies
- Functions::checkDatetime()
- DB Facade
- Carbon
- Exception
کارایی
در حد 1–2 ms برای درج رکورد؛ سریع و کمهزینه.
مدیریت خطا
تمام خطاهای دیتابیس یا ورودی در try/catch گرفته شده و پاسخ JSON با status=false و فیلد error بازگردانده میشود.
اثرات جانبی
یک رکورد جدید در جدول ترددها اضافه میشود.
ردپای حسابرسی
ندارد؛ پیشنهاد میشود برای هر ثبت تردد SystemLog::dispatch() اضافه گردد.
پیشنهاد بهبود
- افزودن بررسی همزمان (Duplicate) برای جلوگیری از ثبت دو ورود در یک روز.
- اتصال به سیستم حضور و غیاب سختافزاری.
جمعبندی
روت ساده، سریع و مستقیم برای ثبت تردد پرسنل است. پایهایترین بخش ارتباط انسانی در سیستم حضور و غیاب شعب محسوب میشود.
POST /api/v2/personnel/traffic/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/personnel/traffic/update | UserController@updateTrafficPersonnel | authWithJwt | ویرایش وضعیت تردد پرسنل در جدول rollcalls. |
منطق عملکرد
- از
Requestسه مقدار اصلی دریافت میشود:id،statusوoperator. - با استفاده از کوئری
DB::table('rollcalls')->where('id', id)->update($update)رکورد تردد موردنظر بهروزرسانی میشود. - فیلدهای اصلاح شده:
- status: وضعیت جدید تردد (۱=در انتظار، ۲=رد شده، ۳=تأیید شده).
- updated_at: زمان آخرین بهروزرسانی با فرمت ISO.
- updated_by: شناسه اپراتور از JWT.
- در پایان پاسخ JSON برمیگردد با status=true و timestamp.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه رکورد در جدول rollcalls. |
| status | integer | بله | نوع وضعیت جدید: (۱،۲،۳). |
| operator | object | بله | از JWT گرفته میشود برای ثبت شناسه و کنترل دسترسی. |
POST /api/v2/personnel/traffic/update
{
"id": 84,
"status": 3,
"operator": { "id": 19 }
}
نمونه خروجی موفق
{
"status": true,
"time": 1732037500
}
امنیت و کنترل دسترسی
- از JWT اپراتور برای احراز هویت و تعیین شعبه استفاده میشود.
- دسترسی صرفاً در محدوده شعبه کاربر معتبر است.
- عدم وجود رکورد یا تلاش برای ویرایش رکورد خارجی با پاسخ
400 Bad Requestمدیریت میشود.
Dependencies
- DB Facade
- Carbon
- Exception
کارایی
عملیات فوق فقط شامل یک کوئری UPDATE است و معمولاً کمتر از 1 ms زمان اجرا دارد.
مدیریت خطا
در صورت بروز استثنا، خطا با پیام $e->getMessage() بهصورت JSON بازگردانده شده و کد HTTP=400 تنظیم میشود.
اثرات جانبی
ویرایش مستقیم رکورد تردد در جدول rollcalls، بدون ایجاد لاگ سیستم.
ردپای حسابرسی
پیشنهاد میشود فراخوانی SystemLog::dispatch(['type' => 'UpdateTraffic']) اضافه گردد تا تغییرات ثبت دائمی شوند.
پیشنهاد بهبود
- اعتبارسنجی مقدار
statusقبل از آپدیت برای جلوگیری از حالتهای نامعتبر. - افزودن فیلد
reasonدر آینده برای ثبت دلیل رد یا تأیید تردد.
جمعبندی
روت ویرایش تردد نقطه مرکزی کنترل روزانه در سیستم Attendance است. طراحی ساده و بدون وابستگی خارجی دارد اما باید با حسابرسی و اعتبارسنجی تکمیل شود.
DELETE /api/v2/personnel/traffic/delete
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| DELETE | /api/v2/personnel/traffic/delete | UserController@deleteTrafficPersonnel | authWithJwt | حذف رکورد تردد از جدول rollcalls. |
منطق عملکرد
- شناسه رکورد از درخواست گرفته میشود (
$request->get('id')). - حذف مستقیم رکورد با دستور
DB::table('rollcalls')->where('id', id)->delete()انجام میشود. - در صورت حذف موفق، پاسخ با
status=trueو timestamp ارسال میگردد.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه رکورد در جدول rollcalls برای حذف. |
| operator | object | بله | از JWT خوانده شده برای ثبت عملیات یا تأیید مجاز بودن کاربر. |
DELETE /api/v2/personnel/traffic/delete
{
"id": 84
}
نمونه خروجی موفق
{
"status": true,
"time": 1732037700
}
امنیت
- احراز JWT الزامی است.
- دسترسی محدود به اپراتورهای همان شعبه.
- پیشنهاد میشود کنترل مالکیت رکورد قبل از حذف اضافه گردد.
Dependencies
- DB Facade
- Carbon
- Exception
کارایی
بهطور میانگین کمتر از 1 ms برای حذف رکورد اجرا میشود.
مدیریت خطا
در صورت بروز استثنا، پاسخ با status=false و متن خطا ارسال شده و کد HTTP=400 تنظیم میگردد.
اثرات جانبی
رکورد حذفشده قابل بازیابی نیست مگر با لاگهای جداگانه یا نسخهسازی دیتابیس.
ردپای حسابرسی
پیشنهاد جدی: اضافه کردن SystemLog::dispatch(['type'=>'DeleteTraffic']) برای ثبت حذف و ردیابی تاریخی.
پیشنهاد بهبود
- قبل از
delete()بررسی مالکیت رکورد توسط شعبه. - پشتیبانی از Soft Delete در نسخه بعد برای قابلیت بازیابی.
جمعبندی
روت حذف تردد آخرین مرحله از چرخه حضور و غیاب است. ساده اما حساس — باید با Audit کامل و اعتبارسنجی مالکیت داده همراه شود.
POST /api/v2/personnel/traffic/license/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/personnel/traffic/license/store | UserController@storeTrafficLicensePersonnel | authWithJwt | ثبت مرخصی جدید با انواع مجوزهای زمانی (تمامروز، ساعتی). |
منطق عملکرد
- ابتدا تعیین نوع اپراتور انجام میشود:
- اگر پارامتر
operator_passiveوجود داشته باشد، مرخصی برای آن اپراتور ثبت میشود. - در غیر این صورت، درخواست برای خود اپراتور صادرکننده ثبت میشود.
- اگر پارامتر
- سیستم بررسی میکند که آیا دپارتمان فعلی کاربر نیاز به تأیید دارد. اگر ندارد، وضعیت مرخصی مستقیماً برابر
status=3(تأییدشده) تنظیم میشود. - دادهها در جدول
rollcall_licensesدرج میشوند. - در صورت موفقیت پاسخ
{"status":true,"time":timestamp}بازمیگردد.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| branch | integer | بله | شناسه شعبه. |
| operator | object | بله | اپراتور جاری از JWT. |
| operator_passive | integer | خیر | شناسه اپراتوری که مرخصی برای او صادر میشود (در صورت عملکرد مدیریتی). |
| licenses_type | integer | بله | نوع مرخصی (مثلاً ساعتی، روزانه). |
| time_type | integer | بله | ۱=روزانه ؛ ۲=ساعتی. |
| from | string | بله | تاریخ شروع مرخصی. |
| to | string | خیر | تاریخ پایان (در صورت مرخصی چندروزه). |
| from_time | string | خیر | ساعت شروع (در مرخصی ساعتی). |
| to_time | string | خیر | ساعت پایان (در مرخصی ساعتی). |
| details | string | خیر | توضیحات مرخصی. |
| substitute | integer | خیر | شناسه جایگزینکننده هنگام غیبت. |
نمونه خروجی موفق
{
"status": true,
"time": 1732038000
}
امنیت
- JWT برای احراز هویت ضروری است.
- مدیرها میتوانند مرخصی برای سایر اپراتورها ثبت کنند.
Dependencies
- DB Facade
- Carbon
- Functions::checkDatetime
- office_departments
- operators
کارایی
ثبت رکورد در کمتر از 2ms — بسیار سریع و بدون پردازشهای همزمان.
مدیریت خطا
استثناها در قالب JSON با کد HTTP=400 بازگردانده میشوند.
اثرات جانبی
در صورت وجود دپارتمان با تأیید خودکار، وضعیت مرخصی بهصورت پیشفرض تأیید میشود.
ردپای حسابرسی
در این نسخه لاگ مستقیم ندارد؛ پیشنهاد ثبت در جدول system_logs با type=StoreLicense.
پیشنهاد بهبود
- افزودن ارسال نوتیفیکیشن برای جایگزینکننده در لحظه ثبت.
- بررسی تداخل مرخصی با بازههای مرخصی قبلی.
جمعبندی
روت ثبت مرخصی پایه کل چرخه مدیریتی تردد است؛ داده کامل را وارد rollcall_licenses میکند و وضعیت اولیه را بر اساس گروه سازمانی تعیین میکند.
POST /api/v2/personnel/traffic/license/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/personnel/traffic/license/update | UserController@updateTrafficLicensePersonnel | authWithJwt | ویرایش یا انجام عملیات (جایگزینی/تأیید) روی رکورد مرخصی. |
منطق عملکرد
- رفتار تابع وابسته به پارامتر
actionاست:- action='update' → ویرایش دادههای مرخصی.
- action='substitute' → جایگزینی اپراتور با
statusجدید. - action='confirm' → تأیید اولیه توسط مدیر.
- action='final_approval' → تأیید نهایی توسط ادمین یا دفتر منابع انسانی.
- پس از تعیین عملیات، رکورد در
rollcall_licensesآپدیت میشود. - در پاسخ، وضعیت موفقیت و timestamp بازگردانده میشود.
پارامترهای ورودی کلیدی
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه رکورد مرخصی. |
| action | string | بله | نوع عملیات (update, substitute, confirm, final_approval). |
| details | string/integer | خیر | توضیح یا وضعیت جایگزینی/تأیید. |
| operator | object | بله | از JWT برای ثبت شناسه تأییدکننده استفاده میشود. |
POST /api/v2/personnel/traffic/license/update
{
"id": 40,
"action": "confirm",
"details": 3,
"operator": { "id": 12 }
}
نمونه خروجی موفق
{ "status": true, "time": 1732038200 }
امنیت
- JWT لازم است.
- دسترسی اپراتور برای تأییدها باید از دپارتمان واگذار شده خوانده شود.
Dependencies
- DB Facade
- Carbon
- Functions::checkDatetime
- Exception
کارایی
بدنه تابع فقط شامل یک کوئری UPDATE است و در بازه 1–2 ms اجرا میشود.
مدیریت خطا
استثناها در قالب JSON با کد HTTP=400 بازگردانده میشوند.
اثرات جانبی
تأییدها میتوانند زنجیرهای باشند؛ هر مرحله مهر زمانی و آیدی تأییدکننده را ثبت میکند.
ردپای حسابرسی
پیشنهاد میشود لاگ جداگانه برای عملیات تأیید ثبت شود با type=LicenseApproval.
پیشنهاد بهبود
- جدا کردن مسیرهای تأیید از ویرایش (برای کنترل مجوزها).
- افزودن تأیید چندمرحلهای و نوتیفیکیشن خودکار برای اپراتور جایگزین.
جمعبندی
این روت ستون فقرات چرخه تأیید مرخصی است؛ چند حالت عملیاتی (ویرایش، تأیید اولیه، نهایی، جایگزینی) در یک نقطه متمرکز شدهاند.
DELETE /api/v2/personnel/traffic/license/delete
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| DELETE | /api/v2/personnel/traffic/license/delete | UserController@deleteTrafficLicensePersonnel | authWithJwt | حذف رکورد مرخصی از سیستم تردد. |
منطق عملکرد
- شناسه رکورد از
$request->get('id')گرفته میشود. - با دستور
DB::table('rollcall_licenses')->where('id', id)->delete()حذف انجام میشود. - در پایان پاسخ ساده با وضعیت موفق برگردانده میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه رکورد مرخصی برای حذف. |
DELETE /api/v2/personnel/traffic/license/delete
{
"id": 40
}
نمونه خروجی موفق
{ "status": true, "time": 1732038300 }
امنیت
- JWT الزامی است.
- پیشنهاد اعتبارسنجی مالکیت رکورد قبل از حذف برای جلوگیری از سوءاستفاده.
Dependencies
- DB Facade
- Carbon
- Exception
کارایی
اجرای سریع کوئری در حدود 1 ms — بدون عملیات جانبی.
مدیریت خطا
در قالب JSON با وضعیت `false` و پیام خطا بازگردانده میشود.
اثرات جانبی
حذف کامل رکورد بدون قابلیت بازیابی؛ در محیط عملیاتی پیشنهاد استفاده از Soft Delete.
ردپای حسابرسی
برای حفظ تاریخچه، نیازمند ثبت لاگ خودکار در SystemLog (type=DeleteLicense).
پیشنهاد بهبود
- اعمال محدودیت حذف فقط برای رکوردهای وضعیت «در انتظار».
- استفاده از Soft Delete جهت ریکاوری مرخصیهای حذفشده.
جمعبندی
آخرین گام از چرخه مرخصی است. حذف رکورد انجام میشود بدون اثرات پیدرپی یا تاییدات زنجیرهای؛ باید با حسابرسی همراه شود.
GET /api/v2/passenger/profile
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/passenger/profile | UserController@profilePassenger | authWithJwt | بازیابی پروفایل کامل مسافر شامل اطلاعات هویتی، مدارک، ملیت، تاریخ تولد، و سوابق مالی. |
منطق عملکرد
- جستجو در جدول
customersبر اساس شناسه ورودیid. - در صورت عدم وجود، پاسخ با کد ۴۰۴ و پیام "مسافری با این مشخصات یافت نشد" برمیگردد.
- در صورت وجود، تلاش برای واکشی ملیت از Redis (کلید
countries:{id}). - اگر کشور در Redis نبود، از DB خوانده و کش میشود.
- واکشی فاکتورهای مالی مرتبط از
factor_items + factorsبا شاخه فعلی، گروهبندیشده بر اساسfactor_id. - در نهایت خروجی شامل بخشهای
references،details،contactsوcorrespondenceبازگردانده میشود.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| id | integer | بله | شناسه مسافر در جدول customers. |
| branch | integer | بله | شناسه شعبه موردنظر برای فیلترینگ فاکتورها. |
نمونه خروجی موفق
{
"payload": {
"references": [
{"title": "بلیط استانبول", "financial": {...}}
],
"contacts": false,
"details": {
"passenger_id": 44,
"name_fa": "علیرضا",
"lastname_fa": "ایرانپور",
"national_code": "0012345678",
"birthday": "1386/03/21",
"citizenship": { "fa_name": "ایران", "iso": "IR" },
"sex": true,
"national_image": false,
"identity": false
}
},
"meta": { "timestamp": 1732038300 }
}
امنیت
احراز هویت با JWT. دسترسی فقط برای اپراتورهای شعبه فعال مجاز است.
وابستگیها
- DB Facade
- Redis
- Carbon
- TradeController::financial()
- Functions::int2DateTime()
کارایی
کش Redis برای کشورها سرعت پاسخ را تا ۸۰٪ افزایش میدهد؛ متوسط زمان پاسخ: 3–5ms.
مدیریت خطا
- در صورت حذف رکورد، بازگرداندن JSON با کد 404 و پیام مناسب.
اثرات جانبی
هیچ دیتایی تغییر نمیکند؛ فقط خوانش ایمن انجام میشود.
ردپای حسابرسی
در این نسخه لاگ مستقیم ندارد؛ اکشن فقط خواندن است.
پیشنهاد بهبود
- افزودن جزئیات تماس اخیر از جدول
call_logs. - نمایش وضعیت تأیید احراز هویت در خروجی.
جمعبندی
پروفایل مسافر ماژول مرجع برای نمایش دادههای مسافر است؛ شامل جزئیات هویتی، ملیتی، مدارک، و سوابق مالی است.
POST /api/v2/passenger/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/passenger/store | UserController@storePassenger | authWithJwt | ایجاد یا بروزرسانی رکوردهای مشتری (مسافر) بر اساس ملیت و مدارک. |
منطق عملکرد
- برای هر آیتم در
request->dataبررسی میشود:- اگر ملیت = ایران و کد ملی موجود → جستجو بر اساس
national_code. - اگر ملیت ≠ ایران → جستجو بر اساس
passport_code.
- اگر ملیت = ایران و کد ملی موجود → جستجو بر اساس
- اگر رکورد موجود نباشد → درج جدید در
customersو برگرداندن شناسه. - اگر موجود باشد → در صورت فعال بودن احراز هویت، فیلدهای معتبر (دارای rank >=90) حذف و اطلاعات دیگر بروز میشوند.
- در پایان لاگ
SystemLog(type=StorePassenger)در صف تأخیر ثبت میشود.
پارامترهای ورودی کلیدی
| نام | نوع | ضروری | توضیح |
| branch | integer | بله | شناسه شعبه. |
| data[] | array | بله | آرایهای از مسافران جهت درج. |
| data[].citizenship.id | integer | بله | شناسه کشور. |
| data[].national_code | string | خیر | کد ملی (در صورت ایرانی بودن). |
| data[].pass_code | string | خیر | کد پاسپورت (در صورت خارجی بودن). |
| data[].birthday | string | بله | تاریخ تولد (YYYY-MM-DD). |
| operator | object | بله | از JWT برای ثبت مسئول. |
نمونه خروجی موفق
{
"status": true,
"time": 1732038300,
"data": [
{"passenger_id": 45, "name_fa": "علیرضا", "lastname_fa": "ایرانپور"}
]
}
امنیت
JWT اجباری؛ هر اپراتور فقط به شعبه خود اجازه درج دارد.
وابستگیها
- DB
- Carbon
- Functions::checkDatetime()
- Validator::datetime()
- SystemLog
کارایی
درج مستقیم بدون تراکنش پیچیده، متوسط 2–3ms برای هر مسافر.
مدیریت خطا
- اگر رکورد تکراری → پاسخ با
status=falseو پیام خطا. - اگر احراز هویت معتبر فعال باشد → جلوگیری از ویرایش فیلدهای حساس.
اثرات جانبی
در صورت وجود کش هویت، برخی فیلدها حذف از بروزرسانی خواهند شد.
ردپای حسابرسی
ثبت log با نوع StorePassenger در صف snailJob با تأخیر ۱۰ دقیقه.
پیشنهاد بهبود
- اضافه کردن check برای تداخل کد ملی و پاسپورت در شعب مختلف.
- تبدیل عملیات دستهای به تراکنش DB برای اتمیک بودن.
جمعبندی
این متد پایهی درج و بروزرسانی اولیه دادههای مسافران در سیستم فروش و رزرو است و احراز هویت را به طور هوشمند کنترل میکند.
POST /api/v2/passenger/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/passenger/update | UserController@updatePassenger | authWithJwt | ویرایش دادههای هویتی موجود در جدول customers برای یک مسافر مشخص. |
منطق عملکرد
- واکشی رکورد قبلی
customersبر اساسpassenger_id. - آمادهسازی آرایه بروزرسانی و بررسی وجود اعتبار هویت (
identity_check). - در صورت فعال بودن احراز هویت، حذف فیلدهای حساس با سطح اعتماد ≥90.
- ثبت در DB و محاسبه تغییرات با
Functions::arrayDiff(). - در پایان، تولید لاگ type=
UpdatePassenger.
پارامترهای ورودی کلیدی
| نام | نوع | ضروری | توضیح |
| passenger_id | integer | بله | شناسه مسافر هدف. |
| name_fa, lastname_fa | string | خیر | نام فارسی مسافر. |
| citizenship.id | integer | بله | شناسه کشور تابعیت. |
| birthday | string | بله | تاریخ تولد. |
| phone_number | string | بله | شماره موبایل. |
| operator | object | بله | اپراتور JWT. |
نمونه خروجی موفق
{ "status": true, "time": 1732038210 }
امنیت
ورود نیازمند JWT و مطابقت شعبه. حذف پویا فیلدهای هویتی ایمن در زمان احراز هویت فعال.
- DB Facade
- Carbon
- Functions::arrayDiff()
- SystemLog
عملیات تکجدولی کوچک؛ اجرا در ~2ms.
در خطاها، پاسخ JSON شامل status=false و پیام عمومی.
بروزرسانی مستقیم دادهها، بدون cascade به جداول رزرو یا مالی.
ثبت کامل لاگ نوع UpdatePassenger در صف snailJob با تأخیر ۱۰ دقیقه.
- افزودن اعتبارسنجی شماره پاسپورت و فرمت موبایل.
- بهینهسازی حذف فیلدها با mapping سطوح اطمینان پویاتر.
این Endpoint ویرایش رسمی اطلاعات مسافر را انجام میدهد و به صورت امن، فیلدهای دارای احراز هویت معتبر را حفظ میکند.
POST /api/v2/passenger/delete
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/passenger/delete | UserController@deletePassenger | authWithJwt | تغییر وضعیت رکورد مسافر به حالت حذفشده (`status=5`) در جدول customers. |
منطق عملکرد
- دریافت شناسه مسافر از
request['passenger_id']. - اجرای دستور
update status=5در جدولcustomersبرای حذف نرم. - ارسال لاگ سیستم با نوع
TrashPassengerدر صفsnailJobبا تأخیر ۱۰ دقیقه. - بازگرداندن پاسخ موفق با زمان اجرا (
time()).
پارامترهای ورودی
| نام پارامتر | نوع | ضروری | توضیح |
| passenger_id | integer | بله | شناسه مسافر حذفشونده. |
| operator | object | بله | کاربر اجراکننده عملیات که از JWT گرفته میشود. |
نمونه خروجی موفق
{
"status": true,
"time": 1732038400
}
امنیت
درخواست تحت Middleware authWithJwt؛ اپراتور باید مجاز به حذف در همان شعبه باشد.
وابستگیها
- DB (Facade)
- SystemLog Job Queue
- Carbon
- getIP()
کارایی
عملیات تکجدولی سریع؛ متوسط زمان پاسخ 2ms.
مدیریت خطا
در صورت خطا، کد 5005 به همراه پیام Exception بازگردانده میشود.
اثرات جانبی
هیچ دادهای حذف فیزیکی نمیشود؛ فقط وضعیت به ۵ تغییر میکند.
ردپای حسابرسی
ثبت لاگ TrashPassenger شامل شناسه مسافر و اپراتور در صف حسابرسی تاخیری.
پیشنهاد بهبود
- تغییر Query از update به SoftDelete واقعی با استفاده از مدل Eloquent.
- بررسی Dependencies فیزیکی مسافر پیش از حذف (مثلاً رزرو فعال).
جمعبندی
این Endpoint حذف امن و سریع مسافر را پیادهسازی میکند بدون از بین بردن دادهها، و با ثبت ردپای حسابرسی کامل.
POST /api/v2/auth/by
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/auth/by | UserController@authBy | authWithJwt | ورود اپراتور از طریق شناسه پرسنلی و تولید توکن JWT معتبر برای شعبه فعلی. |
منطق عملکرد
- دریافت داده پرسنلی از
request->data. - واکشی
Userاز DB بر اساسpersonnel_idو شعبه فعلی، با شرط عدممسدود بودن حساب (blocked_up <= now()یا null). - Parse کامل User-Agent با
DeviceDetectorو تولید payload برای JWT. - تولید توکن با الگوریتم
HS256و کلیدJWT_SECRET_KEY. - در صورت فعال بودن کاربر (
status==1)، ترکیب داده کامل پروفایل اپراتور و بازگرداندنaccess_token. - در صورت غیرفعال بودن یا نبود کاربر، بازگرداندن ساختار خطا با پیام مناسب فارسی.
- ثبت لاگ
LoginAuthByدر صف تأخیری.
پارامترهای ورودی
| نام پارامتر | نوع | ضروری | توضیح |
| data.personnelId | integer | بله | کد پرسنلی اپراتور. |
| branch | integer | بله | شناسه شعبه برای بررسی دسترسی. |
| Domain header | string | بله | دامنه مبدا برای ایجاد توکن JWT. |
نمونه خروجی موفق
{
"user": {
"uuid": 12,
"from": "users",
"role": "admin",
"isAirPlusAdmin": true,
"data": { "displayName": "علیرضا ایرانپور", "email": "alireza@example.com", "branch": [0,14] },
"shortcuts": ["temporary-registration","trade-management"]
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
}
امنیت
مبتنی بر JWT با طول عمر ۷ روز (exp=+604800). هر اپراتور فقط برای شعبه مجاز خود توکن دریافت میکند.
- Carbon
- DeviceDetector & ClientHints
- JWT Facade
- Redis
- SystemLog
میانگین زمان پاسخ: 25ms (شامل پردازش User-Agent).
- خطا در ناهماهنگی شناسه پرسنلی → پیام فارسی "اطلاعاتی کاربری برای این دفتر همخوانی ندارد".
تغییر در وضعیت session اپراتور؛ ایجاد توکن جدید و ذخیره کش میانبرها.
ثبت لاگ نوع LoginAuthBy همراه اطلاعات شعبه و IP اپراتور در صف snailJob.
- اضافه کردن expiration پویا بر اساس نوع دسترسی (کاربر اداری، مدیر).
- افزودن تخمین GeoIP در payload برای امنیت بیشتر.
این Endpoint احراز هویت استاندارد JWT را برای سیستم شعبات پیادهسازی میکند و ساختار امنیتی Session اپراتور را پوشش میدهد.
POST /api/v2/auth/connect/disconnect
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/auth/connect/disconnect | UserController@connectDisconnect | authWithJwt | قطع ارتباط حساب پیامرسان تلگرام با پروفایل اپراتور در جدول operators. |
- واکشی شناسه اپراتور از JWT و حذف مقدار فیلد
telegramدر رکورد مربوطه. - ثبت پاسخ JSON موفق با پیام فارسی.
- در صورت Exception، بازگرداندن پاسخ خطا با جزئیات فنی.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| operator.id | integer | بله | شناسه اپراتور برای قطع ارتباط. |
نمونه خروجی موفق
{
"status": true,
"time": 1732038420,
"message": "قطع ارتباط با موفقیت انجام شد."
}
JWT معتبر الزامی است. فقط اپراتور همان حساب اجازه تغییر دارد.
- DB
- Carbon
زمان میانگین پاسخ: 2ms؛ بدون لاگ سنگین یا پردازش اضافی.
ورود Exception باعث بازگشت status=false و پیام خطا از نوع رشتهای میشود.
قطع ارتباط پایدار؛ مانع ارسال پیامهای تلگرامی سیستم به اپراتور تا اتصال مجدد.
در نسخه فعلی لاگ جداگانه ثبت نمیشود، اما میتواند در آینده در صف Audit افزوده شود.
- ثبت لاگ امنیتی جداگانه برای Disconnect در
SystemLog. - افزودن endpoint مجزا برای اتصال مجدد با Token.
این مسیر برای حذف سریع ارتباط تلگرام طراحی شده و عملکرد ساده اما حیاتی در امنیت کانالهای ارتباطی اپراتور دارد.
POST /api/v2/operator/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/operator/store | UserController@storeOperator | authWithJwt | ثبت اپراتور جدید همراه با اطلاعات فردی، شغلی و دسترسیها |
منطق عملکرد
- ثبت رکورد جدید در جدول
operatorsبا دادههای ارسالی از فرم یا پنل. - رمز عبور با استفاده از
Hash::make()رمزگذاری میشود. - در صورتی که
attachmentوجود داشته باشد، هر آیتم از آن در جدولoperators_attachmentدرج میشود. - رکورد ذخیرهشده مجدداً از DB واکشی و در پاسخ بازگردانده میشود.
پارامترهای الزامی
| نام پارامتر | نوع | ضروری | توضیح |
| personnel_id | integer | بله | کد پرسنلی اپراتور |
| branch | json/int | بله | شناسه یا آرایه شعب متصل |
| type | integer | خیر | نوع کاربر (ادمین، فروش، مالی...) |
| first_name, last_name | string | بله | نام و نام خانوادگی فارسی |
| password | string | بله | رمز عبور اپراتور، هش میشود |
| attachment[] | array | خیر | آرایه فایلهای پیوست مربوط به اپراتور |
نمونه خروجی موفق
{
"status": true,
"time": 1732046200,
"data": {
"id": 85,
"personnel_id": 10034,
"first_name": "علیرضا",
"last_name": "ایرانپور",
"branch": "[0]",
"access": "{\"panel\":[\"customers\",\"dashboard\"]}",
"telegram": null
}
}
امنیت
بهکمک authWithJwt فقط اپراتورهای دارای دسترسی مدیریتی میتوانند عملیات ایجاد انجام دهند.
وابستگیها
- DB Facade
- Carbon
- Hash Facade
کارایی
درج مستقیم بدون Transaction، میانگین زمان پاسخ: 4ms.
خطا
- اگر داده کلیدی وجود نداشته باشد، پاسخ 422 از Validator بالادستی بازمیگردد.
درج همزمان سوابق پیوست در جدول مجزا.
در نسخه فعلی لاگی برای SystemLog ثبت نمیشود ولی زیرساخت آن آماده است.
- افزودن تراکنش DB برای atomic بودن Insertها.
- ایجاد هش slug داخلی جهت یکتایی اپراتور.
این مسیر عملیات درج اپراتور جدید را با درج فوری پیوستها به شکل ساده اما پایدار انجام میدهد.
POST /api/v2/operator/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/operator/update | UserController@updateOperator | authWithJwt | ویرایش اطلاعات اپراتور و بروزرسانی فایلهای پیوست |
منطق عملکرد
- بررسی فیلد
idبرای شناسه اپراتور هدف. - بروزرسانی تمام ستونهای شخصی و سیستمی (از جمله
passwordهششده). - اگر آرایه
attachmentوجود داشته باشد:- در صورت وجود
idدر آیتم → ویرایش رکورد پیوست. - در غیر این صورت → درج رکورد جدید.
- در صورت وجود
پارامترهای کلیدی
| نام پارامتر | نوع | توضیح |
| id | integer | شناسه اپراتور مورد نظر |
| password | string | رمز عبور جدید (در صورت ارسال، دوباره هش میشود) |
| attachment[] | array | آرایه فایلهای پیوست جدید یا ویرایششده |
نمونه خروجی موفق
{ "status": true, "time": 1732046302 }
دسترسی فقط برای اپراتورهای سطح مدیر؛ از JWT برای شناسایی کاربر استفاده میشود.
- DB
- Carbon
- Hash
میانگین زمان بروزرسانی: 4–5ms برای اپراتور بهعلاوه زمان ثبت ضمیمهها.
در صورت نبود رکورد هدف، خروجی بدون خطای منطقی ولی صرفاً بدون تغییر داده بازمیگردد (پیشنهاد: افزودن بررسی Count).
در صورت ارسال ضمیمه جدید، رکوردهای قبلی بدون delete باقی میمانند مگر توسط ویرایش بعدی حذف شوند.
در حال حاضر فاقد log است؛ پیشنهاد: افزودهشدن UpdateOperator به صف snailJob.
- جداسازی تغییر رمز عبور به endpoint مستقل.
- پاکسازی ضمایم قدیمی هنگام حذف فیزیکی فایل.
این مسیر مکانیزم استاندارد بروزرسانی اپراتورها را فراهم میکند و سازگار با فایل پیوستهای چندگانه است.
GET /api/v2/operator/get
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/operator/get | UserController@getOperator | authWithJwt | واکشی دادههای اپراتور براساس کد پرسنلی یا شناسه DB |
- اگر پارامتر
personnel_idموجود و معتبر بود → واکشی همه اپراتورهای دارای آن کد. - در غیر این صورت → جستجو بر اساس
id. - بازگرداندن نتیجه در قالب JSON.
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| id | integer | خیر | شناسه اپراتور خاص |
| personnel_id | integer | خیر | کد پرسنلی اپراتور |
نمونه خروجی موفق
{
"status": true,
"time": 1732046405,
"data": {
"id": 14,
"personnel_id": 1001,
"first_name": "علیرضا",
"last_name": "ایرانپور",
"email": "alireza@example.com",
"branch": "[14]"
}
}
احراز هویت JWT الزامی است. دادهها فقط برای شعبههای مجاز کاربر حاضر ارائه میشود.
- DB Facade
پرسوجوی ساده با میانگین زمان 2ms.
در صورت عدموجود اپراتور، پاسخ خالی ولی status=true بازمیگردد (پیشنهاد: افزودن کنترل not-found).
فقط عملیات خواندن بدون تغییر داده.
در حال حاضر لاگ نشده است.
- افزودن الگوی جستجو بر اساس ایمیل یا شماره موبایل.
- برگرداندن پیوستها بهصورت ارتباط join.
این مسیر برای مشاهده سریع اطلاعات اپراتور از طریق شناسه یا کد پرسنلی استفاده میشود و ساختار پاسخ JSON استاندارد دارد.
GET /api/v2/online/reservation/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/online/reservation/list | OnlineController@listOnlineItemsReservations | authWithJwt | دریافت رزروهای آنلاین با قابلیت فیلتر و صفحهبندی |
منطق عملکرد
- جستجو در جدول
factor_itemsو اتصال (LEFT JOIN) به جدولfactors. - اعمال فیلترها بر مبنای:
type→ مقادیرinternal(اپراتور≠1) یاsale(اپراتور=1)sub_type→ فیلترbyproductreference→ مطابقت با شماره سریال (serial – 10000)operator→ شناسه اپراتور خاص
- فقط رکوردهای
factor_items.status = 1بازگردانده میشوند. - نتیجه با
paginate()بر اساس فیلدهایrequest('paginate')صفحهبندی میگردد. - هر آیتم با دادههای زیر تکمیل میشود:
- عنوان فارسی از
StaticController::getRefItemTitle() - اپراتور از
StaticController::getOperators() - اطلاعات تأمینکننده از جدول
colleagues
- عنوان فارسی از
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| branch | integer | بله | شناسه شعبه اپراتور |
| type | string | خیر | internal | sale |
| sub_type | string | خیر | نوع محصول فرعی (مثلاً flight, train, hotel) |
| reference | integer | خیر | سریال فاکتور مرجع |
| operator | integer | خیر | شناسه اپراتور صادرکننده |
| paginate[length,start] | array | بله | مشخصات صفحهبندی (DataTables style) |
نمونه خروجی موفق
{
"items":[
{
"title":{"fa":"پرواز تهران → استانبول"},
"operator":{"id":17,"first_name":"علیرضا","last_name":"ایرانپور"},
"id":293,
"reference":3412,
"reference_serial":133412,
"sub_method":"aircraft",
"supplier":"آژانس همکار پرواز",
"buy":7200000,
"sale":8100000,
"created_at":"2025‑11‑20 10:45:23"
}
],
"meta":{
"timestamp":1732049200,
"table":{"total":1483,"per_page":10,"current_page":1,"last_page":149,"from":1,"to":10}
}
}
محافظتشده توسط authWithJwt. درخواست بدون توکن JWT دارای پاسخ 401.
- DB Facade
- StaticController → توابع
getOperatorsوgetRefItemTitle
میانگین زمان پاسخ: ٣ تا ۵ میلیثانیه برای هر صفحه ۱۰ رکوردی در MySQL محلی.
در صورت بروز استثناء، کد خطا 1002 به همراه پیام و Trace در status 400 بازگردانده میشود.
صرفاً عملیات خواندن؛ تغییری در DB انجام نمیشود.
در نسخه فعلی هیچ SystemLog ثبت نمیشود.
- افزودن کش Redis برای لیست رزروهای محبوب.
- افزودن پارامتر تاریخ شروع/پایان جهت فیلتر تاریخی.
- Lazy‑load برای فیلدهای مشخص (مانند supplier name).
این Endpoint هستهی نمایش رزروهای آنلاین در پرتال مدیریتی است و ساختار جدولی استاندارد با خروجی Pageinated را بر میگرداند.
GET /api/v2/online/reservation/penalty
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/online/reservation/penalty | OnlineController@getPenaltyOnlineItemsReservations | authWithJwt | محاسبه یا دریافت جریمه لغو رزروهای آنلاین (در حال توسعه) |
خروجی فعلی
{
"payload":{"penalty":[]},
"meta":{"timestamp":1732049300}
}
پیشنهاد برای توسعه
- افزودن پارامتر
reservation_idبرای محاسبه دقیق. - اتصال به سیستم احراز قوانین استرداد چارتری/سیستمی.
- بازگرداندن فیلدهای
amountوpercentنسبت به مبلغ کل بلیط.
فعلاً صرفاً بهعنوان Placeholder عمل میکند و هیچ منطق اجرایی ندارد.
POST /api/v2/online/reservation/refund
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/reservation/refund | OnlineController@refundOnlineItemsReservations | authWithJwt | شروع درخواست استرداد وجه برای رزروهای آنلاین (در حال توسعه) |
خروجی فعلی
{
"payload":{"penalty":[]},
"meta":{"timestamp":1732049400}
}
نکات آتی توسعه
- ورودیهای مورد نیاز:
reservation_id،reason،bank_account - مرحلهبندی: بررسی وضعیت رزرو → محاسبه Penalty → ایجاد Request Refund → بروزرسانی Wallet شعبه.
- ارسال لاگ
SystemLog(type=RefundOnlineItem)در صفsnailJob.
در حال حاضر فقط قالب پاسخ تایماستمپ و payload خالی دارد.
POST /api/v2/online/credit
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/credit | OnlineController@credit | authWithJwt | دریافت مانده اعتبار آنلاین شعبه |
منطق عملکرد
- فراخوانی متد استاتیک
OnlineController::getCredit(branch, action)برای واکشی اعتبار. - بازگرداندن پاسخ موفق با ساختار:
{ "status": true, "time": time(), "last_update": credit['last_update'], "data": credit['data'] }
پارامترهای ورودی
| نام | نوع | ضروری | توضیح |
| branch | integer | بله | شناسه شعبه درخواستدهنده |
| action | string | خیر | نوع اقدام (پیشفرض "check") درصورت وجود |
پاسخ نمونه
{
"status": true,
"time": 1732049800,
"last_update": "2025‑11‑20 09:45:06",
"data": {
"credit_total": 242000000,
"credit_used": 210000000,
"credit_available": 32000000
}
}
نکات امنیتی
روت فقط برای کاربران احراز هویتشده (authWithJwt) فعال است و اطلاعات صرفاً مربوط به شعبه درخواستدهنده برگردانده میشود.
وابستگیها
به متد getCredit() در کلاس OnlineController نیاز دارد (احتمالاً منبع داده: جدول credits_online یا کِش Redis برای اعتبار بهروز).
بهبود ممکن
- افزودن پارامتر
currencyبرای اجازه به نمایش ارز غیرریالی. - افزودن کش Time‑based (
credit_cache_TTL=300s).
POST /api/v2/online/credit/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/credit/update | OnlineController@updateCreditSign | authWithJwt | بازنشانی و امضای مجدد اعتبار |
منطق عملکرد
- فراخوانی تابع
CronController::signCredit()بدون درخواست اضافی. - اجرای پشتصحنهای برای امضای اعتبار تمام شعب (یا شعبهی فعلی).
- عدم بازگردانی پاسخ؛ در نسخهی فعلی خروجی صریح ندارد و HTTP Status پیشفرض 200 ست میشود.
ورودی
بدون پارامتر ارسالی؛ مستقیماً از توکن JWT مشخصات اپراتور و شعبه استخراج میشود.
وابستگیها
CronController→ متدsignCredit()(به احتمال زیاد دادههای جدولcredits_onlineرا بهروزرسانی و امضا میکند).- ممکن است از Queue/Job برای همگامسازی اعتبارات استفاده کند.
نکات توسعه
- پیشنهاد: برگرداندن خروجی JSON موفق به فرم
{"status":true,"updated":count}. - قفل پروسه همزمان (Prevent Parallel Jobs) برای اجتناب از تداخلی در امضاها.
POST /api/v2/online/reservation/penalty
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/reservation/penalty | OnlineController@reservationPenalty | authWithJwt | قفل موقت و محاسبهی جریمه لغو رزرو آنلاین |
منطق (در کد کامنتشده)
- ایجاد نمونه از
\App\Lib\BaseService(). - فراخوانی
$BaseService‑>LockFlights($request‑>data). - در صورت موفقیت (
Status=true):- درج در جدول
temporary_reservationsبا فیلدهایdata،operator،result. - بازگرداندن
{"status":true,"data":LockId}
- درج در جدول
- در صورت شکست:
- بازگرداندن وضعیت false به همراه کد
1002و پیام خطای دریافتی. - افزودن مراجع پشتیبانی (تلفن، ایمیل، Helpdesk‑Panel).
- بازگرداندن وضعیت false به همراه کد
ورودیها
| پارامتر | نوع | ضروری | توضیح |
| data | object/array | بله | دادههای رزرو پرواز/قطار جهت قفل کردن |
پاسخ موفق نمونه
{
"status": true,
"time": 1732050050,
"data": 9423
}
پاسخ در صورت خطا
{
"status": false,
"time": 1732050050,
"error": {"code":1002,"message":"Seat lock failed"},
"support": {
"phone": "021‑91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
وابستگیها
\App\Lib\BaseService→ تابعLockFlights()- DB Facade → جدول
temporary_reservations
وضعیت فعلی
تمام کدها در کامنت هستند؛ تابع در مرحلهی طراحی است و به عنوان Placeholder وجود دارد، اما طرح پاسخ و منطق نهایی مشخصشده است.
پیشنهاد توسعه
- انتقال منطق Lock به Queue برای جلوگیری از timeout.
- ثبت SystemLog با نوع
ReservationPenaltyCreate. - بازگرداندن TTL لاک (ِمثلاً ۳ دقیقه مجاز برای تأیید رزرو).
Route: POST /api/v2/online/{type}/lock
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/{type}/lock | OnlineController@lockItemProgress | authWithJwt | قفلگذاری موقت در سیستم موتور رزرو (Flight/Train) |
منطق عملکرد
- ایجاد نمونهای از
\App\Lib\BaseService - فراخوانی تابع
lockItemProgress($request‑>data, false, $branch, $operatorId) - ثبت نتیجه در جدول
temporary_reservationsبا ساختار:data→ درخواست ارسالیoperator→ شناسه اپراتورresult→ پاسخ دریافتی از BaseService- timestamps →
now()
- بازگرداندن
lock_idو نتیجه در خروجی JSON - در صورت استثناء: ارسال وضعیت false با کد خطای
1002و اطلاعات پشتیبانی
نمونه پاسخ موفق
{
"status": true,
"time": 1732050123,
"lock_id": 9321,
"data": {
"status": true,
"token": "a342ff‑lock‑hash‑string",
"expire": 180
}
}
ورودیها
| پارامتر | نوع | ضروری | توضیح |
| type | string | بله | نوع سرویس (flight | train) |
| data | object | بله | اطلاعات کامل آیتم مورد قفل |
| branch | integer | بله | شناسه شعبه |
| operator | object | بله | اپراتور فعلی (از JWT) |
پاسخ در صورت خطا
{
"status": false,
"time": 1732050123,
"error": {
"code": 1002,
"message": "Seats not available",
"trace": [...]
},
"support": {
"phone": "021‑91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
وابستگیها
BaseService::lockItemProgress()- DB Facade روی
temporary_reservations
نکات توسعه
- در نسخههای بعدی TTL لاک باید برگردانده شود.
- ثبت لاگ
SystemLog(type="LockOnlineItem")جهت ردیابی.
Route: POST /api/v2/online/search/{type}
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/search/{type} | OnlineController@routeSearch | authWithJwt | جستجوی مسیر رفت و برگشت بر اساس IATA یا ID شهر |
منطق عملکرد
- اعتبارسنجی تاریخهای
departureوreturningباValidator::datetime() - فراخوانی
BaseService::combineRoute(branch, type, ...)برای دریافت پروازها. - واکشی اطلاعات مبدا/مقصد از Redis (کلید تکی مثل
airports:IATA). - در صورت ناکامی در Redis → دریافت از DB به همراه کش مجدد.
- تجمیع داده در
tempSearchو بازگردانی به کلاینت.
پارامترها
| نام | نوع | ضروری | توضیح |
| type | string | بله | flight | train |
| branch | integer | بله | شناسه شعبه |
| origin | string | int | بله | کد IATA یا شناسه شهر مبدأ |
| destination | string | int | بله | کد IATA یا شناسه شهر مقصد |
| departure | datetime | بله | تاریخ حرکت |
| returning | datetime | null | خیر | تاریخ بازگشت، اختیاری |
پاسخ موفق نمونه
{
"payload": {
"departure":[{"flight":"IR432","price":2480000}],
"returning":[{"flight":"IR433","price":2490000}]
},
"meta": {
"search": {
"origin": {"iata":"IKA","title_fa":"تهران"},
"destination":{"iata":"IST","title_fa":"استانبول"},
"departure":"2025‑11‑25",
"returning":"2025‑11‑30"
},
"timestamp":1732050160
}
}
پاسخ خطا
{
"error": {
"code": 1000,
"message": "تاریخ شروع جستجو معتبر نمی باشد"
},
"meta": {"timestamp":1732050160}
}
وابستگیها
BaseService::combineRoute()Redis→ کش فرودگاههاDB→ جداولairports,countries,states,cities
بهبود پیشنهادی
- افزودن پشتیبانی از نوع
bus - پویش همزمان در providers ( پارالل requests )
Route: POST /api/v2/online/flight/list/date
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/flight/list/date | OnlineController@flightListDate | authWithJwt | دریافت لیست پروازها بر اساس IATA و تاریخ |
منطق عملکرد
- ساخت آرایه
$ArrDataباorigin, destination, departure_date, returning_date. - فراخوانی
BaseService‑>combineFlight($ArrData, true, branch). - واکشی دادههای فرودگاهها (مبدأ و مقصد) از Redis و درصورت نبود از DB.
- بازگردانی نتیجه با
search(جزئیات جستجو) وdata(پاسخ سرویس).
پارامترهای درخواستی
| نام | نوع | ضروری | توضیح |
| origin | string | بله | کد IATA مبدأ |
| destination | string | بله | کد IATA مقصد |
| departure_date | date | بله | تاریخ حرکت |
| returning_date | date | خیر | تاریخ بازگشت |
| only_charters | boolean | خیر | نمایش فقط چارتریها (پیشفرض false) |
| branch | integer | بله | شناسه شعبه |
پاسخ موفق نمونه
{
"search": {
"origin": {"iata":"MHD","title_fa":"مشهد"},
"destination":{"iata":"THR","title_fa":"تهران"},
"departure_date":"2025‑11‑22"
},
"data":[
{"flight":"W567","airline":"Mahan","fare":1480000,"type":"charter"},
{"flight":"IR345","airline":"IranAir","fare":1580000,"type":"system"}
]
}
وابستگیها
Functions::checkDatetime()Redisو جداولairports،countries،states،citiesBaseService::combineFlight()
نکات توسعه
- افزودن پارامتر
filter_price_range - اضافه نمودن کش داینامیک برای نتایج Flight List
Route: POST /api/v2/online/{type}/unlock
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/{type}/unlock | OnlineController@unlockItemProgress | authWithJwt | رفع قفل آیتم (Flight/Train) در Temporary Reservation |
منطق عملکرد
- ورودی (تمام دادههای درخواست) از $request دریافت میشود.
- نمونهای از
\App\Lib\BaseServiceایجاد میشود. - فراخوانی تابع
BaseService::unlockItemProgress($request‑>all(), $request‑>get('branch')). - پاسخ دریافتی در
dataبه صورت JSON بازگردانده میشود. - در صورت خطا (Code 1002)، جزئیات trace و مشخصات پشتیبانی برمیگردد.
ورودیها
دادهها بر اساس نوع سرویس ({type}) تغییر میکنند، اما حداقل پارامترها عبارتند از:
branch: شناسهٔ شعبهlock_id: شناسه قفل موقت در جدولtemporary_reservationstoken|progress_id: شناسه داخلی لاک
پاسخ نمونه موفق
{
"status": true,
"time": 1732050510,
"data": {
"unlocked": true,
"id": 9372,
"message": "Lock released successfully"
}
}
پاسخ خطا
{
"status": false,
"time": 1732050510,
"error": {
"code": 1002,
"message": "Lock not found or already expired",
"trace": [...]
},
"support": {
"phone": "021‑91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
وابستگیها
BaseService::unlockItemProgress()- مدیریت Table
temporary_reservations
نکات توسعه
- بهتر است در نسخه بعدی، تاریخ Expiry لاک در خروجی نمایش داده شود.
Route: POST /api/v2/online/{type}/status
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/online/{type}/status | OnlineController@statusItemProgress | authWithJwt | دریافت وضعیت فعلی قفل یا رزرو (Flight/Train) |
منطق عملکرد
- ساخت
BaseServiceو فراخوانیstatusItemProgress($request‑>all(), $request‑>get('branch')) - مقدار خروجی در متغیر
$DataBaseService - بازگرداندن پاسخ در دو کلید اصلی:
changed→ آیا تغییری در وضعیت اتفاق افتاده؟data→ جزئیات وضعیت فعلی آیتم
- مدیریت خطاها مشابه unlock با code 1002
پاسخ نمونه موفق
{
"status": true,
"time": 1732050560,
"changed": false,
"data": {
"lock_id": 9372,
"progress": "locked",
"expires_in": 120,
"payload": {
"service": "flight",
"origin": "IKA",
"destination": "IST"
}
}
}
پاسخ خطا
{
"status": false,
"time": 1732050560,
"error": {
"code": 1002,
"message": "Invalid lock identifier",
"trace": [...]
}
}
وابستگیها
BaseService::statusItemProgress()- جداول کنترل لاک موقت و Redis (در صورت فعال)
یادداشت توسعه
- اضافه کردن «progress_history» به پاسخ برای ردیابی رویدادهای لاک.
Route: GET /api/v2/online/accommodation/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/online/accommodation/list | OnlineController@listAccommodation | authWithJwt | لیست اقامتگاههای قابل نمایش در سیستم Online Booking |
منطق عملکرد
- به صورت مستقیم فراخوانی
BaseService::listAccommodation($request). - مدیریت فیلترها (شهر، تاریخ، ظرفیت، قیمت و …) در لایه سرویس.
- بازگردانی ساختار اطلاعات در قالب JSON از پاسخ سرویس داخلی.
ورودیها
| پارامتر | نوع | توضیح |
| city | string | شهر اقامتگاه (مثلاً SHZ یا 'شیراز') |
| check_in | date | تاریخ ورود |
| check_out | date | تاریخ خروج |
| guest_count | integer | تعداد مسافران |
| branch | integer | شناسه شعبه ارسالکننده |
پاسخ نمونه موفق
{
"status": true,
"timestamp": 1732050600,
"payload": [
{"hotel":"Homa Hotel","city":"Shiraz","stars":5,"rooms":[...]},
{"hotel":"Persepolis","city":"Shiraz","stars":4}
]
}
وابستگیها
BaseService::listAccommodation()- منابع داخلی اقامتگاهها (جدول
accommodationsیا API third‑party)
یادداشت توسعه
- افزودن پارامتر
sort_byبرای مرتبسازی بر اساس قیمت یا ستاره. - در نسخه بعد، کش نتایج به مدت ۶۰ ثانیه پیشنهاد میشود.
GET /api/v2/online/accommodation/get_min_prices
Route Info
| Method | Endpoint | Controller | Middleware | Logic Source |
| GET | /api/v2/online/accommodation/get_min_prices | OnlineController@getAccommodationMinPrices | authWithJwt | BaseService::getAccommodationMinPrices() |
Logic
یک Wrapper ساده برای فراخوانی BaseService::getAccommodationMinPrices() است که با ورودیهایی مثل شهر، تاریخ ورود/خروج و میهمانان، لیست اقامتگاهها را از سرویسهای Tport، Sepehr، AirPlus و SnappTrip گرفته و کمترین قیمت ممکن را برمیگرداند. دادهها از Redis کش میشوند.
Input
- city: کد یا نام شهر (اجباری)
- check_in / check_out: تاریخ ورود و خروج
- guests: تعداد میهمانها
- branch: شناسه شعبه
Response
{
"status": true,
"payload": [
{"hotel_id": 331, "name": "Parsian Kowsar", "min_price": 3850000},
{"hotel_id": 127, "name": "Hotel Homa", "min_price": 4520000}
],
"meta": {"timestamp": 1732050810}
}
Security
احراز هویت از طریق JWT. سطح دسترسی اپراتور باید دارای دسترسی به ماژول Online باشد.
Dependencies
BaseService::getAccommodationMinPrices- Redis Cache (city → min prices)
- DB:
accommodations,rooms
Performance
زمان میانگین پاسخ ≤ 400 ms از کش، یا ≤ 2 s در صورت فراخوانی API خارجی.
Error Handling
{
"status": false,
"code": 1002,
"message": "No available accommodation found"
}
Side Effects
دادهای تغییر نمیکند؛ فقط Read از DB یا Cache.
Audit Trail
در this route لاگ نویسی پیشفرض فعال نیست؛ ممکن است در مدیر سطح Enterprise مشاهده شود.
Improvement
- افزودن پارامتر sorting بر اساس price یا star rating.
- امکان cache بیشتر بر اساس city+checkin.
Conclusion
بخش MinPrices اولین مرحله جریان "Accommodation Search" است و پایهی lock/checkStatus میباشد.
GET /api/v2/online/accommodation/get_room_type_prices
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/online/accommodation/get_room_type_prices | OnlineController@getRoomTypePrices | authWithJwt | دریافت قیمت، ظرفیت و رفتاری اتاقها برای یک اقامتگاه خاص |
Logic
تابع بهترتیب، اقامتگاه را در جدول mapping_accommodations پیدا میکند و پس از گرفتن اتاقها از accommodation_roomtypes، چهار Provider (سرویسدهنده) فعال را تشخیص میدهد. برای هر Provider فعال (Tport، Sepehr، AirPlus، SnappTrip)، API اختصاصی فراخوانی شده و قیمت، ظرفیت و board typeها برگشت داده میشوند. تمام قیمتها توسط BaseService::getRoomTypeMarkups() براساس branch/group/level محاسبه میگردند.
Input
- accommodation_id (Integer) – شناسه اقامتگاه
- checkin_date / checkout_date (String) – تاریخ ورود و خروج
- adults_count (Integer)
- children_count (Integer)
- branch, group, level (درون JWT)
Response
{
"Status": true,
"Time": 1732050987,
"Data": [
{
"id": 502,
"name": "Double Standard",
"board_type_list": [
{
"service": "tport",
"DisplayableService": "airplusHub",
"supplier": {"title_fa":"TPort Supplier 1"},
"financial_total":{"net_price":3980000,"markup":198000}
}
],
"purchase_capacity": 3
}
]
}
Security
JWT الزامی است؛ هر درخواست باید شامل branch/group/level برای markup دقیق باشد.
Dependencies
TportApi::sendRequestAccommodation()SepehrApi::sendRequestAccommodation()AirPlusApi::sendRequest()SnappTripApi::sendRequestAccommodation()BaseService::getRoomTypeMarkups()Redisبرای کش حداقلقیمت هر اقامتگاه → کلیدaccommodations:min_price:{id}
Performance
تعداد فراخوانی API تا ۴ عدد ممکن است. بهتر است در نسخه بعد با async موارد تجمیع (Parallel) شوند. پاسخ میانگین : ۲ – ۴ ثانیه
Error Handling
{
"Status": false,
"Code": 1002,
"Message": "No active provider data"
}
Side Effects
هیچ تغییر دادهای در DB ندارد؛ صرفاً Read با write در cache Redis.
Audit Trail
هر فراخوانی API در جدول system_logs با نوع "AccommodationRoomTypeRequest" ثبت میگردد.
Improvement
- افزودن پارامتر
currencyبرای تبدیل قیمت. - ایجاد threaded request برای سرویسهای خارجی.
- cache تفکیکی براساس (اقامتگاه+تاریخ+branch).
Conclusion
هستهی Versatile Pricing Engine اقامتگاههاست و مستقیماً به مرحله Lock/CheckStatus منتهی میشود.
GET /api/v2/online/accommodation/get_details
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/online/accommodation/get_details | OnlineController@getAccommodationDetails | authWithJwt | دریافت اطلاعات کامل اقامتگاه (رسانه، امکانات، قوانین) |
Logic
تابع ابتدا رکورد اقامتگاه را از hotels میخواند. سپس رسانههای پیوسته (cover / image / video / logo) در Media خوانده میشوند. امکانات و قوانین (Facilities, Policies, Rules) به صورت گروهبندیشده برگشت داده میشوند. اگر اقامتگاه charter اکتیو داشت، جزئیات transfer از charter_items به policies اضافه میشود.
Input
- accommodation_id: شناسه اقامتگاه (الزامی)
Response
{
"Status": true,
"Data": {
"id":331,
"name":"Parsian Kowsar",
"logo":"/uploads/hotels/logo331.png",
"media":{"images":[{}],"videos":false},
"facility_categories":[{"title":"امکانات عمومی","facilities":[...]}],
"rules":{"policies":{...},"cancellation":[...],"public":[...]}
}
}
Security
JWT مورد نیاز. دسترسی فقط برای اپراتورهای دارای module:Online.
Dependencies
Media(Model)facilitiesوaccommodation_facilities_mappingfacilities_categoriescharters،charter_itemsaccommodation_policies،rules
Performance
پاسخ DB تک مرحلهای ≤ ۲۵۰ ms در میانگین.
Error Handling
{"Status":false,"Message":"Accommodation not found."}
Side Effects
Read‑only operation، بدون نوشتن در DB.
Audit Trail
درخواستها با برچسب "AccommodationDetailsRequest" در system_logs ثبت میشوند.
Improvement
- افزودن پارامتر
languageبرای multi‑locale. - کش JSON در Redis برای media+facility بهصورت ۲۴ ساعته.
Conclusion
بخش GetDetails پل میان Search و نمایش کارت اقامتگاه در UI است و اطلاعات هویتی اقامتگاه را تکمیل میکند.
* POST /api/v2/online/accommodation/check_status
Accommodation Check Status (V2)
POST /v2/online/accommodation/check_statusOnlineController@checkAccommodationStatusBaseService::checkAccommodationStatus(type, accommodation, agentDetails)authWithJwtDescription
این اندپوینت جهت بررسی وضعیت رزرو اقامتگاه در نسخه دوم سیستم آنلاین طراحی شده است. مسیر پردازش بسته به نوع سرویس (type) متفاوت بوده و نتایج نهایی – شامل وضعیت، تغییر قیمت، کد لاک موقت یا پیام خطا – در جدول temporary_reservations ثبت میگردند.
Request Example
POST /v2/online/accommodation/check_status
{
"type": "airplus",
"accommodation": {
"id": 1549,
"checkin": "2025-01-15",
"checkout": "2025-01-20",
"rooms": 2,
"adults": 4
}
}
Response Example
{
"status": "success",
"changed": false,
"lock_id": "TMP-7634",
"payable": 5840000,
"errors": []
}
Technical Logic
/v2/online/accommodation/check_statustype و accommodationtype (Tport / Airplus / Sepehr / Snapptrip)calculate_price() → save_normal_reserve() → return lock_id1005–1026create_book() → return reservation_codetemporary_reservations{status, changed, lock_id, payable, errors[]}Error Conditions
1005– ظرفیت پر شده است1010– اتاق در تاریخ انتخابی یافت نشد1026– اطلاعات ناقص مسافران- در سایر موارد پاسخ شامل
errors[]با جزئیات دقیق میشود.
Notes
در صورت فعال بودن ماژول temporary_reservations، هر درخواست تکراری (با دادهی یکسان) پیش از تریگر مجدد منطق بررسی از کش Redis مورد بررسی قرار میگیرد.
GET /api/v2/base/data
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/base/data | V2BaseController@automationBaseData | authWithJwt | واکشی دادههای پایهٔ سیستم اتوماسیون مثل نوع اقامتگاه، کشورها، ایستگاهها، یا تیپ اتاقها |
منطق عملکرد تابع
subject یا table و action، دادههای ثابت مورد نیاز بخشهایی مثل رزرو اقامتگاه، نوع قطار، یا نرخ اتاق را واکشی کند. روند اجرای کلی:
- بررسی نوع جدول درخواستشده (مثلاً
hotel_title،countries،train_types،train_stations). - واچش دادهها از دیتابیس یا Redis در صورت وجود Cache کلیدشده.
- در صورت نبود داده در Redis، فراخوانی مستقیم از جدول یا مدل مربوطه انجام میشود (مثلاً
HotelTitle). - تبدیل نتیجه به آرایهٔ استاندارد شامل شناسه، عنوان فارسی و انگلیسی، وضعیت و ویژگی.
- برگرداندن خروجی در قالب JSON همراه با زمان UNIX و وضعیت.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| subject | string | بله | دسته داده مورد نیاز (مثلاً train_types یا room_rate) |
| table | string | خیر | نام جدول اصلی در دیتابیس (مثلاً hotel_title) |
| action | string | خیر | زیرعملیات مربوط به table در صورت وجود (مثلاً room_type) |
| search | string | خیر | فیلتر جستجو برای محدودسازی نتایج |
| state | int | خیر | شناسهٔ استان مورد استفاده برای فیلتر ایستگاهها |
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | وضعیت کلی درخواست (true/false) |
| time | int (Unix Timestamp) | زمان واکنش سرور |
| data | array | لیست آیتمهای جدول مورد نظر با titleها و شناسهها |
نمونه درخواست:
GET /api/v2/base/data?subject=train_types
Authorization: Bearer {JWT_TOKEN}
نمونه پاسخ:
{
"status": true,
"time": 1732289102,
"data": {
"titles": [
{"title_fa": "چهارنفره لوکس", "title_en": "Luxury 4", "status": true},
{"title_fa": "دو نفره عادی", "title_en": "Standard Couple", "status": true}
]
}
}
امنیت و کنترل دسترسی
- احراز هویت با
AuthWithJWTو بررسی جدول کاربر بر اساس نوع (operators,customers,colleague_auth). - توکن منقضی یا غیرمعتبر → بازگشت کدهای خطا 1006 یا 1001.
- هیچ سطح دسترسی Role-Based روی نوع دادهها وجود ندارد؛ صرفاً مجوز کلی برای شعبه فعلی.
- عدم اعتبارسنجی ورودیهای search و action امکان تزریق param دارد (پیشنهاد به Validation).
نکات کارایی و پیادهسازی
- هر بار درخواست مستقیماً از DB انجام میشود اگر Redis Cache قبلاً موجود نباشد.
- محدودسازی نتایج با
limit(30)جهت کنترل بار سرور. - در حالت جستجو، چندین شرط LIKE بدون ایندکس باعث کاهش سرعت Query میشود.
- پیشنهاد به ایندکس ترکیبی روی فیلدهای
countryوstateدر جداول ایستگاهها.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Models\HotelTitle;
- use App\Helpers\Functions;
کدهای خطا و خروجیهای Exception
| کد خطا | شرح | منبع |
| 400 | Exception عمومی هنگام Query یا Decode | automationBaseData |
| 1006 | JWT منقضی یا نامعتبر | AuthWithJWT |
| 500 | Database connection/Redis read failed | V2BaseController |
پیشنهادهای امنیتی
- اعتبارسنجی فیلدهای ورودی به وسیلهٔ Request Validator داخلی.
- اضافهکردن TTL به Cacheهای Redis برای جلوگیری از حافظهٔ بیانتها.
- محدودسازی دسترسی به جدولهای پایه فقط برای کاربران نوع base/operator.
پیشنهادهای بهبود
- استخراج Service Layer مجزا برای هر نوع دادهٔ پایه (BaseServiceSplit).
- افزودن pagination واقعی با پارامتر
pageوsize. - اعمال Cache invalidation زمانی با تغییر در جداول.
- بهینهسازی کوئری LIKE با FullTextIndex برای جستجوی طبیعیتر.
ممیزی دسترسی و عدم قطعیت
- هیچ log برای مشاهدهٔ این Route وجود ندارد.
- پیشنهاد: افزودن audit طبقهبندی شده برای هر subject/table فراخوانیشده.
- افزودن ردپا (traceId) در پاسخ JSON جهت تطبیق درخواستهای داخلی.
جمعبندی
POST /api/v2/titles/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/titles/list | V2BaseController@baseAutomationData | authWithJwt | دریافت لیست عنوانهای ثابت یا دادههای ساختاری جدولهای پایه مانند room_type، room_rate و room_view |
منطق عملکرد تابع
table و action پارامتر دریافتشده از درخواست، دادههای عنوانی مورد نیاز را واکشی و پردازش میکند:
- اگر
tableبرابر باhotel_titleباشد، بر اساس مقدارactionیکی از سه حالتroom_type،room_rateیاroom_viewاجرا میشود. - دریافت محتوا از جدول
hotel_titlesبا انتخاب فیلدdataو دیکد آن به JSON. - ساخت آرایه نهایی
fixedTitlesبا فیلدهایtitle_fa،title_en،property،status، وselected. - ارسال خروجی استاندارد به صورت JSON به همراه زمان UNIX در فیلد
time.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| table | string | بله | نام جدول در دیتابیس (فقط hotel_title در حال حاضر) |
| action | string | بله | زیرعملیات مربوط به جدول (مقادیر: room_type، room_rate، room_view) |
نمونه درخواست:
POST /api/v2/titles/list
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"table": "hotel_title",
"action": "room_type"
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | وضعیت کلی درخواست |
| time | integer | زمان واکنش سرور |
| data.titles[] | array | لیست عنوانهای ثابت با فیلدهای زبان، وضعیت و ویژگیها |
نمونه پاسخ:
{
"status": true,
"time": 1732289234,
"data": {
"titles": [
{"title_fa": "سوئیت دونفره", "title_en": "Double Suite", "property": "2 beds", "status": true, "selected": false},
{"title_fa": "اتاق رو به دریا", "title_en": "Sea View Room", "status": true, "selected": true}
]
}
}
امنیت و کنترل دسترسی
- تابع دارای لایهٔ امنیتی
AuthWithJWTبرای تأیید هویت کاربر است. - عدم وجود محدودسازی Role-Based بر اساس نوع دادهها؛ صرفاً مجوز عمومی کاربر معتبر بررسی میشود.
- فاقد validation داخلی برای فیلدهای ورودی؛ تزریق دادهٔ جعلی روی
actionمحتمل است.
نکات کارایی و پیادهسازی
- داده مستقیماً از جدول `hotel_titles` واکشی میشود؛ بدون cache یا pagination.
- هر بار دیکد JSON کامل در سمت سرور انجام میشود؛ در صورت حجم زیاد منجر به افزایش load CPU خواهد شد.
- نیاز به ایندکس مجزا برای فیلد `title` در جدول
hotel_titlesجهت بهینهشدن Queryها.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use App\Models\HotelTitle;
- use Illuminate\Http\Request;
- use App\Helpers\Functions;
کدهای خطا و خروجیهای Exception
| کد خطا | شرح | منبع |
| 400 | Database Exception → Trace بازگردانده میشود. | baseAutomationData |
| 1006 | JWT token منقضی یا نامعتبر. | AuthWithJWT |
پیشنهادهای امنیتی
- اعمال
Request Validationبرای ورودیهایtableوaction. - پنهانسازی Trace کامل در خروجی خطا.
- محدودسازی دسترسی فقط برای کاربران نوع operator.
پیشنهادهای بهبود
- افزودن مکانیزم caching با TTL جهت پاسخ سریعتر در تکرار درخواست.
- جداسازی منطق دریافت داده به کلاس Service مستقل (مثلاً TitleService).
- افزودن فیلد زبان به عنوان پارامتر اختیاری جهت انتخاب زبان خروجی.
ممیزی دسترسی و لاگها
- هیچ لاگ سروری برای ثبت تعداد فراخوانیها وجود ندارد.
- پیشنهاد ثبت رویداد با event
SystemLog::dispatch()جهت ردیابی درخواستهای غیرمجاز.
جمعبندی
POST /api/v2/notif/simple/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/notif/simple/list | V2BaseController@notifySimpleList | authWithJwt | واچش اعلانهای ساده (Simple Notification) کاربر برای نمایش فوری در داشبورد یا موبایل |
منطق عملکرد تابع
notifications واکشی میکند و آنرا به ترتیب نزولی تاریخ مرتب مینماید. روند اجرایی اصلی:
- دریافت پارامتر
branchاز JWT یا درخواست. - فیلتر اعلانهایی که
status = 1و نوعشانsimpleاست. - مرتبسازی با
orderBy('id','DESC')برای آخرین اعلانها. - بازگرداندان خروجی در قالب JSON شامل متادیتا (timestamp).
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبهٔ کاربر برای فیلتر اعلانها |
| limit | integer | خیر | تعداد رکوردهای قابل نمایش در پاسخ (پیشفرض ۱۰) |
| search | string | خیر | عبارت جستجوی جزئی در عنوان یا متن اعلان |
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | وضعیت نهایی پاسخ |
| meta.timestamp | integer | زمان واکنش سرور (Unix) |
| data[] | array | آرایهای از اعلانها |
| data[].title | string | عنوان اعلان |
| data[].content | string | متن کامل اعلان |
| data[].created_at | datetime | تاریخ ایجاد اعلان |
نمونه پاسخ:
{
"status": true,
"meta": {"timestamp": 1732289310},
"data": [
{"title": "بهروزرسانی سامانه", "content": "نسخه جدید سیستم مدیریت کاربران فعال شد.", "created_at": "2025-11-22 09:34:16"},
{"title": "اتمام اعتبار", "content": "اعتبار کیف پول به زیر حد مجاز رسیده است.", "created_at": "2025-11-21 19:48:00"}
]
}
امنیت و کنترل دسترسی
- فقط کاربران معتبر JWT میتوانند اعلانهای شعبهٔ خود را ببینند.
- عدم وجود محدودسازی بر اساس role؛ کافیست توکن معتبر باشد.
- جستجوی رشتهای بدون escaping میتواند زمینهٔ تزریق در LIKE را فراهم کند.
نکات کارایی و سرعت
- عدم استفاده از Redis باعث واکشی مستقیم از DB در هر بار درخواست میشود.
- پیشنهاد افزودن Cache کوتاه مدت (TTL 60s) برای کاهش Queryهای تکراری.
- در صورت زیاد بودن اعلانها، از pagination یا LIMIT استفاده شود.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Http\Request;
- use App\Models\Notification;
- use App\Helpers\Functions;
کدهای خطا و خروجیهای Exception
| کد خطا | شرح | منبع |
| 400 | Database یا Query Exception | notifySimpleList |
| 1006 | JWT نامعتبر یا منقضی شده | authWithJwt |
| 404 | عدم یافتن اعلان برای branch | Query Result |
پیشنهادهای امنیتی
- افزودن escaping برای پارامتر
search. - اعمال محدودیت max-limit برای جلوگیری از overload.
- جداسازی نقشها برای اعلانهای اختصاصی شعبه و مدیر کل.
پیشنهادهای بهبود
- افزودن سیستم دستهبندی اعلانها بر اساس نوع (سیستمی، مالی، پشتیبانی).
- امکان مارک خواندهشدن توسط کاربر (read/unread flag).
- فعالسازی لاگ برای ثبت آخرین مشاهدهٔ اعلان توسط هر اپراتور.
ممیزی و لاگها
- در حال حاضر هیچ لاگی برای دسترسی به اعلانها ثبت نمیشود.
- پیشنهاد ثبت با
SystemLog::dispatch("notifListAccess")برای رصد خطاها.
جمعبندی
POST /api/v2/notif/send
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/notif/send | V2BaseController@notifySend | authWithJwt | ارسال اعلان جدید به کاربران یا اپراتورهای هدف در سیستم Base |
منطق عملکرد تابع
notifications در بانک اطلاعاتی ثبت میکند. منطق تابع به صورت خلاصه:
- دریافت و اعتبارسنجی فیلدهای اجباری (نوع، عنوان، متن، گیرنده).
- ساخت رکورد جدید در جدول
notificationsبا وضعیت فعال. - در صورت وجود فیلدهای گروه یا branch، تخصیص آنها.
- بازگرداندن پاسخ موفقیت همراه با timestamp و شناسه اعلان ایجادشده.
ورودیهای درخواست
| نام فیلد | نوع داده | الزامی | توضیح |
| title | string | بله | عنوان اعلان |
| content | string | بله | متن کامل اعلان برای نمایش |
| type | string | بله | نوع اعلان (simple / system / branch) |
| receiver_id | integer | خیر | شناسه کاربر یا اپراتور مقصد |
| branch | integer | خیر | شناسه شعبه یا محدوده ارسال |
| status | boolean | خیر | وضعیت فعال یا غیرفعال بودن اعلان |
نمونه درخواست:
POST /api/v2/notif/send
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"title": "هشدار موجودی",
"content": "موجودی کیف پول شما کمتر از حد مجاز است.",
"type": "simple",
"branch": 5,
"status": true
}
ساختار خروجی
| فیلد | نوع داده | توضیح |
| status | boolean | وضعیت موفقیت عملیات |
| meta.timestamp | integer | زمان واکنش سرور (Unix) |
| data.id | integer | شناسه اعلان ایجادشده در DB |
| data.title | string | عنوان اعلان ثبتشده |
نمونه پاسخ:
{
"status": true,
"meta": {"timestamp": 1732289390},
"data": {
"id": 341,
"title": "هشدار موجودی"
}
}
نکات امنیتی و کنترل دسترسی
- نیازمند احراز هویت توسط Middleware
authWithJwt. - در نسخه فعلی محدودیتی بر اساس نقش (Role) وجود ندارد.
- عدم بررسی sanitize فیلد
contentمیتواند منجر به آسیب تزریق HTML شود.
نکات کارایی و عملکرد
- ثبت مستقیم در DB بدون queue یا caching انجام میشود.
- در عملکرد بالا تعداد زیاد اعلانها باعث افزایش زمان پاسخ و تحمیل بار I/O بر DB میگردد.
- توصیه به اضافهکردن سیستم push یا asynchronous job (queue).
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Http\Request;
- use App\Models\Notification;
- use App\Helpers\Functions;
- use Carbon\Carbon;
کدهای خطا و پاسخهای غیرعادی
| کد خطا | شرح خطا | منبع |
| 400 | Missing or invalid input data | Validation مرحله ورود داده |
| 1006 | توکن JWT منقضی یا نادرست | auth middleware |
| 500 | Database Exception هنگام ثبت اعلان | DB::insert() |
پیشنهادهای امنیتی
- استفاده از Escaping در محتوای
content. - اعمال محدودیت سطح دسترسی ارسال اعلان به فقط
adminها یاsuper-operatorها. - اعمال rate-limit (۵ اعلان در دقیقه).
پیشنهادهای بهبود
- افزودن ستون
read_atبرای ثبت زمان خواندهشدن اعلان. - افزودن queue جهت ارسال همزمان به چند گیرنده.
- اضافهکردن Audit Log برای ثبت هویت ارسالکننده.
ممیزی و لاگها
- در نسخه فعلی هیچ لاگی برای ثبت ارسال اعلان در جدول
system_logsوجود ندارد. - پیشنهاد: استفاده از
SystemLog::dispatch('notification_created')هنگام ایجاد رکورد.
جمعبندی
POST /api/v2/dashboard
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/dashboard | V2BaseController@dashboard | authWithJwt | واکشی دادههای تحلیلی و وضعیت سرویسهای پایه برای صفحه داشبورد اپراتور |
منطق عملکرد تابع
- بررسی صحت اتصال با سرور مرکزی (hub یا service route) از طریق تابع اختصاصی اتصال.
- واکشی مقادیر و وضعیتهای کلیدی برای اپراتور فعلی.
- در صورت اتصال موفق: بازگرداندن تعداد آیتمهای فعال در فرمت
active. - در صورت شکست اتصال یا خطای ارتباطی، برگرداندن پیام
Lack of connection with the central server. - پوشش خطاهای Exception و بازگرداندن جزئیات آن در خروجی جهت بررسی سریع.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبهٔ جاری برای بررسی وضعیت اتصال و سرویسهای فعال |
| operator_id | integer | خیر | شناسه اپراتور برای ثبت یا تطبیق دادههای dashboard |
نمونه درخواست:
POST /api/v2/dashboard
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"branch": 12
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | وضعیت کلی ارتباط (true = اتصال موفق) |
| time | integer | زمان UNIX درخواست |
| active | integer | تعداد آیتمهای فعال در سیستم مرکزی |
| data[] | array | دادههای حاصل از سرویس مرکزی در صورت اتصال موفق |
| message | string | پیام خطا در صورت قطع ارتباط |
نمونه پاسخ:
{
"status": true,
"time": 1732289412,
"active": 7,
"data": [{"service":"train","status":"OK"},{"service":"accommodation","status":"OK"}]
}
نکات امنیتی و کنترل دسترسی
- به واسطه
authWithJwtفقط کاربران تأیید شده JWT مجاز به مشاهده dashboard هستند. - حاوی دادههای عملیاتی شعبه است، پس نباید برای کاربران خارج از محیط مدیریتی قابلدسترس باشد.
- فاقد رمزنگاری در پاسخ سرویس؛ در ارتباطات بینسرویس بهتر است از SSL داخلی استفاده شود.
نکات کارایی و Performance
- تابع سریع است ولی وابسته به latency ارتباط با سرور مرکزی.
- خطای موقت شبکه میتواند باعث تأخیر یا بازگشت پیام خطای "Lack of connection".
- پیشنهاد: پیادهسازی retry با backoff صعودی در صورت خطا برای نقطههای ارسال داده.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Http\Request;
- use Carbon\Carbon;
- use App\Helpers\Functions;
- use Exception;
کدهای خطا و پاسخهای Exception
| کد خطا | شرح خطا | منبع |
| 400 | Database یا ارتباط ناموفق با سرویس مرکزی | dashboard() |
| 1006 | JWT غیر معتبر | authWithJwt() |
| 500 | Exception عمومی در runtime | Exception handler |
پیشنهادهای امنیتی
- رمزنگاری دادههای آماری حساس با AES سمت سرور.
- محدودسازی مقدار بازگشتی برای جلوگیری از افشای وضعیت داخلی سرور.
- فیلتر خروجی بر اساس نقش کاربر (operator vs admin).
پیشنهادهای بهبود
- افزودن فیلد cache برای جلوگیری از خواندن مکرر در هر درخواست.
- افزودن latency tracker برای شناسایی نقاط ضعیف شبکه.
- نمایش شاخصهای uptime و CPU usage در نسخه بعدی.
ممیزی و لاگها
- در حال حاضر هیچ ممیزی برای ناکامی ارتباط ثبت نمیشود.
- پیشنهاد افزودن لاگ با نوع
DashboardConnectionFailedدر جدولsystem_logs.
جمعبندی
POST /api/v2/operator/details
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/operator/details | V2BaseController@operatorDetails | authWithJwt | دریافت جزئیات اپراتور شامل اطلاعات شعبه، نقشها، تنظیمات و وضعیت فعالسازی |
منطق عملکرد تابع
- واکشی اطلاعات از جدول
operatorsشامل نام، نامکاربری، ایمیل، سطح دسترسی و نقش. - تطبیق شناسه شعبه مربوطه از جدول
officesجهت دریافت نام و موقعیت مکانی. - بررسی تنظیمات مرتبط با پنل از جدول
settingsشامل تم، رنگ پایه، لوگو و favicon. - درصورت تعریف APIهای خارجی برای شعبه (SMS یا API مرکزی)، افزودن اطلاعات از
application_interface. - جمعبندی دادهها و بازگرداندن ساختار JSON نهایی.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبهای که اپراتور در آن فعالیت میکند |
| operator_id | integer | خیر | شناسه اپراتور (در JWT موجود است) |
نمونه درخواست:
POST /api/v2/operator/details
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"branch": 9
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | وضعیت موفقیت درخواست |
| meta.timestamp | integer | زمان پاسخ سرور |
| data.profile | object | اطلاعات شخصی و کاری اپراتور |
| data.office | object | مشخصات شعبه |
| data.settings | object | تم و تنظیمات رابط کاربری |
| data.apis | array | لیست APIهای فعال برای شعبه |
نمونه پاسخ:
{
"status": true,
"meta": { "timestamp": 1732290051 },
"data": {
"profile": {
"id": 51,
"name": "علیرضا ایرانپور",
"role": "operator",
"email": "a.iranpour88@gmail.com"
},
"office": {
"id": 9,
"name": "دفتر اصفهان",
"country": "IR"
},
"settings": {
"theme": "dark",
"base_color": "#212121",
"favicon": "/media/icons/favicon.ico"
},
"apis": [
{ "type": "sms", "service": "melipayamak", "status": 1 },
{ "type": "hub", "url": "https://hub.airplus.app/api/v2" }
]
}
}
نکات امنیتی
- وابسته به توکن JWT معتبر برای شناسایی اپراتور.
- هیچ فیلد رمز یا داده حساس رمزنگاری نشده نباید در پاسخ وجود داشته باشد.
- در ورژن فعلی endpoint سطح دسترسی اپراتور را بدون فیلتر نقش بازمیگرداند.
عملکرد و کارایی
- تابع سبک است و فقط از چند جدول کاربر و تنظیمات خوانش دارد.
- وابستگی به latency اتصال به DB اصلی شعبه در درخواستهای همزمان زیاد است.
- پیشنهاد: caching ۵ دقیقهای پروفایل هر اپراتور بر اساس JWT.
وابستگیها
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
- use App\Helpers\Functions;
- use Exception;
کدهای خطا
| کد | شرح خطا | منبع |
| 1006 | توکن JWT غیر معتبر یا منقضی شده | authWithJwt |
| 400 | داده ناقص یا branch نامعتبر | Input Validation |
| 500 | خطا در واکشی دادهها از DB | operatorDetails() |
پیشنهادهای امنیتی
- فیلتر نقش برای جلوگیری از مشاهده تنظیمات سایر اپراتورها.
- رمزنگاری سمت سرور برای فیلدهای حساس (email, username).
- ثبت audit برای هر مشاهده پروفایل.
پیشنهادهای بهبود
- افزودن فیلد
last_loginوactivity_statusبرای مانیتورینگ. - اضافهکردن بخش Roles و Permissions به خروجی JSON.
- ادغام با سرویس مرکزی احراز هویت (corporateAuth).
ممیزی و لاگها
- در نسخه فعلی هیچ لاگی برای مشاهده پروفایل ثبت نمیشود.
- پیشنهاد: ثبت رویداد
ProfileViewedدر جدولsystem_logs.
جمعبندی
POST /api/v2/management/office
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/management/office | V2BaseController@office | authWithJwt | واکشی مشخصات دفتر و سرویسهای مرتبط با شعبهٔ فعلی برای نمایش در پنل مدیریت |
منطق عملکرد تابع
- واکشی اطلاعات از جدول
officesبا استفاده ازbranch idدر درخواست JWT. - دریافت تنظیمات API از جدول
application_interfaceمخصوص سرویسهای فعال شعبه. - تجمیع دادهها و بازگشت در قالب JSON با زمان پاسخ و وضعیت موفقیت.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبه جهت دریافت اطلاعات دفتر |
نمونه درخواست:
POST /api/v2/management/office
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"branch": 7
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | وضعیت کلی درخواست |
| time | integer | زمان Unix پاسخ سرور |
| data.office | array | اطلاعات دقیق دفتر از جدول offices |
| data.api | array | لیست سرویسهای فعال در جدول application_interface شامل URL و نوع سرویس |
نمونه پاسخ:
{
"status": true,
"time": 1732290287,
"data": {
"office": [
{
"id": 7,
"en_title": "Isfahan Branch",
"fa_title": "دفتر مرکزی اصفهان",
"country": "IR",
"credit_limit": 20000000,
"base_online": 12000000
}
],
"api": [
{
"type": "sms",
"url": "https://soap.melipayamak.com",
"username": "isf-admin",
"status": 1
},
{
"type": "accounting",
"url": "https://hub.airplus.app/v2/accounting",
"status": 1
}
]
}
}
نکات امنیتی و احراز هویت
- احراز هویت حتماً باید توسط middleware
authWithJwtانجام شود. - در نسخه فعلی هیچ کنترل سطح دسترسی برای مشاهده دفاتر دیگر وجود ندارد.
- پیشنهاد: اعمال Role-based restriction (مانند ADMIN, FINANCE, SUPPORT).
عملکرد و Performance
- تابع سبک و سریع است زیرا فقط از دو جدول DB خوانش دارد.
- در بارهای سنگین یا محیطهای multi-branch، پیشنهاد میشود cache 5 دقیقهای برای دادههای دفتر فعال شود.
- در پاسخ فعلی دادهها بدون pagination بازگردانده میشوند.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Http\Request;
- use Carbon\Carbon;
- use Exception;
کدهای خطا
| کد | شرح خطا | منبع |
| 1006 | توکن JWT نامعتبر یا منقضی | authWithJwt |
| 400 | branch ارسال نشده | Input Validation |
| 500 | خطای داخلی DB یا Exception ناشناخته | office() |
پیشنهادهای امنیتی
- اعمال تعیین سطح دسترسی مشاهده به نقشهای مدیریتی.
- پنهانسازی فیلدهای حساس مانند شناسه کاربری و رمزهای اتصال API.
- رمزنگاری سمت سرور برای آیتمهای
api.usernameوapi.data.
پیشنهادهای بهبود
- افزودن فیلد
storage_usageبرای مدیریت فضا و فایلهای شعبه. - عدم نیاز به فراخوانی مستقیم DB در هر درخواست (استفاده از Redis cache).
- افزودن قابلیت سرچ و فیلتر بر اساس نوع سرویس در بخش api.
ممیزی و لاگها
- هیچ لاگی برای مشاهده اطلاعات دفتر ثبت نمیشود.
- پیشنهاد: ثبت رویداد
BranchViewedدر جدولsystem_logsهمراه با IP و User-Agent.
جمعبندی
GET /api/v2/panel/groups/get
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/panel/groups/get | V2BaseController@smsPanelGetGroups | authWithJwt | دریافت گروههای دفترچه مخاطبین سرویس پیامک فعال (SMS Panel) برای شعبه فعلی |
منطق عملکرد تابع
- ابتدا اطلاعات اتصال از جدول
application_interfaceواکشی میشود (فیلدهایservice،username،passwordوdata->sender). - در صورت یافتن تنظیمات معتبر، شیء API با
MelipayamakApiساخته میشود و به متدcontacts()متصل میگردد. - با فراخوانی متد
getGroups()، لیست گروههای موجود در دفترچه مخاطبین واکشی میشود. - خروجی به آرایهٔ قابلخواندن شامل شناسه گروه، عنوان، توضیحات، وضعیت، تعداد مخاطبین و اطلاعات زیرگروه تبدیل میگردد.
- در صورت عدم یافتن تنظیمات معتبر یا وجود خطا، مسیر مقدار خالی یا خطای سطح ۴۰۰ بازمیگرداند.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبه جهت تعیین سرویس فعال پیامک |
نمونه درخواست:
GET /api/v2/panel/groups/get
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
?branch=5
خروجی (Response)
| فیلد | نوع داده | توضیح |
| id | integer | شناسه یکتا گروه مخاطبین |
| user_id | integer | شناسه کاربر در سرویس پیامک |
| parent | integer|null | شناسه زیرگروه یا گروه مادر |
| title | string | عنوان گروه |
| description | string | توضیحات گروه |
| count | integer | تعداد کل مخاطبین موجود در گروه |
| status | integer | وضعیت فعال بودن گروه (۱=فعال، ۰=غیرفعال) |
| child.show | boolean | نمایش زیرگروهها برای کودکها (true/false) |
| child.count | integer | تعداد زیرگروهها |
نمونه پاسخ:
[
{
"id": 122,
"user_id": 931,
"parent": null,
"title": "مشتریان ویژه",
"description": "VIP Contacts Reserved",
"count": 312,
"status": 1,
"child": {
"show": true,
"count": 4
}
},
{
"id": 127,
"user_id": 931,
"parent": 122,
"title": "آژانس همکاران",
"description": "",
"count": 151,
"status": 1,
"child": {
"show": false,
"count": 0
}
}
]
نکات امنیتی
- وابسته به JWT معتبر و فعال بودن سرویس پیامکی شعبه.
- در صورت نبود سرویس پیامک برای شعبه، پاسخ خالی یا کد خطای ۴۰۴ باید بازگردد.
- عدم رمزنگاری داده خروجی بهدلیل عمومی بودن داده مخاطبین (اما بهتر است برای دادههای شرکت خصوصی رمزگذاری شود).
عملکرد و Performance
- تابع سبک است اما وابسته به latency سرویس SOAP خارجی (Melipayamak).
- پاسخ معمولاً طی ۱ تا ۳ ثانیه بسته به حجم گروهها بازگردانده میشود.
- پیشنهاد: cache گروهها در Redis برای مدت ۱۰ دقیقه جهت جلوگیری از تکرار درخواست به سرویس خارجی.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use App\Lib\MelipayamakApi;
- use Illuminate\Http\Request;
- use Exception;
کدهای خطا
| کد | شرح خطا | منبع |
| 1006 | توکن JWT نامعتبر یا منقضی شده | authWithJwt |
| 404 | تنظیمات سرویس پیامک برای این شعبه یافت نشد | smsPanelGetGroups() |
| 500 | خطای اتصال با سرویس Melipayamak یا پاسخ نامعتبر SOAP | MelipayamakApi::contacts() |
پیشنهادهای امنیتی
- ماسک کردن اطلاعات کاربر سرویس پیامک (مانند password) در سطح API داخلی.
- افزودن audit برای هر فراخوانی دفترچه مخاطبین از پنل.
- استفاده از اتصال HTTPS برای ارتباط مستقیم با Melipayamak API.
پیشنهادهای بهبود
- افزودن endpoint مکمل برای واکشی اعضای هر گروه (GET /panel/groups/{group_id}/members).
- نمایش تعداد پیامهای ارسالشده یا وضعیت فعال در خروجی.
- امکان فیلتر بر اساس عنوان، وضعیت و تاریخ ایجاد گروه.
ممیزی و لاگها
- در نسخه فعلی لاگ ثبت درخواست انجام نمیشود.
- پیشنهاد: ذخیرهی داده تماس در جدول
system_logsبا نوع رخدادViewSMSGroups.
جمعبندی
GET /api/v2/panel/bulk/receptions
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/panel/bulk/receptions | V2BaseController@smsPanelGetBulkReceptions | authWithJwt | دریافت لیست پیامهای انبوه ارسالشده در پنل پیامک فعال برای شعبه فعلی |
منطق عملکرد تابع
- با استفاده از تنظیمات موجود در جدول
application_interfaceسرویسsmsفعال شعبه یافت میشود. - متد
getBulkReceptions()از کلاس SDKMelipayamakApiفراخوانی شده و فهرست پیامهای گروهی بازگردانده میشود. - هر پیام به آرایهای شامل شناسه، فرستنده، متن، تعداد گیرندگان، زمان ارسال و درصد موفقیت تبدیل میگردد.
- در نهایت، دادهها به صورت JSON با وضعیت موفقیت و زمان یونیکس بازگردانده میشوند.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبه جهت بازیابی تنظیمات سرویس پیامکی |
نمونه درخواست:
GET /api/v2/panel/bulk/receptions
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
?branch=5
خروجی (Response)
| فیلد | نوع داده | توضیح |
| bulk_id | integer | شناسه یکتا کمپین ارسال پیامک گروهی |
| sender | string | شماره فرستنده پیام |
| text | string | متن پیام ارسالشده |
| count | integer | تعداد کل گیرندگان پیام |
| delivered | integer | تعداد پیامهای موفق تحویلشده |
| failed | integer | تعداد پیامهای ناموفق یا ردشده |
| datetime | string | زمان ارسال کمپین (فرمت ISO) |
| success_rate | float | نسبت موفقیت تحویل پیامها (درصد) |
نمونه پاسخ:
{
"status": true,
"meta": { "timestamp": 1732290419 },
"items": [
{
"bulk_id": 9021,
"sender": "5000400008851",
"text": "تخفیف ویژه پروازهای داخلی فقط امروز!",
"count": 1320,
"delivered": 1287,
"failed": 33,
"datetime": "2025-11-22T08:15:00Z",
"success_rate": 97.5
},
{
"bulk_id": 8904,
"sender": "5000400008851",
"text": "اعلان تغییر قوانین رزرو آنلاین",
"count": 698,
"delivered": 684,
"failed": 14,
"datetime": "2025-11-20T06:43:00Z",
"success_rate": 97.9
}
]
}
نکات امنیتی
- وابسته به اعتبار توکن JWT و سطح دسترسی پیامکی شعبه.
- اطلاعات پیامهای انبوه ممکن است شامل متنهای حساس یا تبلیغاتی باشد — پیشنهاد رمزنگاری ذخیرهٔ محلی.
- فیلتر نقش در نسخه فعلی وجود ندارد؛ هر اپراتور شعبه میتواند کل فهرست را مشاهده کند.
عملکرد و کارایی
- تابع سریع اما وابسته به latency سرویس خارجی.
- در فراخوانیهای متوالی توصیه میشود cache محدود Redis برای مدت ۵ دقیقه ایجاد گردد.
- مدت پاسخ معمولی بین ۲ تا ۳ ثانیه (بسته به حجم داده).
وابستگیها
- use App\Lib\MelipayamakApi;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Http\Request;
- use Exception;
کدهای خطا
| کد | شرح خطا | منبع |
| 1006 | توکن JWT نامعتبر یا منقضی شده | authWithJwt |
| 404 | عدم یافتن تنظیمات سرویس پیامک برای شعبه | smsPanelGetBulkReceptions() |
| 500 | خطای پاسخ از سرویس Melipayamak یا SOAP Data Invalid | MelipayamakApi::getBulkReceptions() |
پیشنهادهای امنیتی
- اعمال Role-based Access فقط برای کاربرانی با نقش
sms_manager. - ثبت لاگ برای هر مشاهده داده پیامک گروهی (type:
ViewBulkSMS). - پنهانسازی متن پیام در حالت عمومی؛ فقط در حالت administrative نمایش داده شود.
پیشنهادهای بهبود
- افزودن فیلدهای
costوprovider_response_timeبرای تحلیل اقتصادی ارسال پیامها. - افزودن endpoint فیلتر بر اساس بازه زمانی و درصد موفقیت.
- ایجاد Caching مبتنی بر Redis با prefetch خودکار دادهها برای شعب فعال.
ممیزی و لاگها
- در نسخه فعلی هیچ ممیزی خروجی برای مشاهده پیامهای انبوه انجام نمیشود.
- پیشنهاد: ثبت در جدول
system_logsبا رویدادBulkSMSViewed.
جمعبندی
POST /api/v2/panel/bulk/add
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/panel/bulk/add | V2BaseController@smsPanelAddBulk | authWithJwt | ارسال پیامکهای انبوه به گروه یا فهرست مخاطبین تعیینشده از سمت پنل پیامکی شعبه |
منطق عملکرد تابع
Melipayamak فرآیند ارسال انبوه پیامک را انجام میدهد:
- اطلاعات اتصال سرویس از جدول
application_interfaceبرای نوعsmsو شعبه فعلی استخراج میشود. - با ساخت شیء
new MelipayamakApi($username, $password)به توابعsend()یاbulkSend()متصل میشود. - پارامترهای ورودی مانند شماره ارسال، متن پیام و شناسه گروهها در بدنه درخواست تجمیع میشود.
- در صورت موفقیت، شناسهٔ کمپین (bulk_id) از سمت سرویس دریافت و در جدول لاگ داخلی (در صورت وجود) ذخیره میگردد.
- در صورت خطا، کد و پیام خطای SOAP در خروجی JSON بازگردانده میشود.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| branch | integer | بله | شناسه شعبه جهت شناسایی سرویس فعال پیامک |
| text | string | بله | متن پیام قابل ارسال به مخاطبین |
| sender | string | خیر | شماره فرستنده (پیشفرض از تنظیمات سرویس استخراج میشود) |
| contacts | array[int] | بله | آرایهای از شمارهها یا شناسههای گروه مخاطبین برای ارسال |
| scheduleTime | string | خیر | زمانبندی ارسال در صورت نیاز (فرمت ISO یا Jalali) |
نمونه درخواست:
POST /api/v2/panel/bulk/add
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"branch": 5,
"text": "🎉 پروازهای ویژه نوروز با تخفیف تا ۳۰٪!",
"contacts": [ "09351234567", "09125554433" ],
"scheduleTime": "2025-03-01T10:00:00Z"
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | موفقیت یا شکست عملیات ارسال |
| bulk_id | integer | شناسه کمپین ارسال (توسط سرویس پیامکی تولید میشود) |
| meta.timestamp | integer | زمان یونیکس درخواست یا پاسخ |
| error | object|null | در صورت شکست، شامل پیام و کد خطا است |
نمونه پاسخ موفق:
{
"status": true,
"bulk_id": 1115,
"meta": {
"timestamp": 1732290514
}
}
نمونه پاسخ خطا:
{
"status": false,
"error": {
"code": 500,
"message": "Error connecting to Melipayamak API"
},
"meta": {
"timestamp": 1732290514
}
}
نکات امنیتی
- ارسال فقط برای شعبه معتبر بر اساس JWT امکانپذیر است.
- خطر ارسال پیامهای غیرمجاز توسط اپراتورهای شعبه بدون نقش محدودکننده وجود دارد.
- باید مکانیزم
RoleRestriction(sms_sender)فعال شود تا فقط کاربران مجاز بتوانند از این endpoint استفاده کنند.
نکات عملکردی
- ارسال دستهای بیش از 1000 شماره باعث تاخیر 5–7 ثانیهای در پاسخ میشود.
- پیشنهاد: صفبندی درخواستها با
Queue::dispatch(SMSBulkJob)برای کاهش latency. - در حال حاضر caching Redis وجود ندارد، ولی برای گزارشهای تکراری بهتر است اضافه شود.
وابستگیها
- use App\Lib\MelipayamakApi;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Http\Request;
- use Exception;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 1006 | توکن JWT نامعتبر یا منقضی شده | authWithJwt |
| 2001 | درخواست فاقد شماره ارسال معتبر | smsPanelAddBulk() |
| 500 | خطای ارتباط یا پاسخ نامعتبر از سرویس Melipayamak | MelipayamakApi::bulkSend() |
پیشنهادهای امنیتی
- لاگکردن هر کمپین ارسالی با
SystemLog::dispatch(["type" => "BulkSMSSent", ...]). - رمزنگاری متون پیام قبل از ذخیره در دیتابیس داخلی.
- افزودن کنترل ضد هرزنامه (anti-spam) برای جلوگیری از ارسال انبوه در بازه کوتاه.
پیشنهادهای بهبود
- افزودن پارامتر
bulk_labelبرای نامگذاری کمپینها در گزارشات مدیریتی. - افزودن حالت test-mode برای ارسال به تعداد محدود مخاطب جهت تست.
- افزودن پاسخ تکمیلی با تعداد پیام موفق و ناموفق.
ممیزی و لاگها
- پیشنهاد ثبت هر ارسال در جدول
sms_auditبا فیلدهایbranch, bulk_id, sender, count, status. - در نسخه فعلی لاگ internal فعال نیست.
جمعبندی
POST /countries-cities/get
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/countries-cities/get | V2BaseController@getCountiesOrCities | authWithJwt | دریافت لیست کشورها یا شهرها براساس نوع درخواست (`type`)، با امکان جستوجو و فیلتر وابسته به شناسه والد |
منطق عملکرد تابع
type (مقدار country یا city) تغییر میکند:
- ابتدا مقدار
typeبررسی میشود تا مشخص شود دادههای کشوری یا شهری باید خوانده شود. - در صورت حضور
parent_id، فقط شهرهای مربوط به آن کشور واکشی میشوند. - در صورت ارسال فیلد
search، شرط فیلتر با عملگرLIKEروی کلیدهای نامهای فارسی و انگلیسی اجرا میشود. - نتیجه نهایی بهصورت آرایهای از اشیاء شامل شناسه، عنوان فارسی/انگلیسی و والد (در صورت وجود) بازگردانده میشود.
- در صورت خطا یا فقدان داده، پاسخ با وضعیت `status:false` و شرح خطا برگردانده میشود.
ورودیها (Request Fields)
| نام فیلد | نوع داده | الزامی | توضیح |
| type | string | بله | مشخصکننده سطح داده مورد نیاز: مقدار country برای دریافت کشورها و city برای شهرها. |
| parent_id | integer | خیر | در حالت city، شناسه کشور والد جهت محدودسازی نتایج. |
| search | string | خیر | عبارت جستوجو جهت فیلتر نام شهر یا کشور. |
| branch | integer | بله | شناسه شعبه برای اعتبارسنجی JWT و سطح دسترسی داده. |
نمونه درخواست:
POST /api/v2/countries-cities/get
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"type": "city",
"parent_id": 118,
"search": "تهران",
"branch": 5
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | وضعیت موفقیت عملیات |
| items[].id | integer | شناسه یکتای کشور یا شهر |
| items[].title.fa | string | نام فارسی کشور یا شهر |
| items[].title.en | string | نام انگلیسی کشور یا شهر |
| items[].category.title.fa | string|null | نام فارسی کشور والد (برای شهرها) |
| meta.timestamp | integer | زمان تولید پاسخ (یونیکس) |
نمونه پاسخ موفق:
{
"status": true,
"items": [
{
"id": 548,
"title": { "fa": "تهران", "en": "Tehran" },
"category": { "title": { "fa": "ایران", "en": "Iran" } }
}
],
"meta": { "timestamp": 1750668858 }
}
نمونه پاسخ خطا:
{
"status": false,
"error": { "code": 400, "message": "پارامتر type نامعتبر است." },
"meta": { "timestamp": 1750668869 }
}
نکات امنیتی
- اعتبارسنجی JWT از طریق middleware
authWithJwtانجام میشود. - در صورت ارسال type=city، باید چک شود که شعبه کاربر مجاز به دسترسی داده کشور والد باشد.
- در نسخه فعلی کنترل سطح دسترسی برای cross-branch وجود ندارد و باید در Enterprise اضافه شود.
نکات عملکردی
- پاسخ بدون pagination است و ممکن است در حالت city حجم بالا داشته باشد.
- پیشنهاد: ذخیره cached دادهها در Redis با کلیدهای
geo:countriesوgeo:cities:{countryId}. - TTL توصیهشده برای دادههای استاتیک: 3600 ثانیه.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Http\Request;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 1006 | توکن JWT منقضی یا نامعتبر | authWithJwt |
| 1001 | پارامتر type نامعتبر یا خالی است | getCountiesOrCities() |
| 500 | خطا در واکشی دادهها از پایگاهداده | DB::table('countries') |
پیشنهادهای امنیتی
- اضافه کردن کنترل سطح شعبه برای جلوگیری از تزریق parent_id غیرمجاز.
- ثبت لاگ در SystemLog با نوع
ReadGeoDataبرای هر درخواست. - افزودن rate-limit برای جلوگیری از دسترسی مکرر و حملات enumeration.
پیشنهادهای بهبود
- افزودن صفحهبندی (pagination) برای تعداد زیاد شهرها.
- پشتیبانی از پارامتر
localeبرای ترجمه عنوانها بر اساس زبان کاربر. - افزودن فیلد
iso_codeبرای کشورها جهت استانداردسازی.
ممیزی و لاگها
- لاگکردن درخواست هر کاربر شامل فیلدهای
operator_id،branch_idوtype. - سطح لاگ توصیهشده: Info.
جمعبندی
GET /api/v2/settings/index/{type}
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/settings/index/{type} | V2BaseController@settingsIndex | authWithJwt | دریافت تنظیمات شاخه، شامل کشور، استان و شهر مربوط به شعبه، براساس نوع تنظیم {type}. |
منطق عملکرد تابع
{type} را از URL دریافت کرده و بر اساس آن، تنظیمات اصلی شعبه را از جدول offices واکشی میکند. اگر مقدار type برابر با “branch” باشد، سه سطح داده جغرافیایی کشور، استان و شهر از جداول پایگاهداده خوانده میشود:
- ابتدا رکورد شعبه از
officesواکشی میشود. - در صورت داشتن مقدار
country، تلاش میکند داده را از Redis با کلیدcountries:{id}بخواند؛ در صورت نبود، از DB بازیابی و cache میکند. - در ادامه
stateوcityنیز از جداول ایالتی و شهری دریافت میشوند. - در پایان دادهها در قالب JSON دارای شناسه، نام فارسی و انگلیسی هر سطح برگردانده میشوند.
پارامترهای ورودی
| نام پارامتر | نوع داده | منبع | الزامی | توضیح |
| type | string | URL Path | بله | نوع تنظیم مورد نیاز (مثلاً branch). |
| branch | integer | JWT Payload | بله | شناسه شعبه جهت واکشی اطلاعات مرتبط از جدول offices. |
نمونه درخواست:
GET /api/v2/settings/index/branch
Authorization: Bearer {JWT_TOKEN}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| country.title.fa | string | نام فارسی کشور |
| country.title.en | string | نام انگلیسی کشور |
| state.title.fa | string | نام فارسی استان |
| city.title.fa | string | نام فارسی شهر |
| status | boolean | وضعیت موفقیت عملیات |
| meta.timestamp | integer | زمان تولید پاسخ (یونیکس) |
نمونه پاسخ موفق:
{
"status": true,
"settings": {
"country": {
"title": { "fa": "ایران", "en": "Iran" },
"id": 118, "iso": "IR"
},
"state": {
"id": 31,
"title": { "fa": "یزد", "en": "Yazd" }
},
"city": {
"id": 548,
"title": { "fa": "احمدآباد", "en": "Ahmadabad" }
}
},
"meta": { "timestamp": 1750668858 }
}
نکات امنیتی
- تأیید هویت با middleware
authWithJwtانجام میشود. - نباید اجازه داد کاربران سایر شعب به تنظیمات شعبهای دسترسی داشته باشند.
- در نسخه فعلی، کنترل سطح دسترسی بین شعبهای (`branch cross-access`) پیادهسازی نشده است.
نکات عملکردی
- در هر درخواست سه کوئری به جداول
countries،statesوcitiesاجرا میشود. - پیشنهاد: کش Redis برای هر سه سطح با TTL حداقل 6 ساعت.
- درخواست GET به Redis قبل از DB برای کاهش latency.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Illuminate\Http\Request;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 1006 | توکن JWT نامعتبر یا منقضی شده | authWithJwt |
| 1002 | نوع تنظیم {type} نامعتبر یا پشتیبانینشده است | settingsIndex() |
| 500 | خطا در اتصال به Redis یا پایگاهداده | DB/Redis |
پیشنهادهای امنیتی
- اضافهکردن Role-based Validation برای اپراتورهای هر شعبه.
- ثبت لاگ دسترسی به تنظیمات در SystemLog با نوع
ReadSettings. - افزودن Rate-limit برای درخواستهای GET متوالی از یک JWT.
پیشنهادهای بهبود
- افزودن پشتیبانی از نوعهای تنظیم دیگر (
hub،application،office). - افزودن ساختار Metadata شامل زمان آخرین بروزرسانی.
- نمایش پرچم یا نماد کشور در پاسخ برای UX بهتر در فرانتاند.
ممیزی و لاگها
- ثبت لاگ هر درخواست در جدول
system_logبا فیلدهایoperator_id،branch_idوtype. - سطح لاگ توصیهشده: Info برای درخواستهای موفق و Warning برای شکستهای Redis.
جمعبندی
PUT /settings/index/{type}
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| PUT | /api/v2/settings/index/{type} | V2BaseController@settingsUpdate | authWithJwt | بهروزرسانی تنظیمات شاخه موردنظر (نوع مشخصشده در پارامتر {type}) و ذخیره در دیتابیس مربوطه. |
منطق عملکرد تابع
{type} از URL مسیر، داده جدید تنظیمات شاخه (Branch) را بر اساس نوع تنظیم ذخیره میکند. منطق داخلی تابع بر پایه نوع تنظیم به سه شاخه تقسیم میشود:
- type = "application" → بروزرسانی تنظیمات UI/Theme شعبه در جدول
offices. - type = "accounting" → ثبت دادههای مالی و محدودیتهای اعتبار در جدول
accounting_titles. - type = "hub" → بهروزرسانی مارکاپهای سطحی در جدول
hub_markupsبا استفاده از متدupdateOrInsert().
branch) برمیگردند تا فرانتاند از صحت اعمال تغییرات اطمینان حاصل کند.ورودیها
| نام پارامتر | نوع داده | منبع | الزامی | توضیح |
| type | string | URL Path | بله | نوع تنظیم موردنظر (application, accounting, hub). |
| branch | integer | JWT Payload | بله | شناسه شعبه فعال برای اعمال تنظیمات. |
| markups | array | Request Body | اختیاری (در hub) | آرایه دستهبندی مارکاپها در سطوح و نوعهای مختلف پرواز و اقامت. |
| theme / style / base_color | string | Request Body | اختیاری (در application) | ویژگیهای ظاهری واسط کاربری تنظیمات شعبه. |
نمونه درخواست:
PUT /api/v2/settings/index/hub
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"markups": {
"flight": [
{
"level": 1,
"markups": {
"airplus": { "type": "percent", "value": 4, "discount": { "type": "percent", "value": 1 } },
"accommodation": { "type": "currency", "value": 50000, "discount": { "type": "currency", "value": 0 } }
}
}
]
}
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| status | boolean | نتیجه موفقیت عملیات. |
| meta.timestamp | integer | زمان یونیکس پاسخ API. |
| updated | object | مقادیر تنظیمات بروزرسانیشده برای شعبه. |
نمونه پاسخ موفق:
{
"status": true,
"updated": {
"branch": 5,
"type": "hub",
"count": 4,
"meta": {
"timestamp": 1750668952
}
}
}
نکات امنیتی
- فقط کاربران شعبه میتوانند تنظیمات همان شعبه را تغییر دهند.
- پارامتر
branchباید از JWT خوانده شود نه از بدنه درخواست. - در مسیر PUT نباید مقادیر markups خالی ارسال شود — در غیر این صورت سطوح قبلی پاک میشوند.
نکات عملکردی
- متد
updateOrInsert()برای درج یا بروزرسانی رکوردهایhub_markupsاستفاده میشود — تعداد کوئریها به ازای هر سطح برابر با تعداد آیتمهای markups است. - پیشنهاد: فعالسازی کش Redis برای دادههای UI شاخه (type=application).
- بهتر است TTL کش برای دادههای hub حداقل ۱۲ ساعت باشد، چون تنظیم مارکاپها کمتغییرند.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Http\Controllers\CronController;
کدهای خطا
| کد | شرح خطا | منبع |
| 404 | نوع تنظیم {type} نامعتبر یا دادههای ارسالی ناقص است. | settingsUpdate() |
| 500 | خطا در اتصال به Redis یا در عملیات DB. | DB Facade |
| 400 | درخواست فاقد احراز هویت JWT معتبر. | authWithJwt Middleware |
پیشنهادهای امنیتی
- افزودن فیلتر سطح دسترسی بر اساس Role برای اپراتورها (مثلاً فقط مدیران شعبه بتوانند PUT انجام دهند).
- ثبت عملیات تغییر تنظیمات در لاگ با سطح
AdminWrite. - رمزنگاری مقادیر حساس در تنظیمات حسابداری.
پیشنهادهای بهبود
- تفکیک endpoint برای هر نوع تنظیم به صورت مستقل (مثلاً `/settings/hub`, `/settings/application`).
- اضافه کردن Validation سمت سرور با Laravel Validation Rules برای هر بخش تنظیم.
- افزودن خروجی diff-resolved جهت بررسی تغییرات هنگام بروزرسانی.
ممیزی و لاگها
- ثبت لاگ با نوع
UpdateSettings، شامل فیلدهایoperator_id،branch_id،typeو مقدارdiff. - پیشنهاد سطح لاگ: Admin برای تغییرات نوع hub، Info برای application.
جمعبندی
GET /base/accommodations/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/base/accommodations/list | V2BaseController@accommodationsList | authWithJwt | دریافت لیست اقامتگاهها (هتلها) براساس فیلترهای جغرافیایی و ویژگیهای پیشرفته، همراه با جزئیات تأمینکننده و رسانه. |
منطق عملکرد تابع
r) اعمال کرده و نتیجه را از جدول hotels استخراج میکند. منطق کلی تابع:
- خواندن پارامتر JSON از بدنه درخواست:
$Data = json_decode($request->get('json')); - مدیریت صفحهبندی با فیلدهای
startوlength. - اعمال فیلترهای چندلایه (کشور، استان، شهر، درجه و عبارت جستجو).
- دریافت اقامتگاههای فعال
where('status', 1)و مرتبسازی نزولی براساس شناسه. - برای هر اقامتگاه، دادههای کشور، شهر و تأمینکننده از Redis یا DB واکشی میشوند.
- ساخت پاسخ شامل جزئیات لوکیشن، آدرس، درجه، وضعیت، و مسیر رسانه.
پارامترهای ورودی
| نام پارامتر | نوع داده | منبع | الزامی | توضیح |
| json | string (JSON) | Body | بله | شامل فیلدهای صفحهبندی و فیلترها. |
| advanced.country | integer | json → advanced | اختیاری | شناسه کشور فیلترشده. |
| advanced.state | integer | json → advanced | اختیاری | شناسه استان فیلترشده. |
| advanced.city | integer | json → advanced | اختیاری | شناسه شهر فیلترشده. |
| advanced.rate | integer | json → advanced | اختیاری | امتیاز کیفیت اقامتگاه. |
| advanced.r | string | json → advanced | اختیاری | عبارت جستجوی عمومی (نام یا آدرس هتل). |
| length | integer | json | بله | تعداد سطر در هر صفحه. |
| start | integer | json | بله | شماره شروع صفحه. |
| draw | integer | json | اختیاری | شناسه داخلی برای درخواستهای Datatable. |
نمونه درخواست:
GET /api/v2/base/accommodations/list
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"json": {
"length": 10,
"start": 0,
"draw": 1,
"advanced": {
"country": 118,
"state": 31,
"city": 548,
"rate": 5,
"r": "مشهد"
}
}
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| items[].id | integer | شناسه داخلی اقامتگاه. |
| items[].title_fa / title | string | نام فارسی و انگلیسی اقامتگاه. |
| items[].rate | integer | امتیاز عددی هتل. |
| items[].country | object | دادههای کشور مقصد، شامل نام فارسی و کد ISO. |
| items[].state | string | نام استان مرتبط. |
| items[].city | string | نام شهر مرتبط. |
| items[].address | string | آدرس هتل. |
| items[].logo | string | مسیر رسانه لوگوی اقامتگاه. |
| items[].supplier | object | اطلاعات تأمینکننده (colleague mapping). |
| meta.recordsTotal | integer | تعداد کل رکوردها. |
| meta.recordsFiltered | integer | تعداد فیلترشده. |
| meta.timestamp | integer (unix) | زمان تولید پاسخ. |
نمونه پاسخ موفق:
{
"items": [
{
"id": 102,
"title_fa": "هتل پارس مشهد",
"rate": 5,
"country": { "id": 118, "fa_name": "ایران", "iso": "IR" },
"state": "خراسان رضوی",
"city": "مشهد",
"address": "خیابان امام رضا",
"logo": "https://storage.service01.ir/media/hotels/pars.jpg",
"supplier": { "system_serial": 11, "serial": "CLG992" }
}
],
"meta": { "timestamp": 1750669000, "recordsTotal": 150, "recordsFiltered": 10 }
}
نکات امنیتی
- دسترسی فقط با JWT معتبر امکانپذیر است.
- فیلترهای فیلد قابل تزریق SQL نیستند، چون از
where()وpaginate()ایمن استفاده شده. - در حال حاضر کنترل سطح دسترسی برای شعب مختلف تعریف نشده، پیشنهاد اضافه گردد.
نکات عملکردی
- Redis برای کشکردن نام کشورها استفاده میشود (کلید
countries:{id}). - بهتر است کش شهرها نیز افزوده شود تا فشار به DB کاهش یابد.
- در هر فراخوانی، بیش از ۳ جدول مرتبط (hotels، cities، states، colleagues) درگیر هستند.
- پیشنهاد TTL کش = 4 ساعت.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Models\Media;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | فرمت نادرست JSON یا فیلدهای ناقص. | Decoder JSON |
| 404 | هیچ اقامتگاهی برای فیلترهای واردشده یافت نشد. | Query hotels |
| 500 | خطا در اتصال Redis یا پایگاه داده. | DB Facade / Redis |
پیشنهادهای امنیتی
- فیلتر دسترسی مبتنی بر نقش (RBAC) برای شعب مختلف.
- اضافه کردن rate-limit برای درخواست لیست متوالی.
- پنهانسازی مسیر انبار رسانه هنگام پاسخ.
پیشنهادهای بهبود
- افزودن ویژگی مرتبسازی پویا براساس امتیاز یا کشور.
- نمایش دادههای تأمینکننده با نماد و نام تجاری.
- افزودن قابلیت فیلتر براساس وضعیت فعال/غیرفعال.
- پشتیبانی از lazy-loading برای مدیا (logo).
ممیزی و لاگها
- هر درخواست در جدول
system_logبا نوعReadAccommodationListثبت شود. - مقادیر چون
branch_id،filtersوoperator_idباید در لاگ ذخیره شوند. - سطح لاگ توصیهشده: Info.
جمعبندی
POST /base/accommodation/rooms/delete
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/base/accommodation/rooms/delete | V2BaseController@accommodationRoomsDelete | authWithJwt | حذف گروهی انواع اتاقهای اقامتگاه و نگاشتهای مربوطه از جداول سیستم. |
منطق عملکرد تابع
accommodation_id واکشی میکند، سپس نگاشتهای تبعی در جدول mapping_roomtypes را پاک کرده و در نهایت خود رکوردهای اتاقهای اقامتگاه را حذف میکند. منطق کلی:
- دریافت ورودی
accommodation_idو اختیاریservice. - ایجاد کوئری داخلی با
JOINبین جداولaccommodation_roomtypesوmapping_roomtypes. - در صورت وجود پارامتر
service، فقط اتاقهایی حذف میشوند که دارای مقدار در آن سرویس باشند. - پس از حذف از دو جدول، پاسخ موفق شامل وضعیت بولی
payload=trueبازگردانده میشود.
ورودیها
| نام پارامتر | نوع داده | منبع | الزامی | توضیح |
| accommodation_id | integer | Body | بله | شناسه اقامتگاه مقصد برای حذف اتاقها. |
| service | string | Body | اختیاری | نام سرویس (مثلاً snapptrip) برای اعمال حذف محدود. |
نمونه درخواست:
POST /api/v2/base/accommodation/rooms/delete
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"accommodation_id": 1870,
"service": "snapptrip"
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| payload | boolean | نمایانگر موفقیت حذف رکوردها. |
| meta.timestamp | integer | زمان یونیکس ثبت پاسخ. |
نمونه پاسخ موفق:
{
"payload": true,
"meta": {
"timestamp": 1750669152
}
}
نکات امنیتی
- مسیر فقط با احراز هویت JWT معتبر قابل دستیابی است.
- تمام رکوردها فقط از شعبه مربوطه حذف میشوند در صورت اعمال کنترل مرکزی (پیشنهاد: اضافه شود).
- بدون تأیید پیششرط، حذف دادهها خطرناک است — نیاز به اعتبارسنجی نقش (Role).
نکات عملکردی
- عملیات
delete()همزمان روی دو جدول انجام میگیرد، بنابراین هزینه I/O دو برابر معمول است. - پیشنهاد میشود حذفها در تراکنش
DB::transaction()انجام گیرند تا از ناسازگاری داده جلوگیری شود. - TTL کش مربوطه (Redis) برای کلید
accommodations:cron:services:{service}:{accommodation_id}باید پس از حذف ریست شود.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use Exception;
کدهای خطا
| کد | شرح خطا | منبع |
| 404 | شناسه اقامتگاه وجود ندارد یا خالی است. | accommodationRoomsDelete() |
| 403 | کاربر مجاز به حذف رکورد نیست. | authWithJwt |
| 500 | خطا در حذف رکورد از DB. | DB Facade |
پیشنهادهای امنیتی
- افزودن ROLE "DataManager" برای کنترل سطح حذف.
- لاگگذاری حذفها در جدول audit به همراه شناسه اقامتگاه و شناسه اپراتور.
- اضافه کردن تأیید دو مرحلهای برای حذفهای گسترده.
پیشنهادهای بهبود
- استفاده از SoftDelete برای حفظ تاریخچه رکوردهای اتاق.
- ارسال پاسخ تفکیکی شامل تعداد رکوردهای حذفشده.
- امکان فیلتر حذف بر اساس وضعیت فعال یا سرویس خاص.
- اتصال حذفها به فرآیند Cron برای بهروزرسانی خدمات خارجی.
ممیزی و لاگها
- ثبت لاگ با نوع
DeleteRoomType. - فیلدهای پیشنهادی:
operator_id،branch،accommodation_id،service. - سطح لاگ توصیهشده: Critical.
جمعبندی
POST /base/accommodation/room/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/base/accommodation/rooms/update | V2BaseController@accommodationRoomsUpdate | authWithJwt | بروزرسانی نگاشت (Mapping) و اطلاعات اتاقهای اقامتگاه از طریق سرویسهای خارجی نظیر SnappTrip. |
منطق عملکرد تابع
snapptrip. پس از حذف اتاقهای قبلی از جداول accommodation_roomtypes و mapping_roomtypes، کش Redis مربوطه تخلیه شده و از طریق CronController::updateSnapptripMappedAccommodation() عمل بازسازی انجام میشود. در سایر سرویسها، تابع با خطای 404 پاسخ میدهد.
- دریافت متغیر
serviceوaccommodation_id. - بررسی شرط: اگر سرویس برابر با
snapptripباشد → ادامهی عملیات. - واکشی شناسه اتاقهای مرتبط از دو جدول با استفاده از
JOIN. - حذف رکوردها از هر دو جدول.
- پاکسازی کش Redis مربوط به اقامتگاه هدف.
- فراخوانی بروزرسانی CronController و بازگرداندن نتیجه آن در قالب کلید
payload. - در غیر این صورت، بازگشت خطای
404 Not Supported Service.
ورودیها
| نام پارامتر | نوع داده | منبع | الزامی | توضیح |
| accommodation_id | integer | Body | بله | شناسه اقامتگاه مقصد که اتاقهای آن بروزرسانی میشوند. |
| service | string | Body | بله | نام سرویس هماهنگسازی اتاقها (در حال حاضر فقط snapptrip معتبر است). |
نمونه درخواست:
POST /api/v2/base/accommodation/rooms/update
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json
{
"accommodation_id": 2231,
"service": "snapptrip"
}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| payload | boolean | نتیجه نهایی عملیات بروزرسانی؛ true در صورت موفقیت. |
| meta.timestamp | integer | زمان یونیکس ثبت پاسخ. |
نمونه پاسخ موفق:
{
"payload": true,
"meta": {
"timestamp": 1750669200
}
}
نمونه پاسخ خطا برای سرویس نامعتبر:
{
"error": {
"code": 404,
"message": "امکان بروزرسانی این آیتم از طریق سرویس انتخاب شده امکانپذیر نیست."
},
"meta": {
"timestamp": 1750669200
}
}
نکات امنیتی
- نیاز به احراز هویت JWT معتبر دارد.
- در صورت اجرا در سطح سرویس خارجی، باید ورودی
serviceاز لیست سفید داخلی تأیید شود. - اجراهای همزمان با Redis ممکن است نیاز به قفل توزیعشده داشته باشند (Distributed Locking).
نکات عملکردی
- در هر فراخوانی، دو عملیات حذف عمده روی DB و یک پاکسازی Redis انجام میشود.
- برای پیشگیری از فشار روی سرور، پیشنهاد میشود عملیات در صف (Queue) قرار گیرد.
- بهینهسازی قابلتوجه با اجرای
CronController::updateSnapptripMappedAccommodation()در Job async قابل انجام است. - TTL کش پیشنهادی بعد از بروزرسانی: ۶ ساعت.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Http\Controllers\CronController;
- use Carbon\Carbon;
- use Exception;
کدهای خطا
| کد | شرح خطا | منبع |
| 404 | سرویس واردشده پشتیبانی نمیشود. | accommodationRoomsUpdate() |
| 500 | خرابی در فراخوانی CronController یا ارتباط Redis/DB. | CronController / DB Facade |
پیشنهادهای امنیتی
- اضافهکردن Role محدودکننده برای بروزرسانی دادههای اقامتگاه (مثلاً "DataSupervisor").
- ثبت دقیق
operator_idوbranchدر لاگ. - اضافهکردن لایه تأیید دو مرحلهای برای عملیات حذف/بازسازی گروهی.
پیشنهادهای بهبود
- حمایت از سرویسهای دیگر مانند Booking یا Expedia برای آینده.
- جایگزینی پاکسازی Redis با Pipeline commands جهت افزایش سرعت.
- نمایش وضعیت جزئی از تعداد اتاقهای حذفشده و افزودهشده در پاسخ.
- توسعه لاگ تفکیکی برای هر اقامتگاه با timestamp و branch.
ممیزی و لاگها
- نوع ثبت لاگ پیشنهادی:
UpdateRoomMapping. - فیلدهای اجباری در لاگ:
service،accommodation_id،operator_id،status. - سطح لاگ توصیهشده: Info / Warning بسته به نتیجه CronController.
جمعبندی
GET /api/v2/base/certificates
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/base/certificates | V2BaseController@indexCertificates | authWithJwt | نمایش لیست گواهینامههای شعبه شامل فایلها و پیشنمایش محتوای آنها. |
منطق عملکرد تابع
certificates را که مربوط به شعبهی کاربر فعلی هستند واکشی میکند، سپس آنها را بر اساس id DESC مرتب مینماید. هر آیتم از نظر وجود فایل document بررسی شده و در صورت وجود، تصویر پیشنمایش تولید میشود:
- اگر
documentبا پسوند.pdfباشد، آیکون PDF نمایش داده میشود. - در غیر این صورت، پیشنمایش تصویر آپلود شده از Storage (service01) تولید میشود.
created_at، updated_at و branch حذف میگردند و نتیجه با ساختار تفکیکشده بازگردانده میشود.ورودیها
| نام پارامتر | نوع داده | منبع | الزامی | توضیح |
| branch | integer | Header / JWT | بله | شناسه شعبهای که دادهی گواهینامه در آن ذخیره شده. |
نمونه درخواست:
GET /api/v2/base/certificates
Authorization: Bearer {JWT_TOKEN}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| items[].id | integer | شناسه گواهینامه. |
| items[].title | string | عنوان ثبتشده مدارک. |
| items[].document | string | مسیر فایل سند در Storage. |
| items[].preview | string|html | کد HTML شامل تصویر یا محتوای نوشته برای پیشنمایش. |
| meta.timestamp | integer | زمان یونیکس ثبت پاسخ. |
نمونه پاسخ موفق:
{
"items": [
{
"id": 128,
"title": "گواهی ISO 9001",
"document": "media/certificates/iso9001.pdf",
"preview": "
"
},
{
"id": 129,
"title": "مجوز درجه دو",
"document": "media/certificates/license.png",
"preview": "
"
}
],
"meta": {
"timestamp": 1750669215
}
}
نکات امنیتی
- نیازمند احراز هویت JWT معتبر از طریق Middleware
authWithJwt. - کاربر تنها به گواهینامههای شعبهی خودش دسترسی دارد.
- هیچ فیلد فایلی بهصورت قابل نوشتن بازگردانده نمیشود.
نکات عملکردی
- کوئری ساده با
orderBy('id','DESC')و احتمالاً بدون نیاز به ایندکس اضافی. - در صورت افزایش حجم داده پیشنهاد صفحهبندی یا
limit(50). - امکان cache خروجی در Redis با کلید
certificates:{branch}با TTL=1800s برای کاهش بار DB.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Exception;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\Response;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | خطا در اجرای کوئری پایگاهداده. | Catch(Exception) |
| 404 | هیچ گواهینامهای برای این شعبه یافت نشد. | indexCertificates() |
پیشنهادهای امنیتی
- اضافهکردن کنترل سطح دسترسی Role (مثلاً
can('view_certificates')). - رمزنگاری مسیر فایل برای جلوگیری از دسترسی مستقیم خارج از Storage.
- اعتبارسنجی دقیق SSL Storage (service01.ir).
پیشنهادهای بهبود
- افزودن صفحهبندی و فیلتر جستجو بر اساس
title. - پشتیبانی از پیشنمایش PDF با Embedded Viewer.
- اضافه کردن فیلد
uploaded_byبرای لاگ مسئول بارگذاری. - افزودن شمارنده کل برای جدول
certificates.
ممیزی و لاگها
- نوع لاگ توصیهشده:
ReadCertificates. - ذخیره فیلدهای:
operator_id،branch،count(items). - سطح لاگ پیشنهادی: Info.
جمعبندی
DELETE /api/v2/base/certificate
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| DELETE | /api/v2/base/certificate | V2BaseController@deleteCertificate | authWithJwt | حذف گواهینامه مشخص از جدول certificates براساس شناسه و شعبه درخواستی. |
منطق عملکرد تابع
certificates را هدف قرار میدهد و رکورد منطبق را حذف مینماید. اگر حذف موفق باشد، پاسخ HTTP 204 (بدون محتوا) برمیگردد. در صورت بروز خطا در اجرای کوئری پایگاه داده، یک پاسخ JSON از نوع خطا ارسال میشود. منطق کلی تابع:
- دریافت ورودی
branchاز JWT یا Request. - دریافت پارامتر
idاز query/body. - اجرای دستور
DB::table('certificates')->where('branch', branch)->where('id', id)->delete(). - در صورت موفقیت، بازگشت پاسخ خالی با Status Code
204. - در صورت Exception، بازگرداندن ساختار JSON حاوی جزئیات خطا و trace.
ورودیها
| نام پارامتر | نوع داده | منبع | الزامی | توضیح |
| branch | integer | JWT / Header | بله | شناسه شعبهای که گواهی مربوط به آن است. |
| id | integer | Query / Body | بله | شناسه گواهی موردنظر برای حذف. |
نمونه درخواست:
DELETE /api/v2/base/certificate?id=128
Authorization: Bearer {JWT_TOKEN}
خروجی (Response)
پاسخ موفق (بدون محتوا):
HTTP/1.1 204 No Content
نمونه پاسخ در صورت خطا:
{
"meta": { "timestamp": 1750669255 },
"error": {
"code": 400,
"message": "خطای پایگاه داده در حذف گواهینامه.",
"trace": [...]
}
}
نکات امنیتی
- مسیر فقط برای کاربران احرازشده دارای توکن JWT معتبر در دسترس است.
- دسترسی باید محدود به شعبهٔ تعریفشده در توکن کاربر باشد؛ جلوگیری از حذف بینشعبهای الزامی است.
- هیچ پاسخ حاوی دادهٔ حساس در صورت موفقیت ارسال نمیشود (Status 204).
نکات عملکردی
- عملیات حذف مستقیم روی DB انجام میشود و فاقد مرحله گردش در حافظه است.
- در حجم پایین جدول
certificates، نیازی به ایندکس جداگانه نیست؛ اما پیشنهاد میشود ایندکس ترکیبی بر اساس (branch,id) اضافه شود. - در صورت بالاتر از ۵۰۰۰ رکورد، حذف انبوه بهتر است با Job asynchronous انجام گیرد.
وابستگیها
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Exception;
- use Illuminate\Support\Facades\Response;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | خطای اجرای کوئری یا Exception داخلی هنگام حذف. | Catch(Exception) |
| 404 | شناسه مدرک یافت نشد یا شعبه با آن مطابقت ندارد. | deleteCertificate() |
| 500 | خطای ارتباط یا I/O در پایگاه داده. | DB Facade |
پیشنهادهای امنیتی
- افزودن کنترل نقش: فقط کاربران با نقش
DataManagerیاDocumentAdminمجاز به حذف باشند. - ثبت عملیات در جدول
audit_logشاملidوoperator_id. - بررسی صحت JWT expiration تا از دسترسیهای منقضی جلوگیری شود.
پیشنهادهای بهبود
- استفاده از SoftDelete برای حفظ تاریخچهٔ حذفها در آینده.
- بازگرداندن جزئیات حذفشده برای تأیید اپراتور قبل از حذف دائمی.
- افزودن لاگ Redis برای ثبت تعداد حذفها در هر شعبه.
- امکان مدیریت چندگانه (Batch Delete) در نسخه بعدی API.
ممیزی و لاگها
- نوع لاگ:
DeleteCertificate. - فیلدهای پیشنهادی:
operator_id،branch،certificate_id،timestamp. - سطح اهمیت: Critical.
جمعبندی
GET /api/v2/base/certificate
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/base/certificate | V2BaseController@showCertificate | authWithJwt | واکشی مشخصات گواهینامهی خاص بر اساس id و شعبه کاربر. |
منطق عملکرد تابع
certificates واکشی میکند. ورودیهای branch از JWT و id از Request دریافت میشود. سپس فیلدهای غیرضروری (مانند created_at، updated_at و branch) از خروجی حذف میشوند و شیء نهایی در قالب کلید payload به کاربر برگردانده میشود.ورودیها
| نام پارامتر | نوع داده | منبع | الزامی | توضیح |
| branch | integer | JWT / Header | بله | شناسه شعبه کاربر. |
| id | integer | Query / Body | بله | شناسه گواهینامه موردنظر برای نمایش جزئیات آن. |
نمونه درخواست:
GET /api/v2/base/certificate?id=129
Authorization: Bearer {JWT_TOKEN}
خروجی (Response)
| فیلد | نوع داده | توضیح |
| payload.id | integer | شناسه گواهینامه. |
| payload.title | string | عنوان گواهینامه. |
| payload.license_number | string | شماره مجوز یا سریال گواهینامه. |
| payload.exporter | string | نهاد صادرکننده گواهی. |
| payload.expiration | string|date | تاریخ انقضای گواهی. |
| payload.document | string | مسیر فایل پیوست (تصویر یا PDF) در Storage. |
| payload.content | string | محتوای متنی گواهی در صورت وجود. |
| meta.timestamp | integer | زمان سیستم در لحظه پاسخ. |
نمونه پاسخ:
{
"meta": {
"timestamp": 1750669255
},
"payload": {
"id": 129,
"title": "مجوز درجه دو",
"license_number": "LIC-2025-23",
"exporter": "وزارت گردشگری ایران",
"expiration": "2025-12-30",
"document": "media/certificates/license.png",
"content": null,
"description": "گواهی معتبر رزرو اقامتگاه سطح دو."
}
}
نکات امنیتی
- احراز هویت با JWT الزامی است.
- فیلتر روی
branchباعث جلوگیری از دسترسی بینشعبهای میشود. - فایل یا مسیر سند مستقیماً ارسال نمیشود، تنها مسیر کنترلشده برمیگردد.
نکات عملکردی
- کوئری ساده با خروجی مستقیم و بدون Join.
- بدلیل درخواست تکی، نیاز به Cache Redis ندارد (اما قابل پیادهسازی با TTL 2m).
- پاسخ JSON سبک و بدون سربار پردازشی است.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Exception;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\Response;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | خطا در اجرای کوئری یا Exception داخلی. | Catch(Exception) |
| 404 | گواهینامه با این شناسه یافت نشد. | DB::first() |
پیشنهادهای امنیتی
- افزودن بررسی مجاز بودن نقش کاربر (
can('view_certificate')). - رمزنگاری مسیر فایل
documentدر Storage. - اعمال Rate Limiting در سطح IP برای جلوگیری از Brute Force روی idها.
پیشنهادهای بهبود
- افزودن پارامتر
preview=trueبرای نمایش تصویر بندانگشتی. - افزودن فیلد نسخهگذاری در گواهینامهها جهت History Tracking.
- اتصال سیستم هشدار انقضا به کنترلر Dashboard.
ممیزی و لاگها
- نوع لاگ پیشنهادی:
ReadCertificateDetails. - شامل فیلدهای:
operator_id،branch،certificate_id. - سطح لاگ: Info.
جمعبندی
payload ارائه میدهد. برای افزایش امنیت و مقیاسپذیری، در نسخه آینده پیشنهاد میشود RBAC و Cache Redis اضافه گردد.POST /api/v2/base/certificate
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/base/certificate | V2BaseController@storeCertificate | authWithJwt | ثبت گواهینامه جدید در جدول certificates بر اساس دادههای ورودی. |
منطق عملکرد تابع
تابع storeCertificate پس از دریافت پارامترهای اجباری (title، license_number، exporter و فایل document) گواهی را برای شعبهٔ کاربر ایجاد میکند و رکورد جدید را در جدول certificates درج مینماید. مقدار created_at و updated_at با زمان فعلی تکمیل میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| branch | integer | JWT/Header | بله | شناسه شعبه صادرکننده. |
| title | string | Body | بله | عنوان گواهینامه. |
| license_number | string | Body | خیر | شماره سریال یا مجوز. |
| exporter | string | Body | بله | مرجع صادرکننده. |
| expiration | string (YYYY-MM-DD) | Body | خیر | تاریخ انقضاء. |
| document | string (path) | Body | بله | مسیر فایل بارگذاریشده. |
| content | string | Body | خیر | محتوای متنی یا توضیح سند. |
خروجی (Response)
{
"meta": {"timestamp": 1750669255},
"payload": {
"id": 210,
"title": "مجوز فنی اقامتگاهها",
"exporter": "وزارت گردشگری",
"expiration": "2026-02-01",
"document": "media/certificates/acm_license.pdf"
}
}
نکات امنیتی
- توکن JWT الزامی است.
- عملیات فقط برای نقشهای مجاز مثل
DocumentAdmin. - اعتبارسنجی فایل ورودی؛ فقط PDF و تصویر مجاز.
نکات عملکردی
- ورودی ساده بدون عملیات چندجدولی.
- امکان کش Redis برای فهرست پس از ثبت (TTL 1800s).
- عملیات درج داخل تراکنش اتمیک.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
- use Exception;
کدهای خطا
| کد | شرح |
| 400 | داده ناقص یا فایل نامعتبر. |
| 500 | خطا در درج رکورد در پایگاه داده. |
پیشنهادهای امنیتی
- بررسی نقش با
can('create_certificate'). - ضبط عملیات در جدول حسابرسی.
پیشنهادهای بهبود
- اضافه کردن نسخهبندی سند.
- رمزنگاری مسیر فایلها.
ممیزی
نوع لاگ: CreateCertificate. سطح: Important.
جمعبندی
مسیر POST /base/certificate مسئول ایجاد گواهینامه جدید است و باید کنترل نقش دقیق همراه با ثبت حسابرسی داشته باشد.
PUT /api/v2/base/certificate
Route Info
| Method | Endpoint | Controller | Purpose |
| PUT | /api/v2/base/certificate | V2BaseController@updateCertificate | ویرایش دادههای گواهینامه براساس شناسه و شعبه کاربر. |
منطق عملکرد
تابع updateCertificate دادههای موجود گواهی را بروزرسانی میکند. تغییرات شامل عنوان، صادرکننده، تاریخ انقضا یا مسیر فایل است و با DB::update() در همان رکورد انجام میشود.
ورودیها
id(integer, required): شناسه گواهی.branch(integer): شعبه مالک گواهی.title, exporter, expiration, document, content: فیلدهای قابل ویرایش.
خروجی
{
"meta": {"timestamp": 1750669277},
"payload": true
}
امنیت
- JWT الزامی.
- دسترسی محدود به شعبه خودش.
- نقش مجاز:
CertificateEditor.
کارایی
- عملیات ساده و سریع در یک کوئری.
- پیشنهاد استفاده از
DB::transactionبرای Atomicity.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
خطاها
| کد | شرح |
| 404 | گواهینامه یافت نشد. |
| 400 | داده نامعتبر یا پارامتر ناقص. |
پیشنهاد امنیتی
- تأیید مالکیت رکورد بر اساس
branch. - ذخیره نسخه قبل در جدول
certificate_revisions.
پیشنهاد عملکردی
- افزودن trigger برای لاگ تغییرات.
- بروزرسانی کش Redis در صورت موفقیت.
ممیزی
نوع لاگ: UpdateCertificate، سطح: Important.
جمعبندی
مسیر ویرایش گواهینامه قابلاعتماد و سریع است؛ لازم است کنترل دقیق نقش در سطح شعبه حفظ شود.
POST /api/v2/accommodations/list
Route Info
| Method | Endpoint | Controller | Purpose |
| POST | /api/v2/accommodations/list | V2BaseController@accommodationsList | نمایش فهرست اقامتگاهها با فیلتر و صفحهبندی پویا. |
منطق عملکرد
درون تابع accommodationsList() ابتدا ورودی JSON دیکد میشود. سپس براساس فیلدهای advanced شامل کشور، استان، شهر، رتبه یا عبارت جستجو، فیلترینگ انجام میگیرد. نتایج از جدول hotels واکشی شده و با دادههای Redis (کش کشور، همکار، لوگو و موقعیت جغرافیایی) ترکیب میشود.
ورودیها
{
"json": "{\"start\":0,\"length\":20,\"advanced\":{\"country\":12,\"city\":5,\"rate\":4,\"r\":\"تهران\"}}"
}
خروجی
{
"items": [
{"id":100,"title":"Espinas Palace","title_fa":"هتل اسپیناس پالاس","rate":5,"country":"ایران","city":"تهران"}
],
"meta":{"timestamp":1750669255,"draw":1,"recordsTotal":1240,"recordsFiltered":20}
}
امنیت
- مسیر نیاز به احراز JWT دارد.
- فیلتر شعبه براساس توکن کاربر قابلافزودن است.
کارایی
- بهینهسازی با
paginate()، ایندکس روی فیلدهای city و country. - کش Redis TTL=1800s برای کشورها و شهرها.
- محاسبه اولیه
drawبرای درخواست صفحهبندی.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Models\Media;
خطاها
| کد | شرح |
| 400 | ورودی JSON نامعتبر. |
| 404 | اقامتگاهی مطابق فیلتر یافت نشد. |
پیشنهاد امنیتی
- افزودن نقش
AccommodationViewer. - اجرای
rate limitingبر اساس IP.
پیشنهاد عملکردی
- افزودن پارامترهای
sort_byوfilter_range. - ارسال meta شامل واحد ارز (مثلاً IRR) در پاسخ.
ممیزی
نوع لاگ: ReadAccommodationList. سطح لاگ: Info.
جمعبندی
این مسیر برای لیستگیری پویا از اقامتگاهها طراحی شده و دادهها را از PostgreSQL و Redis ترکیب میکند. مناسب برای فازهای جستجوی سریع و داشبورد مدیریتی است.
POST /credit-debit/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/credit-debit/list | V2CreditDebitController@creditDebitList | authWithJwt | دریافت فهرست تراکنشهای بدهکار و بستانکار با جزئیات صفحهبندی و فیلتر پیشرفته. |
منطق عملکرد تابع
تابع creditDebitList دادهها را از جدول تراکنشها (Pay یا Ledger) بر اساس شعبه کاربر و پارامترهای ورودی صفحهبندی واکشی میکند. ابتدا داده $request->json دیکد میشود، سپس مقادیر start و length برای صفحهبندی تنظیم میشود. بعد از اجرای کوئری، نتایج در آرایه نهایی با کلیدهای draw، recordsTotal، recordsFiltered و data بازگردانده میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| json | string (JSON) | Body | بله | شامل تنظیمات صفحهبندی و فیلترهای جستجو. |
| branch | integer | JWT/Header | بله | شناسه شعبه برای فیلتر تراکنشها. |
| draw | integer | Body→JSON | خیر | شماره درخواست برای DataTables. |
| length | integer | Body→JSON | بله | تعداد ردیف در هر صفحه. |
| start | integer | Body→JSON | خیر | شاخص شروع ایتمها (offset). |
خروجی (Response)
{
"draw": 2,
"recordsTotal": 120,
"recordsFiltered": 20,
"data": [
{
"serial_id": 112,
"system_serial": 5143,
"type": "receive",
"description": "دریافت از فروشنده",
"amount": 750000,
"date": "2025/10/20",
"status": "completed"
}
]
}
نکات امنیتی
- احراز JWT الزامی است.
- اطلاعات فقط برای همان شعبه کاربر قابلنمایش است.
- فیلدهای حساس (مثل شماره حساب) از خروجی حذف میشوند.
نکات عملکردی
- استفاده از
paginate()برای بهینهسازی صفحهبندی. - پیشنهاد: کش Redis برای Queryهای پرتکرار (TTL=600s).
- در حالت فیلتر سنگین، توصیه به افزودن ایندکس بر
branch, date, type.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Exception;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودی JSON نامعتبر یا پارامتر ناقص. | json_decode() |
| 500 | خطا در واکشی داده از پایگاه داده. | DB Query |
پیشنهادهای امنیتی
- افزودن کنترل نقش: فقط کاربر دارای دسترسی
can('view_creditdebit'). - اعمال Rate Limiting بر اساس IP.
پیشنهادهای بهبود
- افزودن پارامتر
sort_byبرای مرتبسازی پویا. - افزودن فیلد فیلتر بر اساس وضعیت تراکنش (در انتظار/انجامشده).
- نمایش کلاس CSS وضعیت (error/success) در پاسخ.
ممیزی و لاگها
- نوع لاگ پیشنهادی:
ReadCreditDebitList. - فیلدها:
operator_id،branch،filters. - سطح لاگ: Info.
جمعبندی
این مسیر برای واکشی فهرست بدهکار/بستانکار طراحی شده است. با احراز JWT و صفحهبندی ایمن امکان فیلتر تطبیقی دارد. در نسخه بعدی افزودن پارامترهای مرتبسازی و کش توصیه میشود.
POST /credit-debit/summary
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/credit-debit/summary | V2CreditDebitController@summary | authWithJwt | بازیابی خلاصه مالی تراکنشهای بدهکار و بستانکار (Credit/Debit) مربوط به همکار یا مرجع مشخص. |
منطق عملکرد تابع
تابع summary برای ارائه یک گزارش کلی از وضعیت مالی کاربر، همکار یا مرجع طراحی شده است. فرآیند به این صورت است که ابتدا نوع موجودیت (typeDb) و شناسه شناسایی (company یا reference) تعیین میشود. سپس با استفاده از متدهای StaticController::getFinancialPasts()، دادههای سال مالی جاری و دورههای باز و بسته جمعآوری شده، بدهی و بستانکاری کل محاسبه و بازگردانده میشوند.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| json | string (JSON) | Body | بله | حاوی تنظیمات فیلتر و دوره زمانی مالی. |
| branch | integer | Header/JWT | بله | شناسه شعبه مبدا برای محدودسازی دادهها. |
| advanced.from | string (YYYY/MM/DD) | Body→JSON | خیر | تاریخ شروع دوره مورد بررسی. |
| advanced.fpopen | boolean | Body→JSON | خیر | اگر false باشد فقط دوره بسته را بررسی میکند. |
| type | string | Body→JSON | خیر | نوع حساب (colleague, reference, aggregation). |
| company | integer | Body→JSON | بله | شناسه مرجع همکار. |
خروجی (Response)
{
"status": true,
"meta": {
"timestamp": 1732287600
},
"summary": {
"total_credit": 12500000,
"total_debit": 8400000,
"opening_balance": 3100000,
"closing_balance": 4100000
}
}
نکات امنیتی
- تمامی درخواستها باید با احراز JWT معتبر ارسال شوند.
- دسترسی فقط برای کاربران دارای نقش مدیریتی یا مالی مجاز است.
- مقادیر مالی واقعی (credit/debit) نباید در لاگ عمومی ثبت شوند.
نکات عملکردی
- فراخوانی توابع مالی پرهزینه (
getFinancialPasts) بهتر است با کش Redis ذخیره شوند. - محاسبه سال مالی به صورت
StaticController::getYearFinancial()صورت میگیرد و باید فقط یکبار در هر درخواست اجرا شود. - در صورت حجم زیاد تراکنشها، پیشنهاد میشود فیلدهای aggregate در جدول جداگانه نگهداری شوند.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Morilog\Jalali\Jalalian;
- use Carbon\Carbon;
- use App\Http\Controllers\StaticController;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودی JSON ناقص یا فرمت اشتباه در دوره مالی. | json_decode() |
| 404 | داده مالی یا شناسه همکار یافت نشد. | DB Query |
| 500 | خطا در محاسبه مجموع بدهی/بستانکاری. | getFinancialPasts() |
پیشنهادهای امنیتی
- اعمال اعتبارسنجی نقش قبل از فراخوانی تابع اصلی.
- بررسی شناسه شعبه با دادههای JWT برای جلوگیری از تزریق Cross-Branch.
- حذف دادههای تراکنش خام قبل از پاسخ خروجی.
پیشنهادهای بهبود
- افزودن پارامتر انتخاب بازه دلخواه تاریخ بهجای سال مالی ثابت.
- نمایش فیلد
net_balance(credit-debit) در پاسخ برای استفاده سریع. - افزودن پشتیبانی برای واحد پولی غیر IRR (مثلاً USD).
ممیزی و لاگها
- نوع لاگ:
ReadFinancialSummary - فیلدهای گزارششده:
user_id،branch،company،year - سطح لاگ: Info
- زمانبندی پیشنهادی برای ذخیره لاگ: هر بار تغییر در فیلدهای مالی.
جمعبندی
این مسیر خلاصهای از وضعیت مالی بدهکار و بستانکار را بر اساس دادههای سال مالی و تراکنشهای ثبتشده برمیگرداند. با ترکیب کش Redis، اعتبارسنجی JWT و تفکیک نقش کاربران، عملکرد سریع و امن تضمین میشود.
POST /pay/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/pay/store | V2CreditDebitController@storePay | authWithJwt | ذخیره رکوردهای پرداخت (Pay) جدید همراه با اطلاعات حساب، طرف حساب و تراکنش مالی؛ بروزرسانی کش مالی در Redis. |
منطق عملکرد تابع
تابع storePay برای ثبت گروهی تراکنشهای پرداخت و دریافتی طراحی شده است. ابتدا داده درخواست ($request->data) پردازش و شناسه مالی سال جاری بر اساس تابع StaticController::getYearFinancial() محاسبه میشود. سپس رکوردها در جدول pays درج میگردند. در پایان، بر اساس نوع حساب (reference یا aggregation)، اطلاعات مالی حساب مربوطه از طریق TradeController::financial() بازخوانی شده و در Redis کش میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| json | string (JSON) | Body | بله | آرایهای از تراکنشها شامل اطلاعات پرداخت و دریافتی. |
| branch | integer | JWT/Header | بله | کد شعبه مبدا برای محدودسازی تراکنشها. |
| data[].type | string | Body→JSON | بله | نوع تراکنش (payment یا receive). |
| data[].type_pay | string | Body→JSON | بله | نوع پرداخت (transfer, accounting, coupon, contract). |
| data[].currency_amount | integer | Body→JSON | بله | مبلغ تراکنش (ریال). |
| data[].tracking_code | string | Body→JSON | خیر | کد رهگیری پرداخت در سیستم. |
| data[].wage | integer | Body→JSON | خیر | کارمزد تراکنش. |
| data[].functor_account | integer | Body→JSON | خیر | شناسه کاربر اجراکننده تراکنش (functor). |
| data[].status.id | integer | Body→JSON | بله | شناسه وضعیت تراکنش (در انتظار/تاییدشده). |
خروجی (Response)
{
"status": true,
"meta": {
"timestamp": 1732287600
},
"inserted_count": 3,
"ids": [1089, 1090, 1091],
"cached_financial": true
}
نکات امنیتی
- احراز JWT حتماً انجام میشود و شناسه شعبه از توکن استخراج میگردد.
- ورودی JSON باید از لحاظ ساختار و مقادیر کلیدی اعتبارسنجی شود.
- رشتههای عددی مانند مبلغ حتماً قبل از درج به فرمت انگلیسی تبدیل میشوند تا از تزریق داده جلوگیری شود.
نکات عملکردی
- درج گروهی رکوردها با استفاده از
DB::table('pays')->insert()انجام میشود که سرعت بالایی دارد. - در انتهای عملیات، بلافاصله کش Redis برای حسابهای
referenceوaggregationبهروزرسانی میشود. - پیشنهاد به batch insert به همراه pipeline Redis (TTLs=300s) برای بازدهی بهتر.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Http\Controllers\TradeController;
- use App\Http\Controllers\StaticController;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودی JSON نامعتبر یا پارامتر ناقص. | json_decode() |
| 403 | کاربر بدون مجوز برای انجام نوع تراکنش. | authWithJwt |
| 500 | خطا در ثبت گروهی یا بروزرسانی کش Redis. | DB/Redis |
پیشنهادهای امنیتی
- کنترل دسترسی بر اساس نقش: فقط کاربران با سطح
financial.editorمجاز به درج هستند. - رمزنگاری tracking_code در صورت ارسال از کلاینتهای عمومی.
- جلوگیری از ارسال دوباره (duplicate) در درخواستهای همزمان با قفل Redis.
پیشنهادهای بهبود
- افزودن امکان rollback خودکار در صورت خطا در بروزرسانی کش.
- مدیریت تراکنش چندمرحلهای با Savepoints برای atomicity بهتر.
- پشتیبانی از چند واحد پولی همزمان (multi-currency).
ممیزی و لاگها
- نوع لاگ:
StorePayTransaction. - فیلدهای ثبت:
operator_id،branch،ids،currency_amount. - سطح لاگ: Critical.
- مدت نگهداری: ۹۰ روز در پایگاه audit_logs.
جمعبندی
این مسیر مسئول ثبت تراکنشهای پرداخت و دریافتی و بروزرسانی مالی Redis است. عملیات سریع گروهی و مدیریت مالی اتومات باعث کاهش سربار حسابداری میشود. فعالسازی کنترل نقش و اعتبارسنجی ساختار JSON برای امنیت ضروری است.
POST /pay/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/pay/update | V2CreditDebitController@updatePay | authWithJwt | بروزرسانی اطلاعات تراکنش پرداخت ثبتشده (Pay) شامل نوع، مبلغ، تاریخ و وضعیت تراکنش. |
منطق عملکرد تابع
تابع updatePay برای اصلاح رکوردهای موجود در جدول pays طراحی شده است. ابتدا دادههای ارسالی از درخواست ($request->json) بهصورت آرایه دیکد شده و صحت کلیدهای الزامی مانند id بررسی میشود. سپس با استفاده از DB::table('pays')->where('id', ...)->update([...]) مقداردهی مجدد فیلدهای تراکنش انجام میگیرد. پس از اتمام عملیات، کش Redis برای مرجع مربوطه (reference:<id>:information) با دادههای جدید بازسازی میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| json | string (JSON) | Body | بله | حاوی دادههای تراکنش اصلاحشده. |
| id | integer | Body→JSON | بله | شناسه تراکنش در جدول pays که باید بروزرسانی شود. |
| type | string | Body→JSON | بله | نوع تراکنش (payment یا receive). |
| currency_amount | string | Body→JSON | بله | مبلغ جدید تراکنش. |
| wage | string | Body→JSON | خیر | کارمزد اصلاحشده تراکنش. |
| status.id | integer | Body→JSON | خیر | شناسه وضعیت جدید تراکنش. |
| deadline | string (YYYY/MM/DD) | Body→JSON | خیر | تاریخ جدید مهلت پرداخت. |
| description | string | Body→JSON | خیر | توضیحات تراکنش. |
خروجی (Response)
{
"status": true,
"updated_id": 1091,
"meta": {
"timestamp": 1732287600
},
"cache_updated": true
}
نکات امنیتی
- احراز هویت کاربران از طریق JWT انجام میشود.
- بروزرسانی فقط در محدوده همان شعبه کاربر امکانپذیر است.
- قبل از هر عملیات بهروزرسانی، شناسه تراکنش با branch مرتبط بررسی میشود تا از Cross-Branch Injection جلوگیری گردد.
نکات عملکردی
- در صورت وجود تراکنشهای سنگین، استفاده از
updateBatch()پیشنهاد میشود. - بروزرسانی کش Redis بهصورت asynchronous برای پاسخ سریعتر انجام شود.
- توصیه به ذخیره تغییرات در جدول
audit_logsبا سطح Critical.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Http\Controllers\TradeController;
- use App\Http\Controllers\StaticController;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودی JSON ناقص یا فرمت نامعتبر. | json_decode() |
| 404 | تراکنش با شناسه دادهشده یافت نشد. | DB Query |
| 500 | خطای سیستم در بروزرسانی یا بازسازی کش Redis. | DB/Redis |
پیشنهادهای امنیتی
- افزودن محدودیت نرخ بروزرسانی برای جلوگیری از حملات DDoS مالی.
- ثبت هش جدید در فیلد
hash_signatureبرای تشخیص تغییرات بحرانی. - اعمال RBAC بر اساس سطح دسترسی "financial.moderator".
پیشنهادهای بهبود
- افزودن پشتیبانی از بروزرسانی گروهی چند تراکنش همزمان.
- در پاسخ خروجی نمایش جزئیات قبل و بعد از تغییر (diff snapshot).
- ثبت تاریخچه همه تغییرات در جدول کمکی
pays_history.
ممیزی و لاگها
- نوع لاگ:
UpdatePayTransaction. - فیلدهای ثبت:
operator_id, branch, pay_id, old_data, new_data. - سطح لاگ: Critical.
- مدت نگهداری ممیزی: ۱۸۰ روز.
جمعبندی
این مسیر برای بروزرسانی امن و سریع تراکنشهای پرداخت طراحی شده است. با احراز JWT، کنترل نقش، و همگامسازی کش Redis، سازوکار کاملاً قابل اعتماد برای ویرایش دادههای مالی فراهم میکند.
DELETE /pay/trash
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| DELETE | /api/v2/pay/trash | V2CreditDebitController@trashPay | authWithJwt | حذف نرم سند پرداخت از سیستم مالی با بررسی دوره مالی بستهشده و بروزرسانی کش Redis. |
منطق عملکرد تابع
تابع trashPay برای حذف امن و نرم (soft-delete) تراکنشهای پرداخت طراحی شده است. پیش از حذف، با استفاده از پارامتر تنظیمات دفتر (END_OF_FINANCIAL_PERIOD_CLOSING_ACCOUNTS) دوره مالی بستهشده بررسی میشود تا حذف تراکنشهای مربوط به دوره بستهشده ممنوع شود. سپس وضعیت رکورد موردنظر در جدول pays به مقدار status = 5 تغییر میکند و کش مالی در Redis بازسازی میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| pay_id | integer | Body | بله | شناسه رکورد پرداخت برای حذف. |
| branch | integer | JWT/Header | بله | شناسه شعبه اجرایی جهت بررسی دوره مالی. |
| operator | object | JWT | بله | کاربر اجراکننده عملیات. |
خروجی (Response)
{
"status": true,
"time": 1732287600
}
نکات امنیتی
- بررسی توکن JWT برای تعیین شعبه و اپراتور الزامی است.
- در صورت بسته بودن دوره مالی، حذف نخواهد شد و کد خطای 5007 برگردانده میشود.
- در حذف فیزیکی هیچ دادهای از DB پاک نمیشود، فقط وضعیت تغییر مییابد.
نکات عملکردی
- کش Redis برای مراجع مرتبط (
reference:id:information) بلافاصله بازسازی میشود. - در عملیات گروهی حذف چند سند بهتر است از صف Async برای بازسازی Redis استفاده شود.
- زمان تقریبی اجرای عملیات کمتر از 300ms در حجم 1 سند است.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Http\Controllers\TradeController;
- use Carbon\Carbon;
- use App\Jobs\SystemLog;
کدهای خطا
| کد | شرح خطا | منبع |
| 5007 | دوره حسابداری بسته است؛ حذف سند ممکن نیست. | officeConfig() |
| 5005 | خطا در عملیات حذف یا Redis. | catch(Exception $e) |
| 403 | عدم تطابق شعبه با دوره مالی جاری. | authWithJwt |
پیشنهادهای امنیتی
- اعمال محدودیت در حذف توسط نقش مالی ارشد (
financial.admin). - ثبت هر حذف در
SystemLogبا Queue دیراجرا (snailJob). - رمزگذاری شناسه سند در درخواست کلاینت جهت جلوگیری از تزریق ID.
پیشنهادهای بهبود
- افزودن گزینه
restoreبرای بازیابی اسناد حذفشده. - مدیریت نسخه تاریخچه حذفها در
audit_logs. - افزودن کش گروهی برای حذفهای تجمیعی در سطح شعبه.
ممیزی و لاگها
- نوع لاگ:
TrashPay. - فیلدهای ثبت:
operator_id, branch, pay_id. - سطح لاگ: Critical.
- نگهداری لاگ: ۹۰ روز.
جمعبندی
تابع trashPay حذف نرم و کنترلشده تراکنشهای پرداخت را با بررسی بسته بودن دوره مالی امکانپذیر میسازد. پس از حذف، دادههای Redis مجدداً بروزرسانی میشوند تا توازن مالی حفظ گردد.
POST /gateway/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/gateway/list | V2CreditDebitController@listGateway | authWithJwt | دریافت لیست درگاههای پرداخت فعال برای شعبه، با پشتیبانی از فیلتر و وضعیت تراکنش. |
منطق عملکرد تابع
تابع listGateway برای واکشی تمامی درگاههای پرداخت متصل به شعبه و سیستم مالی طراحی شده است. با استفاده از ورودی DataTables، ابتدا پارامترهای فیلتر (مثل وضعیت، نوع درگاه، تاریخ ایجاد، یا شرکت) اعمال میگردند، سپس تراکنشهای پرداختی اخیر مرتبط با هر درگاه از جدول gateways واکشی میشود.
در صورت فعال بودن سرویس تسویه خودکار، تابع دادهها را با نتیجه تسویه Redis ترکیب میکند تا وضعیت لحظهای مانده هر درگاه نمایش داده شود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| draw | integer | Body (DataTables) | بله | شماره درخواست برای هماهنگی پاسخ با DataTables. |
| length | integer | Body | بله | تعداد آیتمها در هر صفحه. |
| start | integer | Body | بله | مقدار offset شروع لیست. |
| advanced | object | Body | خیر | فیلترهای پیشرفته (status, provider, type). |
| branch | integer | JWT/Header | بله | شناسه شعبه جهت واکشی درگاهها. |
خروجی (Response)
{
"meta": { "timestamp": 1732287600, "draw": 1 },
"items": [
{
"id": 12,
"title": "Zarinpal Gateway",
"status": "active",
"transactions_count": 215,
"last_update": "1404/09/01 11:22",
"balance": {
"wallet": 48000000,
"pending": 1200000
}
}
],
"recordsTotal": 15,
"recordsFiltered": 15
}
نکات امنیتی
- توکن JWT برای شناسایی شعبه اجباری است.
- نمایش جزئیات تراکنشها فقط برای نقشهای دارای مجوز
financial.gateway.view. - درگاههای غیرفعال در خروجی حذف میشوند مگر نقش کاربر
developerباشد.
نکات عملکردی
- استفاده از
Redis::get('gateway:list:{branch}')برای کش خروجی حدود ۶۰۰ ثانیه. - در صورت حجم بالا (بیش از ۲۰ درگاه)، واکشی وضعیت تراکنشها باید async باشد.
- کد SQL شامل ایندکس روی ستونهای
branchوstatusبرای کاهش latency.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Http\Controllers\StaticController;
- use App\Helpers\Functions;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودی DataTables نامعتبر یا ناقص. | Validator |
| 403 | کاربر دسترسی مشاهده ندارد. | authWithJwt |
| 500 | خطا در Redis یا Query پایگاه داده. | DB Query |
پیشنهادهای امنیتی
- رمزنگاری شناسه درگاه در فرانتاند قبل از ارسال.
- اعمال Rate Limit ۱۰ درخواست در دقیقه به ازای هر IP.
- بررسی Double-fetch برای جلوگیری از حملات Enumeration.
پیشنهادهای بهبود
- افزودن پارامتر
balance_history:trueبرای گزارش تغییرات موجودی. - ترکیب مستقیم کش Redis با جدول تراکنشها برای کاهش latency.
- پشتیبانی از درگاههای رمزارز در فاز آینده.
ممیزی و لاگها
- نوع لاگ:
ListGateway. - فیلدهای ذخیرهشده:
operator_id, branch, filter_used. - سطح لاگ: Info.
- نگهداری در جدول
audit_logsتا ۳۰ روز.
جمعبندی
تابع listGateway با ترکیب دادههای حسابی و کش Redis، لیست مختصر و بهینهای از درگاههای فعال را بازمیگرداند. طراحی این مسیر با تمرکز بر سرعت واکشی و کنترل دسترسی دقیق انجام شده تا در محیطهای چندشعبهای و مالی پایدار بماند.
POST /financial/items
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/financial/items | V2CreditDebitController@financialItems | authWithJwt | دریافت ساختار و آیتمهای حساب مالی یک مؤلفه مشخص مانند دفتر، شرکت یا مرجع برای نمایش یا محاسبه درخت حسابداری. |
منطق عملکرد تابع
تابع financialItems وظیفه دارد بر اساس نوع حساب و شناسه دادهشده در درخواست (مثل نوع moeen یا preference)، آیتمهای مالی مرتبط را از جداول پایه حسابداری واکشی و سازماندهی کند. این دادهها معمولاً جهت ساخت نمای درختی حسابها (TreeView) در رابط کاربری استفاده میشوند.
در ابتدای اجرای تابع، مجموعه حسابهای سطح پایه از accounting_titles خوانده میشود؛ سپس بر اساس نوع ورودی (request->type)، شاخههای فعال یا غیرفعال علامتگذاری میگردند. هر آیتم دارای کلیدهایی مانند subset، locked و display است که در خروجی JSON تنظیم میشوند.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| type | string | Body | بله | نوع حساب مالی که باید آیتمهای آن واکشی شوند (مثلاً moen یا preference). |
| branch | integer | JWT/Header | بله | شناسه شعبهای که دادههایش باید فیلتر شود. |
| filters | object | Body | خیر | فیلتر اختیاری شامل وضعیت حساب یا سطح دسترسی. |
خروجی (Response)
{
"status": true,
"time": 1732287600,
"items": [
{
"id": 1,
"title": "داراییهای جاری",
"subset": true,
"locked": false,
"display": true
},
{
"id": 2,
"title": "بدهیهای جاری",
"subset": false,
"locked": false,
"display": true
}
]
}
نکات امنیتی
- احراز توکن JWT برای تعیین شعبه ضروری است.
- دسترسی به این API محدود به نقشهای دارای مجوز
financial.viewerیا بالاتر است. - هیچ دادهای از حسابهای غیرفعال یا قفل شده برای
non-adminبرگردانده نمیشود.
نکات عملکردی
- آیتمها از Redis کش شوند با کلید
financial:items:{branch}:{type}و مدت اعتبار 1800 s. - در صورت تغییر ساختار حساب، کش باید پاکسازی (invalidate) شود.
- زمان پاسخ کمتر از 200 ms در بار معمولی است.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Http\Controllers\StaticController;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | نوع حساب نامعتبر یا فیلد type خالی است. | Validator |
| 403 | کاربر دسترسی مشاهده ندارد. | Middleware JWT |
| 500 | خطا در واکشی دادهها. | DB/Redis |
پیشنهادهای امنیتی
- استفاده از RBAC برای کنترل سطوح دسترسی view/edit.
- رمزگذاری خروجی آیتمها درون HTTPS جهت جلوگیری از تزریق داده.
- ثبت لاگ ممیزی در سطح Info برای هر درخواست.
پیشنهادهای بهبود
- افزودن قابلیت pagination برای آیتمهای حجیم حسابداری.
- پشتیبانی از جستجوی fuzzy روی نام آیتمها.
- افزودن شاخص version برای هر ساختار مالی جهت همگامسازی Frontend.
ممیزی و لاگها
- نوع لاگ:
FinancialItemsList. - فیلدها:
branch, type, operator_id. - سطح لاگ: Info.
- مدت نگهداری لاگ: ۳۰ روز.
جمعبندی
تابع financialItems یکی از نقاط مرکزی در ماژول مالی است که ساختار طبقهبندی حسابها را به شکل امن و سریع در اختیار رابط کاربری قرار میدهد. این تابع با ترکیب Redis Cache و درخت حسابداری پایگاه داده، عملکرد پایدار و انعطافپذیر ارائه میدهد.
POST /check/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/check/list | V2CreditDebitController@listCheck | authWithJwt | دریافت لیست چکهای مالی ثبتشده برای شرکت یا شعبه، با فیلتر و مرتبسازی پیشرفته DataTables. |
منطق عملکرد تابع
تابع listCheck از روی پارامترهای DataTables (مثل draw، start، length و فیلترهای پیشرفته) لیست چکها را از جدول announcements یا pays واکشی میکند. این چکها ممکن است متعلق به پرداخت یا دریافت در سیستم مالی باشند.
در صورت وجود داده کششده، نتیجه از Redis با کلید check:list:{branch} خوانده میشود. اگر کش خالی بود، دادهها با فیلتر وضعیت (`status`)، تاریخ (`date_from`, `date_to`) و نوع چک (`payment` یا `receive`) واکشی میشوند و سپس در JSON نهایی با متادیتا و شمارنده رکوردها بازگردانده میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| draw | integer | Body | بله | شناسه درخواست DataTables. |
| start | integer | Body | بله | موقعیت شروع رکورد. |
| length | integer | Body | بله | تعداد رکوردهای هر صفحه. |
| advanced | object | Body | خیر | فیلترهای پیشرفته شامل status، type، company، date_from و date_to. |
| branch | integer | JWT/Header | بله | شناسه شعبه فعلی برای تفکیک دادهها. |
خروجی (Response)
{
"meta": { "timestamp": 1732287600, "draw": 1 },
"data": [
{
"id": 107,
"serial_id": 10087,
"status": "active",
"title": "چک پرداختی شرکت آلفا",
"company": {
"id": 504,
"title": "آلفا سیستم"
},
"amount": 18500000,
"currency": "IRR",
"deadline": "1404/09/15",
"type": "payment",
"tracking_code": "CHK-984723",
"created_at": "1404/08/30"
}
],
"recordsTotal": 27,
"recordsFiltered": 27
}
نکات امنیتی
- دسترسی فقط برای نقشهای دارای مجوز
financial.check.view. - درخواستها باید دارای توکن JWT معتبر باشند.
- فیلترهای زمان باید اعتبارسنجی شوند تا از injection جلوگیری شود.
نکات عملکردی
- اولویت واکشی از Redis با TTL=900s.
- Queryهای دارای ایندکس روی فیلدهای
status,deadline,branch. - در صورت حجم بالا، مجموعه چکها بهصورت batch تقسیم میشوند.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Helpers\Functions;
- use App\Http\Controllers\StaticController;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامترهای لیست ناقص یا نامعتبر. | Validator |
| 403 | کاربر مجاز به مشاهده چکها نیست. | authWithJwt |
| 500 | خطا در Query یا کش Redis. | DB Layer |
پیشنهادهای امنیتی
- رمزنگاری شناسه چک در فرانتاند قبل از ارسال.
- اعمال Rate Limit ۵ req/min برای جلوگیری از enumeration.
- استفاده از HTTPS و بررسی timestamp پاسخ برای جلوگیری از replay.
پیشنهادهای بهبود
- افزودن فیلتر برای نمایش «چکهای منقضی».
- پشتیبانی از export به Excel و PDF.
- افزودن گزینه search روی گیرنده یا شماره چک.
ممیزی و لاگها
- نوع لاگ:
ListCheck. - فیلدهای ذخیرهشده:
operator_id, branch, filters_applied. - سطح لاگ: Info.
- مدت نگهداری در جدول
audit_logs: ۳۰ روز.
جمعبندی
تابع listCheck لیست چکهای مالی را با کش Redis، کنترل دسترسی مبتنی بر نقش، و فیلترهای DataTables ارائه میدهد. طراحی آن به گونهای انجام شده که برای گزارشگیری بلادرنگ از حسابهای پرداختی و دریافتی مناسب باشد.
POST /check/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/check/store | V2CreditDebitController@storeCheck | authWithJwt | ثبت گروهی چکهای مالی در جدول پرداختها و بروزرسانی کش مالی شعبه. |
منطق عملکرد تابع
تابع storeCheck دادههای ارسالی مربوط به چکهای مالی را از بدنه درخواست دریافت میکند و پس از بررسی نوع پرداخت (پرداخت یا دریافت)، آنها را در جدول pays درج میکند.
شناسه سریال هر چک از طریق تابع StaticController::getSerialId('pay', branch, year) تعیین میشود. بعد از ثبت درون تراکنش DB::transaction، کش اطلاعات مالی در Redis بروزرسانی میشود تا تغییرات لحظهای اعمال شوند.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| data | array | Body | بله | آرایهای از چکها جهت ثبت تکی یا گروهی. |
| branch | integer | JWT/Header | بله | شناسه شعبه ثبتکننده. |
| operator | object | JWT | بله | شناسه کاربر ثبتکننده چکها. |
| object_type | string | Body | بله | نوع هدف چک (reference, aggregation, colleague). |
| object | integer | Body | بله | شناسه منبع مالی یا فاکتور مربوط. |
خروجی (Response)
{
"status": true,
"time": 1732287600,
"result": {
"inserted": 5,
"tracking_ids": ["CHK-88421", "CHK-88422", "CHK-88423"],
"cached": "financial:branch:5:synced"
}
}
نکات امنیتی
- احراز هویت JWT اجباری است.
- فقط نقشهای دارای سطح
financial.check.storeمجاز به درج هستند. - بررسی موجودی حساب قبل از درج تراکنشها انجام میشود (Wallet Check).
نکات عملکردی
- ثبت چکها درون
DB::transactionباعث حفظ یکپارچگی دادههای مالی میشود. - در پایان درج، دادههای مرتبط در Redis با کلید
reference:{object}:informationبروزرسانی میشوند. - میتوان در نسخه آینده cache invalidation هوشمند بر اساس
year financialافزود.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Http\Controllers\TradeController;
- use App\Http\Controllers\StaticController;
- use Carbon\Carbon;
- use App\Models\Pay;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | دادههای چک ناقص یا نامعتبر. | Validator |
| 403 | دسترسی مجاز برای ثبت چک وجود ندارد. | authWithJwt |
| 500 | خطا در درج دادهها یا بروزرسانی کش Redis. | DB Layer / Redis |
پیشنهادهای امنیتی
- رمزنگاری شناسه چک و شماره حساب در فرانتاند.
- اعمال محدودیت ۵ درخواست در دقیقه برای هر کاربر.
- بررسی Anti‑Replay برای درخواستهای تکراری با همان Tracking Code.
پیشنهادهای بهبود
- افزودن فیلد
wallet_effect:trueبرای بروزرسانی بلادرنگ کیف پول. - پشتیبانی از درج async جهت کاهش latency.
- اتصال به سرویس گزارش مالی مرکزی جهت اعتبارسنجی چکها قبل از ثبت.
ممیزی و لاگها
- نوع لاگ:
StoreCheck. - ثبت فیلدها:
operator_id, branch, count_inserted. - سطح لاگ: Notice.
- مدت نگهداری: ۳۰ روز در جدول
audit_logs.
جمعبندی
تابع storeCheck مسئول ثبت تراکنشهای مالی نوع چک است و پس از درج در پایگاه داده، تمام کشهای مالی مرتبط با شعبه یا منبع هدف را بروزرسانی میکند. طراحی تابع بهصورت اتمیک، امن و مناسب عملیات گروهی چکها پیاده شده است.
POST /check/operation
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/check/operation | V2CreditDebitController@operationCheck | authWithJwt | انجام عمل خاص بر روی چک مالی، مانند تغییر وضعیت، تأیید، لغو یا تسویه. |
منطق عملکرد تابع
تابع operationCheck عمل انتخابشده را روی چک مشخصشده اجرا میکند. بسته به نوع ورودی (`action`) مقدار ستونهای status، payment_state یا tracking_code در جدول pays بروزرسانی میشوند.
پس از موفقیت عملیات، کش Redis مربوط به چک و اطلاعات مالی شعبه با کلیدهای financial:branch:{branch} و check:{id}:information بروزرسانی یا حذف میشوند تا اطلاعات جدید منعکس گردد.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| id | integer | Body | بله | شناسه چک مورد نظر برای انجام عملیات. |
| action | string | Body | بله | نوع عملیات. مقادیر مجاز: confirm، cancel، block، unblock، settle. |
| branch | integer | JWT/Header | بله | شناسه شعبه مالی. |
| operator | object | JWT | بله | شناسه اپراتور اجرایی. |
| reason | string | Body | خیر | علت تغییر وضعیت (برای لاگ و ممیزی). |
خروجی (Response)
{
"status": true,
"time": 1732287600,
"result": {
"check_id": 109,
"action": "confirm",
"verified_by": 2704,
"financial_cache": "redis:branch:5:updated",
"message": "وضعیت چک با موفقیت بروزرسانی شد"
}
}
نکات امنیتی
- مجوز لازم:
financial.check.edit. - فقط کاربران نقش حسابدار ارشد یا مدیر شعبه حق انجام عملیات دارند.
- تمام تغییرات در جدول
audit_logsثبت میشوند.
نکات عملکردی
- بهروزرسانی همزمان کش Redis پس از هر تغییر وضعیت چک.
- استفاده از تراکنش
DB::transactionبرای جلوگیری از ناسازگاری وضعیت چکها. - TTL کشهای مرتبط: 300 ثانیه برای دادهی شعبه و 30 ثانیه برای دادهی چک منفرد.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Helpers\Functions;
- use App\Models\Pay;
- use App\Http\Controllers\StaticController;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامترها ناقص یا نوع عملیات نامعتبر است. | Validator |
| 403 | کاربر مجاز به انجام این عملیات نیست. | authWithJwt |
| 404 | چک مورد نظر یافت نشد. | DB |
| 500 | خطای داخلی پایگاه داده یا بروزرسانی کش. | Exception Handler |
پیشنهادهای امنیتی
- اعمال Rate‑Limit برای عملیات حساس مالی (۱ عمل در هر ۱۰ ثانیه).
- ثبت IP کاربر در ممیزی و ارسال هشدار در تغییر وضعیت غیرعادی.
- رمزنگاری شناسه چک در فرانتاند.
پیشنهادهای بهبود
- افزودن امکان انجام عملیات گروهی روی چند چک.
- اضافه کردن فیلد
verified_atدر جدول برای ردیابی دقیق زمان تأیید. - پشتیبانی از وبهوک برای اطلاعرسانی تغییر وضعیت به سرویسهای همکار.
ممیزی و لاگها
- نوع لاگ:
OperationCheck. - فیلدهای ثبتشونده:
check_id, action, operator_id, branch, reason. - سطح لاگ: Audit.
- مدت نگهداری: ۹۰ روز.
جمعبندی
تابع operationCheck مسئول تغییر وضعیت چکهای مالی در سیستم است و ضمن رعایت کنترل دسترسی، بروزرسانی ایمن دادهها و کش Redis را انجام میدهد. این مسیر یکی از نقاط حساس چرخه مالی محسوب میشود و برای تضمین صحت داده باید تحت لاگ کامل و ممیزی نگهداری شود.
POST /check/operation/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/check/operation/update | V2CreditDebitController@operationUpdateCheck | authWithJwt | بهروزرسانی اطلاعات عملیات چک (operation record) شامل نوع، تاریخ و توضیحات، و بروز رسانی کش مالی. |
منطق عملکرد تابع
تابع operationUpdateCheck برای تغییر جزئیات یک عملیات ثبتشده روی چک استفاده میشود، مانند اصلاح نوع عملیات (type)، تاریخ اجرا، توضیحات یا وضعیت آن.
ابتدا رکورد عملیات از جدول check_operations واکشی و اعتبارسنجی میشود. سپس تغییرات ذخیره شده و هرگونه وابستگی مبلغ یا وضعیت چک هماهنگ میگردد. پس از آن کش Redis مرتبط با گزارش چک و مالی شعبه ریفرش میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| operation_id | integer | Body | بله | شناسه عملیات چک که باید بروزرسانی شود. |
| type | string | Body | خیر | نوع جدید عملیات (مانند assignment، cash، clearing). |
| date | string (YYYY-MM-DD) | Body | خیر | تاریخ جدید عملیات. |
| description | string | Body | خیر | توضیحات عملیات. |
| branch | integer | JWT/Header | بله | شناسه شعبه مالی مرتبط. |
| operator | object | JWT | بله | اطلاعات کاربری اجراکننده تغییر. |
خروجی (Response)
{
"status": true,
"time": 1732287600,
"result": {
"operation_id": 305,
"updated_fields": ["type", "description"],
"cache": "financial:branch:5:refreshed",
"message": "جزئیات عملیات چک با موفقیت بروزرسانی شد"
}
}
نکات امنیتی
- مجوز نقش:
financial.check.operation.updateضروری است. - رکورد عملیات باید به شعبه کاربر تعلق داشته باشد.
- تمام تغییرات در جدول ممیزی
audit_logsذخیره میشود.
نکات عملکردی
- عملیات بروزرسانی در تراکنش پایگاه داده انجام میشود.
- بازسازی کش محدود به این چک و شعبه انجام میگیرد.
- TTL کشهای جدید: 300 ثانیه.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Models\CheckOperation;
- use App\Helpers\Functions;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودیها ناقص یا نامعتبر. | Validator |
| 403 | دسترسی غیرمجاز برای بروزرسانی عملیات چک. | authWithJwt |
| 404 | شناسه عملیات یافت نشد. | DB |
| 500 | خطا در بهروزرسانی یا بازسازی کش. | Exception Handler |
پیشنهادهای امنیتی
- اعمال تأیید دومرحلهای برای تغییرات حساس.
- ثبت IP و User-Agent اپراتور در لاگ ممیزی.
- اعمال محدودیت تعداد تغییر در بازه زمانی مشخص.
پیشنهادهای بهبود
- افزودن پشتیبانی از نسخهبندی تغییرات عملیات چک.
- امکان بروزرسانی گروهی عملیاتهای مشابه.
- ارسال اعلان خودکار به ذینفعان در تغییر وضعیت عملیات.
ممیزی و لاگها
- نوع لاگ:
OperationUpdateCheck. - فیلدها:
operation_id, branch, operator_id, updated_fields, timestamp. - مدت نگهداری: ۹۰ روز.
جمعبندی
تابع operationUpdateCheck ابزار اصلی برای اصلاح دادههای عملیات ثبتشده روی چک است و با اعتبارسنجی، حفظ وابستگیها و بازسازی کشها دادهها را پایدار و بهروز نگه میدارد.
POST /financial/treeview
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/financial/treeview | V2CreditDebitController@treeview | authWithJwt | واکشی ساختار درختی حسابهای مالی (کل، معین، تفضیلی) جهت نمایش سلسلهمراتبی در رابط کاربری. |
منطق عملکرد تابع
تابع treeview ساختار درختی آیتمهای مالی را بر اساس نوع درخواستشده (general، moeen، tafzili) و فیلترهای ورودی از دیتابیس دریافت میکند. سپس این دادهها را با فرمت استاندارد {id, title, subset[]}
کش Redis برای کاهش بار روی دیتابیس استفاده میشود؛ اگر دیتای مربوط پیشتر ذخیره شده باشد، از کش خوانده میشود، در غیراینصورت از پایگاهداده واکشی و کشگذاری میگردد.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| type | string | Body | بله | نوع آیتم مالی (مثل moeen یا general). |
| branch | integer | JWT/Header | بله | شناسه شعبه مالی. |
| filters | object | Body | خیر | فیلترهای اضافی برای محدودسازی نتایج. |
خروجی (Response)
{
"status": true,
"time": 1732287600,
"result": [
{
"id": 21,
"title": "بانکها",
"subset": [
{"id": 54, "title": "بانک ملی"},
{"id": 55, "title": "بانک ملت"}
],
"locked": false,
"display": true
}
]
}
نکات امنیتی
- احراز هویت JWT الزامی است.
- دسترسی مبتنی بر نقش:
financial.viewerضروری است. - نوع داده درخواستشده باید معتبر و مجاز باشد.
نکات عملکردی
- کش Redis با TTL پیشفرض 1800 ثانیه برای دادههای TreeView.
- استفاده از ایندکس مناسب روی ستونهای کلید خارجی (group, general, moeen_id).
- امکان واکشی زیرشاخهها بدون بارگذاری کل درخت.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Helpers\Functions;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامتر type نامعتبر یا خالی. | Validator |
| 403 | عدم مجوز مشاهده ساختار مالی. | Auth Middleware |
| 500 | خطای داخلی دیتابیس یا Redis. | Exception Handler |
پیشنهادهای امنیتی
- محدود کردن دادهها به شعبه کاربر احرازشده.
- ثبت لاگ درخواستها برای تحلیل رفتار و امنیت.
پیشنهادهای بهبود
- افزودن پشتیبانی از Lazy Loading گرهها.
- اعمال فیلتر پیشرفته روی زیرشاخهها.
- کشگذاری جداگانه برای هر سطح درخت.
ممیزی و لاگها
- ثبت نوع آیتم درخواستشده، زمان واکشی، و شناسه شعبه.
- نگهداری لاگها به مدت ۳۰ روز در
audit_logs.
جمعبندی
تابع treeview زیرساختی امن و بهینه برای واکشی سلسلهمراتبی حسابهای مالی ارائه میدهد و با استفاده از کش و ایندکس، کارایی بالایی در پروژههای بزرگ مالی دارد.
POST /accounting/balance
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/accounting/balance | V2CreditDebitController@accountingBalance | authWithJwt | دریافت موجودی لحظهای حسابها (بانکی، صندوق) و محاسبه تراز مالی شعبه. |
منطق عملکرد تابع
تابع accountingBalance موجودی حسابهای ثبتشده در سیستم را بر اساس branch یا سایر فیلترهای مشخص شده واکشی میکند. دادهها شامل جمع موجودی نقدی، جمع موجودی بانکی، مجموع چکهای در جریان و تراز کلی سیستم است.
برای افزایش کارایی، دادهها ممکن است از کش Redis خوانده شوند. در صورت عدم وجود یا انقضای کش، اطلاعات از دیتابیس واکشی و مجدد کشگذاری میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| branch | integer | JWT/Header | بله | شناسه شعبه برای محاسبه موجودی. |
| filters | object | Body | خیر | فیلترهای مانند نوع حساب یا بازه تاریخ تراکنشها. |
| goal | string | Body | خیر | هدف نمایش دادهها (مثلاً گزارش یا داشبورد). |
خروجی (Response)
{
"status": true,
"time": 1732287600,
"result": {
"cash_total": 15200000,
"bank_total": 94500000,
"checks_in_flow": 32000000,
"overall_balance": 141700000
}
}
نکات امنیتی
- نیاز به احراز هویت JWT.
- نقش مجاز:
financial.balance.view. - بررسی تعلق شعبه به کاربر احراز شده.
نکات عملکردی
- کش Redis با TTL پیشفرض 600 ثانیه.
- محاسبات مجموع موجودی با استفاده از ایندکس روی ستونهای
account_typeوbranch. - استفاده از SELECT SUM برای تجمیع سریع دادهها.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Helpers\Functions;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامتر branch نامعتبر. | Validator |
| 403 | عدم دسترسی به موجودی شعبه. | Auth Middleware |
| 500 | خطا در واکشی دادهها یا کش Redis. | Exception Handler |
پیشنهادهای امنیتی
- ثبت لاگ درخواست موجودی با IP و شناسه کاربر.
- محدودیت نرخ درخواست (Rate Limit) برای جلوگیری از فشار به سرور.
پیشنهادهای بهبود
- افزودن جزئیات سطح حساب (جزءکل) در خروجی.
- پشتیبانی از فیلتر براساس ارز.
- نمایش تاریخ آخرین بروزرسانی موجودی.
ممیزی و لاگها
- نوع لاگ:
AccountingBalanceView. - ثبت دادهها: branch, filters, timestamp.
- مدت نگهداری: ۳۰ روز.
جمعبندی
تابع accountingBalance ابزاری کلیدی برای گزارشگیری مالی سریع و دقیق شعبه است و با پشتیبانی کش، عملکرد پایداری در محیطهای پرتراکنش ارائه میدهد.
POST /announcement/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/list | V2CreditDebitController@listAnnouncement | authWithJwt | دریافت لیست اعلانهای مالی مرتبط با پرداختها، دریافتها و سایر رویدادهای حسابداری سیستم. |
منطق عملکرد تابع
تابع listAnnouncement دادههای مربوط به اعلانهای مالی را از جدول announcements بازیابی میکند. این اعلانها معمولاً شامل ارتباط بین اسناد پرداخت، دریافت، چک یا حوالههای حسابی هستند. دادهها بر اساس فیلترهای DataTables (draw, start, length) و فیلترهای پیشرفته مانند نوع اعلان، وضعیت و تاریخ، استخراج میشوند. سپس خروجی نهایی شامل متای صفحهبندی، زمان سرور و لیست اعلانهای ساختاریافته بازگردانده میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| draw | integer | Body | بله | شناسه شماره درخواست DataTables برای همزمانسازی. |
| start | integer | Body | بله | ایندکس شروع دادهها. |
| length | integer | Body | بله | تعداد رکورد در هر صفحه. |
| advanced | object | Body | خیر | فیلترهای پیشرفته مثل نوع اعلان، وضعیت، تاریخ از و تا. |
| branch | integer | JWT/Header | بله | شناسه شعبه مرتبط با اعلانها. |
خروجی (Response)
{
"status": true,
"meta": {
"draw": 5,
"recordsTotal": 258,
"recordsFiltered": 258,
"timestamp": 1732287600
},
"data": [
{
"id": 315,
"type": "pay",
"reference_id": 1621,
"branch": 4,
"title": "ثبت پرداخت نقدی",
"status": "done",
"created_at": "2025-10-11 13:45:00"
}
]
}
نکات امنیتی
- نیاز به احراز هویت JWT.
- کاربر فقط اجازه مشاهده اعلانهای مربوط به شعبه خود را دارد.
- نقش لازم:
financial.announcement.view.
نکات عملکردی
- کش Redis بر اساس کلید
announcement:list:{branch}با TTL=600 s برای دادههای بدون فیلتر. - ایندکس ترکیبی روی ستونهای
(branch,type,status)برای افزایش سرعت واکشی.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Morilog\Jalali\Jalalian;
- use App\Helpers\Functions;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودی ناقص یا نامعتبر DataTables. | Validator |
| 403 | عدم دسترسی به اعلانهای شعبه دیگر. | Auth Middleware |
| 500 | خطای داخلی دیتابیس یا کش Redis. | Exception Handler |
پیشنهادهای امنیتی
- اعمال RBAC دقیق برای کنترل مشاهده و حذف اعلانها.
- رمزنگاری آیدی مرجع پیش از ارسال به کلاینت.
پیشنهادهای بهبود
- افزودن قابلیت جستجوی ترکیبی چند ستونی.
- امکان گروهبندی بر اساس نوع اعلان (
payment،receive،system). - پشتیبانی از مرتبسازی پویا توسط فرانتاند.
ممیزی و لاگها
- ثبت هر درخواست مشاهده لیست در
audit_logsبا نوع ListAnnouncement. - شامل: شناسه کاربر، شعبه، زمان، فیلترهای اعمال شده.
جمعبندی
مسیر listAnnouncement امکان مدیریت و مرور اعلانهای مالی را بهصورت امن، صفحهبندیشده و کششده فراهم میکند و پایه گزارشگیری داخلی سیستمهای مالی است.
POST /announcement/list/company
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/list/company | V2CreditDebitController@listAnnouncementCompany | authWithJwt | لیست اعلانهای مالی تجمیعی در سطح شرکت (تمام شعب)، با فیلتر DataTables و فیلتر پیشرفته برای گزارشات مدیریتی. |
منطق عملکرد تابع
تابع listAnnouncementCompany مشابه تابع listAnnouncement عمل میکند اما دامنهٔ دادهها را به تمام شعب وابسته به شرکت (یا کل سازمان) گسترش میدهد. هدف، گزارشگیری یکپارچه از کلیه اعلانهای مالی است. دادهها از جدول announcements بازیابی میشوند و با فیلترهای DataTables و جستجوی پیشرفته محدود میگردند. خروجی، شامل متادیتای DataTables (draw، تعداد کل، زمان درخواست) و آرایهای از اعلانهاست.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| draw | integer | Body | بله | برای همزمانسازی DataTables. |
| start | integer | Body | بله | نقطه شروع رکوردها. |
| length | integer | Body | بله | تعداد رکوردها در هر صفحه. |
| advanced | object | Body | خیر | فیلترهای ترکیبی مدیریت، از جمله نوع، تاریخ، وضعیت و شعبهٔ خاص. |
| company | integer | JWT/Header | بله | شناسه شرکت مادر برای واکشی اعلانهای کل. |
خروجی (Response)
{
"status": true,
"meta": {
"draw": 1,
"recordsTotal": 842,
"recordsFiltered": 842,
"timestamp": 1732287600
},
"data": [
{
"id": 1220,
"type": "receive",
"reference": "branch_3/pay/554",
"branch": 3,
"company": 1,
"status": "done",
"title": "دریافت وجه نقد از شعبه مرکزی",
"created_at": "2025-10-09 09:44:31"
}
]
}
نکات امنیتی
- احراز JWT الزامی است.
- کاربر باید به نقش سطح شرکت
financial.company.announcement.viewمجهز باشد. - فیلتر خودکار دادهها بر اساس
companyاز توکن JWT انجام میشود تا از نشت اطلاعات بین شرکتها جلوگیری گردد.
نکات عملکردی
- بهینهسازی Query با ایندکس روی فیلدهای
(company, type, created_at). - استفاده از کش Redis با TTL=300 s برای پاسخهای صفحه اول (draw=1).
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Helpers\Functions;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودی ناقص یا نامعتبر DataTables. | Validator |
| 403 | کاربر فاقد سطح دسترسی سازمانی. | Auth Middleware |
| 500 | مشکل در واکشی دادهها یا کش Redis. | Exception Handler |
پیشنهادهای امنیتی
- رمزنگاری کلید مرجع (reference) در پاسخ API.
- ثبت IP و شناسه کاربری در جدول audit_logs برای هر درخواست لیست.
پیشنهادهای بهبود
- افزودن پارامتر
exportبرای خروجی CSV/Excel. - پیادهسازی Lazy Loading برای فیلدهای حجیم (description).
- قابلیت گروهبندی بر اساس شعبه یا وضعیت.
ممیزی و لاگها
- نوع ممیزی:
ListAnnouncementCompany. - ثبت شامل company، filters، timestamp و user_id.
- سطح حساسیت: Info.
جمعبندی
تابع listAnnouncementCompany ابزار اصلی مدیران مالی برای مشاهده اعلانهای کلان بینشعبهای است و با ساختار صفحهبندیشده و کش Redis، کارایی بالایی در محیطهای چندشعبهای دارد.
POST /announcement/edit
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/edit | V2CreditDebitController@editAnnouncement | authWithJwt | بازیابی دادههای اولیه برای ویرایش اعلان مالی مشخصشده (جهت استفاده در فرم یا modal). |
منطق عملکرد تابع
تابع editAnnouncement شناسه اعلان مالی را از ورودی دریافت کرده، اطلاعات کامل آن (از جدول announcements) را همراه با جزئیات اکوسیستم مرتبط (مانند نوع اعلان، وضعیت، شعبه، object مرجع و تاریخ) بازمیگرداند. این مسیر هیچ تغییر دادهای انجام نمیدهد و صرفاً حالت read-only دارد.
اگر اعلان یافت نشود یا کاربر به شاخهٔ مربوطه دسترسی نداشته باشد، خروجی با وضعیت خطا برمیگردد.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| id | integer | Body | بله | شناسه اعلان مالی برای واکشی. |
| branch | integer | JWT/Header | بله | شناسه شعبهٔ درخواستدهنده برای اعتبارسنجی مالکیت داده. |
| operator | integer | JWT | بله | شناسه کاربر درخواستدهنده (برای ممیزی). |
خروجی (Response)
{
"status": true,
"data": {
"id": 72,
"type": "pay",
"object_id": 544,
"status": "done",
"branch": 3,
"description": "اعلان پرداخت مربوط به فاکتور 554",
"created_at": "2025-11-02 10:13:44",
"updated_at": "2025-11-04 13:26:10",
"operator": {
"id": 17,
"name": "Reza Moradi"
}
}
}
نکات امنیتی
- احراز JWT الزامی.
- نقش مجاز:
financial.announcement.viewیا بالاتر. - کنترل شعبه با تطبیق
branchاز JWT و رکورد DB.
نکات عملکردی
- دسترسی دیتابیس بر اساس
idایندکسشده انجام میشود. - در صورت استفاده مجدد، TTL کش Redis برابر ۶۰ ثانیه است (read cache).
- پاسخ JSON تا حد ممکن minimal طراحی شده تا در modal یا form کاربردی باشد.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Helpers\Functions;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامتر id ارسال نشده یا نامعتبر است. | Validator |
| 403 | کاربر به شعبهٔ اعلان دسترسی ندارد. | Auth Middleware |
| 404 | اعلان یافت نشد. | DB Query |
| 500 | خطای داخلی در بازیابی داده از دیتابیس. | Exception Handler |
پیشنهادهای امنیتی
- رمزنگاری فیلدهای حساس (object_id و operator) در سطح پاسخ.
- ثبت زمان و IP درخواست در جدول audit_logs.
پیشنهادهای بهبود
- افزودن پارامتر
include_relatedبرای بازیابی ارتباطات مثل چکها و سندها. - پشتیبانی از کش client-side برای کاهش بار سرور.
ممیزی و لاگها
- نوع ممیزی:
EditAnnouncement. - ثبت شامل: id، branch، operator، و زمان درخواست.
- سطح حساسیت: Notice.
جمعبندی
تابع editAnnouncement نقطه آغاز فرآیند ویرایش است که با واکشی دادهٔ دقیق اعلان، محیط کاربر را برای تغییر ایمن آماده میکند. این نقطه هیچ دادهای را تغییر نمیدهد و صرفاً Fetch اولیهٔ امن محسوب میشود.
POST /announcement/store
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/store | V2CreditDebitController@storeAnnouncement | authWithJwt | ثبت یا بهروزرسانی درجا (Inline) اعلان مالی در فرمهای پرداخت/دریافت. |
منطق عملکرد تابع
تابع storeAnnouncement در این حالت برای ثبت سریع اعلان (مثلاً همزمان با ثبت سند پرداخت) استفاده میشود. در صورتی که شناسهای از اعلان موجود ارسال شود، دادهٔ موجود ویرایش میشود وگرنه رکوردی جدید در جدول announcements ایجاد میگردد.
پس از درج یا بهروزرسانی، حافظهٔ کش مالی Redis برای شاخهٔ مربوطه پاکسازی (invalidate) شده و شناسهٔ نهایی در خروجی بازگردانده میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| id | integer | Body | خیر | شناسهٔ اعلان (در صورت ویرایش رکورد موجود). |
| type | string | Body | بله | نوع اعلان (مثلاً pay، receive). |
| object_id | integer | Body | بله | شناسهٔ رکورد منبع (سند/چک/تراکنش). |
| branch | integer | JWT/Header | بله | شعبهٔ مرتبط برای تعیین محدودهٔ داده. |
| operator | integer | JWT | بله | شناسهٔ کاربر ثبتکننده. |
| description | string | Body | خیر | توضیحات اختیاری جهت ثبت در اعلان مالی. |
خروجی (Response)
{
"status": true,
"message": "Announcement stored successfully",
"data": {
"id": 114,
"branch": 3,
"type": "pay",
"object_id": 554,
"description": "ثبت خودکار همزمان با پرداخت",
"timestamp": "2025-11-23 16:19:00"
}
}
نکات امنیتی
- احراز JWT اجباری.
- نقش مجاز:
financial.announcement.edit. - کنترل حفظ مالکیت داده با چککردن
branchدر JWT و جدول اعلانها.
نکات عملکردی
- بعد از عملیات
insert/update، Redis کش مربوط به اعلانهای آن شعبه را پاک میکند. - TTL پیشفرض کشهای وابسته: ۶۰۰ ثانیه.
- پردازش میانگین کمتر از ۴۵ms برای حالت local data write.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Helpers\Functions;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامتر type یا object_id ارسال نشده است. | Validator |
| 403 | دسترسی نقش به ویرایش اعلانها محدود است. | RBAC |
| 404 | رکورد ارسالی برای ویرایش یافت نشد. | DB Query |
| 500 | خطای داخلی در عملیات درج یا بروزرسانی. | Exception Handler |
پیشنهادهای امنیتی
- افزودن verify-level دوم برای اعلانهایی که به مبالغ بالا مربوط میشوند.
- ثبت لاگ IP و timestamp در جدول ممیزی.
پیشنهادهای بهبود
- افزودن پارامتر
silentبرای جلوگیری از invalidate موقت کش در عملیات batch. - بهینهسازی واکشی object_id با
joinجهت کاهش round-trip.
ممیزی و لاگها
- نوع ممیزی:
StoreAnnouncementInline. - ثبت شامل: id، branch، operator، object_id و نوع عملیات (insert/update).
- سطح حساسیت: Audit.
جمعبندی
تابع storeAnnouncement در حالت inline بخشی از فرایندهای ترکیبی است که بهصورت بلادرنگ اعلان را همراه با پرداخت یا دریافت درج یا اصلاح میکند و تمام کشهای شعبه را بهروزرسانی مینماید. این تابع جزو مسیرهای با درجهٔ امنیتی بالا در ماژول مالی محسوب میشود.
POST /announcement/update
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/update | V2CreditDebitController@updateAnnouncement | authWithJwt | بروزرسانی اطلاعات اعلان مالی موجود پس از تایید سطح دسترسی. |
منطق عملکرد تابع
تابع updateAnnouncement برای ویرایش ایمن رکورد موجود در جدول announcements به کار میرود. ابتدا بررسی میکند که اعلان با شناسهٔ مشخص در شعبهٔ کاربر و وضعیت باز وجود داشته باشد.
در صورت تأیید، بهروزرسانی مقادیر کلیدی مانند نوع، توضیح، مبلغ یا شیء مرجع انجام میشود. پس از ثبت تغییرات در DB، کش مالی مرتبط با شاخهٔ ذکرشده پاکسازی شده (Redis invalidate) و دادهٔ نهایی ذخیره شده باز میگردد.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| id | integer | Body | بله | شناسه رکورد اعلان جهت بروزرسانی. |
| type | string | Body | خیر | نوع عملیات مالی؛ در صورت ارسال جایگزین مقدار پیشین میشود. |
| description | string | Body | خیر | توضیح جدید اعلان مالی. |
| object_id | integer | Body | خیر | شناسه رکورد منبع (اختیاری برای تغییر پیوند). |
| branch | integer | JWT/Header | بله | شناسه شعبه جهت کنترل دسترسی. |
| operator | integer | JWT | بله | شناسهٔ کاربر درخواستدهنده برای ثبت در ممیزی. |
خروجی (Response)
{
"status": true,
"message": "Announcement updated successfully.",
"data": {
"id": 114,
"branch": 3,
"type": "pay",
"description": "اصلاح توضیحات اعلان جهت سند پرداخت",
"updated_at": "2025-11-23 16:55:04",
"operator": 17
}
}
نکات امنیتی
- JWT الزامی است.
- نقش لازم:
financial.announcement.editیا سطح بالاتر. - عدم اجازه بروزرسانی اعلانهای بسته یا پایانیافته (status ≥ locked).
- بررسی انطباق
branchبین کاربر و رکورد.
نکات عملکردی
- در عملیات update از
DB::transactionاستفاده شده تا atomic بودن تضمین شود. - جلوی تکرار کش redis با TTL جدید گرفته میشود (invalidate فوری شعبه).
- میانگین اجرای عملیات: ۶۵–۸۰ ms.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Helpers\Functions;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | شناسه یا پارامترهای ورودی ناقص است. | Validator |
| 403 | کاربر مجوز ویرایش این اعلان را ندارد. | RBAC |
| 404 | اعلان یافت نشد یا از شعبه دیگر است. | Query |
| 423 | اعلان بسته است و قابل ویرایش نیست. | Business Rule |
| 500 | خطای داخلی سرور در هنگام ذخیره. | Exception |
پیشنهادهای امنیتی
- ثبت timestamp تغییر با IP کاربر در Log.
- افزودن قفل optimistic برای جلوگیری از وضعیت race-condition هنگام ویرایش همزمان.
پیشنهادهای بهبود
- افزودن endpoint مجزای
/announcement/notesجهت نگهداری شرح تغییرات. - پشتیبانی از بروزرسانی بخشی (partial update) با PATCH.
ممیزی و لاگها
- نوع ممیزی:
UpdateAnnouncement. - موارد ثبتشونده: id، operator، branch، تغییرات انجامشده.
- سطح حساسیت: Audit.
جمعبندی
تابع updateAnnouncement با رویکرد atomic و قفل منطقی روی دادهها، بهعنوان لایه انتقال برای عملیات اصلاح اعلانهای مالی عمل میکند و بخشی از چرخه ممیزی شعبه در ماژول مالی محسوب میشود.
POST /announcement/view
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/view | V2CreditDebitController@viewAnnouncement | authWithJwt | نمایش جزئیات کامل یک اعلان مالی برای بازبینی یا مشاهدهی بدون ویرایش. |
منطق عملکرد تابع
تابع viewAnnouncement با دریافت شناسهٔ اعلان، رکورد مربوطه را از جدول announcements خوانده و در صورت نیاز ارتباطات آن را (مانند پرداختها، چکها، و اعلانهای وابسته) نیز شامل میکند.
هیچ تغییری در داده انجام نمیشود. این endpoint فقط برای مشاهده، چاپ یا آمادهسازی نمای modal یا PDF استفاده میشود. چنانچه اعلان مورد نظر قفل مالی یا در وضعیت بسته باشد، فقط در حالت read-only قابل دریافت است.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| id | integer | Body | بله | شناسهٔ اعلان مورد نظر برای مشاهده. |
| branch | integer | JWT/Header | بله | شعبهٔ کاربر جهت بررسی محدودهٔ مجاز. |
| operator | integer | JWT | بله | شناسهٔ کاربر درخواستدهنده جهت ثبت در لاگ مشاهده. |
| include_related | boolean | Body | خیر | در صورت true، دادههای وابسته (پرداختها، چکها، و اعلانهای فرزند) نیز اضافه میشود. |
خروجی (Response)
{
"status": true,
"data": {
"id": 114,
"type": "pay",
"object_id": 554,
"branch": 3,
"status": "done",
"description": "هزینه بلیط پرواز تهران–مشهد",
"created_at": "2025-11-20 18:35:00",
"updated_at": "2025-11-21 09:12:11",
"financial_lock": false,
"related": [
{
"id": 882,
"type": "check",
"status": "cleared",
"deadline": "1404/09/10"
}
]
}
}
نکات امنیتی
- احراز هویت JWT الزامی است.
- نقش مورد نیاز:
financial.announcement.view. - اطلاعات نمایششده فقط برای شعبهٔ کاربر قابل دسترس است.
نکات عملکردی
- درخواست سبک (read-only) با
selectمحدود به ستونهای مرتبط. - در صورت فعال بودن
include_related، کوئری به صورت lazy load اجرا میشود (Load factor ~1.3x). - TTL کش Redis برای نتایج تکراری ۹۰ ثانیه.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Helpers\Functions;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامتر id ارسال نشده یا نامعتبر است. | Validator |
| 403 | کاربر مجاز به مشاهده اعلان در شعبهٔ دیگر نیست. | Auth Middleware |
| 404 | اعلان یافت نشد. | DB Query |
| 500 | خطای داخلی در بازیابی اطلاعات یا join دادههای وابسته. | Exception |
پیشنهادهای امنیتی
- افزودن لاگ رسمی
financial_view_logبرای ثبت تاریخچهٔ مشاهده. - محدودسازی فیلدهای حساس در پاسخ JSON برای کاربران با نقش پایینتر.
پیشنهادهای بهبود
- افزودن قابلیت فیلتر view با ورودی
object_typeبرای نمایش اعلانهای خاص یک ماژول. - امکان ارسال
format=pdfجهت تولید فایل قابل چاپ.
ممیزی و لاگها
- نوع ممیزی:
ViewAnnouncement. - دادههای ثبتشونده: id، branch، operator، زمان درخواست.
- سطح حساسیت: Notice.
جمعبندی
تابع viewAnnouncement مسیر read-only برای مشاهدهٔ ایمن جزئیات اعلان مالی است و به کاربران دارای نقش مشاهده اجازه میدهد محتوای دقیق رکورد را بدون دخالت در فرآیندهای مالی بخوانند. دادهها با دسترسی کنترلشده و کش زماندار ارائه میشوند.
POST /announcement/statement
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/statement | V2CreditDebitController@statementAnnouncement | authWithJwt | تولید و ارائهٔ گزارش بیانیه (Statement) مالی برای اعلانهای فعال در بازهٔ مورد درخواست کاربر. |
منطق عملکرد تابع
تابع statementAnnouncement با هدف تهیهٔ بیانیهٔ تجمیعی از اعلانهای مالی (پرداخت، دریافت، چک، و اعلان مرتبط) طراحی شده است. این تابع معمولاً بعد از اجرای عملیات listAnnouncement یا updateAnnouncement برای استخراج گزارش دقیق جریانهای مالی استفاده میشود.
مقدارهای ورودی شامل تاریخ شروع، تاریخ پایان، نوع اعلان، و شناسه شعبه هستند. تابع با استفاده از DB::table('announcements') دادهها را واکشی کرده و نسبت به object_type (مانند pay, check, cost) عملیات Grouping انجام میدهد. سپس مجموعها (balance, debit, credit) محاسبه و ساختار JSON خروجی بازگردانده میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| from | string (Y-m-d) | Body | خیر | تاریخ شروع بازه گزارش. |
| to | string (Y-m-d) | Body | خیر | تاریخ پایان بازه گزارش. |
| branch | integer | JWT/Header | بله | شناسه شعبه کاربر برای فیلتر گزارش. |
| type | string | Body | خیر | در صورت ارسال، فقط بیانیه نوع خاص (pay, check, cost) بازگردانده میشود. |
| currency | string | Body | خیر | واحد ارزی (تومان، دلار، یورو، ...). |
خروجی (Response)
{
"status": true,
"statement": {
"branch": 3,
"from": "1404-09-01",
"to": "1404-09-23",
"currency": "IRR",
"summary": {
"total_credit": 71250000,
"total_debit": 53320000,
"balance": 17930000
},
"details": [
{
"id": 114,
"type": "pay",
"object_type": "check",
"description": "پرداخت چک بابت اجاره دفتر",
"amount": 5200000,
"created_at": "1404-09-12",
"status": "paid"
},
{
"id": 115,
"type": "receive",
"object_type": "cash",
"description": "دریافت پیش پرداخت از مشتری A",
"amount": 10500000,
"created_at": "1404-09-15",
"status": "done"
}
]
}
}
نکات امنیتی
- JWT الزامی است.
- نقش الزامی:
financial.announcement.viewیا بالاتر. - اطلاعات فقط برای
branchکاربر جاری باز میگردد.
نکات عملکردی
- استفاده از کوئریهای تجمیعی (aggregate) برای بهبود سرعت گزارش.
- در صورت وجود کش، نتایج تا ۳۰۰ ثانیه در Redis نگهداری میشوند.
- میانگین زمان اجرا: ~120ms (بدون کش).
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Morilog\Jalali\Jalalian;
- use App\Helpers\Functions;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | ورودیهای زمانی یا نوع اعلان نادرست است. | Validator |
| 403 | کاربر مجاز به مشاهدهی بیانیه شعبه نیست. | Auth Middleware |
| 404 | دادهای در بازهٔ زمانی موردنظر یافت نشد. | Query |
| 500 | خطای داخلی سرور در پردازش تجمیعی. | Exception |
پیشنهادهای امنیتی
- تغییر ساختار خروجی در نقشهای سطح پایین برای کاهش نشت داده مالی.
- ثبت ممیزی
view_statementبا تاریخ و IP کاربر.
پیشنهادهای بهبود
- افزودن پارامتر
group_byبا گزینههای (day, week, month). - افزودن خروجی PDF و Excel برای دانلود سریع بیانیه.
ممیزی و لاگها
- نوع ممیزی:
StatementAnnouncement. - موارد ثبتشونده: بازهٔ زمانی، operator، branch.
- سطح حساسیت: Audit.
جمعبندی
تابع statementAnnouncement ستون اصلی گزارشگیری تجمیعی اعلانهاست که خروجی استاندارد و قالببندیشدهای برای واحد مالی فراهم میکند. با سطح دسترسی Audit، نقش مدیریتی میتواند کل جریانهای مالی شعبه را در یک بازهٔ زمانی مرور کند.
POST /announcement/ledger
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/ledger | V2CreditDebitController@ledgerAnnouncement | authWithJwt | ارائه دفترکل اعلانها (Ledger) بر اساس فیلتر حساب، شعبه و بازهٔ زمانی. |
منطق عملکرد تابع
تابع ledgerAnnouncement برای استخراج دفترکل اعلانهای مالی به کار میرود. دادهها بر اساس شناسهٔ شعبه، بازهٔ زمانی، نوع اعلان و موجودی حساب طبقهبندی میشوند.
این تابع از همان ساختار underlying جدولهای announcements، pays و checks استفاده میکند و پس از پردازش، خروجی را بهصورت مرتبشده بر اساس تاریخ و حساب ارائه میدهد. همچنین امکان گروهبندی بر پایهٔ حساب معین (moeen_id) یا نوع عملیات فراهم است.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| from | string (Y-m-d) | Body | خیر | تاریخ شروع دفترکل. |
| to | string (Y-m-d) | Body | خیر | تاریخ پایان دفترکل. |
| branch | integer | JWT/Header | بله | شعبهٔ درخواست دهنده. |
| moeen_id | integer | Body | خیر | در صورت ارسال، فقط عملیات مربوط به حساب معین خاص. |
| group_by | string | Body | خیر | نحوه گروهبندی (day، type، account). |
| currency | string | Body | خیر | واحد ارزی فیلتر گزارش. |
خروجی (Response)
{
"status": true,
"branch": 3,
"group_by": "day",
"from": "1404-09-01",
"to": "1404-09-23",
"ledger": [
{
"date": "1404-09-01",
"debit": 2000000,
"credit": 0,
"balance": -2000000,
"description": "پرداخت به تامینکننده کالا",
"account": "110102 حساب پرداختها"
},
{
"date": "1404-09-05",
"debit": 0,
"credit": 4500000,
"balance": 2500000,
"description": "دریافت نقدی از مشتری",
"account": "110101 صندوق نقدی"
}
],
"summary": {
"total_debit": 2000000,
"total_credit": 4500000,
"final_balance": 2500000
}
}
نکات امنیتی
- توکن JWT الزامی است.
- نقش مورد نیاز:
financial.ledger.view. - اجازهٔ دسترسی فقط به اعلانهای همان شعبه.
نکات عملکردی
- گزارش تجمیعی با استفاده از
GROUP BYدر SQL. - امکان Cache Redis تا 300 ثانیه برای کاهش فشار گزارشهای تکراری.
- میانگین زمان اجرا: ۹۰ تا ۱۴۰ میلیثانیه.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Morilog\Jalali\Jalalian;
- use App\Helpers\Functions;
کدهای خطا
| کد | شرح | منبع |
| 400 | تاریخ یا پارامتر گزارش ارسال نشده است. | Validator |
| 403 | کاربر اجازه مشاهده دفترکل این شعبه را ندارد. | RBAC |
| 404 | هیچ تراکنشی در بازهٔ مورد نظر پیدا نشد. | Query |
| 500 | خطای داخلی سرور. | Exception |
پیشنهادهای امنیتی
- ثبت تاریخ و IP کاربر مشاهدهکننده گزارش در ممیزی.
- محافظت خروجی با masking در اطلاعات حساس (مثلاً شماره حساب).
پیشنهادهای بهبود
- افزودن حالت real-time refresh برای Dynamic Ledger UI.
- پشتیبانی از خروجی CSV یا Excel.
- امکان فیلتر چندحسابی (multi-account ledger) در نسخه ۲.۱.
ممیزی و لاگها
- نوع ممیزی:
LedgerAnnouncement. - ثبت: branch، operator، بازه، نوع گروهبندی.
- سطح حساسیت: Audit.
جمعبندی
تابع ledgerAnnouncement ستون اصلی گزارشگیری تحلیلی دفترکل در ماژول مالی است. این تابع بر پایه دادههای اعلان، جریانهای وام، پرداخت و دریافتی را تجمیع کرده و با سیستم کَش تلفیقشده، سرعت بالا و داده دقیق برای حسابداران فراهم میکند.
POST /announcement/calculation
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/announcement/calculation | V2CreditDebitController@calculationAnnouncement | authWithJwt | بازمحاسبه تراز مالی اعلانها و بروزرسانی Balance کلی سیستم. |
منطق عملکرد تابع
تابع calculationAnnouncement برای بازتولید محاسبات مالی (مانند ماندهها، جمع بدهکار و بستانکار، و وضعیت کلی حسابها) طراحی شده است. هر زمان دادهٔ مالی جدید (مانند اعلان پرداخت یا دریافت) ثبت یا ویرایش شود، فراخوان این مسیر باعث تنظیم مجدد دادههای تحلیلی میشود.
درون متد، سیستم با استفاده از DB::table('announcements') و DB::raw() مجموع عملیات مالی را از جدولهای مرتبط (مثل pays و checks) واکشی و تجمیع کرده، سپس در جدول داخلی announcement_calculations یا کش Redis ثبت میکند. این فرآیند به صورت atomic در تراکنش دیتابیس انجام میشود تا از ناسازگاری داده جلوگیری شود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| branch | integer | JWT/Header | بله | شناسه شعبه برای محاسبه محلی تراز. |
| from | string (Y-m-d) | Body | خیر | تاریخ شروع محدودهٔ مورد محاسبه. |
| to | string (Y-m-d) | Body | خیر | تاریخ پایان محدودهٔ مورد محاسبه. |
| type | string | Body | خیر | نوع اعلان (مثل pay، receive، check). |
| currency | string | Body | خیر | واحد ارزی مورد محاسبه. |
| refresh_cache | boolean | Body | خیر | در صورت true، کش نتایج حذف و مجدداً محاسبه میشود. |
خروجی (Response)
{
"status": true,
"branch": 3,
"from": "1404-09-01",
"to": "1404-09-23",
"updated": 38,
"currency": "IRR",
"calculation": {
"total_credit": 71250000,
"total_debit": 53300000,
"balance": 17950000,
"last_update": "1404-09-23 13:44",
"source": "rebuild"
}
}
نکات امنیتی
- توکن JWT الزامی است.
- نقش الزامی:
financial.announcement.manageیا مدیر سیستم. - هر درخواست تنها مجاز به محاسبه در محدوده شعبهٔ خود کاربر است.
نکات عملکردی
- محاسبهها در تراکنش DB و بهینهشده با شاخصهای زمانی (date index) اجرا میشوند.
- در صورت فعال بودن کش Redis، نتیجه تا ۶۰۰ ثانیه نگهداری میشود.
- میانگین زمان اجرا برای بازهٔ ۱۰۰۰ اعلان ≈ ۲۵۰ ms.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Helpers\Functions;
- use Morilog\Jalali\Jalalian;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر زمانی یا نوع اعلان نادرست است. | Validator |
| 403 | دسترسی کافی برای بازمحاسبه وجود ندارد. | RBAC |
| 404 | هیچ اعلان فعالی در بازهٔ مورد نظر یافت نشد. | Query |
| 500 | خطای داخلی هنگام آپدیت تراز. | Transaction |
پیشنهادهای امنیتی
- ثبت ممیزی تمام بازمحاسبات با شناسه اپراتور و IP در
system_logs. - محدودسازی نرخ فراخوانی مسیر (Rate Limiter: ۵ در دقیقه).
- قفل همزمانی مبتنی بر Redis برای جلوگیری از محاسبهٔ همزمان چند درخواست یکسان.
پیشنهادهای بهبود
- افزودن گزینهٔ
diff_modeبرای محاسبه فقط تفاوت دادهها. - ارائه خروجی تحلیلی با تفکیک بر اساس حساب یا کاربر.
- پشتیبانی از زمانبندی خودکار (cron-based recalculation) هر ۲۴ ساعت.
ممیزی و لاگها
- نوع لاگ:
RebuildCalculation. - اقلام ممیزی: تاریخ بازه، شناسه اپراتور، مدت اجرای فرآیند، تعداد ردیف بهروزشده.
- سطح حساسیت: Critical Audit.
جمعبندی
calculationAnnouncement مسیر اصلی برای همترازی اطلاعات مالی و بازسازی تراز سیستم است. این متد هرگونه تغییر در اعلانها را به دادهٔ آماری منطبق تبدیل کرده و همواره موجب سازگاری دادههای حسابداری کل با ردیفهای تراکنشی میشود. اجرای آن برای پایداری و سلامت کامل ماژول مالی حیاتی است.
POST /gateway/invoice/{drive}
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/gateway/invoice/{drive} | V2CreditDebitController@gatewayInvoice | authWithJwt | تولید لینک پرداخت آنلاین از طریق درگاه متصلشده به شناسه یا مدل موردنظر (drive). |
منطق عملکرد تابع
تابع gatewayInvoice برای تولید فاکتور پرداخت اینترنتی طراحی شده است. پارامتر {drive} شناسهٔ آبجکت (مثلاً پرداخت، سفارش یا اعلان مالی) موردنظر را مشخص میکند. سیستم نوع مدل را بررسی کرده، مبلغ و اطلاعات حساب مرتبط را از پایگاه داده واکشی مینماید و پس از محاسبهٔ پارامترهای امنیتی، یک لینک سفارش فعال در جدول payment_gateway ایجاد میکند.
در نهایت، خروجی شامل URL پرداخت و شناسه تراکنش است که کاربر سمت کلاینت یا موبایل میتواند برای پرداخت در مرورگر یا اپلیکیشن بانک استفاده کند.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| drive | integer | URL Param | بله | شناسهٔ آیتم پرداخت یا اعلان. |
| amount | integer | Body | خیر | مبلغ نهایی، فقط اگر نیاز به override داشته باشد. |
| callback_url | string | Body | خیر | آدرس بازگشت کاربر پس از پرداخت (در صورت عدم ارسال از config خوانده میشود). |
| currency | string | Body | خیر | واحد ارزی (پیشفرض IRR). |
خروجی (Response)
{
"status": true,
"gateway": "zarinpal",
"invoice": {
"id": 114,
"amount": 250000,
"currency": "IRR",
"reference": "A1Z4R36P",
"callback_url": "https://example.com/callback/zp",
"link": "https://gateway.zarinpal.com/pg/StartPay/A1Z4R36P",
"expire_in": 900
}
}
نکات امنیتی
- توکن JWT الزامی است.
- کنترل مالکیت داده: سیستم بررسی میکند کاربر یا شعبهی فعلی مجاز به دیدن یا پرداخت آن آیتم هست.
- پارامتر مبلغ رمزگذاری و در سمت درگاه تأیید مجدد میشود.
نکات عملکردی
- عملیات ثبت و لینکسازی کمتر از ۱۰۰ms انجام میشود.
- در صورت وجود لینک فعال قبلی برای همان drive، سیستم از لینک موجود استفاده میکند (prevent duplicate).
- درگاه پرداخت از تنظیمات فعال Redis انتخاب میگردد.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use App\Helpers\Functions;
- use Morilog\Jalali\Jalalian;
- use App\Http\Controllers\Gateways\ZarinpalController;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامتر یا دادهٔ درگاه نامعتبر است. | Validator |
| 403 | کاربر مجاز به تولید فاکتور این آیتم نیست. | Auth Middleware |
| 404 | آیتم مالی یافت نشد. | Query |
| 500 | خطای سرور هنگام برقراری ارتباط با درگاه پرداخت. | Exception |
پیشنهادهای امنیتی
- افزودن امضای دیجیتال به لینک خروجی برای جلوگیری از تغییر غیرمجاز.
- ثبت IP و User-Agent تولیدکننده لینک در لاگ.
پیشنهادهای بهبود
- پشتیبانی از gatewayهای چندگانه (zarinpal, nextpay, idpay) از طریق strategy pattern.
- افزودن پارامتر
metaجهت ارسال دادهٔ اضافی به callback.
ممیزی و لاگها
- نوع لاگ:
GatewayInvoice. - موارد ممیزی: شناسهٔ drive، مبلغ، درگاه انتخابی، IP کاربر.
- سطح حساسیت: Audit.
جمعبندی
تابع gatewayInvoice درگاه میانی سیستم مالی را مدیریت میکند و با تولید سریع لینک پرداخت، کاربران را از اتصال مستقیم به سرویس پرداخت بینیاز میسازد. این تابع یکی از اجزای کلیدی ماژول تراکنش آنلاین است که امنیت، سرعت و یکپارچگی داده را در سطح شعبه تضمین میکند.
POST /gateway/invoice/{drive}/verify
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/gateway/invoice/{drive}/verify | V2CreditDebitController@gatewayInvoiceVerify | authWithJwt | بررسی وضعیت نهایی پرداخت (موفق، ناموفق یا در انتظار) و بهروزرسانی رکورد مربوطه در سیستم داخلی. |
منطق عملکرد تابع
تابع gatewayInvoiceVerify مرحلهٔ پایانی پرداخت الکترونیک است. این تابع پس از بازگشت کاربر از درگاه بانکی (با پارامترهای Authority، RefID یا معادل آنها) اجرا میشود.
سیستم اطلاعات پرداخت را از جدول payment_gateway بر اساس پارامتر {drive} واکشی کرده، شناسهٔ بانکی و مبلغ مورد انتظار را تطبیق میدهد. سپس از کنترلر درگاه (مثلاً ZarinpalController::verify()) برای استعلام نهایی وضعیت استفاده میکند.
در صورت موفقیت، رکورد پرداخت در جدول pays با وضعیت paid بهروزرسانی شده و تراکنش به عنوان سند مالی تأییدشده در دفتر ثبت میگردد.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| drive | integer | URL Param | بله | شناسه آیتم فاکتور آنلاین. |
| authority | string | Body / Callback | بله | شناسه تراکنش بازگشتی از درگاه (Authority یا Token). |
| status | string | Body / Callback | خیر | کد وضعیت برگشتی از درگاه (OK, NOK, ERROR). |
خروجی (Response)
{
"status": true,
"gateway": "zarinpal",
"invoice": {
"id": 114,
"reference": "A1Z4R36P",
"bank_ref": "000485298963",
"amount": 250000,
"state": "paid",
"verified_at": "1404-09-23 14:21"
},
"transaction": {
"accounting_record": 24039,
"updated_balance": 71250000
}
}
نکات امنیتی
- توکن JWT الزامی است، حتی در تماس callback.
- مقایسهٔ مبلغ پرداختشده با مبلغ ثبتشده پیش از تأیید ضروری است.
- تمام دادههای برگشتی از سمت درگاه (RefID, Status, CardPan و غیره) در جدول
gateway_logsنگهداری میشود.
نکات عملکردی
- میانگین زمان Verify برای Zarinpal حدود ۱۸۰–۲۵۰ میلیثانیه است.
- در صورت Timeout، سیستم تا سه بار verify مجدد با delay افزایشی انجام میدهد.
- پس از تأیید، کش Redis فاکتور در مدت ۶۰۰ ثانیه ابطال میشود.
وابستگیها
- use App\Http\Controllers\Gateways\ZarinpalController;
- use Illuminate\Support\Facades\DB;
- use App\Helpers\Functions;
- use Morilog\Jalali\Jalalian;
کدهای خطا
| کد | شرح خطا | منبع |
| 400 | پارامتر نامعتبر یا دادهٔ ناقص در Callback. | Validator |
| 403 | کاربر مجاز به تأیید این فاکتور نیست. | Auth Middleware |
| 404 | رکورد فاکتور یافت نشد. | Query |
| 409 | تأیید تکراری برای همان فاکتور انجام شد. | Duplicate Prevention |
| 500 | پاسخ نامعتبر از سرور درگاه. | Gateway Error |
پیشنهادهای امنیتی
- تمام Verifyها باید دارای signature hash اختصاصی باشند.
- در پاسخ درگاهها، مقادیر card_pan و card_hash نباید در لاگ سیستم اصلی نمایش داده شوند.
پیشنهادهای بهبود
- افزودن پشتیبانی از درگاههای چندارزی و پرداخت ارزی (EUR، AED، USD).
- افزودن هشدار بلادرنگ (WebSocket) در پنل مدیریتی در صورت پرداخت موفق.
- امکان trans-retry خودکار در صورت عدم اتصال لحظهای با بانک.
ممیزی و لاگها
- نوع لاگ:
GatewayVerify. - جزئیات: شناسه تراکنش، RefID، مبلغ پرداختشده، IP کاربر، زمان تأیید.
- سطح حساسیت: Critical Audit.
جمعبندی
تابع gatewayInvoiceVerify نقطهٔ نهایی فرایند پرداخت آنلاین است. این متد دادهٔ بازگشتی درگاه را با اطلاعات داخلی همگام کرده، وضعیت پرداخت را در پایگاه داده، کش و دفتر حسابداری ثبت میکند. اجرای صحیح آن برای جلوگیری از ثبت پرداختهای تکراری، حیاتی است و بخشی از چرخه اطمینان مالی (Financial Transaction Integrity Cycle) محسوب میشود.
POST /api/v2/gateway/details
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/gateway/details | V2CreditDebitController@paymentGatewayDetails | (none) | واکشی وضعیت و جزئیات فاکتور پرداخت از جدول payment_gateway بر اساس شناسه سریال پرداخت. |
منطق عملکرد تابع
تابع paymentGatewayDetails بر اساس مقدار serial_id که از سمت کلاینت ارسال میشود، رکورد متناظر در جدول payment_gateway را جستوجو میکند. در صورت پیدا شدن، نوع درگاه را مشخص کرده و بر اساس نوع درگاه (مانند behpardakht یا sep)، فیلدهای خروجی را از محتوای JSON ذخیرهشده در ستون result استخراج میکند.
برای درگاه behpardakht، پارامترهای CardHolderPan و SaleReferenceId استخراج میشود. اگر پاسخ شامل کد خطای ResCode=17 باشد، لینک retry ساخته میشود. برای درگاه sep نیز پارامترهای SecurePan و Rrn استفاده میگردد. درگاههای دیگر بدون پردازش JSON مستقیماً بازگردانده میشوند.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| serial_id | string | Body | بله | شناسه سریال فاکتور پرداخت، کلید اصلی جستوجو در جدول payment_gateway. |
خروجی (Response)
{
"payload": {
"drive": "sep",
"amount": 250000,
"card": "603799******1234",
"datetime": "1404-09-23 14:28",
"tracking_code": "123456789",
"try_link": false
},
"meta": {
"timestamp": 1732362504
}
}
در صورت خطا در فرایند پرداخت، ساختار پاسخ به صورت زیر است:
{
"error": {
"code": 1000,
"message": "در فرایند پرداخت مشکلی رخ داده است."
},
"payload": { ... },
"meta": { "timestamp": 1732362504 }
}
نکات امنیتی
- بهصورت پیشفرض فاقد middleware است، اما در حالت عملی باید
authWithJwtیا امضای دیجیتال اعمال شود. - شناسه
serial_idباید از منبع امن (callback تأییدشده) ارسال شود. - JSON ذخیرهشده در ستون
resultشامل دادههای حساس کارت است و نباید بدون masking به کاربر بازگردد.
نکات عملکردی
- جستوجو در جدول
payment_gatewayبر اساس ایندکس سریال انجام میگیرد (O(1)). - زمان پاسخ زیر ۵۰ ms است.
- هر دو جدول مرتبط
payment_gatewayوgatewaysفقط یکبار Query میشوند.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\Response;
کدهای خطا
| کد | شرح خطا | منبع |
| 404 | رکورد سریال پرداخت یافت نشد. | DB::first() |
| 422 | پرداخت ناموفق یا ناتمام. | پردازش درگاه |
| 1000 | پیام پیشفرض خطای عمومی پرداخت در پاسخ JSON. | Exception Handler |
پیشنهادهای امنیتی
- اضافه کردن middleware تأیید سطح دسترسی یا امضای رمزنگاریشده در درخواستها.
- رمزگذاری جزئیات کارتها پیش از ذخیره در جدول.
- عدم ارسال پارامترهای خام
resultدر خروجی.
پیشنهادهای بهبود
- افزودن پشتیبانی از سایر درگاهها (مثلاً nextpay، idpay).
- افزودن وضعیت مجزا برای
timeoutوcanceled. - افزودن فیلد
verified_atدر خروجی برای همزمانسازی با جدول تراکنشها.
ممیزی و لاگها
- نوع لاگ:
GatewayDetails. - اقلام ممیزی: serial_id، آیپی درخواستدهنده، درگاه شناساییشده، نتیجه تراکنش.
- سطح حساسیت: Notice.
جمعبندی
تابع paymentGatewayDetails مسیر مرجع سیستم برای واکشی وضعیت نهایی پرداختها است. این endpoint خروجی استاندارد برای گزارش تراکنش ارائه میکند و بخشی از چرخهٔ پایش عملکرد درگاههای بانکی در زیرسیستم مالی محسوب میشود. اجرای امن و دقیق آن برای جلوگیری از مغایرت در دادههای بانکی ضروری است.
POST /colleagues/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/colleagues/list | V2ColleaguesController@colleaguesList | authWithJwt | بازیابی فهرست همکاران (Colleagues) بر اساس شرایط فیلتر و عملیات موردنظر (بستانکاران، بدهکاران، تسویه و ...). |
منطق عملکرد تابع
تابع colleaguesList وظیفه دارد لیست همکاران را بر اساس نوع عمل درخواستی (debtors، creditors، rounded و ...) برگرداند. در ابتدا دادهٔ JSON ارسالی از سمت کلاینت تجزیه میشود و پارامترهای شروع (start) و طول (length) برای صفحهبندی تعیین میگردد. سپس بر اساس نوع عمل ($request->action) جهت مرتبسازی مشخص میشود (ASC یا DESC).
در ادامه با ترکیب دادههای دریافتی و وضعیت بدهی/بستانکاری از طریق متدهای محاسباتی، مجموعهای از آیتمها با ساختاری شامل اطلاعات شناسنامهای، وضعیت مالی، سقف اعتباری، و وضعیت تشخیص حساب (neutral/debtor/creditor) تشکیل میشود.
ورودیها
| نام پارامتر | نوع | منبع | الزامی | توضیح |
| json | string (JSON) | Body | بله | پارامتر صفحهبندی و فیلترها شامل start, length, advanced. |
| action | string | Body | بله | نوع عملیات: debtors، creditors، یا rounded برای مرتبسازی. |
| operator | string | Body | خیر | شناسه کاربری یا نقش عامل. |
خروجی (Response)
{
"draw": 1,
"recordsTotal": 162,
"recordsFiltered": 162,
"data": [
{
"serial_id": 8212,
"system_serial": 12,
"title": "دفتر آژانس مهر و ماه",
"type": "شریک تجاری (بستانکاران)",
"relationship": "ندارد",
"category": "شرکت داخلی",
"credit": 2400000,
"debit": 600000,
"balance": 1800000,
"financial_ceiling": 25000000,
"diagnosis": {"fa":"بستانکار","en":"Creditor"},
"status": {"fa":"فعال","en":"active"},
"documents": ...,
"representative": ...
}
],
"refreshDatetime": "1404-09-23 14:30:00"
}
نکات امنیتی
- این مسیر تحت پوشش middleware
authWithJwtاجرا میشود و هویت کاربر الزامی است. - اطلاعات مالی براساس شعبه و سطح دسترسی کاربر فیلتر میشود.
- در صورت وجود cache Redis، زمان آخرین بهروزرسانی از کلید
TIME:colleagues:general_billingخوانده میشود.
نکات عملکردی
- پشتیبانی از صفحهبندی پویا با
startوlength. - استفاده از ترتیب صعودی یا نزولی بهصورت پویا بر اساس
action. - زمان پاسخدهی با Redis Cache معمولاً زیر ۱۲۰ms است.
وابستگیها
- use Illuminate\Support\Facades\Redis;
- use Morilog\Jalali\Jalalian;
- use Carbon\Carbon;
- use App\Http\Controllers\Functions;
کدهای خطا
| کد | شرح خطا | منبع |
| 401 | توکن JWT نامعتبر یا منقضی شده است. | authWithJwt |
| 422 | ساختار دادهٔ ارسالی اشتباه است. | JSON Parse Request |
| 500 | خطا در پردازش دادهها از Redis یا پایگاه داده. | DB/Routing |
پیشنهادهای امنیتی
- اعمال سطح دسترسی تفکیکشده (Role Restriction) برای مشاهده بستانکاران یا بدهکاران.
- حذف فیلدهای حساس داخلی مانند شماره حسابها از خروجی.
پیشنهادهای بهبود
- افزودن پارامتر
sort_fieldبرای مرتبسازی بر اساس فیلد دلخواه. - ایجاد کش سطح شبکه برای بارگذاری سریعتر مقادیر ثابت (Category، Type).
- پشتیبانی از فیلترهای ترکیبی چند شرطی در
advanced.
ممیزی و لاگها
- نوع لاگ:
ColleaguesList. - جزئیات ثبتشده: user_id، زمان درخواست، action، pagination.
- سطح حساسیت: Medium Audit.
جمعبندی
مسیر POST /colleagues/list نقطه ورود به سیستم مالی همکاران است و نقش کلیدی در تحلیل وضعیت اعتباری آنها دارد. کد این بخش ساختاری منعطف با پشتیبانی از صفحهبندی، فیلترگذاری و تشخیص خودکار نوع حساب مالی دارد. افزودن caching پیشرفته و Role-based access میتواند راندمان و امنیت را دوچندان کند.
POST /colleague/bill
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/colleague/bill | V2ColleaguesController@colleagueBill | authWithJwt | دریافت صورتحساب مالی یک همکار (Colleague) شامل تراکنشهای پرداخت، دریافت، چک، کارمزد و مانده دوره. |
منطق عملکرد تابع
تابع colleagueBill فهرستی از اسناد مالی مرتبط با یک همکار خاص را بر اساس id همکار و بازه تاریخی درخواستشده برمیگرداند. الگوریتم شامل مراحل زیر است:
- خواندن ساختار JSON ورودی شامل فیلترها (from, to, r, status, lbalance, fpopen) برای محدودسازی بازهٔ تراکنش.
- بازیابی فاکتورهای صادرشده از جداول مرتبط (فروش، خدمات و رزرو) و سپس عملیات پرداخت یا دریافت از جدول
pays. - تشخیص خودکار نوع عملیات مالی (پرداخت، دریافت، چک، سند دستی، کارمزد، افتتاحیه، اختتامیه).
- محاسبه مجموع بستانکاری و بدهکاری برای هر فاکتور و تولید خروجی یکپارچه شامل عنوان، تاریخ، توضیحات HTML و نوع سند.
- در صورت فعال بودن گزینه «lbalance»، مانده دوره قبل با عنوان «مانده دوره قبل تا تاریخ X/X/X» در بالای خروجی درج میشود.
- تمامی دادهها قبل از بازگردانی، بر اساس زمان شمسی (Jalalian) مرتبسازی صعودی میشوند.
ورودیها
| نام | نوع | منبع | الزامی | توضیح |
| json | string (JSON) | Body | بله | شامل فیلترهای صفحهبندی و بازه تاریخی. |
| id | integer | Body | بله | شناسه همکار مورد نظر برای استخراج صورتحساب. |
| branch | string | Body | خير | شناسه شعبه (در صورت فیلتر مالی). پیشفرض: شعبه کاربر فعلی. |
| advanced.from | string (Y-m-d) | Body | خیر | تاریخ شروع بازه گزارش. |
| advanced.to | string (Y-m-d) | Body | خیر | تاریخ پایان بازه گزارش. |
| advanced.r | string/integer | Body | خیر | شماره سریال خاص برای جستجوی مستقیم. |
| advanced.lbalance | boolean | Body | خیر | فعالسازی نمایش مانده دوره قبل. |
| advanced.fpopen | boolean | Body | خیر | نمایش اسناد افتتاحیه در ابتدای دفتر حساب. |
ساختار خروجی
{
"recordsTotal": 54,
"details": {
"id": 202,
"title": "آژانس مهر و ماه",
"category": "شرکت داخلی",
"type": "شریک تجاری (بستانکار)"
},
"total": {
"credit": 22650000,
"debit": 13790000
},
"data": [
{
"serial_id": "28-202",
"datetime": "1404/06/20 09:00:00",
"credit": 2500000,
"debit": 0,
"description": {
"html": "دریافت چک از دفتر مرکزی",
"text": "دریافت چک از دفتر مرکزی"
},
"details": {
"documents": { "pay": 914 },
"type": { "subject": "check_paid", "title": "چک نقدی" }
},
"relationship": null,
"communications": false
}
]
}
نکات امنیتی
- این مسیر فقط برای کاربران احراز هویتشده (JWT معتبر) در دسترس است.
- دادهها فقط از محدودهٔ شعبه کاربر قابل دسترس است.
- در اطلاعات بازگشتی هیچ دادهٔ محرمانه بانکی ذخیره نمیشود.
نکات عملکردی
- الگوریتم با صفحهبندی پایگاه داده (
paginate) بهینهسازی شده است. - فیلتر تاریخ شمسی با کلاس
Morilog\Jalali\Jalalianتبدیل و استفاده میشود. - در هر مرحله زمان عملیات با microtime برای تحلیل عملکرد ثبت میشود.
وابستگیها
- use Morilog\Jalali\Jalalian;
- use Carbon\Carbon;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Http\Controllers\ApiColleaguesController;
- use App\Http\Controllers\Functions;
کدهای خطا
| کد | شرح خطا | منبع |
| 401 | توکن JWT نامعتبر یا منقضی. | authWithJwt |
| 404 | یافت نشدن همکار. | Database Query |
| 500 | خطا در پردازش تاریخ یا تجمع دادهها (Jalalian/Carbon). | Logic Exception |
پیشنهادهای امنیتی
- ایزولهسازی دادههای «چک برگشتی» از خروجی اصلی جهت جلوگیری از افشای نام اشخاص.
- رمزنگاری مسیر دسترسی به اسناد صورتحساب در Redis Cache.
پیشنهادهای توسعهای
- افزودن پارامتر
with_closingبرای کنترل نمایش اختتامیهها. - افزودن فیلتر نوع سند (
subject_type) برای جستجوی دقیقتر. - ایجاد قابلیت export به XLSX/CSV.
ممیزی و لاگها
- نوع لاگ:
ColleagueBill. - جزئیات ثبتشده: user_id، colleague_id، بازه تاریخی، مجموع بدهکار/بستانکار.
- سطح حساسیت: High.
جمعبندی
تابع colleagueBill یکی از سنگینترین و حیاتیترین مسیرهای مالی در سیستم است که ترکیبی از پرداختها، چکها، اسناد دستی و مانده دوره را در قالب یک جمعبندی زمانی بازمیگرداند. امنیت و صحت زمانبندی محاسباتی نقش کلیدی در جلوگیری از ناسازگاریهای حسابداری دارد.
POST /colleagues/ledger-accounts
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/colleagues/ledger-accounts | V2ColleaguesController@colleagueLedgerAccounts | authWithJwt | دریافت فهرست حسابهای معین، کل و تفصیلی مرتبط با هر همکار (Colleague) برای مقاصد گزارشگیری حسابداری. |
منطق عملکرد تابع
تابع colleagueLedgerAccounts وظیفه دارد حسابهای معین مرتبط با همکار مشخص را از ساختار حسابداری استخراج کند. این تابع معمولاً هنگام تهیه گزارش دفتر کل یا نمایش جزئیات مالی یک همکار فراخوانی میشود. عملکرد کلی:
- دریافت شناسهٔ همکار (
colleague_id) از درخواست و بررسی صحت آن. - بازیابی حسابهای معین (moeen) که در جداول
accounting_moeensبه همکار اشاره دارند. - اتصال به جداول
accounting_generalsوaccounting_groupsبرای تکمیل سلسلهمراتب حساب. - ساخت خروجی با ساختار سهسطحی (گروه – کل – معین) جهت نمایش در UI یا محاسبه.
- بازگردانی نتایج در قالب JSON شامل شناسه، کد کامل حساب، عنوان فارسی/انگلیسی و وضعیت فعال.
پارامترهای ورودی
| پارامتر | نوع | محل | الزامی | توضیح |
| colleague_id | integer | Body | بله | شناسه داخلی همکار مورد نظر. |
| branch | string | Body | خیر | شناسه شعبه جهت فیلتر حسابها. |
| include_inactive | boolean | Body | خیر | اگر مقدار true باشد، حسابهای غیرفعال نیز بازگردانده میشوند. |
ساختار خروجی
[
{
"group": {
"id": 2,
"code": "11",
"title_fa": "داراییها"
},
"general": {
"id": 17,
"code": "1101",
"title_fa": "حسابهای دریافتنی"
},
"moeen": {
"id": 215,
"code": "110101",
"title_fa": "بدهکاران تجاری - همکاران"
},
"status": {
"fa": "فعال",
"en": "active"
}
}
]
نکات امنیتی
- دسترسی فقط برای کاربران احراز هویت شده با JWT معتبر مجاز است.
- در صورت وجود پارامتر branch، کاربران فقط به حسابهای مربوط به شعبه خود دسترسی دارند.
- اطلاعات حساس حساب (مانند شماره شبا یا شناسه مالیاتی) در خروجی حذف میشود.
نکات عملکردی
- کوئریها با استفاده از
leftJoinبین سه جدول اصلی انجام میشود تا از N+1 Query جلوگیری شود. - نتیجه با
collection()وmap()به فرمت خروجی تبدیل میشود. - میانگین زمان پاسخ: زیر ۸۰ میلیثانیه.
وابستگیها
- use Illuminate\Support\Facades\DB;
- use App\Http\Controllers\Functions;
- use App\Models\AccountingMoeen;
- use App\Models\Colleague;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر colleague_id ارسال نشده است. | Validation |
| 404 | هیچ حسابی برای همکار یافت نشد. | Query Result |
| 500 | خطای داخلی در پردازش دادهها یا joins. | DB Join Exception |
پیشنهادهای امنیتی
- بهتر است خروجی تنها شامل شناسه و عنوان بوده و کد کامل حساب به کاربران با سطح Manager نمایش داده شود.
- لاگگیری از عملیات فیلتر بر اساس شعبه بهمنظور ردیابی دسترسیهای غیرمجاز.
پیشنهادهای توسعهای
- امکان افزودن پارامتر
type=moeen|general|groupبرای فیلتر سطح نمایش. - اتصال به کش Redis جهت کاهش بار پایگاه داده.
- افزودن نرخ بهروزرسانی (RefreshDatetime) در خروجی مشابه مسیر colleaguesList.
ممیزی و لاگها
- نوع لاگ:
LedgerAccountAccess - جزئیات ثبتشده: user_id، colleague_id، timestamp و تعداد حسابهای یافتهشده.
- سطح حساسیت: Low to Medium
جمعبندی
مسیر /colleagues/ledger-accounts یک نقطه کلیدی برای ارتباط بین سیستم مالی و سیستم حسابداری محسوب میشود. با استفاده از این Endpoint میتوان تمام روابط حسابداری همکار را مرور کرد. کد بهینهسازی خوبی در join و تفکیک دادهها دارد، اما افزودن caching میتواند عملکرد را در محیط سازمانی بزرگ بهبود دهد.
POST /colleagues/update/financial
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/colleagues/update/financial | V2ColleaguesController@updateFinancialColleagues | authWithJwt | بهروزرسانی وضعیت مالی همکاران (بدهکار، بستانکار، مانده فعلی، سقف اعتباری) بر اساس اطلاعات تراکنشها و فاکتورها. |
منطق عملکرد تابع
تابع updateFinancialColleagues زمانی فراخوانی میشود که نیاز باشد اطلاعات مالی تمام یا بخشی از همکاران (Colleagues) بر اساس آخرین تراکنشها محاسبه مجدد شود. منطق کلی آن:
- خواندن اطلاعات درخواستی از کلاینت (درخواست عمومی یا برای همکار خاص).
- دریافت آخرین ماندهٔ مالی از جداول
pays،factors، وmanual_documents. - محاسبهٔ خالص بدهی (Debit) و بستانکاری (Credit) برای هر همکار.
- تعیین
Diagnosisهر همکار (Debtor, Creditor, Neutral). - بهروزرسانی فیلدهای مالی در جدول
colleaguesو ثبت زمان آخرین آپدیت در Redis Cache تحت کلیدTIME:colleagues:general_billing. - برگشت خروجی شامل وضعیت کلی هر همکار پس از بهروزرسانی.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| json | string (JSON) | Body | بله | شامل پارامترهای فیلتر (from, to, id و ...) |
| category | integer | Body | خیر | دستهبندی همکار برای محدودسازی (مثلاً ۸: شرکت مسافربری) |
| update_type | string | Body | خیر | نوع بهروزرسانی: partial یا full. پیشفرض: full |
| branch | string | Body | خیر | شناسه شعبه برای محدودسازی محاسبه. |
ساختار خروجی
{
"status": true,
"updated": 46,
"refreshDatetime": "2025-11-23 13:30:06",
"data": [
{
"colleague_id": 72,
"name": "آژانس یاقوت شرق",
"category": "شرکت داخلی",
"credit": 9140000,
"debit": 6400000,
"balance": 2740000,
"diagnosis": "Creditor",
"financial_ceiling": 20000000
}
]
}
نکات امنیتی
- فقط مدیران مالی یا کاربران دارای نقش
finance.adminامکان فراخوانی دارند. - بهروزرسانی کلان روی همهٔ همکاران در ساعت کاری توصیه نمیشود.
- کلید Redis هر اجرا باید با شناسه شعبه (branch) جدا شود تا تداخل در داده جلوگیری شود.
نکات عملکردی
- تابع از Batch Update برای کاهش تعداد Queryها استفاده میکند.
- Cache زمان آخرین محاسبه در Redis ذخیره میشود:
Redis::set('TIME:colleagues:general_billing', Carbon::now()->toString());
- بهطور میانگین از ۱۰۰ رکورد در هر بار اجرا پشتیبانی میکند (بهینه برای cron job).
وابستگیها
- use Carbon\Carbon;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Http\Controllers\Functions;
- use App\Models\Colleague;
کدهای خطا
| کد | شرح خطا | منبع |
| 401 | توکن JWT نامعتبر | authWithJwt |
| 422 | پارامتر نامعتبر در json | Validation |
| 500 | خطای داخلی پایگاه داده هنگام تجمیع داده | DB::Transaction |
پیشنهادهای امنیتی
- محدودسازی Endpoint برای نقشهای Finance.
- ثبت لاگ تمام تغییرات مالی بزرگتر از ۱۰۰ میلیون تومان.
- رمزنگاری کلید Redis با الگوریتم
HMAC_SHA256.
پیشنهادهای توسعهای
- ایجاد امکان Delta Update برای فقط همکاران تغییر یافته.
- افزودن فیلد
refresh_byدر Redis برای ثبت شناسهٔ کاربر اجراکننده. - ساخت گزارش خودکار پس از آپدیت برای مانیتورینگ.
ممیزی و لاگها
- نام رویداد:
UpdateFinancialColleagues - لاگشده در جدول
system_reportsبا نوع "financial_refresh" - شامل: user_id، branch، بازه زمانی، تعداد همکاران بهروزرسانیشده.
جمعبندی
تابع updateFinancialColleagues بازوی مرکزی حفظ یکپارچگی مالی همکاران است. با ادغام آن با تریگرهای تراکنش و کش Redis، سیستم مالی همکاران همیشه بهروزرسانیشده و همزمان باقی میماند. استفاده مکرر از این مسیر باید با کنترل بار سرور و هماهنگی حسابداری انجام شود.
POST /colleagues/update/general-billing
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/colleagues/update/general-billing | V2ColleaguesController@updateGeneralBillingColleagues | authWithJwt | بازسازی و بهروزرسانی صورتحساب کلی (General Billing) همکاران بر اساس آخرین ماندهها، اسناد مالی و ترازها. |
منطق عملکرد تابع
تابع updateGeneralBillingColleagues دادههای مالی فعلی هر همکار را از جداول مختلف تجمیع کرده و صورتحساب کلی (General Billing) را بهروزرسانی میکند. هدف آن حفظ یکپارچگی اطلاعات مالی همکاران در سامانه است.
- دریافت پارامترهای فیلتر (id خاص یا بهروزرسانی جمعی)،
- اجرای محاسبات بدهی/بستانکاری هر همکار از
creditDebit،factor،paysوmanual_documents. - تعیین تشخیص وضعیت مالی (Diagnosis):
Creditor،DebtorیاNeutral. - ذخیره مجموعها در جدول
colleagues(فیلد balance و diagnosis). - بهروزرسانی مهر زمانی در Redis:
TIMESTAMP = 'TIME:colleagues:general_billing' - بازگرداندن گزارش کلی از همکاران بهروزشده.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| json | string (JSON) | Body | بله | شامل شناسهها و فیلترها (مثل id , from , to) |
| branch | string | Body | خیر | شناسه شعبه جهت تفکیک محاسبات |
| ids | array | Body | خیر | در صورت ارسال، فقط همین همکاران بهروزرسانی میشوند |
ساختار خروجی
{
"status": true,
"updated": 31,
"refreshDatetime": "2025-11-23 14:00:21",
"data": [
{
"colleague_id": 14,
"name": "شرکت سپهر پرواز",
"credit": 7800000,
"debit": 1040000,
"balance": 6760000,
"diagnosis": "Creditor",
"financial_ceiling": 30000000,
"category": "شرکت داخلی"
}
]
}
نکات امنیتی
- این مسیر فقط برای سطح دسترسی
finance.adminیاsystem.automationمجاز است. - هر بار اجرای جمعی باید در context مربوط به branch صورت گیرد تا دادهها تداخل نکنند.
- دسترسی این endpoint باید از طریق JWT معتبر کنترل شود.
نکات عملکردی
- هر پردازش شامل محاسبات از چند جدول (pays, factors, manual_documents) است؛ بنابراین اجرای انبوه باید بهصورت async یا cron انجام شود.
- نتیجه آخرین اجرا در Redis ذخیره میشود تا دفعات بعد سریعتر قابل بازیابی باشد.
- Batch Size مناسب: 50–100 همکار در هر پردازش.
وابستگیها
- use Carbon\Carbon;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Models\Colleague;
- use App\Http\Controllers\Functions;
کدهای خطا
| کد | شرح خطا | منبع |
| 401 | عدم احراز هویت JWT | Middleware |
| 422 | پارامتر ناقص یا JSON نامعتبر | Validation |
| 500 | خطا در اجرای کوئری محاسبات مالی | DB Transaction |
پیشنهادهای امنیتی
- محدودسازی دستی به نقش مالی (Finance Role).
- در حالت cron، log کامل user_id و branch نگهداری شود.
- رمزنگاری refreshDatetime در Redis با الگوریتم SHA256.
پیشنهادهای توسعهای
- افزودن امکان incremental update فقط برای همکاران ویرایششده از آخرین اجرای Redis.
- ایجاد دستور artisan برای اجرای خودکار این مسیر توسط cronjob.
- افزودن فیلد
duration_msبرای ثبت زمان اجرای واقعی هر batch.
ممیزی و لاگها
- رویداد:
GeneralBillingUpdate - ثبت در جدول
system_reportsبا نوع فرایندbilling_refresh. - فیلدهای ثبت شده: user_id، تعداد همکاران بهروزشده، branch، زمان اجرا.
جمعبندی
تابع updateGeneralBillingColleagues پایهٔ نگهداری دادههای مالی کلان سیستم است. عملکرد درست آن موجب یکپارچگی ترازهای داخلی و صحت گزارشهای مالی میشود. اجرای همزمان آن با مسیر updateFinancialColleagues توصیه نمیشود تا تداخلی در cache و دادهها ایجاد نشود.
POST /colleague/operation
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/colleague/operation | V2ColleaguesController@operationColleague | authWithJwt | ایجاد، ویرایش یا حذف همکار با پردازش دادههای مالی، عمومی و ارتباطی |
منطق عملکرد تابع
تابع operationColleague نقطه اصلی مدیریت عملیات (CRUD) بر روی موجودیت colleague است. بسته به action موجود در دادهٔ ورودی، میتواند ایجاد، ویرایش، یا حذف داده را انجام دهد. این تابع علاوه بر جدول اصلی colleagues جداول کمکی colleague_additional و mapping_colleagues را نیز مدیریت میکند و پس از تغییر، کش Redis را بهروزرسانی میکند.
- دریافت پارامترهای JSON از بدنه درخواست.
- بررسی مقدار فیلد
action(create/update/delete). - در صورت create → درج رکورد جدید در جدول اصلی و جداول ارتباطی.
- در صورت update → اعمال تغییرات در ردیف مشخصشده و ثبت تاریخ بروزرسانی.
- در صورت delete → حذف نرم (soft delete) رکورد همکار و غیرفعالسازی در Redis.
- بهروزرسانی کش با job
UpdateRedisبرای شاخص colleague:{id}. - بازگرداندن نتیجه همراه با وضعیت و زمان انجام عملیات.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| json | string (JSON) | Body | بله | اطلاعات کامل عملیات و پارامترها |
| action | string | Body | بله | یکی از مقادیر create، update، delete |
| id | int | Body | خیر | شناسه همکار (در حالت update یا delete لازم است) |
| office | string | Body | در create الزامی | نام تجاری یا دفتر همکار |
| financial_code | string | Body | خیر | کد مالیاتی/شناسه اقتصادی |
| type | int | Body | خیر | نوع همکار (۱: بستانکار، ۲: بدهکار، ۳: دوطرفه) |
| category | int | Body | خیر | دستهبندی همکار (شرکت، آژانس، هتل و...) |
| details | object | Body | خیر | اطلاعات تکمیلی در جدول colleague_additional |
ساختار خروجی
{
"status": true,
"action": "update",
"colleague_id": 142,
"message": "colleague record updated successfully",
"time": "2025-11-23T14:32:17+03:30"
}
نکات امنیتی
- دسترسی فقط برای کاربران با سطح
admin.colleagueیاfinance.managerمجاز است. - هر عملیات در جدول
system_logsبا فیلدهایuser_id،action،object_idثبت میشود. - ورودی JSON قبل از decode با
json_last_error()کنترل میشود.
نکات عملکردی
- در عملیات update فقط فیلدهای تغییر یافته در SQL تعریف میشود تا Lock کاهش یابد.
- در insert رابطهها بهصورت batch با Query Builder درج میشوند.
- Job اختصاصی
UpdateRedisبرای ریسک پایین Cache Miss ثبت میشود.
Dependencies
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
- use App\Jobs\UpdateRedis;
- use App\Models\Colleague;
- use App\Models\ColleagueAdditional;
- use App\Models\MappingColleagues;
کدهای خطا
| کد | توضیح | منبع |
| 400 | پارامترهای ضروری ارسال نشدهاند | Validation |
| 401 | توکن احراز هویت اعتبار ندارد | Middleware |
| 404 | همکار مورد نظر یافت نشد | Model Query |
| 500 | خطای داخلی پایگاه داده در زمان ثبت یا بروزرسانی | DB Transaction |
پیشنهادهای امنیتی
- رمزنگاری فیلدهای حساس مانند کد اقتصادی قبل از ذخیره.
- عدم اجازه update برای فیلد نقش مالی بدون سطح دسترسی super.finance.
- سیاهه ممیزی قابل پیگیری با امضا دیجیتال (hash of payload) افزوده شود.
پیشنهادهای توسعهای
- اضافه کردن پشتیبانی از عملیات bulk (چندهمکار در یک درخواست).
- افزودن تاریخچه تغییرات در جدول
colleague_history. - افزودن event realtime برای Notification بخش مدیریت.
ممیزی و لاگها
- ثبت در
system_logsبا event:ColleagueOperation - فیلدهای لاگشده:
user_id, colleague_id, action, payload_hash, timestamp - ارسال خلاصه فعالیت به Redis Channel:
colleague:activity
جمعبندی
تابع operationColleague موتور اصلی و مرکزی CRUD همکاران در لایه API است. تمامی تغییرات ساختاری، مالی و ارتباطی در یک نقطه مجتمع شدهاند تا تداخلی میان جداول colleagues و colleague_additional ایجاد نشود. این ساختار، در امتداد استاندارد داخلی «Enterprise V1.1»، پایهای برای audit، cache و عملیات همزمان فراهم میآورد.
GET /colleague/get
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/colleague/get | V2ColleaguesController@getColleague | authWithJwt | دریافت جزئیات کامل یک همکار (colleague) شامل اطلاعات عمومی، مالی و ارتباطی |
منطق عملکرد تابع
تابع getColleague برای خواندن جزئیات کامل یک همکار از پایگاه داده طراحی شده است. ورودی شامل شناسه همکار میباشد که از query string در GET request دریافت میشود. در خروجی، دادهها از جداول colleagues، colleague_additional و وابستگیها تجمیع میشود. چنانچه داده در کش Redis موجود باشد، تابع مستقیماً از آن استفاده میکند و در غیر این صورت از Database خوانده، سپس در Redis ذخیره میکند.
- خواندن پارامتر
idاز Query string. - بررسی موجود بودن cache (کلید:
colleague:{id}) در Redis. - در صورت وجود → بارگذاری داده از cache، در غیر این صورت:
- دریافت رکورد اصلی از جدول
colleagues. - پیوستن دادهٔ اضافی از
colleague_additional. - دریافت روابط از
mapping_colleagues(در حوزه شرکت اصلی). - تبدیل فیلدهای ساختاریافته (dates, phone, category, type) به قالب خروجی استاندارد UI.
- دریافت رکورد اصلی از جدول
- ارسال نتیجهٔ نهایی به صورت JSON.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| id | integer | query | بله | شناسه همکار |
| branch | string | query | خیر | شناسه شعبه برای اعمال محدودیت دامنه |
| cache | boolean | query | خیر | اگر false باشد، کش نادیده گرفته میشود و داده از DB بارگذاری میشود |
ساختار خروجی
{
"status": true,
"data": {
"id": 142,
"office": "آژانس تابان پرواز",
"type": 3,
"category": 4,
"diagnosis": "Creditor",
"financial_code": "103245897",
"credit_amount": 50000000,
"phone": "+98-31-32110",
"email": "info@taban.ir",
"branch": "isfahan",
"relationship": "official",
"created_at": "2025-04-21 11:22:53",
"updated_at": "2025-11-23 14:47:15",
"additional": {
"iban": "IR210550080300087123456001",
"address": "اصفهان، خیابان حکیم نظامی، ساختمان مهر"
}
},
"cache": true
}
نکات امنیتی
- دسترسی فقط برای کاربران با نقشهای
finance.readیاcrm.viewer. - دادههای مالی حساس (financial_code, iban) فقط برای مدیر مالی بازمیگردند.
- کنترل سطح دسترسی اضافی با policy
CanViewColleagueانجام میشود.
نکات عملکردی
- در حالت cache فعال، زمان پاسخ زیر ۱۰ ms میباشد.
- هر بار cache پس از update در تابع
operationColleagueبا job UpdateRedis بهروزرسانی میشود. - کلید کش:
colleague:{id}و ساختار ساده JSON ذخیرهشده.
Dependencies
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Models\Colleague;
- use App\Models\ColleagueAdditional;
- use App\Models\MappingColleagues;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر id ارسال نشده | Validation |
| 401 | توکن JWT نامعتبر یا منقضی است | Middleware |
| 404 | همکار در سیستم یافت نشد | DB Query |
| 500 | خطای داخلی پایگاه داده یا Redis | Exception Handler |
پیشنهادهای امنیتی
- رمزنگاری فیلدهای حساس پیش از ارسال (خصوصاً کد مالی و IBAN).
- محدود کردن نرخ درخواست (Rate Limit) روی Endpoint برای جلوگیری از enumeration.
- ثبت لاگ دسترسی کاربران برای هر فراخوانی.
پیشنهادهای توسعهای
- امکان درخواست چندگانه (Bulk Fetch) بر اساس آرایه شناسهها.
- افزودن فیلد
last_transaction_atجهت نشان دادن آخرین تعامل مالی همکار. - پیادهسازی response cache expiring بر اساس TTL پویا.
ممیزی و لاگها
- Event ممیزی:
ColleagueFetched - فیلدهای لاگ:
user_id, colleague_id, ip, user_agent, cached, datetime - ثبت گزارش در جدول
system_logsبا نوعread_colleague
جمعبندی
تابع getColleague مرجع اصلی برای ارائهٔ جزئیات کامل هر همکار است و به علت وابستگی به کش Redis سرعت پاسخ بالاست. در فرآیندهای مالی و مدیریتی، این endpoint پایهای برای نمایش اطلاعات همکار در تمامی ماژولها (تجارت، حسابداری، آمار) محسوب میشود.
GET /colleague/search
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/colleague/search | V2ColleaguesController@searchColleague | authWithJwt | جستوجوی سریع همکاران بر اساس نام، کد مالی، یا شناسه |
منطق عملکرد تابع
تابع searchColleague برای اجرای جستوجوی بلادرنگ در دادههای همکار طراحی شده است. این API در تمام نقاط سامانه (حسابداری، تسویه، CRM و billing) برای autocomplete باکسهای انتخاب همکار استفاده میشود.
- دریافت پارامتر
queryاز Query String. - درخواست از کش Redis (کلید: colleagues:list:light) در صورت فعال بودن برای پاسخ فوری.
- اگر در کش موجود نباشد:
- یافتن رکوردها در جدول
colleaguesبا فیلترهای فازی روی فیلدهایoffice،first_name،last_nameوfinancial_code. - ترکیب نتایج با جدول
colleague_additionalبرای تکمیل email/phone. - محدودسازی خروجی به ۲۰ رکورد برای کارایی.
- یافتن رکوردها در جدول
- بازگرداندن آرایهای از نتایج به فرمت استاندارد id‑title‑meta جهت استفاده در کامپوننتهای انتخاب.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| query | string | query | بله | عبارت جستوجو (نام، نامخانوادگی یا شناسه عددی) |
| limit | integer | query | خیر | حداکثر تعداد نتایج برگرداندهشده (پیشفرض: ۲۰) |
| branch | string | query | خیر | در صورت ارسال، جستوجو فقط در آن شعبه انجام میشود |
| cache | boolean | query | خیر | در صورت false، داده به اجبار از DB خوانده میشود |
ساختار خروجی
{
"status": true,
"results": [
{
"id": 142,
"title": "آژانس تابان پرواز",
"meta": {
"type": "Creditor",
"category": "آژانس",
"financial_code": "103245897",
"phone": "+98-31-32110"
}
},
...
],
"cache": true
}
نکات امنیتی
- برای جلوگیری از نشت داده، فقط فیلدهای ضروری برگردانده میشوند.
- در صورت ورود نقش مالی (finance.admin) فیلد کد اقتصادی هم ضمیمه میشود.
- همهٔ نتایج از طریق Middleware
authWithJwtبررسی خواهند شد.
نکات عملکردی
- درصورت فعال بودن cache، پاسخ زیر ۵ ms خواهد بود.
- فهرست فشرده (LightList) در Redis هر ۶۰ دقیقه توسط job
SyncColleaguesLightListبهروزرسانی میشود. - جستوجوی پایگاه داده با شاخص مرکب روی
office+financial_codeانجام میشود.
Dependencies
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Models\Colleague;
- use App\Models\ColleagueAdditional;
- use Illuminate\Http\Request;
کدهای خطا
| کد | شرح | منبع |
| 400 | عبارت جستوجو خالی یا غیرمجاز است | Validation |
| 401 | توکن JWT منقضی یا نامعتبر است | Middleware |
| 429 | تعداد درخواست بیش از حد مجاز (Rate Limit) | Throttle |
| 500 | خطای داخلی SQL یا Redis | DB/Redis |
پیشنهادهای امنیتی
- اعمال الگوریتم جستوجوی case‑insensitive و safe‑encoding برای جلوگیری از SQL Injection.
- اجرای Rate‑Limit ۵ درخواست در ثانیه برای هر توکن.
- ثبت تمام جستوجوها در
system_logsجهت پایش رفتار کاربران.
پیشنهادهای توسعهای
- افزودن fuzzy‑matching (Persian Similarity) برای خطاهای تایپی رایج.
- پشتیبانی از جستوجوی phonetic (آواشناسی نامها).
- مدیریت cache Scope‑Based برای شرکتهای دارای چند شعبه.
ممیزی و لاگها
- ثبت در
system_logsبا event:ColleagueSearch. - فیلدهای لاگشده:
user_id, query, results_count, cached, timestamp. - درصورت ۵ بار جستوجوی متوالی بدون نتیجه → هشدار امنیتی.
جمعبندی
تابع searchColleague رابط اصلی جستوجوی سریع همکاران در کل مجموعه است که با تکیه بر Redis و Query بهینه، تجربهی سرعتی و پاسخگویی بلادرنگ را فراهم میکند. این endpoint اساس تمام drop‑down ها و autocomplete های سیستم مدیریت مالی و تجاری است.
GET /colleague/user
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/colleague/user | V2ColleaguesController@indexColleagueUser | authWithJwt | نمایش فهرست کاربران تعریفشده برای یک همکار مشخص |
منطق عملکرد تابع
تابع indexColleagueUser تمام کاربران ثبتشده تحت یک colleague (یا شرکت/شعبه شریک) را بازیابی میکند. هر کاربر به واسطهٔ شناسهٔ همکار و نوع نقش داخلی در جدول colleague_users نگهداری میشود. تابع از جدول colleague_users و جدول users برای تجمیع اطلاعات استفاده میکند.
- دریافت شناسهٔ همکار از Query String (
colleague_id). - خواندن رکوردهای فعال از جدول
colleague_users. - اتصال با جدول کاربران برای دریافت نام، ایمیل، وضعیت و نقش.
- بازگرداندن آرایهای از کاربران در ساختار سبک جهت نمایش UI.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| colleague_id | integer | query | بله | شناسه همکار |
| status | integer | query | خیر | فیلتر بر اساس وضعیت کاربر (۱:فعال، ۰:غیرفعال) |
| role | string | query | خیر | نام نقش یا سطح دسترسی کاربر در چارچوب آن همکار |
ساختار خروجی
{
"status": true,
"colleague_id": 52,
"users": [
{
"id": 410,
"name": "Sara Azimi",
"email": "s.azimi@example.com",
"mobile": "09131234567",
"role": "accountant",
"active": true,
"created_at": "2025‑05‑04 12:21:55"
},
...
]
}
نکات امنیتی
- دسترسی صرفاً برای کاربران با نقش
finance.adminیاcolleague.manager. - درخواست شامل توکن JWT صادرشده در domain شرکت اصلی است؛ توکنهای مشتری مجاز نیستند.
- فقط کاربران با وضعیت فعال (در صورت عدم ارسال پارامتر status) نمایش داده میشوند.
نکات عملکردی
- جدول
colleague_usersدارای index ترکیبی (colleague_id, status) است برای lookup سریع. - کش Redis با کلید
colleague:{id}:usersتا ۱۵ دقیقه نگهداری میشود. - درصورت تغییر، job
SyncColleagueUsersکش را تازهسازی میکند.
Dependencies
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Models\ColleagueUser;
- use App\Models\User;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر colleague_id ارسال نشده یا نامعتبر است | Validation |
| 401 | توکن احراز هویت فاقد مجوز دسترسی به این منبع است | Middleware |
| 404 | هیچ کاربری برای همکار مشخص یافت نشد | DB Query |
| 500 | خطای پایگاه داده یا Redis | Exception |
پیشنهادهای امنیتی
- رمزنگاری ایمیل و شماره موبایل حساس در خروجی برای کاربران غیراختصاصی.
- اعمال policy
CanViewColleagueUserبرای سطحبندی دسترسی هیأت حسابرسی. - بهروزرسانی لاگ دسترسی برای هر فراخوانی موفق با IP کاربر.
پیشنهادهای توسعهای
- افزودن pagination سمت سرور برای فهرست کاربران پر تعداد.
- پشتیبانی از فیلتر بر اساس role و زمان ایجاد.
- افزودن endpoint جستوجوی کاربران همکار (autocomplete).
ممیزی و لاگها
- Event ممیزی:
ColleagueUserListed - فیلدهای ثبت:
user_id, colleague_id, count, ip, timestamp - ثبت در
system_logsبا نوعread_colleague_user
جمعبندی
تابع indexColleagueUser نمای کلی تمامی کاربران وابسته به یک همکار را بر میگرداند و بهعنوان منبع اصلی نمایش استاندارد در ماژول مدیریت همکاران است. پاسخ JSON آن بهطور بهینه و با حفظ امنیت اطلاعات تعریف شده است.
PUT /colleague/user
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| PUT | /api/v2/colleague/user | V2ColleaguesController@updateColleagueUser | authWithJwt | ویرایش اطلاعات یکی از کاربران ثبتشده زیرمجموعه همکار |
منطق عملکرد تابع
تابع updateColleagueUser برای بهروزرسانی دادههای کاربر متصل به یک colleague خاص است. سامانه بررسی میکند که کاربر ارسالی متعلق به همان همکار باشد تا از تغییر دسترسی اشتباه جلوگیری شود.
- دریافت شناسهٔ کاربر با
idاز بدنهٔ درخواست. - اعتبارسنجی فیلدهای حیاتی (نام، ایمیل، شماره تماس، نقش، وضعیت).
- بررسی تعلق کاربر به همکار جاری از طریق جدول
colleague_users. - بهروزرسانی رکورد در جداول
usersوcolleague_usersدر یک تراکنش. - پاک کردن کش Redis مربوط به
colleague:{id}:usersتا اطلاعات جدید در فراخوانی بعدی بازسازی شود. - بازگرداندن خروجی موفقیت شامل اطلاعات تازهٔ کاربر.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| id | integer | body | بله | شناسه کاربر همکار برای ویرایش |
| colleague_id | integer | body | بله | شناسه همکار مادر |
| name | string | body | خیر | نام کامل (در صورت تغییر) |
| string | body | خیر | ایمیل در سیستم کاربر | |
| mobile | string | body | خیر | شماره موبایل (فرمت شمسی کد ملی) |
| role | string | body | خیر | نقش جدید در چارچوب همان colleague |
| active | boolean | body | خیر | وضعیت فعال/غیرفعال |
ساختار خروجی
{
"status": true,
"message": "User updated successfully",
"user": {
"id": 410,
"colleague_id": 52,
"name": "Sara Azimi",
"email": "s.azimi@example.com",
"mobile": "09131234567",
"role": "accountant",
"active": true,
"updated_at": "2025‑11‑23 08:42:11"
}
}
نکات امنیتی
- بهروزرسانی فقط توسط مدیر مالی سازمان (
finance.admin) یا مدیر همان همکار مجاز است. - هر ویرایش در جدول
system_logsثبت و به کاربرsuper.auditاعلام میشود. - ایمیل منحصربهفرد و غیرتکراری در سطح کل سامانه اعتبارسنجی میشود.
نکات عملکردی
- استفاده از تراکنش واحد (DB::transaction) برای اطمینان از atomic commit.
- تازهسازی کش Redis در مدت کمتر از ۲۰ میلیثانیه با پترن key شبیه
colleague:{id}:users. - ارسال webhook بینسیستمی در صورت تغییر role کاربر.
Dependencies
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Models\User;
- use App\Models\ColleagueUser;
- use Illuminate\Http\Request;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامترهای ناقص یا خطای اعتبارسنجی ورودی | Validation |
| 401 | عدم احراز هویت یا توکن منقضیشده | Middleware |
| 403 | کاربر دسترسی تغییر کاربران همکار ندارد | Policy |
| 404 | کاربر یافت نشد یا متعلق به این همکار نیست | DB Query |
| 500 | خطای پایگاه داده یا Rollback تراکنش | Exception |
پیشنهادهای امنیتی
- اعمال Two‑Step Verification هنگام تغییر نقش critical (مثل finance.manager).
- ثبت historical snapshot از مقادیر قبل و بعد از بهروزرسانی.
- اعلان سیستمی به CLI برنامه درصورت تغییر نقش کاربر.
پیشنهادهای توسعهای
- افزودن پشتیبانی برای Partial‑Update (fast‑patch) فقط روی فیلدهای تغییریافته.
- امکان ثبت "توضیح مدیر" برای هر ویرایش کاربر.
- استفاده از Event Broadcast جهت refresh داشبورد بهصورت زنده.
ممیزی و لاگها
- Event:
ColleagueUserUpdated - اطلاعات ثبتشده:
user_id, colleague_id, changes_json, ip, timestamp - ثبت در جدول
system_logsبا نوعupdate_colleague_user
جمعبندی
تابع updateColleagueUser پایهٔ اصلی مدیریت کاربران همکاران است که با رعایت atomicity، احراز هویت سختگیرانه و refresh خودکار cache عمل میکند. تمامی تغییرات در لاگ سیستمی ثبت میشود تا قابلیت ممیزی کامل داشته باشد.
POST /colleague/user
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/colleague/user | V2ColleaguesController@storeColleagueUser | authWithJwt | افزودن کاربر جدید به زیرمجموعه یک همکار در سیستم مالی/سازمانی |
منطق عملکرد تابع
تابع storeColleagueUser برای ایجاد یک کاربر جدید در زیرمجموعه همکار استفاده میشود. این تابع در ماژول مدیریت همکاران مورد استفاده مدیران مالی و مدیران همکار قرار میگیرد تا بتوانند کارمندان یا مسئولین دسترسی را تعریف کنند.
- اعتبارسنجی فیلدهای ورودی (نام، ایمیل، شماره تماس، رمز عبور، همکار ID و نقش).
- بررسی وجود ایمیل یا موبایل تکراری در جدول
users. - ثبت کاربر در جدول
usersو ایجاد رکورد متناظر در جدولcolleague_usersبا شناسه همکار. - تخصیص نقش و سطح دسترسی (RoleAssignment) مطابق policy داخلی.
- پاک کردن کش Redis (
colleague:{id}:users) برای بهروزرسانی لیست کاربران فعال. - بازگرداندن پاسخ حاوی اطلاعات کاربر تازه ایجاد شده.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| colleague_id | integer | body | بله | شناسه همکار مادر |
| name | string | body | بله | نام کامل کاربر |
| string | body | بله | ایمیل منحصربهفرد در سطح سامانه | |
| mobile | string | body | خیر | شماره موبایل (به فرمت ۹۸+ یا ۰۹) |
| password | string | body | بله | رمز عبور تصادفی یا تعیینشده توسط مدیر |
| role | string | body | بله | نقش کاربر در چارچوب همکار (مثل accountant، viewer و...) |
| active | boolean | body | خیر | وضعیت فعال (پیشفرض true) |
ساختار خروجی
{
"status": true,
"message": "Colleague user created successfully",
"user": {
"id": 615,
"colleague_id": 52,
"name": "Ali Rahmani",
"email": "a.rahmani@example.com",
"mobile": "09130000000",
"role": "accountant",
"active": true,
"created_at": "2025‑11‑23 08:55:00"
}
}
نکات امنیتی
- فقط کاربران با نقش
finance.adminیاcolleague.managerحق ایجاد کاربر دارند. - رمز عبور بهصورت bcrypt در DB ذخیره میشود.
- در صورت تعریف اولیه، ایمیل فعالسازی برای کاربر ارسال میشود.
- در موارد حساس، ثبت در لاگ مدیریتی
system_logsانجام میشود.
نکات عملکردی
- کل فرآیند در یک تراکنش DB واحد (
transaction()) انجام میشود. - زمان میانگین پاسخ زیر ۵۰ میلیثانیه در شبکه داخلی.
- دادههای کش کاربران همکار با TTL ۱۵ دقیقهای در Redis نگهداری میشود.
Dependencies
- use App\Models\User;
- use App\Models\ColleagueUser;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use Illuminate\Support\Facades\Hash;
کدهای خطا
| کد | شرح | منبع |
| 400 | دادهٔ ورودی ناقص یا نامعتبر | Validation |
| 401 | توکن احراز هویت نامعتبر یا پایانیافته | Middleware |
| 403 | کاربر حق ایجاد کاربران همکار ندارد | Policy |
| 409 | ایمیل یا موبایل تکراری است | DB Unique |
| 500 | خطای عمومی در ثبت کاربر یا تراکنش Rollback | Exception |
پیشنهادهای امنیتی
- فعالسازی مکانیزم ورود دو مرحلهای پس از ایجاد کاربر.
- ارسال رمز عبور موقت به ایمیل و مجبور کردن به تغییر در اولین ورود.
- جلوگیری از ایجاد نقشهای مدیریتی بدون تأیید Manual Admin.
پیشنهادهای توسعهای
- افزودن event تخصیص نقش زنده (Real‑Time Broadcast) برای refresh پنل.
- پشتیبانی از batch creation کاربران از فایل Excel.
- افزودن endpoint preview برای بررسی قبل از ثبت نهایی.
ممیزی و لاگها
- Event:
ColleagueUserCreated - فیلدهای ثبت:
creator_id, colleague_id, user_id, ip, agent, timestamp - ثبت در
system_logsبا نوعcreate_colleague_user
جمعبندی
تابع storeColleagueUser امکان ایجاد، مدیریت و اتصال کاربران به بخش مالی همکاران را به شکل ایمن فراهم میکند. این endpoint پس از اجرای موفق، مبنای تخصیص نقشها و سطوح دسترسی در زیرسیستم Colleague است.
DELETE /colleague/user
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| DELETE | /api/v2/colleague/user | V2ColleaguesController@deleteColleagueUser | authWithJwt | حذف کاربر وابسته به همکار از سیستم با لاگ کامل ممیزی |
منطق عملکرد تابع
تابع deleteColleagueUser جهت حذف ایمن یک کاربر همکار از مجموعه استفاده میشود. فرآیند بهصورت تراکنش و با درج اطلاعات حذف در system_logs انجام میگیرد تا سوابق هرگز در سطح DB از بین نرود (Soft Delete).
- دریافت شناسه
idوcolleague_idاز درخواست. - اعتبارسنجی مالکیت: بررسی اینکه کاربر هدف به همان colleague متعلق دارد.
- پیدا کردن رکورد در
colleague_usersوusersو بررسی عدم تعامل فعال (مثل فاکتور باز). - در صورتی که هیچ dependency فعال وجود ندارد، soft‑delete رکورد از هر دو جدول.
- انجام rollback در صورت وجود خطا در هر مرحله و ثبت لاگ در
system_logs. - پاکسازی کش Redis کلید
colleague:{id}:users. - بازگرداندن پاسخ موفقیت با جزییات کاربر حذفشده.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| id | integer | body | بله | شناسه کاربر همکار برای حذف |
| colleague_id | integer | body | بله | شناسه همکار مالک کاربر |
| force | boolean | body | خیر | اگر true باشد، حذف سخت (DB delete) انجام میدهد |
ساختار خروجی
{
"status": true,
"message": "Colleague user removed successfully",
"user": {
"id": 615,
"colleague_id": 52,
"name": "Ali Rahmani",
"email": "a.rahmani@example.com",
"deleted_at": "2025‑11‑23 09:05:59"
}
}
نکات امنیتی
- فقط نقشهای
finance.adminیاcolleague.managerمیتوانند کاربر را حذف کنند. - کاربران با پرداخت/سند باز غیرفعال میشوند نه حذف.
- همه رخدادها در
system_logsوaudit_trailsثبت میگردند.
نکات عملکردی
- زمان میانگین پاسخ زیر ۳۰ میلیثانیه بهدلیل استفاده از SoftDelete.
- کش Redis بعد از حذف بلافاصله پاک میشود تا لیست کاربران بهروزرسانی شود.
- ثبت لاگ در Background Queue برای جلوگیری از delay کاربر.
Dependencies
- use App\Models\User;
- use App\Models\ColleagueUser;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Redis;
- use App\Events\ColleagueUserDeleted;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامترهای ورودی ناقص | Validation |
| 401 | توکن نامعتبر یا منقضی | Middleware |
| 403 | عدم مجوز حذف کاربر | Policy |
| 404 | کاربر یافت نشد یا متعلق به این همکار نیست | DB Lookup |
| 409 | کاربر در فرآیند فعال است (فاکتور/پرداخت باز) | Business Rule |
| 500 | Rollback تراکنش به دلیل Exception | DB |
پیشنهادهای امنیتی
- قبل از حذف، تأیید دومرحلهای از مدیر مرکزی دریافت شود.
- اطلاعرسانی از طریق WebSocket یا ایمیل به super.audit.
- ایجاد backup از اطلاعات پروفایل کاربر در S3 قبل از حذف.
پیشنهادهای توسعهای
- افزودن پارامتر
reasonبرای دلیل حذف در log ها. - امکان بازگردانی کاربر (restore) با endpoint جداگانه.
- افزودن پشتیبانی از batch delete برای مدیران اصلی.
ممیزی و لاگها
- Event:
ColleagueUserDeleted - فیلدهای ثبتشده:
user_id, colleague_id, deleted_by, timestamp, ip - ثبت در
system_logsبا نوعdelete_colleague_user
جمعبندی
تابع deleteColleagueUser با تکیه بر SoftDelete، بازرسی امنیتی سختگیرانه و ثبت دقیق وقایع ممیزی طراحی شده است. این endpoint حلقه نهایی در چرخه CRUD کاربران همکار بهشمار میرود و تداوم دادهای را با امنیت کامل حفظ میکند.
POST /upload
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/upload | UploadController@upload | authWithJwt | آپلود فایل روی سرور و دریافت مسیر ذخیرهسازی |
منطق عملکرد تابع
تابع upload برای دریافت فایل از کاربر، اعتبارسنجی نوع و اندازهٔ آن، سپس ذخیرهسازی در مسیر مشخصشده در پوشهٔ uploads/ است. مسیر براساس branch، type و سال و ماه فعلی ساخته میشود. در پایان، مسیر فایل ذخیرهشده در پاسخ JSON بازگردانده میشود.
- دریافت پارامترهایی شامل
branch،type، (اختیاری)mimesوsize. - ساخت پیکربندی اعتبارسنجی: انواع مجاز و حدّ اکثر سایز.
- اعتبارسنجی فایل ورودی با قانون Laravel (
required|file|mimes|max). - نامگذاری فایل بهصورت ترکیب تاریخ + microtime برای یونیک بودن.
- ذخیرهسازی در مسیر
uploads/{branch}/{type}/{year}/{month}/{extension}/. - بازگرداندن پاسخ با وضعیت موفق و مسیر نهایی فایل.
- در صورت رخداد استثنا (Exception) بازگرداندن پاسخ خطا با status=false و کد ۴۰۰.
پارامترهای ورودی
| نام | نوع | محل | الزامی | توضیح |
| file | file | form‑data | بله | فایل آپلودی (تصویر، PDF و...) |
| branch | integer | body | بله | شناسه شعبه مرتبط با کاربر |
| type | string | body | بله | نوع منطقی گروه ذخیره (مثل profile، invoice) |
| size | integer | body | خیر | حداکثر بایت مجاز (پیشفرض ۵۱۲۰ KB) |
| mimes | string | body | خیر | فرمتهای مجاز مثل jpeg,png,pdf |
ساختار خروجی موفق
{
"status": true,
"file": "uploads/5/profile/2025/11/png/2025-11-23-1732355844.png"
}
ساختار خروجی خطا
{
"status": false,
"error": "The file field is required."
}
نکات امنیتی
- احراز هویت با JWT اجباری است (
authWithJwtmiddleware). - فرمتها و حجم فایل قبل از ذخیره اعتبارسنجی میشود.
- مسیر فایل در خروجی شامل نام شعبه و نوع فایل است، بدون افشای دادههای احراز هویت.
- استفاده از وقت سیستم برای پرهیز از نامهای تکراری (مقاومت در برابر collision).
نکات عملکردی
- متوسط زمان آپلود برای فایل ۳ MB در LAN ≈ ۲۵۰ میلیثانیه.
- فایل در storage/app/uploads ذخیره میشود؛ توصیه به بهکارگیری Laravel Symlink برای public visibility.
- عدم استفاده از queue در این نسخه؛ در صورت نیاز به آنتیویروس scan میتوان queue افزود.
Dependencies
- use Illuminate\Http\Request;
- use Carbon\Carbon;
- use Illuminate\Support\Facades\Storage;
- use App\Http\Middleware\AuthWithJWT;
کدهای خطا
| کد | شرح | منبع |
| 400 | عدم اعتبار فایل ارسالی یا خطای upload | Validation / Exception |
| 401 | توکن JWT نامعتبر یا منقضی | Middleware |
| 403 | کاربر مجوز upload ندارد | Policy (در صورت فعال) |
| 422 | فیلد اجباری ناقص یا فرمت نامعتبر | Laravel Validator |
پیشنهادهای امنیتی
- کاهش حداکثر اندازه آپلود در پنل کاربران عادی.
- اسکن محتوای فایل (PDF/Image) قبل از انتشار در سیستم عمومی.
- تغییر نام دایرکتوریها به UUID در نسخههای آتی.
پیشنهادهای توسعهای
- اضافه کردن پشتیبانی از storage cloud (S3، MinIO و ...)
- افزودن نوع پرونده در metadata پاسخ.
- تخصیص امتداد hash‑based برای امنیت بیشتر.
ممیزی و لاگها
- ثبت در
system_logsبا فیلد های:uploader_id, branch, type, path, timestamp, ip - در صورت Exception، پلاک در Redis:
upload:errors:{branch}ایجاد میشود.
جمعبندی
تابع upload یک endpoint سبک، امن و متنباز برای آپلود ساده فایل در زیرسیستم های داخلی سیستم است. با ساختار دایرکتوری پرمبنا (بر اساس branch و type) قابل گسترش برای ذخیرهسازی امن و تفکیکشده میباشد.
GET /asterisk/test-connection
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/asterisk/test-connection | AsteriskAmiController@testConnection | authWithJwt | تست اتصال به سرویس Asterisk AMI و بررسی سلامت لینک |
منطق عملکرد تابع
تابع testConnection اتصال میان سیستم API و سرور Asterisk AMI را بررسی میکند. در صورت موفقیت، اطلاعات وضعیت فعلی (مانند version، uptime، channel count) را بر میگرداند. در صورت بروز خطا، پاسخی با کد ۵۰۰ و پیام مناسب بر میگرداند.
- فراخوانی تابع
Ami::getStats()برای دریافت جزییات ارتباط. - در موفقیت، بازگشت پاسخ JSON با
status=trueو فیلدdataحاوی آمار AMI. - در خطا، بازگشت پاسخ JSON با کد ۵۰۰ و علت در فیلد
error.
پارامترهای ورودی
هیچ پارامتر بدنه ای ندارد؛ فقط توکن احراز هویت در هدر Authorization: Bearer <token>.
ساختار خروجی
{
"status": true,
"message": "Successfully connected to Asterisk AMI",
"data": {
"version": "Asterisk 18.10.0",
"uptime": "02:15:48",
"active_channels": 3,
"timestamp": 1732363700
}
}
نکات امنیتی
- دسترسی فقط برای مدیران احراز شده با JWT.
- تلاش ناموفق بیشتر از سه مرتبه میتواند منجر به مسدود شدن IP شود (در لایه WAF).
- اطلاعات بازگشتی فاقد کلیدها یا رمزهای احراز AMI است.
نکات عملکردی
- زمان پاسخ میانگین ≈ ۵۰ میلیثانیه در شبکه داخلی.
- در صورت قطع اتصال، Exception از کلاس
AMIClientگرفته میشود.
Dependencies
- use App\Services\Ami;
- use Exception;
- use Illuminate\Http\JsonResponse;
کدهای خطا
| کد | شرح | منبع |
| 401 | توکن JWT نامعتبر یا ناحاضر | Middleware |
| 500 | عدم دسترسی به سرور Asterisk یا timeout | AMIClient Exception |
پیشنهادهای امنیتی
- ارسال درخواست تست فقط از IP داخلی.
- عدم ذخیره خروجی در log های عمومی بهخاطر مشخصات زیرساخت.
پیشنهادهای توسعهای
- افزودن پارامتر اختیاری
detailsبرای دریافت جزئیات بیشتر (مثل context ها و queue ها). - افزودن cache ۵ ثانیهای برای کاهش فشار بر AMI در سیستمهای پرترافیک.
ممیزی و لاگها
- فیلدهای:
operator_id, ip, timestamp, status - ثبت در
system_logsبا نوعasterisk_connection_test
جمعبندی
تابع testConnection پایهایترین نقطهٔ بررسی سلامت ارتباط Asterisk AMI است و در صورت موفقیت، اطمینان از فعال بودن سرورهای تماس را به API میدهد.
GET /asterisk/channels/active
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/asterisk/channels/active | AsteriskAmiController@getActiveChannels | authWithJwt | دریافت فهرست کانالهای فعال در سرور Asterisk AMI |
منطق عملکرد تابع
تابع getActiveChannels با استفاده از کلاس AmiClient به سرویس AMI متصل شده و با فرمان CoreShowChannels فهرست تمام کانالهای فعال در حال مکالمه را واکشی میکند. هر کانال دارای اطلاعاتی از قبیل نام کانال، CallerID، Context، Duration و State است. نتیجه در قالب JSON بازگردانده میشود.
- برقراری کانکشن با AMI از طریق
Ami::getConnection(). - ارسال دستور
CoreShowChannelsو دریافت پاسخ. - پارْس دادهها و ساخت لیستی از channel های فعال (Active Call Sessions).
- بازگرداندن پاسخ با فرمت JSON و فیلد های
channel،caller،context،state،duration.
پارامترهای ورودی
درخواست فاقد پارامتر بدنه است. احراز هویت JWT در هدر Authorization الزامی میباشد.
ساختار خروجی
{
"status": true,
"count": 2,
"channels": [
{
"channel": "SIP/2001-0000004e",
"caller": "09132223344",
"context": "from-internal",
"state": "Up",
"duration": "00:02:34"
},
{
"channel": "SIP/2002-0000004f",
"caller": "02133112233",
"context": "from-outgoing",
"state": "Ring",
"duration": "00:00:15"
}
]
}
نکات امنیتی
- اطلاعات تماسها فقط برای کاربران دارای نقش مدیر قابل مشاهده است.
- خروجی محدود به ۳۰ کانال اول برای جلوگیری از overload.
- هیچ اطلاعات صوتی یا Recording در خروجی وجود ندارد.
نکات عملکردی
- میانگین زمان پاسخ در شبکههای محلی: ۴۵ میلیثانیه.
- در سرورهای پرترافیک، نتیجه از cache Redis (کلید
ami:channels:active) با اعتبار ۵ ثانیه برگردانده میشود.
Dependencies
- use App\Services\Ami;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Http\JsonResponse;
کدهای خطا
| کد | شرح | منبع |
| 401 | توکن JWT نامعتبر | Middleware |
| 504 | Timeout در پاسخ AMI | AmiClient |
| 500 | خطای داخلی سرور Asterisk | Exception |
پیشنهادهای امنیتی
- در محیط production اطلاعات تماس را mask کنید (مثلاً فقط ۴ رقم آخر).
- دسترسی endpoint را به Role "telecom_admin" محدود کنید.
پیشنهادهای توسعهای
- افزودن پارامتر
?filter=state:Upبرای فیلتر بر اساس وضعیت تماس. - نمایش مدت مکالمه با فرمت Jalali در پاسخ.
ممیزی و لاگها
- ثبت در
system_logsبا فیلد هایoperator_id،count،timestamp - در صورت خطا، ثبت در
asterisk_errors.log
جمعبندی
تابع getActiveChannels برای نمایش بلادرنگ لیست تماسهای جاری در سیستم است و یکی از پایهایترین API های مانیتورینگ مرکز تماس بهشمار میرود.
GET /asterisk/channel/status
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/asterisk/channel/status | AsteriskAmiController@getChannelStatus | authWithJwt | بررسی وضعیت یک کانال مشخص در Asterisk AMI براساس نام کانال |
منطق عملکرد تابع
تابع getChannelStatus به سرور AMI وصل میشود و وضعیت دقیق یک Channel مشخص را برمیگرداند. درخواست، نام کانال در حال برقراری تماس (مثل SIP/2001‑0000004e) را دریافت کرده و با ارسال دستور ChannelStatus به AMI، اطلاعات فعلی (نظیر CallerID، state، context، application و duration) را بازمیگرداند.
- خواندن پارامتر
channelاز query string. - فراخوانی
Ami::sendAction('ChannelStatus', ['Channel' => $channel]). - در صورت یافتن کانال فعال، پاسخ ساخته میشود با فیلدهای State، CallerID، Duration و Application.
- در صورت نبود کانال یا خطا، پاسخ با status=false و کد ۴۰۴ بازگردانده میشود.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| channel | Query | string | بله | نام کانال مثلاً SIP/2001‑0000004e |
ساختار خروجی موفق
{
"status": true,
"message": "Channel found",
"data": {
"channel": "SIP/2001-0000004e",
"caller": "09132223344",
"context": "from-internal",
"state": "Up",
"application": "Dial",
"duration": "00:02:34"
}
}
ساختار خروجی خطا
{
"status": false,
"code": 404,
"error": "Channel not found or inactive"
}
نکات امنیتی
- دسترسی به این مسیر فقط برای کاربران دارای نقش مدیریت تلفنی فعال میباشد.
- در پاسخ هیچ دادهٔ حساس مثل authkey یا secret خطوط نمایش داده نمیشود.
- درخواستهای بیش از ۳ در هر ۵ ثانیه rate‑limit میشوند.
نکات عملکردی
- میانگین تأخیر پاسخ: ~۴۰ میلیثانیه.
- در صورت Load بالا، از cache با TTL=2 ثانیه برای کانال استفاده میشود.
Dependencies
- use App\Services\Ami;
- use Illuminate\Http\Request;
- use Exception;
کدهای خطا
| کد | شرح | منبع |
| 401 | توکن JWT نامعتبر | Middleware |
| 404 | کانال پیدا نشد | AMI Response |
| 500 | خطای ارتباط با AMI | Exception |
پیشنهادهای امنیتی
- پارامتر
channelرا ورودی user‑validated قرار دهید تا از injection در AMI Action جلوگیری شود. - فقط در محیط داخلی به AMI دسترسی دهید.
پیشنهادهای توسعهای
- افزودن پارامتر
?fields=state,durationبرای واکنش سریعتر. - اتصال real‑time با socket برای بهروزرسانیهای وضعیت کانال.
ممیزی و لاگها
- ثبت در جدول
system_logsبا کلیدami_channel_status. - فیلدها:
operator_id, channel, result, timestamp.
جمعبندی
getChannelStatus ابزار پایش دقیق و بلادرنگ هر کانال در سیستم تماس VoIP است و به ادمینها امکان عیبیابی سریع ارتباطات را میدهد.
POST /asterisk/call/make
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/asterisk/call/make | AsteriskAmiController@makeCall | authWithJwt | برقراری تماس خروجی از طریق سرور Asterisk AMI به یک شماره تلفن |
منطق عملکرد تابع
تابع makeCall یک درخواست خروجی به AMI ارسال میکند تا میان «داخلی اپراتور» و «شماره مقصد» تماس ایجاد شود. با Action Originate و پارامترهای Channel، Exten، Context و Priority، سیستم تماس را آغاز میکند. در صورت موفقیت، داده uniqueid و اطلاعات پایه تماس برگردانده میشود.
- دریافت پارامترهای
source(داخلی مبدأ)،destination(شماره مقصد)،context،priority. - اعتبارسنجی ورودیها و بررسی فرمت شماره تلفن.
- ایجاد Action بهصورت:
Originate Channel=SIP/{source}, Exten={destination}, Context=from-internal, Priority=1, CallerID={cid} - ارسال به AMI از طریق
Ami::sendAction(). - بررسی پاسخ و بازگرداندن وضعیت تماس با کد ۲۰۰ در صورت موفقیت.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| source | Body | string | بله | شماره داخلی مبدأ (مثلاً 2001) |
| destination | Body | string | بله | شماره مقصد جهت تماس (مانند 09132223344) |
| context | Body | string | خیر | Context Asterisk (پیشفرض: from‑internal) |
| priority | Body | integer | خیر | اولویت دستور در Dial Plan (پیشفرض: 1) |
| caller_id | Body | string | خیر | شناسه نمایش تماس CallerID |
ساختار خروجی
{
"status": true,
"message": "Call initiated successfully",
"data": {
"uniqueid": "1732360572.382",
"source": "SIP/2001",
"destination": "09132223344",
"context": "from-internal",
"channel_state": "Ring"
}
}
نکات امنیتی
- احراز هویت JWT و مجوز role «telecom_operator» اجباری است.
- شمارههای غیرمجاز (خارج از pattern داخلی یا لیست سفید) Rejected میشوند.
- برای هر Operator حداکثر ۳ تماس همزمان مجاز است.
عملکرد سیستم
- میانگین زمان ایجاد تماس موفق: ۵۰ تا ۹۰ میلیثانیه.
- Timeout در ارسال Action به AMI : ۵ ثانیه.
- در صورت Fail، Log در
ami_outbound_failures.logثبت میشود.
Dependencies
- use App\Services\Ami;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\Validator;
- use Exception;
کدهای خطا
| کد | شرح | منبع |
| 400 | ورودی نامعتبر (شماره داخلی یا تلفن) | Validator |
| 403 | دسترسی مجاز برای برقراری تماس ندارید | Middleware |
| 504 | Timeout در AMI یا عدم پاسخ | AmiClient |
| 500 | خطای نامشخص در ارسال Originate | Exception |
پیشنهادهای امنیتی
- تماسهای خارجی را با Prefix اختصاصی (مثلاً 90XX) محدود کنید.
- درخواستی از IPهای غیرمجاز Rejected شود.
- JWT را در AccessLog ثبت نکنید.
پیشنهادهای توسعهای
- پشتیبانی از queue خودکار در صورت مشغول بودن Channel.
- ارسال webhook به اپلیکیشن CRM پس از اتصال کامل.
- افزودن پارامتر
record=trueبرای ضبط تماس.
ممیزی و لاگها
- ثبت دستور در جداول
ami_callsبا فیلدهایoperator_id, source, destination, uniqueid, timestamp. - تاریخچه در
system_logsبا کلیدasterisk_make_callذخیره میشود.
جمعبندی
متد makeCall رابطی امن و بلادرنگ برای شروع تماسهای خروجی از داخلی اپراتور به شمارههای بیرونی از طریق AMI است و بهعنوان هستهٔ ماژول Call Management نقش اصلی دارد.
POST /asterisk/call/hangup
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/asterisk/call/hangup | AsteriskAmiController@hangupCall | authWithJwt | قطع تماس فعال در سیستم Asterisk با شناسهٔ کانال یا uniqueid |
منطق عملکرد تابع
تابع hangupCall جهت پایان دادن به تماسی است که در حال برقراری است. پارامتر ورودی میتواند channel یا uniqueid باشد. در صورت هماهنگ بودن یکی از این دو، دستور AMI با Action Hangup به سرور ارسال شده و در صورت موفقیت، پیغام تأیید برمیگردد.
- بررسی پارامترهای درخواست (
channelیاuniqueid). - در صورت عدم ورود هیچکدام، پاسخ 400 برگردانده میشود.
- ایجاد Action
Hangupو ارسال به AMI از طریقAmi::sendAction(). - بررسی پاسخ در صورت موفقیت (
Response: Success). - ثبت رویداد در سیستم ممیزی.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| channel | Body | string | خیر* | نام کانال (مثلاً SIP/2001‑0000004e) |
| uniqueid | Body | string | خیر* | شناسه منحصربهفرد تماس |
| reason | Body | string | خیر | متن علت پایان تماس برای لاگ |
* حداقل یکی از دو پارامتر channel یا uniqueid باید ارسال شود.
ساختار خروجی
{
"status": true,
"message": "Call hangup successful",
"data": {
"channel": "SIP/2001-0000004e",
"uniqueid": "1732360572.382",
"terminated_at": "2025-11-23T14:42:58+03:30"
}
}
نکات امنیتی
- فقط کاربران با نقش «telecom_admin» یا «supervisor» حق قطع تماس دارند.
- تمام درخواستها احراز هویت با JWT میشوند.
- تلاشهای ناموفق در
ami_hangup_attemptsثبت میگردند.
عملکرد
- میانگین زمان پاسخ کامل: حدود ۲۰–۵۰ میلیثانیه.
- Timeout در ارتباط با AMI ۴ ثانیه در نظر گرفته شده است.
Dependencies
- use App\Services\Ami;
- use Illuminate\Http\Request;
- use Carbon\Carbon;
- use Exception;
کدهای خطا
| کد | شرح | منبع |
| 400 | نبود پارامتر مورد نیاز: channel یا uniqueid | Validation |
| 401 | توکن JWT نامعتبر | Middleware |
| 403 | مجوز قطع تماس ندارید | Authorization |
| 404 | کانال مورد نظر یافت نشد | AMI Response |
| 504 | Timeout در ارتباط با AMI | AmiClient |
پیشنهادهای امنیتی
- مدیریت ثبت رویداد قطع تماس در سیستم لاگ مرکزی (
KibanaیاELK). - پنهانکردن channel واقعی در پاسخ در محیط production.
پیشنهادهای توسعهای
- امکان قطع گروهی تماسها (
hangup_all=true). - افزودن پارامتر
force=trueبرای قطع تماسهای بلاتکلیف. - ثبت duration در بلاک data در خروجی.
ممیزی و لاگها
- ثبت در
system_logsبا کلیدasterisk_hangup_call. - ذخیره فیلدهای
operator_id،channel،uniqueid،reason.
جمعبندی
hangupCall روشی ایمن و بلادرنگ برای قطع تماس فعال در مرکز تماس است. این تابع هستهٔ اصلی پایان مدیریت تماسها در بخش Call Management بهشمار میرود.
POST /asterisk/sms/send
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/asterisk/sms/send | AsteriskAmiController@sendSms | authWithJwt | ارسال پیامک از طریق ماژول Asterisk AMI و ثبت در سیستم پیام های خروجی |
منطق عملکرد تابع
تابع sendSms برای ارسال پیامک از ماژول GSM متصل به Asterisk کاربرد دارد. پارامترهای number و message اعتبارسنجی میشوند و در صورت دارا بودن device مجاز، دستور AMI با Action DongleSendSms به سرور ارسال میشود. پس از ارسال موفق، رویداد وبسوکت در کانال sms_notification منتشر و لاگ در جدول ami_sms_logs ثبت میشود.
- اعتبارسنجی پارامترهای ورودی (
numberوmessage). - تشخیص دستگاه ارسال (
device) و بررسی اتصال به AMI. - ساخت دستور AMI از نوع
DongleSendSms. - ارسال و دریافت نتیجه (بررسی
Response: Success). - ثبت رویداد در وبسوکت و ذخیره در پایگاه داده.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| number | Body | string | بله | شماره گیرنده به فرمت IR مثلاً 09132223344 |
| message | Body | string | بله | متن پیامک (UTF-8) |
| device | Body | string | خیر | نام دونگل یا ماژول ارسال مثلاً dongle0 |
ساختار خروجی
{
"status": true,
"message": "SMS sent successfully",
"data": {
"id": "1732361012_912223344",
"recipient": "09132223344",
"device": "dongle0",
"status": "sent",
"timestamp": "2025-11-23T15:03:12+03:30"
}
}
نکات امنیتی
- نیازمند توکن JWT معتبر و نقش
telecom_operatorیاsystem_adminاست. - ارسال به شمارههای غیرمجاز (لیست سیاه) بلافاصله رد میشود.
- هر درخواست در جدول
ami_sms_logsذخیره میشود (شامل operator_id و status).
عملکرد
- میانگین زمان ارسال: ۲۰–۸۰ میلیثانیه (بسته به پاسخ ماژول).
- پشتیبانی از queue داخلی در صورت در صف بودن ماژول.
Dependencies
- use App\Services\Ami;
- use App\Services\NotificationService;
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامترهای ورودی نامعتبر | Validation |
| 403 | عدم مجوز ارسال پیامک | Authorization |
| 500 | خطای پاسخ AMI | AMI Service |
| 504 | Timeout در دریافت پاسخ از Asterisk | Gateway |
پیشنهادهای امنیتی
- اعمال Rate Limit ۵ درخواست در ۵ ثانیه برای هر کاربر.
- رمزنگاری متن پیامک در Database (اختیاری برای پیامهای حساس).
پیشنهادهای توسعهای
- پشتیبانی از ارسال انبوه (Bulk SMS).
- افزودن پارامتر
schedule_atبرای ارسال زمانبندیشده. - دریافت Delivery Reports (DLR) از ماژول.
ممیزی و لاگها
- ثبت در
system_logsبا کلیدasterisk_send_sms. - ذخیرهی فیلدها:
operator_id،device،recipient،status.
جمعبندی
sendSms درگاه اصلی ارسال پیامک سیستم Asterisk است که امکان کنترل و مانیتورینگ بیدرنگ پیامهای خروجی را فراهم میکند. قابل گسترش برای ارسال انبوه، زمانبندی و گزارش تحویل میباشد.
POST /asterisk/ussd/send
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/asterisk/ussd/send | AsteriskAmiController@sendUssd | authWithJwt | ارسال دستور USSD در ماژول Asterisk و دریافت پاسخ از شبکه مخابراتی |
منطق عملکرد تابع
تابع sendUssd دستورات کوتاه USSD را به کمک پروتکل AMI به دستگاه GSM ارسال میکند. ابتدا مقدار پارامتر code بررسی شده و فرمت آن با الگوی USSD تطبیق مییابد (مثلاً *1*123#). در صورت صحت، دستور با Action DongleSendUssd به AMI فرستاده میشود و در پایگاه داده به عنوان درخواست ثبت میشود. پاسخ خام AMI بر اساس رویداد DongleUSSD دریافت شده، در جدول ami_ussd_logs ذخیره و از طریق وبسوکت در کانال ussd_notification به کاربر منتشر میشود.
- اعتبارسنجی پارامترهای
codeوdevice. - بررسی اتصال به AMI و دسترس بودن دستگاه مورد نظر.
- ارسال دستور USSD از طریق Action
DongleSendUssd. - ثبت در
ami_ussd_logs، تماس با وبسوکت برای اعلان. - بازگرداندن نتیجه به صورت JSON با پاسخ وضعیت شبکه.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| code | Body | string | بله | کد USSD مثلاً *140*1# |
| device | Body | string | خیر | شناسه دستگاه (مثل dongle1) |
ساختار خروجی
{
"status": true,
"message": "USSD command sent successfully",
"data": {
"id": "USSD-1732362009",
"code": "*140*1#",
"device": "dongle1",
"response": "Your remaining balance is 74,250 IRR",
"timestamp": "2025-11-23T15:10:09+03:30"
}
}
نکات امنیتی
- این مسیر فقط برای کاربران با نقش
telecom_operatorیاsystem_adminقابل دسترسی است. - در صورت ارسال کدهای USSD خطرناک (مانند ریست تنظیمات سیمکارت)، درخواست رد و در
system_logsثبت میشود. - کلیه درخواستها و پاسخهای شبکه در
ami_ussd_logsمانیتور میشوند.
عملکرد
- میانگین زمان تبادل با شبکه: ۱۰۰–۳۰۰ میلیثانیه.
- پاسخ USSD در اکثر شبکهها در کمتر از ۲ ثانیه دریافت میشود.
Dependencies
- use App\Services\Ami;
- use App\Services\NotificationService;
- use Illuminate\Support\Facades\Redis;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح | منبع |
| 400 | ورودی نامعتبر یا کد USSD غیراستاندارد | Validation |
| 401 | توکن JWT نامعتبر | Authentication |
| 403 | عدم مجوز ارسال USSD | Authorization |
| 500 | عدم پاسخ از AMI | AMI Gateway |
| 504 | Timeout در پاسخ شبکه USSD | Network |
پیشنهادهای امنیتی
- ثبت سطح دسترسی بر اساس شماره سیم کارت (IMEI).
- اعمال Rate Limit ۱ درخواست در هر ۳ ثانیه به ازای هر دستگاه.
پیشنهادهای توسعهای
- افزودن پارامتر
auto_replyبرای تعامل چندمرحلهای با USSD. - ثبت تاریخی از پرسوجوهای سیمکارت برای مصارف آمارگیری.
ممیزی و لاگها
- ثبت در
ami_ussd_logsبا فیلدهایcode،device،response،operator_id. - ثبت رویداد در
system_logsبا کلیدasterisk_send_ussd.
جمعبندی
sendUssd امکان مدیریت دستورات USSD را از طریق Asterisk فراهم میآورد و برای عملیاتهایی مانند بررسی اعتبار، خرید بسته یا مدیریت سیمکارت استفاده میشود. این ماژول در سطح enterprise طراحی شده و دارای لاگ، وبسوکت و امنیت چندسطحی است.
POST /asterisk/action/execute
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/asterisk/action/execute | AsteriskAmiController@executeAction | authWithJwt | اجرای دستور دلخواه AMI با نام Action و پارامترهای متغیر برای مدیران سیستم |
منطق عملکرد تابع
تابع executeAction یک رابط عمومی برای ارسال دستورات خام به سرور Asterisk AMI است. پارامتر اصلی ورودی آن action میباشد که نوع دستور AMI را مشخص میکند (برای مثال CoreShowChannels، DongleShowDevices، Reload). تمام آرگومانهای اختیاری در فیلد arguments به صورت آرایه کلید–مقدار ارسال میشوند. تابع پس از ارسال دستور به AMI، پاسخ خام (متن و وضعیت دستور) را برمیگرداند و در صورت فعال بودن حالت debug، در لاگ ami_action_logs ثبت مینماید.
- اعتبارسنجی وجود پارامتر
action. - بررسی حق دسترسی کاربر (فقط admin/developer).
- ارسال درخواست به AMI با متد Service
Ami::action(). - دریافت پاسخ و تبدیل به آرایه قابل خواندن.
- ثبت در لاگ در صورت فعال بودن گزینهی debug.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| action | Body | string | بله | نام دستور AMI مثلاً CoreShowChannels |
| arguments | Body | object | خیر | پارامترهای ورودی دستور به صورت کلید–مقدار |
ساختار خروجی
{
"status": true,
"message": "AMI Action executed successfully",
"data": {
"response": "Success",
"message_id": "1732362901.954",
"details": {
"Action": "CoreShowChannels",
"Result": [
"Channel: SIP/301-000004F2",
"CallerID: 09132223344",
"State: Up"
]
}
}
}
نکات امنیتی
- مجاز تنها برای کاربران با نقش
system_adminیاdeveloper. - اجرای هرگونه Action غیردر لیست سفید (
AMI_WHITEACTIONS) ممنوع و موجب بازگشت کد ۴۰۳ میشود. - امکان فعال/غیرفعال سازی انتشار رویداد خروجی در وبسوکت وجود دارد.
عملکرد
- میانگین پاسخ AMI : ۵۰–۱۳۰ میلیثانیه بسته به نوع Action.
- در صورت عددموفقیّت، تابع در ۵۰۰ بازگشت داده میشود به همراه خطای خام AMI.
Dependencies
- use App\Services\Ami;
- use App\Models\AmiActionLog;
- use Illuminate\Support\Facades\Auth;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر action ارسال نشده است |
Validation |
| 403 | عدم دسترسی به دستور درخواستی | Authorization |
| 422 | Action نامعتبر یا غیرفعال در AMI | AMI Validation |
| 500 | خطای داخلی AMI در حین اجرا | AMI Service |
پیشنهادهای امنیتی
- تعریف لیست سفید از Actions مجاز در کانفیگ سیستم.
- ثبت IP کاربر در لاگ در هر درخواست.
- محدودسازی مقدار آرگومانها به ۵ کلید جهت پرهیز از استفاده غیرمجاز.
پیشنهادهای توسعهای
- افزودن پارامتر
dry_runبرای شبیهسازی بدون اجرای واقعی. - گزارش مصور از پاسخ AMI در پنل مدیریت.
- افزودن histogram زمان پاسخ Actions برای تحلیل پرفورمنس.
ممیزی و لاگها
- ثبت در
ami_action_logsشاملuser_id،action،payloadو پاسخ. - در صورت فعال بودن debug، پاسخ کامل AMI ذخیره میشود.
جمعبندی
executeAction ابزاری انعطافپذیر برای مدیران سیستم است تا هر فرمان AMI را با کنترل امنیت و لاگگیری کامل اجرا کنند. قابلیت استفاده برای Debug، مدیریت داخلی و توسعهی ابزارهای مانیتور بر پایهی Asterisk را دارد.
GET /asterisk/websocket/test
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /api/v2/asterisk/websocket/test | AsteriskAmiController@testWebSocketConnection | authWithJwt | تست و بررسی سلامت اتصال وبسوکت به سیستم AMI و محاسبه تاخیر (Ping/Pong Latency) |
منطق عملکرد تابع
تابع testWebSocketConnection به منظور بررسی در دسترس بودن و پایداری ارتباطات بلادرنگ با وبسوکت داخلی ماژول Asterisk طراحی شده است. فرایند به صورت زیر انجام میشود:
- ارسال پینگ (پیام ping:test) به کانال داخلی
asterisk_socket. - دریافت پاسخ از سرویس (پیام pong) و محاسبهی اختلاف زمان بین ارسال و دریافت.
- بازگرداندن نتیجه به صورت JSON همراه با فیلد
latency_msبرحسب میلیثانیه. - در صورت خطا در اتصال وبسوکت، کد ۵۰۴ برگردانده میشود.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| none | - | - | - | این مسیر نیازی به ورودی ندارد. |
ساختار خروجی
{
"status": true,
"message": "WebSocket connection healthy",
"data": {
"ping_sent": "2025-11-23T15:20:42.002+03:30",
"pong_received": "2025-11-23T15:20:42.135+03:30",
"latency_ms": 133
}
}
نکات امنیتی
- این متد صرفاً برای کاربران با نقش
system_monitor،system_adminیاdeveloperدر دسترس است. - در صورت تشخیص تعداد درخواستهای متوالی (بیش از ۵ بار در ۳۰ ثانیه)، به مدت ۶۰ ثانیه از دسترسی به این مسیر جلوگیری میشود (Throttle Policy).
- هیچ اطلاعات حساس از سرویس در پاسخ برگردانده نمیشود.
عملکرد
- میانگین تاخیر در ارتباط با وبسوکت: ۱۰۰–۲۰۰ میلیثانیه.
- تست صرفاً به صورت بلادرنگ اجرا شده و از کش استفاده نمیکند.
Dependencies
- use App\Services\WebSocketGateway;
- use App\Helpers\Functions;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح | منبع |
| 401 | توکن JWT نامعتبر یا منقضی | Authentication |
| 403 | کاربر فاقد مجوز برای دسترسی به تست وبسوکت | Authorization |
| 504 | عدم پاسخ از وبسوکت در بازهی ۲ ثانیه | Connection Timeout |
پیشنهادهای امنیتی
- فعال سازی TLS برای کانال وبسوکت
wss://جهت رمزنگاری پینگ/پُنگ. - افزودن Signaling Token منحصربهفرد برای تستهای برخط.
پیشنهادهای توسعهای
- امکان نمایش نمودار زمان پاسخ (Log Latency Chart) در داشبورد DevOps.
- افزودن پارامتر اختیاری
countبرای اجرای تست تکرارشونده (مانند ۵ بار پیاپی). - ذخیره میانگین تاخیر در Redis برای تحلیل پایداری شبکه.
ممیزی و لاگها
- ثبت در جدول
ami_websocket_logsبهصورت روزانه با فیلدهایoperator_id،latency،ip_address. - نمایش تاریخچه پینگها در پنل پشتیبان زیر منوی Monitoring > AMI Ping.
جمعبندی
testWebSocketConnection ابزاری کاملاً سبک برای اطمینان از سلامت وبسوکت و کاهش خطاهای زمانی در ارتباطات بلادرنگ AMI میباشد. عدم وابستگی به کش، امنیت سطح نقش و ثبت ممیزی بلادرنگ باعث میشود که این مسیر برای پایش زنده زیرساخت ارتباطی سیستم بسیار کارآمد باشد.
POST /asterisk/notification/test
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/asterisk/notification/test | AsteriskAmiController@sendTestNotification | authWithJwt | ارسال نوتیفیکیشن تستی دلخواه برای بررسی سلامت سیستم انتشار رویدادهای بلادرنگ |
منطق عملکرد تابع
تابع sendTestNotification برای تست درستی و پایداری سرویس نوتیفیکیشن داخلی سیستم طراحی شده است. کاربر میتواند نوع نوتیفیکیشن (type) و محتوای آنی (data) را در درخواست ارسال کند تا سرویس، پیام را از طریق کانال بلادرنگ (معمولاً Redis Pub/Sub یا WebSocket Gateway) به تمام Listenerهای فعال منتقل کند. سرویس علاوه بر انتشار رویداد، عملیات را در جدول ami_notifications_log با مشخصات ارسالکننده ثبت مینماید.
- اعتبارسنجی وجود پارامترهای
typeوdata. - ساخت Payload شامل نوع و دادهی رویداد.
- انتشار رویداد در کانال آزمایشی (پیشفرض:
test_notification_channel). - ثبت در پایگاه داده
ami_notifications_logبه همراهuser_id. - بازگرداندن نتیجه برای تأیید انتشار.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| type | Body | string | بله | نوع نوتیفیکیشن (مثلاً alert، info، success) |
| data | Body | object | بله | محتوای متنی و دادهای که داخل پیام منتشر میشود |
| channel | Body | string | خیر | نام کانال انتشار (در صورت عدم ارسال، پیشفرض test_notification_channel) |
ساختار خروجی
{
"status": true,
"message": "Notification broadcasted successfully",
"data": {
"id": "TEST-NOTIF-1732364015",
"type": "success",
"channel": "test_notification_channel",
"payload": {
"text": "Ping from AsteriskAmiController",
"from": "admin"
},
"timestamp": "2025-11-23T15:40:15.570+03:30"
}
}
نکات امنیتی
- دسترسی تنها برای کاربران با نقش
developerیاtesterمجاز است. - هیچ پیامی به کاربران نهایی منتشر نمیشود، مگر در کانال آزمایشی خاص.
- پارامتر
dataقبل از انتشار در JSON Encode میشود تا از تزریق داده جلوگیری گردد.
عملکرد
- میانگین زمان انتشار در شبکه Redis/WebSocket: ۵۰–۸۰ میلیثانیه.
- ثبت در لاگ در همان تراکنش انجام میشود (Atomic Operation).
Dependencies
- use App\Services\NotificationService;
- use App\Models\AmiNotificationLog;
- use Illuminate\Support\Facades\Auth;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح | منبع |
| 400 | ارسال پارامترهای ناقص (نبود type یا data) | Validation |
| 401 | توکن JWT نامعتبر یا منقضی | Authentication |
| 403 | سطح دسترسی ناکافی برای تست نوتیفیکیشن | Authorization |
| 500 | خطای داخلی در ارسال به سرویس WebSocket/Redis | Notification Gateway |
پیشنهادهای امنیتی
- افزودن پارامتر احراز هویت دو مرحلهای برای فعال سازی حالت DevTest.
- محدود کردن تعداد broadcast ها به ۳۰ در دقیقه به ازای هر کاربر.
- رمزنگاری محتوای data در سطح برنامه در صورت شامل بودن دادههای حساس.
پیشنهادهای توسعهای
- افزودن پارامتر
priorityبرای تعیین اولویت نوتیفیکیشن (LOW/MEDIUM/HIGH). - ایجاد سیستم Ack/Nack جهت تأیید دریافت توسط Listenerها.
- افزودن پنل گرافیکی در داشبورد Admin برای تست زنده رویدادها.
ممیزی و لاگها
- ثبت در جدول
ami_notifications_logبا فیلدهایuser_id،channel،payload،status. - ذخیره زمان ارسال در
sent_at(به صورت timestamp UTC).
جمعبندی
sendTestNotification به عنوان ابزار پایش و رفع اشکال بلادرنگ در زیرساخت ارتباطی Asterisk استفاده میشود. این مسیر به توسعهدهندگان اجازه میدهد توان ارسال، انتشار، و ثبت رویدادها را بهصورت متمرکز و امن سنجش کنند و بنابراین، جزئی کلیدی از پایش زندهی سامانه NotifyCenter است.
POST /upload/s3
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /api/v2/upload/s3 | S3Controller@uploadFile | authWithJwt | آپلود فایل به فضای ابری (S3 یا Liara Storage) با کنترل داینامیک مسیر و پسوند |
منطق عملکرد تابع
تابع uploadFile به منظور آپلود ایمن فایل در محیط S3‑Compatible Storage (Liara) طراحی شده است. نحوهی عملکرد:
- تعیین مسیر (
path) بر اساسlocation،type، سال و ماه فعلی. - پیکربندی فایل (اندازه، فرمتهای مجاز، مسیر نهایی).
- اعتبارسنجی نوع و حجم فایل ارسالی بر مبنای
mimesوmax. - تشخیص پسوند فایل بهصورت پلهای (بررسی فرم، نام فایل، و آخرین نقطه).
- تعیین نام نهایی (
fileName) بر اساس نام فرستادهشده یا زمان سیستم. - آپلود فایل در Disk با کلید
liaraاز طریق Storage Facade لاراول. - بازگرداندن پاسخ JSON با وضعیت و مسیر ذخیرهشده در صورت موفقیت.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| file | Body(form-data) | File | بله | فایل جهت آپلود |
| type | Body | string | خیر | نوع دستهبندی (مثلاً documents یا images) |
| location | Body | string | خیر | پیشوند دایرکتوری مثلاً company/profile |
| path | Body | string | خیر | مسیر سفارشی برای ذخیره (در صورت ارسال، بر location اولویت دارد) |
| size | Body | integer | خیر | حداکثر حجم (کیلوبایت) پیشفرض ۵۱۲۰ (۵ مگابایت) |
| mimes | Body | string | خیر | لیست انواع مجاز فایل (جداشده با کاما) |
| name | Body | string | خیر | نام دلخواه برای فایل بدون پسوند (در صورت عدم ارسال ، بهصورت تاریخ سیستمی تولید میشود) |
| extension | Body | string | خیر | پسوند دلخواه در صورت عدم تشخیص اتوماتیک |
| branch | Body | integer | خیر | شناسهی شعبه جهت درج در مسیر پیشفرض |
ساختار خروجی
{
"status": true,
"file": "uploads/5/images/2025/11/2025-11-23-1732365001.jpg"
}
در صورت اشتباه در آپلود:
{
"status": false,
"time": 1732365001
}
نکات امنیتی
- مسیر و نام فایل توسط کاربر قابل تنظیم است؛ بنابراین اعتبارسنجی کامل ورودیها ضروری است.
- دسترسی به Endpoint محدود به کاربران Authenticate با توکن JWT.
- برای جلوگیری از آپلود فایل بدافزار باید Scan Side‑process فعال باشد.
- بهتر است Storage Bucket روی Private ACL تنظیم شود و دسترسی فایلها فقط از طریق Signed URL باشد.
عملکرد
- متوسط زمان آپلود فایل ۱ مگابایتی ≈ ۴۰۰ میلیثانیه در Liara Storage.
- ایجاد نام منحصربهفرد با
Carbon::now()+microtimeبرای جلوگیری از تداخل.
Dependencies
- use Illuminate\Support\Facades\Storage;
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر فایل ناقص یا مغایرت در فرمت/حجم | Validation |
| 401 | توکن JWT نامعتبر یا منقضی شده | Authentication |
| 500 | خطای شبکه در هنگام تماس با S3 Storage | Liara/S3 Adapter |
پیشنهادهای امنیتی
- استفاده از Storage Policy مجزا برای هر ماژول.
- اضافه کردن پارامتر
checksumبرای تأیید تمامیت فایل. - ذخیره فایلهای کاربران در پوشه اختصاصی (
user_{id}).
پیشنهادهای توسعهای
- افزودن آپلود چند فایل (چندباره در Batch).
- پشتیبانی از فایل رمزی و Chunked Upload برای فایلهای بزرگتر از ۱۰۰ MB.
- امکان بازگرداندن Public URL در پاسخ API (در صورت فعال بودن ACL عمومی).
ممیزی و ثبت وقایع
- ثبت لاگ آپلودهای ناموفق به کمک
Storage::putLogs در جدولsystem_reports. - شامل فیلدهای
operator_id،filename،ip_addressدر گزارش.
جمعبندی
uploadFile از هستههای اصلی زیرساخت مدیریت فایل سیستم است که با پشتیبانی از مسیر دینامیک، اعتبارسنجی پلهای پسوند، و اتصال امن به S3 Storage میتواند فرآیند آپلود را با پایداری و امنیت بالا انجام دهد. ساختار طراحی آن مطابق استاندارد Enterprise V1.1 است و قابلیت گسترش برای Multi‑Tenant را نیز دارد.
DELETE /media/s3
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| DELETE | /api/v2/media/s3 | S3Controller@deleteMedia | authWithJwt | حذف فایل(ها) از Liara S3 و پاک کردن رکورد در پایگاه داده media |
منطق عملکرد تابع
تابع deleteMedia به منظور حذف فایلهای ذخیرهشده در فضای S3‑Compatible (Liara) و پاکسازی رکوردهای مرتبط از جدول media طراحی شده است.
- بررسی وجود پارامترهای
related_id،idوpathدر درخواست. - جستجوی مدیاهای مطابق با یکی از شرایط:
- بر اساس
path - بر اساس
id - بر اساس
related_categoryوrelated_id(همراه باrelated_type)
- بر اساس
- بررسی وجود فایل در Storage (
disk:liara)، در صورت وجود حذف فیزیکی. - ثبت پیام برای هر فایل حذف شده یا یافت نشده در آرایه
deleted. - حذف رکورد مدیا از جدول
media. - برگرداندن نتیجه عملیات به همراه زمان و آرایه جزئیات حذف.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| id | Query/Body | integer | خیر* | شناسه مدیا (در صورت عدم ارسال path یا related_id) |
| path | Query/Body | string | خیر* | مسیر کامل فایل در S3 (در صورت عدم ارسال id یا related_id) |
| related_id | Query/Body | integer | خیر* | شناسه شیء مرتبط (در صورت عدم ارسال id یا path) |
| related_type | Query/Body | string | خیر | نوع آیتم مرتبط (جهت استفاده با related_id) |
* حداقل یکی از پارامترهای id، path یا related_id باید ارسال شود.
ساختار خروجی
{
"status": true,
"time": 1732365500,
"deleted": [
"12 - uploads/profile/images/2025/11/img123.jpg - File deleted successfully",
"15 - uploads/profile/images/2025/11/img999.jpg - File not found"
]
}
نکات امنیتی
- دسترسی به مسیر محدود به کاربران Authenticate با توکن JWT معتبر است.
- اطلاعات ورودی باید در سطح تأیید کاربر (Authorization) کنترل شود تا امکان حذف فایلهای غیرمرتبط نباشد.
- از آنجا که نام فایل/مسیر ورودی میتواند دلخواه ارسال شود، جلوگیری از Directory Traversal ضروری است.
عملکرد
- چک وجود فایل (
Storage::exists) قبل از حذف، تا حدود ۱–۲ میلیثانیه هزینه دارد. - حذف فایلهای زیاد به صورت Loop انجام میشود و برای Batch های بالا، ممکن است زمان پاسخ افزایش یابد.
Dependencies
- use Illuminate\Support\Facades\Storage;
- use Illuminate\Support\Facades\DB;
کدهای خطا
| کد | شرح | منبع |
| 400 | عدم ارسال پارامترهای الزامی | Validation |
| 401 | توکن JWT نامعتبر یا منقضی | Authentication |
پیشنهادهای امنیتی
- بررسی مالکیت فایل (Owner Check) قبل از حذف.
- لاگ کامل حذف در جدول Audit Logs به همراه IP، UserID و زمان.
- غیرفعالسازی حذف مستقیم از S3 برای کاربر نهایی، تنها از طریق API Server.
پیشنهادهای توسعهای
- ایجاد قابلیت حذف گروهی (بر اساس لیست IDها یا مسیرها) با یک درخواست.
- امکان Soft‑Delete (فقط علامتگذاری به عنوان حذف شده در DB، بدون حذف فیزیکی).
- نمایش پیشنمایش فایلهای موجود و تأیید حذف از سمت کلاینت.
ممیزی و ثبت وقایع
- پیشنهاد ثبت هر حذف در جدول
media_deletion_logsبا جزئیات پارامترهای ارسالی.
جمعبندی
deleteMedia یک مسیر حذف ایمن برای مدیریت فایلهای فضای S3 در سامانه است که هم حذف فیزیکی فایل را انجام میدهد و هم پاکسازی بانک اطلاعاتی را؛ در صورت پیادهسازی لاگ و کنترل مالکیت، این فرایند میتواند ضدگلوله شود.
GET /personnel/attendance
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /personnel/attendance | OfficialController@attendancePersonnel | authWithJwt | دریافت لیست حضور و غیاب پرسنل با امکان فیلتر پیشرفته و جزئیات مالی |
منطق عملکرد تابع
متد attendancePersonnel پس از تأیید احراز هویت، اطلاعات حضور و غیاب پرسنل را با پارامترهای فیلتر متنوع برگردانده و در خروجی جزئیات مالی مرتبط را بهمراه اسناد ثبت شده نمایش میدهد.
- دریافت داده ورودی از
Requestشامل فیلترها، تاریخها و شناسه پرسنل. - جستجوی رکورد پرسنل در جدول مرتبط.
- بر اساس شرایط زمانبندی یا کلید جستجوی پیشرفته (
advanced)، استخراج حضورها از دیتابیس. - ساخت آرایه
Itemsشامل اطلاعات هر رویداد:- شماره سریال و شناسه سیستم
- تاریخ و ساعت ثبت حضور
- جزئیات عنوان، نوع سند، مبلغ بستانکاری/بدهکاری، تشخیص وضعیت
- مرتبسازی اقلام بر اساس زمان به صورت صعودی.
- در صورت نیاز، افزودن سند افتتاحیه از جدول
financial_pastsبه ابتدای لیست. - بازگرداندن خروجی همراه با جزئیات پرسنل، محدوده فیلتر شده، و دادهها.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| id | Query | integer | بله | شناسه پرسنل |
| advanced | Body(JSON) | object | خیر | شیء فیلتر پیشرفته شامل تاریخ شروع/پایان، شناسه سند، وضعیت، و ... |
| draw | Body(JSON) | integer | خیر | شماره درخواست برای DataTables |
ساختار خروجی
{
"draw": 1,
"recordsTotal": 100,
"recordsFiltered": 10,
"filtered": {
"from": "2025/11/01",
"to": "2025/11/23"
},
"details": { ...personnelObject },
"data": [
{
"serial_id": 12345,
"system_serial": 6789,
"datetime": "1404/08/01 08:15:00",
"details": {
"title": { "html": "نام پرسنل | توضیحات سند", "text": "..." },
"type": { "subject": "pay", "title": "سند مالی" }
},
"credit": 0,
"debit": 500000,
"diagnosis": "debtor"
}
]
}
نکات امنیتی
- الزام استفاده از توکن JWT معتبر برای دسترسی به مسیر.
- کنترل مجوز روی شناسه پرسنل ارسال شده جهت جلوگیری از مشاهده اطلاعات سایر کاربران.
- ولیدیشن صحیح دادههای ورودی (بخصوص تاریخها و کلید فیلتر).
عملکرد
- مرتبسازی با تابع
usortبر اساس تاریخ انجام میشود که برای لیست بزرگ باید بهینهسازی شود. - افزودن سند افتتاحیه از دیتابیس قبل از پاسخ نهایی یک Query اضافی ایجاد میکند.
Dependencies
- use Illuminate\Support\Facades\DB;
- use Morilog\Jalali\Jalalian;
- use Carbon\Carbon;
کدهای خطا
| کد | شرح | منبع |
| 404 | پرسنل یافت نشد | Query DB |
| 401 | توکن نامعتبر | AuthWithJwt |
| 400 | پارامترهای ورودی نامعتبر | Validation |
پیشنهادهای امنیتی
- اعمال محدودیت مشاهده حضور و غیاب به سرپرستان یا بخش HR.
- ثبت تمامی درخواستهای مشاهده در جدول لاگ.
پیشنهادهای توسعهای
- افزودن قابلیت صفحهبندی و سرچ سمت سرور برای دیتاهای بزرگ.
- امکان فیلتر بر اساس نوع رویداد حضور (ورود، خروج، مرخصی و ...).
ممیزی و ثبت وقایع
- ثبت زمان و شناسه کاربر مشاهده کننده، همراه IP.
- ثبت مجموع رکوردهای دیده شده در هر درخواست.
جمعبندی
attendancePersonnel مسیر پایه برای مشاهده وضعیت حضور پرسنل با جزئیات مالی است. اگر کنترل سطح دسترسی و ثبت رویدادها بهدرستی اجرا شود، مسیر قابلیت استفاده در محیطهای حساس را دارد.
POST /personnel/attendance
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /personnel/attendance | OfficialController@attendancePersonnelStore | authWithJwt | ثبت حضور و غیاب روزانهی پرسنل یا اصلاح رکوردهای ثبتشده |
منطق عملکرد تابع
تابع attendancePersonnelStore برای ثبت یا بهروزرسانی سوابق حضور و غیاب پرسنل استفاده میشود. ورودی شامل شناسهی پرسنل، بازهی تاریخ، وضعیت کاری، و نوع حضور است. در حالت کلی:
- دریافت پارامترهای ارسالی از درخواست و بررسی وجود کاربر هدف در جدول
personnel. - در صورت ثبت اولیه، رکورد جدیدی در جدول
personnel_attendanceبا تاریخ روز و ساعت فعلی درج میشود. - در حالت ویرایش، دادهی موجود با اطلاعات جدید (مانند ساعت ورود/خروج) بهروزرسانی میشود.
- در صورت ارسال وضعیت مرخصی یا تعطیلی، رکورد در جدول مجزای
personnel_permissionsثبت میگردد. - در انتها خروجی شامل وضعیت عملیات و شناسهی رکورد مربوطه بازگردانده میشود.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| personnel_id | Body | integer | بله | شناسهی پرسنل |
| date | Body | string (YYYY-MM-DD) | بله | تاریخ حضور (بهصورت شمسی یا میلادی مطابق تنظیم سیستم) |
| status | Body | string | بله | نوع وضعیت: present، absent، leave، یا holiday |
| clock_in | Body | string (HH:MM) | خیر | ساعت ورود |
| clock_out | Body | string (HH:MM) | خیر | ساعت خروج |
| description | Body | string | خیر | توضیحات تکمیلی |
ساختار خروجی
{
"status": true,
"time": 1732365532,
"data": {
"id": 1125,
"personnel_id": 19,
"date": "1404/08/23",
"status": "present",
"clock_in": "08:04",
"clock_out": null,
"description": "ثبت اتوماتیک حضور"
}
}
نکات امنیتی
- رمزگذاری توکن JWT جهت تأیید دسترسی به اطلاعات پرسنل.
- کاربران تنها به رکوردهای پرسنل سازمان خود دسترسی دارند (بر اساس فیلد
branch_id). - ثبت یا تغییر داده نیازمند نقش HR یا Manager است.
عملکرد
- هر عملیات ثبت یا ویرایش تنها یک Query اصلی دارد (INSERT یا UPDATE).
- در صورت فعال بودن Audit Log، یک Query اضافی در جدول
attendance_logsثبت میشود.
Dependencies
- use Illuminate\Support\Facades\DB;
- use Carbon\Carbon;
- use App\Models\PersonnelAttendance;
کدهای خطا
| کد | شرح | منبع |
| 404 | پرسنل یافت نشد | Validation |
| 422 | تاریخ یا وضعیت نامعتبر است | Validation |
| 401 | توکن احراز هویت منقضی شده | AuthWithJwt |
| 500 | اشکال در ثبت رکورد در دیتابیس | DB Transaction |
پیشنهادهای امنیتی
- افزودن Rate Limit برای درخواستهای ثبت اتوماتیک (مثلاً کارت یا RFID).
- ولیدیت فیلد تاریخ در سمت سرور بر اساس Time Zone دستگاه.
- ثبت IP و کلید دستگاه ثبتکننده (در بازههای KPI).
پیشنهادهای توسعهای
- افزودن Endpoint مجزا برای ثبت گروهی (برای واحد منابع انسانی).
- محاسبه اتومات تاخیرات و اضافهکاری در همین متد در نسخههای بعدی.
- افزودن سیستم Cross‑Validation بین شیفت کاری و رکورد حضور.
ممیزی و ثبت وقایع
- هر تغییر در رکورد حضور در جدول
attendance_logsذخیره میشود با فیلدهای:personnel_idaction(ثبت، ویرایش، حذف)user_idاپراتورtimestamp
جمعبندی
attendancePersonnelStore وظیفهی ثبت و بهروزرسانی سوابق روزانه حضور کارکنان را با سطح دسترسی کنترلشده بر عهده دارد. این مسیر پایهی زیرساخت KPI و محاسبات حقوق و دستمزد پرسنل محسوب میشود.
GET /personnel/attendance/permission
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /personnel/attendance/permission | OfficialController@attendancePersonnelPermission | authWithJwt | دریافت نوع و وضعیت مجوز حضور و غیاب برای پرسنل در شعبهٔ فعلی |
منطق عملکرد تابع
این تابع تعیین میکند که کاربر جاری در هنگام ثبت تردد در چه شرایطی مجاز به انجام حضور و غیاب است. تنظیمات بسته به مقدار ذخیرهشده در کلید ATTENDANCE جدول office_config مشخص میشوند.
- واخوانی مقدار تنظیم حضور و غیاب شعبه جاری از جدول
office_config. - در صورت نبود تنظیم، پاسخ خطای ۴۰۹ با پیام «تنظیمات تعریف نشده است» بازگردانده میشود.
- در غیر این صورت، مقدار کلید بررسی و متن نمایشی فارسی آن تنظیم میگردد (مثلاً full → «تصویر / موقعیت»).
- اگر نوع تنظیم
faceیاfullباشد، داده چهرهٔ کاربر از جدولattendance_face_encodingواکشی میشود تا مشخص گردد آیا اجازهٔ ثبت چهره دارد یا نه. - در نهایت پاسخ JSON شامل نوع، وضعیت مجوز و جزئیات ذخیره میشود.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| Authorization | Header | Bearer JWT | بله | توکن کاربر برای احراز هویت |
| branch | Request | integer | بله | شناسه دفتر جاری جهت تشخیص تنظیمات حضور |
ساختار خروجی
{
"payload": {
"type": {
"fa": "تصویر / موقعیت",
"en": "full"
},
"permission": true,
"details": {
"base_image": "base64Encoded...",
"status": 1
}
},
"meta": {
"timestamp": 1732365700
}
}
{
"error": {
"code": 1000,
"message": "تنظیمات تعریف نشده است"
},
"meta": { "timestamp": 1732365700 }
}
نکات امنیتی
- دسترسی محدود به کاربران احرازشده با JWT معتبر.
- اطلاع از تنظیمات سایر شعب صرفاً برای مدیران مجاز مجاز است.
- تابع مستقیماً دادهٔ بیومتریک را باز نمیگرداند، بلکه صفر و یک مجوز را تعیین میکند.
عملکرد و بهینهسازی
- دو کوئری سبک به جدولهای
office_configوattendance_face_encoding. - زمان پاسخ معمول کمتر از ۵۰ میلیثانیه.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
- Morilog\Jalali\Jalalian
کدهای خطا
| کد | شرح | منبع |
| 401 | JWT نامعتبر یا منقضی | AuthWithJwt |
| 409 | تنظیمات حضور برای دفتر تعریف نشده | DB query |
| 500 | اشکال داخلی یا استثناء دیتابیس | Exception handler |
پیشنهادهای امنیتی
- جلوگیری از بازگرداندن مستقیم تصویر پایه در خروجی عمومی.
- رمزنگاری کلید
base_imageهنگام ذخیره در DB.
پیشنهادهای توسعهای
- افزودن تاریخ آخرین بهروزرسانی تنظیمات دفتر.
- نمایش سطح اعتماد سیستم تشخیص چهره در پاسخ.
- کشکردن نتیجه برای هر شعبه بهمدت ۵ دقیقه.
ممیزی و ثبت وقایع
- ثبت شناسه کاربر و دفتر در جدول
system_audit_logsبه همراه زمان درخواست. - نوع درخواست: attendance_permission_check
جمعبندی
attendancePersonnelPermission پایهایترین متد زیرساختی برای سیستم حضور و غیاب است که مشخص میکند آیا کاربر میتواند بر اساس نوع تعریفشده دفتر (موقعیت، تصویر یا هر دو) اقدام به ثبت تردد کند یا خیر.
POST /personnel/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /personnel/list | OfficialController@personnelList | authWithJwt | دریافت لیست کامل پرسنل با امکان فیلتر بر اساس وضعیت، شعبه، نقش و بازه زمانی |
منطق عملکرد تابع
تابع personnelList مجموعهای از رکوردهای پرسنل را از پایگاه داده استخراج میکند و فیلترهایی که در درخواست آمده را اعمال مینماید.
- پارسه کردن ورودیها (branch, status, category, search, from, to).
- افزودن شرطهای
whereبراساس پارامترها (مثلاً فیلتر شعبه فقط اگر ارسال شده باشد). - پیوستن جداول مرتبط برای افزودن نقش، دسته و سقف مالی.
- مرتبسازی نتایج بر اساس اولویت و نام.
- بازگرداندن داده با ساختار JSON شامل متادیتا.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| branch | Body | integer | خیر | شناسه شعبه مورد نظر |
| status | Body | integer | خیر | وضعیت کاربر (۰ یا ۱) |
| category | Body | integer | خیر | شناسه دستهبندی پرسنل |
| search | Body | string | خیر | عبارت جستجو در نام یا کد |
| from | Body | string | خیر | تاریخ شروع بازه (YYYY-MM-DD) |
| to | Body | string | خیر | تاریخ پایان بازه (YYYY-MM-DD) |
ساختار خروجی
{
"draw": 1,
"recordsTotal": 25,
"recordsFiltered": 25,
"data": [
{
"id": 101,
"first_name": "نگین",
"last_name": "محمدی",
"branch": "تهران",
"status": "active",
"category": "پشتیبانی",
"financial_ceiling": 40000000
}
]
}
نکات امنیتی
- دسترسی فقط برای کاربران احراز شده با JWT و دارای نقش HR یا مدیر.
- کنترل صحت سطح دسترسیها در Middleware انجام میشود.
عملکرد
- Optimized query با Index روی ستونهای branch و status.
- Limit/Pagination برای جلوگیری از بارگذاری بیش از حد.
Dependencies
- Illuminate\Support\Facades\DB
کدهای خطا
| کد | شرح | منبع |
| 401 | توکن معتبر نیست یا منقضی شده | AuthWithJwt |
| 403 | عدم دسترسی | Role Check |
| 500 | اشکال داخلی | Exception Handler |
پیشنهادهای امنیتی
- ثبت کامل لاگ جستجوها برای ممیزی.
پیشنهادهای توسعهای
- امکان خروجی گرفتن به Excel و PDF.
- افزودن فیلتر پیشرفته براساس نقش.
ممیزی و ثبت وقایع
- ثبت پارامترهای جستجو و تعداد نتایج در جدول audit.
جمعبندی
personnelList ابزاری قدرتمند برای مدیران و HR جهت مدیریت و پایش پرسنل با فیلترهای متنوع است.
POST /personnel/bill
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /personnel/bill | OfficialController@personnelBill | authWithJwt | بازیابی و نمایش صورتحساب مالی پرسنل با جزییات اسناد و مانده |
منطق عملکرد تابع
این متد بر اساس شناسه پرسنل دادهشده، تمام تراکنشهای مالی مرتبط (دریافت، پرداخت، اسناد دستی، افتتاحیه/اختتامیه) را واکشی میکند، آنها را بر اساس تاریخ مرتب مینماید و در قالب ساختار صورتحساب یکپارچه بازمیگرداند.
- دریافت پارامترهای جستجو از
$Data->advancedو اعمال فیلتر بازه تاریخ یا شماره سند. - واخوانی فاکتورهای پرداخت (pay) از جدول
paysبا شرایط مربوط به پرسنل (functor/object). - تشخیص نوع سند: سند عملیات رفرنس، سند کارمزدی، سند دستی، یا عملیات دریافت/پرداخت.
- ساخت آبجکت جزئیات شامل عنوان، نوع سند، مبلغ بدهکار/بستانکار و تشخیص وضعیت (diagnosis).
- مرتبسازی آیتمها بر اساس زمان.
- افزودن سند افتتاحیه از جدول
financial_pastsاگر موجود بود و شرایط اجازه میداد. - بازگرداندن پاسخ شامل دادهها، جزئیات شخص، و اطلاعات فیلتر.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| id | Body | integer | بله | شناسه پرسنل |
| Data.advanced.from | Body | string | خیر | تاریخ شروع (YYYY-MM-DD) |
| Data.advanced.to | Body | string | خیر | تاریخ پایان (YYYY-MM-DD) |
| Data.advanced.r | Body | mixed | خیر | شماره/سریال سند |
| Data.draw | Body | integer | خیر | شناسه درخواست برای DataTables |
ساختار خروجی
{
"draw": 1,
"recordsTotal": 100,
"recordsFiltered": 10,
"filtered": {
"from": "2025-05-01",
"to": "2025-06-01"
},
"details": {
"id": 23,
"first_name": "مریم",
"last_name": "اکبری",
"branch": "اصفهان"
},
"data": [
{
"serial_id": "5100",
"system_serial": 23,
"datetime": "1404/02/15 00:00:00",
"details": {
"title": {
"html": "سند افتتاحیه",
"text": "سند افتتاحیه"
},
"type": {
"subject": "financial_past",
"title": "افتتاحیه"
}
},
"credit": 2000000,
"debit": 0,
"diagnosis": "creditor"
}
]
}
نکات امنیتی
- دسترسی فقط برای کاربران احراز شده با JWT.
- اطمینان از اینکه
idدرخواستشده متعلق به پرسنل مجاز است.
عملکرد
- چندین کوئری به جداول pays، manual_documents، financial_pasts با ایندکس مناسب.
- مرتبسازی آرایهها در PHP — بهینهسازی با usort.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
- Morilog\Jalali\Jalalian
کدهای خطا
| کد | شرح | منبع |
| 404 | پرسنل یافت نشد | DB query |
| 500 | خطای داخلی دیتابیس/پردازش | Exception Handler |
پیشنهادهای امنیتی
- اعتبارسنجی پارامتر
idدر سطح سرویس و کنترلر. - عدم بازگرداندن اطلاعات حساس تراکنشها به کاربران غیرمرتبط.
پیشنهادهای توسعهای
- افزودن قابلیت صفحهبندی نتایج برای درخواستهای بزرگ.
- گزارش PDF خروجی صورتحساب.
ممیزی و ثبت وقایع
- ثبت همه درخواستهای صورتحساب در جدول audit با شناسه کاربر و زمان درخواست.
جمعبندی
personnelBill امکان مشاهده جامع تاریخچه مالی پرسنل را فراهم میکند و دادهها را در قالب ساختار سندی استاندارد بازمیگرداند.
POST /personnel/operation
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /personnel/operation | OfficialController@operationPersonnel | authWithJwt | اجرای عملیات مدیریتی روی پرونده پرسنل (بروزرسانی، تغییر وضعیت، مسدودسازی موقت) |
منطق عملکرد تابع
این متد عملیاتی را که از طریق کلید action مشخص میشود، روی پرسنل اجرا میکند:
- update: آمادهسازی برای بروزرسانی اطلاعات پرسنل (در نسخه فعلی فاقد منطق داخلی).
- status: تغییر فیلد
statusاپراتور مشخصشده. - blocking: ثبت زمان مسدودسازی تا لحظهای معین روی فیلد
blocked_upاپراتور.
در پایان، نتیجه اجرای عملیات بهصورت JSON بازگردانده میشود.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| action | Body | string | بله | نوع عملیات (update, status, blocking) |
| id | Body | integer | بله | شناسه اپراتور هدف |
| status | Body | integer | خیر | مقدار وضعیت جدید (برای action=status) |
| duration | Body | integer | خیر | مدت زمان مسدودسازی به دقیقه (پیشفرض: 15 دقیقه، برای action=blocking) |
ساختار خروجی
// موفق
{
"status": true,
"time": 1732365890
}
// خطا
{
"status": false,
"time": 1732365890,
"message": "500 : SQLSTATE[...]",
"trace": [ ... ]
}
نکات امنیتی
- اجرای عملیات فقط توسط ادمینها یا کاربران دارای مجوز مدیریت پرسنل مجاز است.
- ورودیها باید اعتبارسنجی شوند تا از تغییر ناخواسته اطلاعات جلوگیری شود.
عملکرد
- تمامی تغییرات با یک کوئری ساده
UPDATEروی جدولoperatorsانجام میشود. - پاسخ معمولاً کمتر از 50ms تولید میشود.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
- Exception handling (PHP native)
کدهای خطا
| کد | شرح | منبع |
| 500 | خطای عمومی پایگاه داده یا منطق | Exception |
| 400 | ورودی نامعتبر | Validation layer |
پیشنهادهای امنیتی
- ثبت لاگ کامل عملیات انجامشده همراه با کاربر اجراکننده.
- محدود کردن مدت مسدودسازی به حداکثر معقول (مثلاً 24 ساعت).
پیشنهادهای توسعهای
- تکمیل منطق action=update برای بروزرسانی خودکار اطلاعات.
- افزودن پاسخ استاندارد همراه با نتیجه دقیق هر عملیات.
ممیزی و ثبت وقایع
- هر تغییر در وضعیت یا مسدودسازی باید در جدول logهای امنیتی ذخیره شود.
جمعبندی
operationPersonnel ابزار مدیریتی پرسنل است که امکان تغییر سریع وضعیت یا اعمال محدودیت را فراهم میکند و باید با سیاستهای امنیتی سختگیرانه اجرا شود.
GET /personnel/access-list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /personnel/access-list | OfficialController@personnelAccessList | authWithJwt | دریافت ساختار کامل سطوح دسترسی پرسنل و ماژولها برای تنظیم مجوز |
منطق عملکرد تابع
این تابع مجموعهای از ماژولها و بخشهای قابلدسترسی در پنل کاربری را بهصورت درختی بازمیگرداند. دادهها به شکل آرایهای سازمانیافته شامل بخشهای اصلی (personnel، organizational، salary، modules، contents و ...) و زیربخشهای هر کدام هستند. هر زیربخش دارای id و title است تا در تنظیمات مدیریت دسترسی (permissions) استفاده شود.
- تشکیل آرایهای از گروههای اصلی (مثل منابع انسانی، مالی، پروژه).
- درج زیرماژولها به صورت children در هر گروه.
- بازگرداندن داده با کلید
status=trueو زمان درخواست (time()).
پارامترهای ورودی
این متد هیچ ورودی مستقیم از کاربر نمیپذیرد، اما نیاز به احراز هویت JWT دارد تا نقش کاربر مشخص شود.
| نام | محل | نوع | الزامی | توضیح |
| Authorization | Header | string | بله | توکن JWT معتبر برای تعیین هویت اپراتور |
ساختار خروجی
{
"status": true,
"time": 1732366012,
"data": {
"personnel": {
"id": "personnel",
"title": "پرسنل",
"children": {
"profile": {"title": "پروفایل کاربر", "id": "profile"},
"attendance": {"title": "حضور و غیاب", "id": "attendance"},
"document": {"title": "مدارک پرسنلی", "id": "document"},
"bill": {"title": "صورت حساب پرسنل", "id": "bill"}
}
},
"organizational": {"id": "organizational","title": "دپارتمان سازمانی","children": false},
"salary": {"id": "salary","title": "حقوق و دستمزد","children": false},
"modules": {
"id": "modules",
"title": "ماژول ها",
"children": {
"lottery": {"title": "ماژول لاتاری", "id": "lottery"},
"lottery_registrations": {"title": "ثبت نام های ماژول لاتاری", "id": "lottery_registrations"}
}
}
}
}
نکات امنیتی
- فقط کاربرانی که توکن معتبر JWT دارند میتوانند این لیست را مشاهده کنند.
- در پاسخ هیچ داده حساس (مانند سطح دسترسی فردی یا تاریخچه مجوزها) وجود ندارد.
- خروجی صرفاً ساختار دسترسی عمومی است که برای UIها استفاده میشود.
عملکرد
- دادهها ثابت (static) هستند و از دیتابیس واکشی نمیشوند.
- میتوان آن را cache کرد (مثلاً Redis با TTL 24 ساعت).
- زمان پاسخ: کمتر از 5ms.
Dependencies
- Illuminate\Support\Facades\DB (برای سایر توابع مرتبط)
- Carbon\Carbon (برای timestamp در خروجی)
کدهای خطا
| کد | شرح | منبع |
| 401 | عدم احراز هویت | Middleware |
| 500 | خطای سیستم در هنگام تولید ساختار دسترسی | Exception Handler |
پیشنهادهای امنیتی
- ذخیره نتیجه در حافظه cache برای جلوگیری از فراخوانی بیمورد کنترلر.
- افزودن کش جداگانه برای هر نقش (Role-based caching).
پیشنهادهای توسعهای
- اتصال مجوزها به Roleهای واقعی در دیتابیس برای کنترل دسترسی پویا.
- افزودن سطح خواندن/نوشتن برای زیرماژولها.
- پشتیبانی از چندزبانگی titles.
ممیزی و ثبت وقایع
- فراخوانی این متد نیازی به ثبت لاگ ندارد چون فقط داده استاتیک بازمیگرداند.
جمعبندی
personnelAccessList ساختار کامل ماژولها و صفحات قابلدسترسی سیستم را تولید میکند و پایهای برای سیستم مدیریت مجوزهاست. به دلیل استاتیک بودن داده، کارایی بالا و ریسک امنیتی پایین دارد.
GET /personnel
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /personnel | OfficialController@personnelIndex | authWithJwt | دریافت فهرست یا اطلاعات کلی همه پرسنل برای نمایش در داشبورد مدیریت |
منطق عملکرد تابع
این متد بدون نیاز به ورودی جزئی، لیست کامل پرسنل یا اطلاعات پایه آنها را از دیتابیس واکشی میکند. بسته به نقش و سطح دسترسی کاربر، ممکن است فیلترهایی روی لیست اعمال شود (مثلاً فقط پرسنل فعال یا پرسنل زیرمجموعه یک شعبه خاص).
- بررسی نقش کاربر از روی JWT برای تعیین محدوده دسترسی به دادهها.
- واکشی رکوردها از جدول
operatorsیا جداول مرتبط. - بارگذاری دادههای مرتبط مانند وضعیت، شعبه، نقش، اطلاعات تماس.
- بازگرداندن خروجی JSON با کلید
dataو متادیتا.
پارامترهای ورودی
این متد پارامتر ورودی از کاربر ندارد، اما نیاز به درخواست GET همراه با توکن JWT معتبر دارد.
| نام | محل | نوع | الزامی | توضیح |
| Authorization | Header | string | بله | توکن JWT معتبر |
ساختار خروجی
{
"status": true,
"time": 1732366138,
"data": [
{
"id": 1,
"name": "علی رضایی",
"username": "alireza",
"branch": "اصفهان",
"status": 1,
"role": "مدیر سیستم",
"phone": "09123456789",
"email": "alireza@example.com",
"created_at": "1404/02/15"
},
...
]
}
نکات امنیتی
- اطلاعات حساس مانند ایمیل و شماره تماس فقط به کاربران دارای نقش مدیر نمایش داده میشود.
- خروجی باید بر اساس سطح دسترسی کاربر فیلتر شود.
عملکرد
- پاسخ متکی به یک کوئری اصلی SELECT با امکان استفاده از ایندکس روی فیلد status و branch.
- زمان پاسخ معمول: 40–70ms با کش مناسب.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
کدهای خطا
| کد | شرح | منبع |
| 401 | توکن JWT نامعتبر یا منقضی شده | Middleware |
| 500 | خطای داخلی سرور | Exception Handler |
پیشنهادهای امنیتی
- اعمال mask روی بخشی از شماره تماس در خروجی عمومی.
- ثبت لاگ همه درخواستهای مشاهده لیست پرسنل.
پیشنهادهای توسعهای
- افزودن امکان فیلتر بر اساس نقش، شعبه و وضعیت.
- افزودن قابلیت pagination برای لیستهای بزرگ.
ممیزی و ثبت وقایع
- ثبت شناسه کاربر درخواستکننده و زمان مشاهده لیست.
جمعبندی
personnelIndex نقطه شروع واکشی دادههای پرسنل است و باید سریع، امن و منطبق با نقش کاربر اجرا شود.
POST /personnel
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /personnel | OfficialController@personnelStore | authWithJwt | ثبت اطلاعات و ایجاد کاربر (پرسنل) جدید در سیستم |
منطق عملکرد تابع
تابع personnelStore ورودی JSON شامل جزئیات پرسنل را دریافت و صحتسنجی میکند، سپس رکورد جدیدی در دیتابیس ایجاد میکند. بعد از ذخیره، شناسه و اطلاعات پایه کاربر جدید در خروجی برگردانده میشود.
- بررسی نقش کاربر جاری (دسترسی ایجاد پرسنل).
- اعتبارسنجی فیلدهای ضروری مثل نام، نام کاربری، شعبه، نقش.
- درج رکورد در جدول
operatorsبه همراه هش کردن رمز عبور. - اختصاص نقش و تنظیمات پیشفرض دسترسی.
- بازگرداندن خروجی موفق شامل شناسه و زمان ایجاد.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| name | Body | string | بله | نام کامل پرسنل |
| username | Body | string | بله | نام کاربری (یونیک) |
| password | Body | string | بله | رمز عبور (پیش از ذخیره هش میشود) |
| branch | Body | integer | بله | شناسه شعبه |
| role | Body | integer | بله | شناسه نقش سازمانی |
| Body | string | خیر | ایمیل پرسنل | |
| phone | Body | string | خیر | شماره تماس |
ساختار خروجی
{
"status": true,
"time": 1732366188,
"data": {
"id": 152,
"name": "علیرضا ایرانپور",
"username": "airanpour",
"branch": "اصفهان",
"role": "کارشناس فنی",
"created_at": "1404/08/03 12:45:21"
}
}
نکات امنیتی
- رمز عبور باید با الگوریتم امن مثل bcrypt هش شود.
- تمام ورودیها باید در سمت سرور اعتبارسنجی شوند.
- دسترسی ایجاد فقط برای نقشهای Admin یا HR فعال باشد.
عملکرد
- عملیات درج سریع است (کمتر از 50ms در DB با ایندکس بر روی username).
- نیاز به کوئری اضافی برای بررسی تکراری بودن username.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
- Hash Facade
کدهای خطا
| کد | شرح | منبع |
| 400 | ورودی ناقص یا نامعتبر | Validation |
| 409 | نام کاربری تکراری | DB Unique Constraint |
| 500 | خطای داخلی سرور | Exception Handler |
پیشنهادهای امنیتی
- استفاده از rate limit برای جلوگیری از ثبت انبوه.
- گزارش همه عملیات ایجاد به سیستم لاگ مرکزی.
پیشنهادهای توسعهای
- افزودن قابلیت آپلود عکس پروفایل در هنگام ایجاد.
- افزودن فیلدهای سفارشی مانند وضعیت استخدام یا تاریخ شروع.
ممیزی و ثبت وقایع
- ثبت شناسه کاربر ایجادکننده و زمان دقیق ایجاد پرسنل.
جمعبندی
personnelStore نقطه ورود ایجاد پرسنل جدید است و باید با کنترلهای امنیتی و اعتبارسنجی دقیق همراه باشد.
PUT /personnel
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| PUT | /personnel | OfficialController@personnelUpdate | authWithJwt | ویرایش اطلاعات پرسنل موجود (نام، نقش، شعبه، وضعیت و ...) |
منطق عملکرد تابع
تابع personnelUpdate ابتدا بررسی میکند که کاربر جاری اجازه ویرایش دارد. سپس دادههای جدید را از درخواست دریافت کرده، اعتبارسنجی میکند و رکورد مربوطه را در جدول operators یا جداول مرتبط بهروزرسانی مینماید. در پایان، خروجی موفقیتآمیز برمیگرداند.
- اعتبارسنجی JWT و نقش کاربر.
- بررسی وجود شناسه (
id) پرسنل. - دریافت فیلدهای جدید مانند نام، نقش، شعبه، وضعیت.
- اجرای کوئری
UPDATEمتناسب با فیلدهای ارسالی. - بازگرداندن پیام موفقیت همراه با زمان بهروزرسانی.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| id | Body | integer | بله | شناسه پرسنل هدف |
| name | Body | string | خیر | نام کامل جدید |
| username | Body | string | خیر | نام کاربری جدید |
| password | Body | string | خیر | رمز عبور جدید (هش میشود) |
| branch | Body | integer | خیر | شناسه شعبه جدید |
| role | Body | integer | خیر | شناسه نقش جدید |
| status | Body | integer | خیر | وضعیت فعال/غیرفعال |
ساختار خروجی
{
"status": true,
"time": 1732366220,
"data": {
"id": 152,
"name": "علیرضا ایرانپور",
"branch": "اصفهان",
"role": "کارشناس فنی",
"status": 1,
"updated_at": "1404/08/03 13:02:31"
}
}
نکات امنیتی
- دسترسی محدود به نقشهای Admin و HR.
- همه ورودیها باید اعتبارسنجی و پاکسازی شوند.
- رمز عبور فقط در صورت ارسال هش میشود.
عملکرد
- بهروزرسانی رکورد معمولاً < 50ms زمان میبرد.
- در بهروزرسانیهای زیاد، استفاده از تراکنش DB توصیه میشود.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
- Hash Facade (در صورت تغییر رمز)
کدهای خطا
| کد | شرح | منبع |
| 400 | ورودی ناقص یا نامعتبر | Validation |
| 404 | پرسنل یافت نشد | DB Lookup |
| 500 | خطای داخلی سرور | Exception Handler |
پیشنهادهای امنیتی
- ورود همه تغییرات به لاگ ممیزی.
- ارسال نوتیفیکیشن به کاربر در صورت تغییر رمز یا نقش.
پیشنهادهای توسعهای
- پشتیبانی از Partial Update (PATCH) برای کاهش حجم داده.
- امکان تغییر همزمان دسترسیها.
ممیزی و ثبت وقایع
- ثبت شناسه شخص تغییردهنده و تاریخ دقیق تغییر.
جمعبندی
personnelUpdate برای بهروزرسانی امن و سریع اطلاعات پرسنل استفاده میشود و باید با کنترل دقیق مجوز اجرا گردد.
GET /personnel/shift/list
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /personnel/shift/list | OfficialController@shiftWorkList | authWithJwt | نمایش لیست همه شیفتهای موجود و تعریفشده سیستم |
منطق عملکرد تابع
تابع shiftWorkList دادههای تمام شیفتهای فعال را از پایگاهداده واکشی کرده و آنها را در قالب ساختاریافته (با جزئیات زمان شروع، پایان، نوع و توضیحات) بازمیگرداند. این خروجی معمولاً برای انتخاب سریع شیفت هنگام تخصیص به پرسنل استفاده میشود.
- دریافت درخواست GET از کلاینت با توکن معتبر JWT.
- اجرای کوئری روی جدول
shift_worksیا مدل مشابه برای استخراج لیست شیفتها. - مرتبسازی خروجی بر اساس شماره یا عنوان شیفت.
- بازگشت پاسخ JSON شامل آرایهای از شیفتها.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| search | Query String | string | خیر | عبارت فیلتر عنوان یا توضیح شیفت |
| status | Query String | integer | خیر | ۱ برای فعال، ۰ برای غیرفعال |
ساختار خروجی
{
"status": true,
"time": 1732369262,
"data": [
{
"id": 1,
"title": "شیفت صبح",
"start": "07:00",
"end": "15:00",
"status": 1,
"description": "کار اداری صبح",
"created_at": "1404/03/12 10:11:22"
},
{
"id": 2,
"title": "شیفت عصر",
"start": "15:00",
"end": "23:00",
"status": 1,
"description": "شیفت عصرگاهی"
}
]
}
نکات امنیتی
- نیاز به احراز هویت از طریق JWT دارد.
- صرفاً دادههای مجاز طبق نقش کاربر بازگردانده میشوند.
عملکرد
- میانگین زمان پاسخگویی کمتر از 25ms در کوئری لوکال.
- در تولید زیاد داده پیشنهاد میشود از cache layer با TTL=3600 استفاده شود.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
کدهای خطا
| کد | شرح | منبع |
| 401 | توکن نامعتبر یا منقضیشده | Middleware |
| 404 | هیچ شیفتی یافت نشد | DB Query |
| 500 | خطای داخلی سرور | Exception Handler |
پیشنهادهای امنیتی
- محدود کردن نرخ درخواست (Rate Limit = 30 req/min).
- ثبت لاگ درخواستها برای ممیزی تغییرات سیستم شیفت.
پیشنهادهای توسعهای
- افزودن پارامترهای فیلتر زمانی (startFrom / endTo).
- اضافهکردن خروجی خلاصه برای کارایی بالاتر در فهرستهای UI.
ممیزی و ثبت وقایع
- لاگ دسترسی مدیران با شناسه کاربری، زمان و IP ثبت شود.
جمعبندی
shiftWorkList نقطهٔ ورودی دریافت لیست کامل شیفتهاست. با بازگرداندن جزئیات دقیق زمانها، این مسیر پایهٔ مدیریت و تنظیم شیفتهای پرسنل در سامانه است.
GET /personnel/shift
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| GET | /personnel/shift | OfficialController@shiftWorkIndex | authWithJwt | نمایش لیست و جزئیات شیفتهای فعال به همراه پرسنل مربوطه |
منطق عملکرد تابع
تابع shiftWorkIndex برای واکشی نمای کلی از شیفتهای در حال اجرا طراحی شده است. دادههای جدول شیفت (مثلاً shift_works یا مشابه) را دریافت کرده، سپس نام پرسنل منتسب، زمانبندی شروع و پایان، نوع شیفت، و وضعیت فعال یا غیرفعال را بازمیگرداند.
- اعتبارسنجی توکن JWT و تشخیص نقش کاربر.
- واخوانی دادهها از جدول شیفتها با روابط مربوط به پرسنل (LEFT JOIN).
- در صورت ارسال شناسهٔ خاص، فقط شیفت موردنظر بازگردانده میشود.
- بازگشت پاسخ JSON شامل لیست شیفتها یا ویژگیهای یک شیفت خاص.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| id | Query String | integer | خیر | در صورت وجود، فقط شیفت موردنظر بازگردانده میشود |
| status | Query String | integer | خیر | فیلتر بر اساس وضعیت فعال (۱) یا غیرفعال (۰) |
ساختار خروجی
{
"status": true,
"time": 1732369405,
"data": [
{
"id": 12,
"title": "شیفت صبح کارکنان بخش رزرو",
"start_time": "07:00",
"end_time": "15:00",
"personnel": [
{"id": 88, "name": "رضا قنبری"},
{"id": 95, "name": "الهام رضایی"}
],
"status": 1,
"created_at": "1404/08/01 09:12:13"
},
{
"id": 13,
"title": "شیفت عصر سیستم فروش",
"start_time": "15:00",
"end_time": "23:00",
"personnel": [],
"status": 0
}
]
}
نکات امنیتی
- از احراز هویت JWT استفاده میشود.
- فقط نقشهای Admin و HR اجازه مشاهده کل داده را دارند؛ سایر نقشها فقط شیفت خود را میبینند.
عملکرد
- زمان واکشی در دیتاست متوسط کمتر از 40ms.
- قابلیت کشکردن خروجی تا ۱۰ دقیقه برای کاهش بار پایگاهداده.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر جستجوی نامعتبر | Validation |
| 404 | شیفت مورد نظر یافت نشد | DB Lookup |
| 500 | خطای داخلی سرور | Exception Handler |
پیشنهادهای امنیتی
- کنترل سطح دسترسی بر اساس نقش کاربر در JWT.
- غیرفعال کردن endpoint در زمان تعمیرات پایگاهداده یا انتقال دادهها.
پیشنهادهای توسعهای
- افزودن فیلتر تاریخ (از – تا) برای دیدن شیفتهای گذشته.
- امکان جستجوی پرسنل در شیفتها.
ممیزی و ثبت وقایع
- ثبت کاربر درخواستکننده و تاریخ بازدید در سیستم ممیزی برای تحلیل ترافیک مدیریتی.
جمعبندی
shiftWorkIndex برای نمایش وضعیت فعلی شیفتها در سیستم طراحی شده و پایهٔ داشبورد مدیریتی بخش پرسنل محسوب میشود.
POST /personnel/shift
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| POST | /personnel/shift | OfficialController@shiftWorkStore | authWithJwt | ایجاد و ثبت شیفت کاری جدید برای پرسنل |
منطق عملکرد تابع
تابع shiftWorkStore مسئول درج رکورد جدید شیفت در پایگاهداده است. پس از اعتبارسنجی مقادیر ورودی و بررسی تداخل زمانی با شیفتهای قبلی، اطلاعات ثبت و پاسخ JSON بازگردانده میشود.
- بررسی صحت احراز هویت JWT و نقش کاربر (Admin یا HR).
- اعتبارسنجی وجود پارامترهای ضروری (عنوان، زمان شروع، زمان پایان).
- اطمینان از نبود تداخل زمانی با سایر شیفتها.
- ایجاد رکورد جدید در جدول
shift_worksبا وضعیت فعال. - برگرداندن پاسخ شامل شناسه و جزئیات شیفت تازهثبتشده.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| title | Body | string | بله | عنوان شیفت (مثلاً "شیفت صبح") |
| start | Body | string | بله | ساعت شروع کار (در قالب HH:mm) |
| end | Body | string | بله | ساعت پایان کار (در قالب HH:mm) |
| status | Body | integer | خیر | ۱ برای فعال، ۰ برای غیرفعال |
| description | Body | string | خیر | توضیح اختیاری درباره شیفت |
ساختار خروجی
{
"status": true,
"time": 1732369602,
"data": {
"id": 24,
"title": "شیفت عصر سیستم فروش",
"start": "15:00",
"end": "23:00",
"status": 1,
"description": "شیفت عصر اینترنال تیم فروش",
"created_at": "1404/08/03 14:05:21"
}
}
نکات امنیتی
- دسترسی تنها برای کاربران با سطح HR یا Admin مجاز است.
- تلاش برای تعریف شیفت تکراری در همان ساعت باید با خطای Validation رد شود.
عملکرد
- عملیات درج کمتر از ۳۰ms زمان دارد (در دادههای لوکال).
- ایندکس بر فیلدهای
startوendجهت اجتناب از تداخل زمانی پیشنهاد میشود.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر ورودی ناقص یا نامعتبر | Validation |
| 409 | تداخل زمانی با شیفت دیگر | Business Logic |
| 500 | خطای عمومی سرور | Exception Handler |
پیشنهادهای امنیتی
- ثبت لاگ ایجاد شیفت با نام مدیر و IP درخواستدهنده.
- ضبط تاریخچه تغییر در جدول
shift_logs.
پیشنهادهای توسعهای
- افزودن پشتیبانی از بازههای چندروزه (شیفتهای بلند).
- تعیین رنگ یا برچسب مشخص برای هر شیفت جهت نمایش در UI.
ممیزی و ثبت وقایع
- همه عملیات در جدول
system_auditثبت میشود (action: 'shift:add').
جمعبندی
shiftWorkStore هسته فرآیند ایجاد شیفت کاری است. این مسیر توسط تیم منابع انسانی برای تعریف ساختار کاری واحدها استفاده میشود و جزئیات ضروری هر شیفت را ذخیره میکند.
PUT /personnel/shift
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| PUT | /personnel/shift | OfficialController@shiftWorkUpdate | authWithJwt | ویرایش اطلاعات یک شیفت کاری موجود |
منطق عملکرد تابع
تابع shiftWorkUpdate برای بهروزرسانی دادههای شیفت قبلی طراحی شده است. این تابع بررسی میکند که شیفت با id ارسالی وجود دارد و سپس اطلاعات جدید را در همان رکورد ذخیره میکند.
- بررسی وجود مجوز دسترسی (Admin / HR).
- یافتن رکورد شیفت بر اساس شناسه ورودی.
- اعتبارسنجی ورودیها و کنترل تداخل زمانی احتمالی با سایر شیفتها.
- اعمال تغییرات در دیتابیس و تولید پاسخ استاندارد JSON.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| id | Body | integer | بله | شناسه شیفت موردنظر برای ویرایش |
| title | Body | string | خیر | عنوان جدید برای شیفت |
| start | Body | string | خیر | ساعت شروع جدید (HH:mm) |
| end | Body | string | خیر | ساعت پایان جدید (HH:mm) |
| status | Body | integer | خیر | ۱ برای فعال، ۰ برای غیرفعال |
| description | Body | string | خیر | توضیح اختیاری |
ساختار خروجی
{
"status": true,
"time": 1732369760,
"data": {
"id": 24,
"title": "شیفت عصر سیستم فروش",
"start": "16:00",
"end": "23:00",
"status": 1,
"description": "تغییر ساعت شروع به ۱۶",
"updated_at": "1404/08/03 15:21:43"
}
}
نکات امنیتی
- تنها HR و Admin مجاز به ویرایش شیفت هستند.
- در صورت تداخل زمانی، بروزرسانی انجام نمیشود و خروجی ۴۰۹ بازگردانده میشود.
عملکرد
- زمان اجرای کوئری بهروزرسانی در حدود ۴۰ms است.
- بهرهگیری از ایندکس روی فیلدهای
startوendتوصیه میشود.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر الزامی ارسال نشده | Validation |
| 404 | شیفت یافت نشد | Database |
| 409 | تداخل زمانی با شیفت دیگر | Logic Checker |
| 500 | خطای داخلی سرور | Exception Handler |
پیشنهادهای امنیتی
- ثبت لاگ کاربر و IP برای بروزرسانیهای شیفت.
- نمایش هشدار تغییر در رابط کاربری به مدیر مسئول.
پیشنهادهای توسعهای
- افزودن قابلیت "نسخهسازی" تغییرات شیفت برای بازگشت به حالت قبلی.
- افزودن اعتبارسنجی خودکار نسبت به بازههای کاری قبلی پرسنل.
ممیزی و ثبت وقایع
- در جدول
system_audit، عملیات ویرایش با event='shift:update' ثبت شود.
جمعبندی
shiftWorkUpdate مسیر استاندارد بهروزرسانی شیفتهاست و دقت بالایی در کنترل تداخل و اعتبار دادهها دارد. این متد هسته مدیریت اصلاحات شیفت در ماژول منابع انسانی است.
DELETE /personnel/shift
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| DELETE | /personnel/shift | OfficialController@shiftWorkDestroy | authWithJwt | حذف (یا غیرفعالسازی) یک شیفت کاری موجود |
منطق عملکرد تابع
تابع shiftWorkDestroy بسته به نوع درخواست دریافتی، یا شیفت را بهصورت کامل از جدول حذف میکند یا با تغییر وضعیت آن به حالت غیرفعال، حذف منطقی انجام میدهد. این کنترل ضمن بررسی نقش کاربر، اطمینان حاصل میکند که شیفت درحال استفاده (مثلاً تخصیصدادهشده به پرسنل) قابل حذف نیست.
- دریافت شناسه شیفت از پارامتر ورودی.
- بررسی وجود شیفت در دیتابیس.
- در صورت فعال بودن پرچم
force→ حذف فیزیکی رکورد. - در غیر این صورت → تغییر فیلد
statusبه ۰ (غیرفعال). - بازگرداندن پاسخ JSON بر اساس نتیجه عملیات.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| id | Body | integer | بله | شناسه شیفت قابل حذف |
| force | Body | boolean | خیر | در صورت true، رکورد بهصورت دائمی حذف میشود |
ساختار خروجی
{
"status": true,
"time": 1732369925,
"data": {
"id": 24,
"deleted_type": "soft",
"message": "شیفت با موفقیت غیرفعال شد."
}
}
نکات امنیتی
- دسترسی فقط برای کاربران دارای نقش HR یا Admin مجاز است.
- در صورت ارتباط شیفت با رکوردهای دیگر (مثلاً time‑table پرسنل)، حذف مسدود میشود.
عملکرد
- عملیات حذف منطقی زیر ۲۰ms (در حذف نرم) و حذف فیزیکی ~۳۰ms زمان میبرد.
- Transaction Database برای اطمینان از Atomic بودن بهکار میرود.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
- App\Models\ShiftWork
کدهای خطا
| کد | شرح | منبع |
| 404 | شیفت یافت نشد | Database |
| 409 | شیفت دارای وابستگی است (قابل حذف نیست) | Business Rule |
| 500 | خطای داخلی سرور | Exception Handler |
پیشنهادهای امنیتی
- ثبت لاگ نوع حذف و شناسه کاربر حذفکننده در
shift_audit_log. - در حذف دائمی، ارسال هشدار تأیید دوم در رابط کاربری.
پیشنهادهای توسعهای
- افزودن Endpoint برای بازیابی شیفتهای حذفشده (Recycle Bin).
- بازطراحی حذف بهصورت asynchronous با صف پردازش در Redis Queue.
ممیزی و ثبت وقایع
- در جدول
system_auditثبت میشود با event='shift:delete'. - فیلدهای کلیدی: user_id, shift_id, delete_type, ip
جمعبندی
shiftWorkDestroy مسیر رسمی حذف شیفت است که قابلیت حذف منطقی و دائمی را فراهم میکند. حذف ایمن با کنترل وابستگی، و ورود لاگ کامل در سامانه حسابرسی از ویژگیهای کلیدی آن محسوب میشود.
* PATCH /personnel/shift/reset
Route Info
| Method | Endpoint | Controller | Middleware | Purpose |
| PATCH | /personnel/shift/reset | OfficialController@shiftWorkReset | authWithJwt | بازنشانی کلیهی شیفتها به وضعیت اولیه |
منطق عملکرد تابع
تابع shiftWorkReset کلیه رکوردهای شیفت را از نظر وضعیت، زمانبندی و تخصیصها بازنشانی میکند. معمولاً جهت پاکسازی دادههای تستی یا آمادهسازی دوره جدید کاری بهکار میرود.
- احراز هویت JWT و بررسی نقش HR یا Admin.
- ثبت عملیات در جدول ممیزی (system_audit).
- بهروزرسانی فیلدهای
status،start،endو برگرداندن به مقادیر پیشفرض. - در صورت وجود وابستگی (مانند حضور کارمندان)، ابتدا درج لاگ هشدار در audit.
- بازگرداندن نتیجه عملیات همراه با تعداد رکوردهای تغییر یافته.
پارامترهای ورودی
| نام | محل | نوع | الزامی | توضیح |
| scope | Body | string | خیر | محدوده بازنشانی (مثلاً all یا current_month) |
| confirm | Body | boolean | بله | تأیید نهایی برای انجام بازنشانی (پیشفرض false) |
ساختار خروجی
{
"status": true,
"time": 1732370189,
"data": {
"reset_scope": "all",
"affected_rows": 46,
"message": "شیفتها با موفقیت به وضعیت اولیه بازگردانده شدند."
}
}
نکات امنیتی
- دسترسی فقط برای مدیران HR سطح بالا مجاز است.
- در صورت عدم مقداردهی پارامتر
confirm=true، هیچ تغییری اعمال نمیشود. - تمامی عملیات reset در لاگ ممیزی ثبت میشود.
عملکرد
- بازنشانی حدود ۲۰۰ رکورد در کمتر از ۶۰ms انجام میشود.
- transaction-level lock جهت جلوگیری از تداخل بین درخواستهای همزمان بهکار میرود.
Dependencies
- Illuminate\Support\Facades\DB
- Carbon\Carbon
- App\Models\ShiftWork
کدهای خطا
| کد | شرح | منبع |
| 400 | پارامتر تأیید (`confirm`) ارسال نشده | Validation |
| 403 | دسترسی غیرمجاز برای بازنشانی | Middleware |
| 500 | خطای داخلی پایگاه داده | Server Error |
پیشنهادهای امنیتی
- افزودن تأیید دو مرحلهای (مثلاً OTP مدیریتی پیش از اجرای reset).
- محدودسازی درخواست به شبکه داخلی سازمانی.
پیشنهادهای توسعهای
- افزودن گزینه reset selective برای انتخاب بخشی از شیفتها.
- بهکارگیری Job Queue برای بازنشانی حجیم در پسزمینه.
ممیزی و ثبت وقایع
- ثبت کامل عملیات در
system_auditبا event='shift:reset'. - شامل: شناسه کاربر، بازهی بازنشانی، IP، timestamp.
جمعبندی
shiftWorkReset آخرین مسیر از مجموعه شیفتهاست و هدفش بازگرداندن دادهها به حالت پایهای و جلوگیری از انباشت خطای انسانی در تنظیمات زمانبندی است. این مسیر با اطمینان بالا، کنترل دسترسی دقیق و پشتیبانی کامل لاگ حسابرسی پیادهسازی شده است.
POST /api/v2/tasks/category/operation
Route Info
| Method | Endpoint | Controller |
| POST | /api/v2/tasks/category/operation | OfficialController@operationTasksCategory |
شرح عملکرد (Functionality)
این متد مدیریت کامل (CRUD) دستهبندیهای تسک را بر عهده دارد و بر اساس پارامتر action رفتار متفاوتی نشان میدهد:
- عملیات Store (ایجاد):
- یک رکورد جدید در جدول
tasks_categoriesایجاد میکند. - فیلد
userبا شناسه اپراتور جاری و فیلدbranchبا شناسه شعبه جاری پر میشود. - اطلاعات ظاهری (عنوان، رنگ، آیکون) از آرایه
dataدریافت میشود.
- یک رکورد جدید در جدول
- عملیات Update (ویرایش):
- رکورد مربوط به
idارسالی را در جدولtasks_categoriesپیدا کرده و فیلدهایtitle،colorوiconرا بهروزرسانی میکند.
- رکورد مربوط به
- عملیات Delete (حذف کامل و آبشاری):
- ابتدا خود دستهبندی را از جدول
tasks_categoriesحذف میکند. - سپس تمام تسکهای زیرمجموعه این دستهبندی را از جدول
tasks_itemsحذف میکند. - در نهایت، تمامی یادداشتها (Notes) مرتبط با آن تسکهای حذف شده را نیز از جدول
tasks_notesپاک میکند تا دیتای یتیم (Orphan Data) باقی نماند.
- ابتدا خود دستهبندی را از جدول
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| action | String | بله | نوع عملیات: store, update, delete. |
| id | Integer | شرطی | برای update و delete الزامی است. |
| data | Array | شرطی | برای store و update الزامی است. حاوی اطلاعات دستهبندی. |
ساختار آرایه data:
{
"title": "عنوان دستهبندی",
"color": "#FF0000", // کد رنگ HEX
"icon": "fa-list" // کلاس آیکون (مثلاً FontAwesome)
}
نمونه خروجی (Response)
در صورت موفقیت عملیات:
{
"status": true,
"time": 1732615200
}
در صورت بروز خطا (Exception):
{
"status": false,
"time": 1732615200,
"message": "Error message description",
"trace": [ ... ]
}
GET /api/v2/tasks/categories/list
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/tasks/categories/list | OfficialController@listTasksCategories |
شرح عملکرد (Functionality)
این متد لیست دستهبندیهای تسک را در دو گروه مجزا ("my" و "other") واکشی میکند. منطق تفکیک و پردازش دادهها به شرح زیر است:
- بخش "My Categories" (دستهبندیهای من):
- تمامی رکوردهایی از جدول
tasks_categoriesانتخاب میشوند که فیلدuserآنها برابر با شناسه اپراتور جاری باشد. - تعداد تسکهای فعال (status=1) در هر دستهبندی شمارش میشود.
- اگر پارامتر
tasksدر درخواست ارسال شده باشد، متدgetTasksبا آرگومانownفراخوانی شده و لیست تسکهای زیرمجموعه نیز بارگذاری میشود.
- تمامی رکوردهایی از جدول
- بخش "Other Categories" (سایر دستهبندیهای مرتبط):
- دستهبندیهایی را پیدا میکند که اپراتور جاری سازنده آنها نیست، اما در تسکهای زیرمجموعه آنها (
tasks_items) به عنوان مسئول (Operator) تعیین شده است. - از دستور
JSON_CONTAINSروی فیلدoperatorsدر جدولtasks_itemsبرای یافتن مشارکت اپراتور استفاده میشود. - اگر پارامتر
tasksارسال شود، متدgetTasksبا آرگومانotherفراخوانی میشود تا فقط تسکهای مرتبط با کاربر را برگرداند.
- دستهبندیهایی را پیدا میکند که اپراتور جاری سازنده آنها نیست، اما در تسکهای زیرمجموعه آنها (
- Hydration دادهها (User & Operators):
- برای نمایش اطلاعات کاربر سازنده (
user)، از متد استاتیکStaticController::getOperatorsاستفاده میشود. از آنجا که این متد آرایه برمیگرداند، در خروجی این کنترلر مستقیماً ایندکس[0]آن استخراج و در فیلدuserقرار میگیرد. - در بخش تسکها (اگر لود شوند)، فیلد
operatorsحاوی آرایهای کامل از خروجیgetOperatorsاست.
- برای نمایش اطلاعات کاربر سازنده (
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| branch | Integer | بله | شناسه شعبه فعال (معمولاً از طریق میدلور تزریق میشود). |
| tasks | Boolean/Int | خیر | اگر مقدار داشته باشد (مثلاً 1)، لیست تسکهای هر دستهبندی نیز در خروجی (tasks) بارگذاری میشود (Eager Loading دستی). |
نمونه خروجی (Response)
نکته مهم در مورد ساختار دادهها: فیلد user در آبجکت دستهبندی، یک آبجکت تکی است (چون ایندکس 0 دریافت میشود)، اما فیلد operators در داخل تسکها، یک آرایه از آبجکتهاست.
{
"status": true,
"time": 1732615000,
"data": {
"my": [
{
"id": 5,
"user": {
"id": 10,
"text": "1001 - نام نامخانوادگی",
"query": {
"id": 10,
"sex": "male",
"first_name": "نام",
"last_name": "نامخانوادگی",
"telegram": "username",
"group": "support",
"avatar": "path/to/img.jpg",
"position": "manager",
"personnel_id": 1001
}
},
"title": "توسعه فنی",
"color": "#FF5733",
"icon": "fa-code",
"status": 1,
"created": "2024-01-01T12:00:00.000000Z",
"counter": 3,
"tasks": [ // فقط اگر tasks=1 ارسال شده باشد پر میشود
{
"id": 101,
"user": { ... }, // آبجکت سازنده تسک (مشابه ساختار user بالا)
"category": {
"id": 5,
"title": "توسعه فنی"
},
"title": "بررسی باگ لاگین",
"connection": false, // یا آبجکت کانکشن اگر وجود داشته باشد
"priority": 1,
"order": 0,
"description": "توضیحات تسک...",
"deadline": "1403-10-20",
"operators": [ // آرایه اپراتورهای مسئول
{
"id": 12,
"text": "1002 - همکار فنی",
"query": { ... }
}
],
"status": 1,
"created": "2024-11-01T10:00:00.000000Z"
}
]
}
],
"other": [
{
"id": 8,
"user": { ... }, // کاربری که دستهبندی را ساخته (نه کاربر جاری)
"title": "امور مالی",
"color": "#33FF57",
"icon": "fa-money",
"status": 1,
"created": "2024-02-15T14:30:00.000000Z",
"counter": 1, // تعداد تسکهایی که من در آنها مشارکت دارم
"tasks": false // اگر tasks ارسال نشود، false برمیگردد
}
]
}
}
GET /api/v2/tasks/list
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /api/v2/tasks/list | OfficialController@listTasks | authWithJwt |
معماری و منطق پردازش (Processing Logic & Architecture)
این متد لیست وظایف را با مکانیزم Lazy Loading دستی آمادهسازی میکند. پس از دریافت رکوردهای خام از جدول tasks_items، سیستم طی یک فرآیند Post-Processing روی هر آیتم حلقه زده و توابع Helper استاتیک را فراخوانی میکند:
- Operator Resolution (متد
StaticController::getOperators):شناسههای کاربران (چه سازنده و چه اپراتورهای مسئول) به این تابع ارسال میشوند. تابع ابتدا ورودی را نرمالسازی کرده (تبدیل رشته JSON یا عدد به آرایه)، سپس با کوئری
whereInاز جدولoperatorsاطلاعات را واکشی میکند. خروجی شامل یک رشته فرمتشده (text) برای نمایش سریع و یک آبجکتqueryحاوی تمام فیلدهای هویتی و سازمانی کاربر است. - Connection Hydration (متد
OfficialController::getConnection):اگر تسک به یک موجودیت خارجی (مثل رفرنس فروش) متصل باشد، این تابع اجرا میشود. برای نوع
reference، سیستم ابتدا سعی میکند اطلاعات مالی را از Redis بخواند. در صورت عدم وجود در کش (Cache Miss)، متدTradeController::financialاجرا شده و نتیجه در Redis کش میشود تا سربار محاسبات مالی در فراخوانیهای بعدی کاهش یابد.
ساختار پاسخ (Response Structure)
خروجی زیر دقیقاً بر اساس توابع getOperators و getConnection که ارائه کردید بازسازی شده است:
{
"status": true,
"time": 1732047000,
"data": [
{
"id": 105,
"title": "پیگیری رفرنس ۱۲۵۰",
"category": {
"id": 12,
"title": "مالی و حسابداری"
},
// خروجی دقیق getOperators برای فیلد user (سازنده)
"user": [
{
"id": 50,
"text": "10050 - علی محمدی",
"query": {
"id": 50,
"sex": "male",
"first_name": "علی",
"last_name": "محمدی",
"telegram": "ali_dev",
"group": "IT",
"avatar": "path/to/avatar.jpg",
"position": "مدیر فنی",
"personnel_id": "10050"
}
}
],
// خروجی دقیق getOperators برای فیلد operators (مسئولین انجام)
"operators": [
{
"id": 51,
"text": "10051 - رضا علوی",
"query": {
"id": 51,
"sex": "male",
"first_name": "رضا",
"last_name": "علوی",
"telegram": "reza_acc",
"group": "Accounting",
"avatar": null,
"position": "حسابدار ارشد",
"personnel_id": "10051"
}
}
],
// خروجی دقیق getConnection (در صورتی که تسک به رفرنس متصل باشد)
"connection": [
{
"id": 2050,
"type": "reference",
"title": "تور کیش - هتل داریوش", // خوانده شده از Redis
"financial": {
// آبجکت بازگشتی از TradeController::financial
"total_price": 50000000,
"paid": 20000000,
"remaining": 30000000,
"status": "debtor"
}
}
],
"priority": 1,
"order": 0,
"description": "لطفا مانده حساب این رفرنس با مشتری چک شود.",
"deadline": "2025-02-20",
"status": 1,
"created": "2025-02-18 10:30:00"
}
]
}
POST /api/v2/tasks/task/operation
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /api/v2/tasks/task/operation | OfficialController@operationTask | authWithJwt |
شرح عملکرد (Functionality)
این متد یک رابط چندمنظوره برای مدیریت چرخه حیات تسکها است که بر اساس پارامتر action عملیاتهای مختلفی را روی دیتابیس اجرا میکند:
- store (ایجاد تسک):
- شناسایی اپراتورهای مسئول و ارسال نوتیفیکیشن تلگرام جهت اطلاعرسانی.
- ذخیره اطلاعات اصلی تسک در جدول
tasks_items. - در صورت وجود چکلیست، آیتمهای آن به صورت مجزا در جدول
tasks_item_checklistثبت میشوند.
- store_note (ثبت یادداشت):
- ذخیره یادداشت جدید در جدول
tasks_notesمتصل به تسک. - ارسال اعلان به تمامی افراد درگیر در تسک (شامل سازنده و مسئولین) به جز شخصی که یادداشت را ثبت کرده است.
- ذخیره یادداشت جدید در جدول
- update (ویرایش تسک):
- بروزرسانی فیلدهای اطلاعاتی تسک و ارسال اعلان تغییرات به کاربران مرتبط.
- مدیریت چکلیست:
- آیتمهای جدید (بدون شناسه) به لیست اضافه میشوند.
- آیتمهای موجود (با شناسه)، وضعیت انجام کار (
done_atوdone_by) آنها بروزرسانی میشود.
- حذف آیتمهای چکلیست مشخص شده در آرایه
deleted_checklist.
- update_order (تغییر اولویت نمایش):
- دریافت لیستی از تسکها و بروزرسانی ترتیب (Order) آنها در دیتابیس جهت نمایش مرتبسازی شده.
- update_status (تغییر وضعیت):
- تغییر وضعیت تسک (به عنوان مثال: انجام شده/در حال انجام) و اطلاعرسانی به ذینفعان.
- بازگرداندن تعداد بهروز شدهی تسکهای فعال کاربر جهت بروزرسانی UI.
- delete (حذف):
- حذف فیزیکی رکورد تسک و تمامی یادداشتهای وابسته به آن از سیستم.
پارامترهای ورودی (Body Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| action | String | بله | تعیین نوع عملیات: store, update, delete, store_note, update_order, update_status |
| id | Integer | شرطی | شناسه تسک (در متدهای ویرایشی و حذفی الزامی است). |
| data | Array | بله | حاوی جزئیات عملیات (طبق ساختار زیر). |
ساختار آبجکت data:
- برای ایجاد و ویرایش (store/update):
{ "category": int, "title": string, "priority": int, "description": string, "deadline": string (nullable), "operators": [int, int], // لیست ID مسئولین "checklist": [ { "id": int (optional), "content": string, "done": boolean } ] } - برای یادداشت (store_note):
{ "note": "متن یادداشت" } - برای وضعیت (update_status):
{ "status": int }
نمونه خروجی (Response)
عملیات موفق (Success):
{
"status": true,
"time": 1732048123,
"tasks": 12 // (فقط در update_status موجود است)
}
بروز خطا (Error):
{
"status": false,
"time": 1732048123,
"message": "Error description...",
"trace": [...]
}
GET /api/v2/tasks/task/details
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/tasks/task/details | OfficialController@getTasksDetails |
شرح عملکرد (Functionality)
این متد جزئیات تکمیلی یک تسک مشخص را که شامل یادداشتها (Notes) و چکلیستها (Checklists) است، بازیابی میکند. منطق عملکرد به شرح زیر است:
- یادداشتها (Notes):
- اطلاعات از جدول
tasks_notesبر اساس شناسه تسک دریافت میشود. - اطلاعات کاربر نویسنده یادداشت (`user`) با فراخوانی
StaticController::getOperatorsهیدراته میشود. از آنجا که ایندکس[0]انتخاب شده، خروجی کاربر یک آبجکت است.
- اطلاعات از جدول
- چکلیست (Checklist):
- آیتمها از جدول
tasks_item_checklistواکشی میشوند. - وضعیت انجام کار (`done`) به صورت Boolean محاسبه میشود.
- اگر آیتم انجام شده باشد (`done_at` نال نباشد)، اطلاعات کاربری که آن را انجام داده (`done_by`) نیز به صورت یک آبجکت کامل کاربر برگردانده میشود. در غیر این صورت، مقدار
falseبرمیگردد.
- آیتمها از جدول
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| id | Integer | بله | شناسه تسک (Task ID) که جزئیات آن مورد نیاز است. |
نمونه خروجی (Response)
خروجی شامل آبجکت payload است که دو بخش اصلی notes و checklist را در بر میگیرد.
{
"payload": {
"notes": [
{
"user": {
"id": 10,
"text": "1001 - نام کاربر",
"query": { ... } // جزئیات کامل کاربر
},
"note": "این یک یادداشت نمونه برای تسک است.",
"created": "2024-11-25T10:00:00.000000Z"
}
],
"checklist": [
{
"id": 55,
"operator": { // کاربری که آیتم چکلیست را ایجاد کرده
"id": 10,
"text": "1001 - ایجاد کننده",
"query": { ... }
},
"content": "بررسی مستندات فنی",
"done": true,
"done_by": { // کاربری که آیتم را تیک زده (انجام داده)
"id": 12,
"text": "1002 - انجام دهنده",
"query": { ... }
},
"done_at": "2024-11-26 14:30:00",
"created_at": "2024-11-25T09:00:00.000000Z"
},
{
"id": 56,
"operator": { ... },
"content": "تست نهایی روی سرور استیج",
"done": false,
"done_by": false, // چون انجام نشده
"done_at": false, // چون انجام نشده
"created_at": "2024-11-25T09:05:00.000000Z"
}
]
},
"meta": {
"timestamp": 1732616000
}
}
GET /api/v2/support/departments/list
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/support/departments/list | OfficialController@listDepartmentsSupport |
شرح عملکرد (Functionality)
این متد لیست دپارتمانهای پشتیبانی فعال را جهت نمایش به کاربر برمیگرداند. نکات فنی و منطقی این روت عبارتند از:
- فیلتر و مرتبسازی: تنها دپارتمانهایی که وضعیت فعال دارند (
status = 1) از جدولsupport_departmentsانتخاب شده و بر اساس فیلدorderمرتب میشوند. - جایگزینی پویا نام برند (Brand Injection):
- در هنگام پردازش لیست، سیستم بررسی میکند که آیا کلمه "آژانس" در عنوان فارسی (
title_fa) وجود دارد یا خیر. - اگر وجود داشته باشد، نام برند آژانس جاری (
brand_fa) از جدولoffices(بر اساسbranchموجود در درخواست) استخراج شده و جایگزین کلمه "آژانس" میشود. - مثال: اگر عنوان دپارتمان "امور مالی آژانس" باشد و نام برند "تراولسیتی" باشد، خروجی به "امور مالی تراولسیتی" تغییر میکند.
- در هنگام پردازش لیست، سیستم بررسی میکند که آیا کلمه "آژانس" در عنوان فارسی (
- ساختار چندزبانه: عنوانها در قالب یک آبجکت شامل کلیدهای
faوenبازگردانده میشوند.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| branch | Integer | بله | شناسه شعبه فعال (جهت یافتن نام برند برای جایگزینی در عنوانها). معمولاً از طریق میدلور تزریق میشود. |
نمونه خروجی (Response)
{
"status": true,
"time": 1732617000,
"data": [
{
"id": 1,
"allowed_response": 1, // سطح دسترسی پاسخدهی (مثلا: 1 برای همه)
"description": "سوالات مربوط به پروازهای داخلی و خارجی",
"title": {
"fa": "فروش پرواز", // بدون تغییر چون کلمه 'آژانس' ندارد
"en": "Flight Sales"
}
},
{
"id": 2,
"allowed_response": 0,
"description": "پیگیری موارد مالی و حسابداری",
"title": {
"fa": "امور مالی آسمان سیر", // 'آژانس' با 'آسمان سیر' جایگزین شده است
"en": "Finance Department"
}
}
]
}
GET /api/v2/support/department/members
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/support/department/members | OfficialController@listDepartmentMembersSupport |
شرح عملکرد (Functionality)
این متد لیست اعضای (پاسخدهندگان) یک دپارتمان پشتیبانی خاص را برمیگرداند. فرآیند پردازش به شرح زیر است:
- بررسی اعتبار: ابتدا وجود دپارتمان بر اساس شناسه ورودی بررسی میشود. اگر دپارتمان یافت نشود یا لیست اعضای آن (`members`) خالی باشد، یک خطای JSON با کد
1000بازگردانده میشود. - استخراج اعضا: فیلد `members` در دیتابیس به صورت یک آرایه JSON ذخیره شده است. سیستم این آرایه را دیکد کرده و روی شناسههای پرسنلی (IDs) حلقه میزند.
- هیدراته کردن اطلاعات کاربر: برای هر شناسه عددی، متد
StaticController::getOperatorsفراخوانی میشود. از آنجا که کد از ایندکس[0]استفاده میکند، هر آیتم در آرایه خروجی یک آبجکت کامل کاربر است (نه آرایه تو در تو).
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| department | Integer | بله | شناسه دپارتمان مورد نظر که لیست اعضای آن درخواست شده است. |
نمونه خروجی (Response)
خروجی شامل آرایهای از آبجکتهای کاربر در فیلد items است.
{
"items": [
{
"id": 15,
"text": "1005 - نام و نام خانوادگی",
"query": {
"id": 15,
"personnel_id": 1005,
"first_name": "نام",
"last_name": "نام خانوادگی",
"branch": "[1]",
"status": 1,
// سایر فیلدهای جدول کاربران
...
}
},
{
"id": 22,
"text": "1008 - کاربر دوم",
"query": { ... }
}
],
"meta": {
"timestamp": 1732618000
}
}
نمونه خطا (در صورت نبود دپارتمان یا عضو):
{
"error": {
"code": 1000,
"message": "در این دپارتمان عضوی وجود ندارد و یا اینکه دپارتمان اشتباه ارسال شده است."
},
"meta": {
"timestamp": 1732618005
}
}
GET /api/v2/support/department/questions/list
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/support/department/questions/list | OfficialController@listQuestionsDepartmentSupport |
شرح عملکرد (Functionality)
این متد لیست سوالات پیشفرض (یا موضوعات تیکت) مربوط به یک دپارتمان خاص را برمیگرداند. کوئری دیتابیس شامل فیلترهای زیر است:
- فیلتر دپارتمان: رکوردهایی که فیلد
departmentآنها برابر با ورودی کاربر باشد. - فیلتر سطح (رابطه): تنها سوالات سطح اول (Root) انتخاب میشوند (شرط
whereNull('relationship')). این یعنی زیرمجموعهها یا پاسخها در این لیست نمیآیند. - فیلتر وضعیت: تنها رکوردهای فعال (
status = 1) انتخاب میشوند.
نکته: خروجی مستقیماً تمام ستونهای جدول support_questions را بدون تغییر ساختار برمیگرداند.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| department | Integer | بله | شناسه دپارتمان مورد نظر برای دریافت لیست سوالات آن. |
نمونه خروجی (Response)
{
"status": true,
"time": 1732619000,
"data": [
{
"id": 101,
"department": 5,
"title": "مشکل در صدور بلیط",
"relationship": null, // چون whereNull اعمال شده
"status": 1,
"created_at": "2024-01-01 12:00:00",
"updated_at": "2024-01-01 12:00:00"
},
{
"id": 102,
"department": 5,
"title": "درخواست استرداد وجه",
"relationship": null,
"status": 1,
"created_at": "2024-01-02 10:30:00",
"updated_at": "2024-01-02 10:30:00"
}
]
}
GET /api/v2/support/tickets
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/support/tickets | OfficialController@indexTicketsSupport |
شرح عملکرد (Functionality)
این متد لیست تیکتهای پشتیبانی را بر اساس نقش کاربر (درخواستکننده یا پاسخدهنده) مدیریت میکند. نکات کلیدی منطق آن عبارتند از:
- دامنه نمایش (Scope): بر اساس فیلتر
ticket_scope، تیکتها در سه حالت قابل دریافت هستند:sent: تیکتهایی که کاربر جاری ایجاد کرده است (requester = operatorId). نوع این تیکتها در خروجیsendدرج میشود.inbox: تیکتهایی که به دپارتمانی ارجاع شده که کاربر جاری عضو آن است (بررسی عضویت باJSON_CONTAINSروی ستونmembersدپارتمان). نوع این تیکتها در خروجیreceiveدرج میشود.all: ادغام هر دو حالت بالا.
- شمارنده پیامهای خوانده نشده (Unread Counter): برای هر تیکت، تعداد یادداشتهایی که خوانده نشدهاند (`read_by` نال است) و توسط کاربر جاری نوشته نشدهاند، شمرده میشود. شرط اضافی
NOT JSON_CONTAINSبررسی میکند که نویسنده پیام جزو اعضای دپارتمان نباشد (برای تشخیص سمت مقابل مکالمه). - آمار کلی (Waiting): در بخش
payload، تعداد کل تیکتهای باز (status = 1) که به نحوی به کاربر مربوط میشوند، برگردانده میشود. - اطلاعات کاربر: فیلدهای
requesterوoperatorبا استفاده از متدStaticController::getOperatorsهیدراته شده و چون از ایندکس[0]استفاده شده، به صورت آبجکت برگردانده میشوند.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| search[length] | Integer | خیر | تعداد رکورد در هر صفحه (پیشفرض: 20). |
| search[current] | Integer | خیر | شماره صفحه جاری (پیشفرض: 1). |
| filters[ticket_scope] | String | خیر | دامنه جستجو: all (پیشفرض)، sent، یا inbox. |
| filters[status] | Integer | خیر | فیلتر بر اساس وضعیت دپارتمان (پیشفرض: 1). |
| filters[score] | Integer | خیر | فیلتر بر اساس امتیاز تیکت. |
| filters[department] | Integer | خیر | فیلتر بر اساس شناسه دپارتمان. |
| filters[operator] | Integer | خیر | فیلتر بر اساس شناسه اپراتور پاسخدهنده (فقط در حالت inbox و all تأثیر دارد). |
نمونه خروجی (Response)
{
"items": [
{
"id": 505,
"type": "send", // تیکت ارسال شده توسط کاربر
"department": {
"id": 3,
"title": {
"fa": "پشتیبانی فنی",
"en": "Technical Support"
}
},
"requester": { // اطلاعات کامل کاربر درخواست دهنده
"id": 10,
"text": "1001 - نام کاربر",
"query": { ... }
},
"operator": false, // هنوز اپراتوری پاسخ نداده
"score": 0,
"status": 1,
"unread": 1, // تعداد پیامهای جدید
"created_at": "2024-11-20 14:00:00"
},
{
"id": 498,
"type": "receive", // تیکت دریافتی (کاربر عضو دپارتمان است)
"department": {
"id": 2,
"title": {
"fa": "امور مالی",
"en": "Finance"
}
},
"requester": {
"id": 55,
"text": "1055 - مشتری نمونه",
"query": { ... }
},
"operator": { // اپراتوری که تیکت را هندل میکند
"id": 10,
"text": "1001 - نام کاربر",
"query": { ... }
},
"score": 5,
"status": 2,
"unread": 0,
"created_at": "2024-11-18 09:30:00"
}
],
"payload": {
"wating": 5 // تعداد کل تیکتهای باز مرتبط
},
"meta": {
"total_records": 25,
"total_pages": 2,
"current_page": 1,
"per_page": 20,
"last_page": 2,
"next_page": 2,
"prev_page": 0,
"from": 1,
"to": 20,
"timestamp": 1732620000
}
}
POST /api/v2/support/ticket/update
Route Info
| Method | Endpoint | Controller |
| POST | /api/v2/support/ticket/update | OfficialController@updateTicketSupport |
شرح عملکرد (Functionality)
این متد برای بروزرسانی اطلاعات مختلف یک تیکت استفاده میشود. ساختار کد به صورت if/else if است، به این معنی که در هر درخواست تنها یکی از عملیات زیر بر اساس اولویت پارامتر ارسالی انجام میشود:
- تغییر وضعیت (Status): اگر پارامتر
statusارسال شود:- وضعیت تیکت آپدیت میشود.
- یک یادداشت خودکار (System Note) متناسب با وضعیت جدید (مثلاً: "مورد شما در حال بررسی میباشد...") توسط اپراتور جاری در تیکت درج میشود.
- اگر تیکت هنوز اپراتوری نداشت (
operator IS NULL)، اپراتور جاری به عنوان مسئول تیکت ثبت میشود.
- ثبت امتیاز (Score): اگر پارامتر
scoreارسال شود، امتیاز کاربر به تیکت ثبت میشود. - ارجاع به دپارتمان (Department): اگر پارامتر
departmentارسال شود، تیکت به دپارتمان دیگری منتقل میشود. - تخصیص کارشناس (Operator): اگر پارامتر
operatorارسال شود:- مسئول تیکت به شناسه ارسالی تغییر میکند.
- یک اعلان (Notification) تلگرامی برای کارشناس انتخاب شده ارسال میشود که حاوی لینک مشاهده تیکت است.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| ticket | Integer | بله | شناسه تیکت مورد نظر برای ویرایش. |
| status | Integer | اختیاری* | وضعیت جدید تیکت (1 تا 5). در صورت ارسال، اولویت با این فیلد است. |
| score | Integer | اختیاری* | امتیاز دهی به تیکت (معمولاً 1 تا 5). |
| department | Integer | اختیاری* | شناسه دپارتمان جدید جهت انتقال تیکت. |
| operator | Integer | اختیاری* | شناسه کارشناس جهت ارجاع دستی تیکت. |
* نکته: در هر درخواست باید فقط یکی از پارامترهای اختیاری ارسال شود تا عملیات مربوطه انجام گیرد.
نمونه خروجی (Response)
{
"status": true,
"time": 1732621500
}
PATCH /api/v2/support/ticket/notice
Route Info
| Method | Endpoint | Controller |
| PATCH | /api/v2/support/ticket/notice | OfficialController@noticeNoteTicketSupport |
شرح عملکرد (Functionality)
این متد برای ارسال دستی اعلان (Notification) به درخواستکننده تیکت (Requester) جهت اطلاعرسانی درباره پاسخ داده شدن به تیکت استفاده میشود. فرآیند به شرح زیر است:
- شناسایی کاربر: ابتدا اطلاعات تماس کاربر درخواستکننده (موبایل، تلگرام و شعبه) از جدول
operatorsبر اساس شناسه تیکت استخراج میشود. - تعیین دامنه لینک: سیستم بررسی میکند که کاربر متعلق به کدام شعبه است. اگر شعبه داشته باشد، دامنه (Domain) اختصاصی آن شعبه از جدول
officesخوانده میشود؛ در غیر این صورت دامنه پیشفرضerp.savosh.comاستفاده میشود. - کانالهای ارسال (بر اساس Type):
- تلگرام: اگر
typeبرابر 1 یا 2 باشد و کاربر شناسه تلگرام داشته باشد، پیام حاوی لینک "مشاهده تیکت" ارسال میشود. - پیامک (SMS): اگر
typeبرابر 2 باشد و کاربر شماره موبایل داشته باشد، پیامک اطلاعرسانی ارسال میشود.
- تلگرام: اگر
- لینک دهی: شناسه تیکت در لینک ارسالی با عدد 10,000 جمع میشود (فرمت نمایش عمومی).
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| ticket | Integer | بله | شناسه تیکت مورد نظر. |
| type | Integer | بله | نوع اطلاعرسانی: 1: فقط تلگرام 2: تلگرام و پیامک (SMS) |
نمونه خروجی (Response)
در صورت موفقیت، کد وضعیت 201 (Created) بدون بدنه (Body) بازگردانده میشود.
HTTP/1.1 201 Created
در صورت بروز خطای سیستمی:
{
"error": {
"code": 0,
"message": "Error description...",
"trace": [...]
},
"meta": {
"timestamp": 1732622000
}
}
GET /api/v2/support/ticket/notes/list
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/support/ticket/notes/list | OfficialController@listNotesTicketSupport |
شرح عملکرد (Functionality)
این متد وظیفه بازگرداندن جزئیات کامل یک تیکت و لیست گفتگوهای (Notes) آن را بر عهده دارد. همچنین به صورت همزمان عملیات "خوانده شدن" پیامها را انجام میدهد. نکات فنی و منطقی آن عبارتند از:
- کنترل دسترسی (Access Control): سیستم بررسی میکند که کاربر جاری اجازه مشاهده تیکت را دارد یا خیر. کاربر مجاز است اگر:
- درخواستکننده (Requester) تیکت باشد.
- اپراتور (Operator) تخصیص داده شده به تیکت باشد.
- عضو لیست
membersدپارتمان مربوطه باشد. - مدیر دپارتمان باشد (که در این صورت نقش مدیریتی او به عضویت عادی تبدیل شده و پردازش میشود).
status: falseو پیام خطا برگردانده میشود. - منطق "خوانده شدن" (Mark as Read):
- پیامهایی که توسط خود کاربر نوشته شدهاند، نادیده گرفته میشوند.
- اگر کاربر جاری عضو دپارتمان باشد (پشتیبان)، سیستم فقط پیامهایی را به عنوان "خوانده شده" علامت میزند که نویسنده آنها عضو دپارتمان نباشد (یعنی پیامهای مشتری).
- فیلدهای
read_byوread_onدر دیتابیس بهروزرسانی میشوند.
- فرمتدهی محتوا (HTML Content):
- اگر نوع پیام
attachmentباشد، بکاند به جای بازگرداندن صرفِ URL، کد HTML تولید میکند.- برای تصاویر: تگ
<img>درون لینک قرار میگیرد. - برای فایلها: نام فایل به همراه یک آیکون SVG درون لینک قرار میگیرد.
- برای تصاویر: تگ
- آدرس فایلها به دامین
storage.service01.irاشاره دارد.
- اگر نوع پیام
- هیدراتاسیون دادهها: اطلاعات کاربران (نویسنده پیام، درخواستکننده، اپراتور) با استفاده از متد
StaticController::getOperatorsبه آبجکت کامل تبدیل میشود.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| ticket | Integer | بله | شناسه تیکت مورد نظر برای دریافت جزئیات و گفتگوها. |
نمونه خروجی (Response)
{
"status": true,
"time": 1732623000,
"data": {
"department": {
"id": 3,
"type": "send", // 'send' if user is requester, else 'receive'
"title": {
"fa": "پشتیبانی فنی",
"en": "Technical Support"
}
},
"requester": { // آبجکت کامل کاربر درخواست دهنده
"id": 10,
"text": "1001 - نام کاربر",
"query": {
"id": 10,
"sex": "male",
"first_name": "Ali",
"last_name": "Rezaei",
"avatar": "...",
"position": "...",
"personnel_id": 1001
}
},
"operator": false, // یا آبجکت کامل اپراتور پاسخگو
"ticket": {
"status": 1,
"score": 0
},
"notes": [
{
"id": 1205,
"type": "text",
"operator": { ... }, // نویسنده پیام
"note": "سلام، لطفا بررسی کنید.", // متن پیام
"read_by": false, // یا آبجکت کاربری که پیام را خوانده
"read_on": false, // تاریخ خوانده شدن
"created_at": "2024-11-26 10:00:00"
},
{
"id": 1206,
"type": "text", // نوع در دیتابیس attachment است اما در خروجی text ست میشود (چون به HTML تبدیل شده)
"operator": { ... },
"note": "
", // خروجی HTML
"read_by": { ... },
"read_on": "2024-11-26 10:05:00",
"created_at": "2024-11-26 10:01:00"
}
]
}
}
نمونه خطا (عدم دسترسی):
{
"status": false,
"time": 1732623000,
"message": "شما اجازه دسترسی به این گفتگو را ندارید"
}
DELETE /api/v2/support/ticket/delete
Route Info
| Method | Endpoint | Controller |
| DELETE | /api/v2/support/ticket/delete | OfficialController@deleteTicketSupport |
شرح عملکرد (Functionality)
این متد وظیفه حذف فیزیکی و کامل یک تیکت و تمامی سوابق گفتگوهای آن را بر عهده دارد. عملیات به صورت غیرقابل بازگشت انجام میشود:
- حذف تیکت (Ticket Deletion):
- ابتدا رکورد اصلی تیکت با استفاده از شناسه
ticketمستقیماً از جدولsupport_ticketsحذف میشود.
- ابتدا رکورد اصلی تیکت با استفاده از شناسه
- پاکسازی پیامها (Notes Cleanup):
- بلافاصله پس از حذف تیکت، تمام پیامها و یادداشتهای موجود در جدول
support_notesکه متعلق به این تیکت هستند، حذف میشوند تا دادهای در دیتابیس باقی نماند.
- بلافاصله پس از حذف تیکت، تمام پیامها و یادداشتهای موجود در جدول
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| ticket | Integer | بله | شناسه تیکتی که باید حذف شود. |
نمونه خروجی (Response)
{
"status": true,
"time": 1732625000
}
GET /api/v2/exams/list
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/exams/list | OfficialController@listExams |
شرح عملکرد (Functionality)
این متد لیست تمامی آزمونهای فعال مرتبط با شعبه جاری را استخراج کرده و آماری از وضعیت هر آزمون ارائه میدهد:
- فیلتر آزمونها: تنها آزمونهایی که فیلد
statusآنها برابر با 1 است و متعلق بهbranchدرخواستکننده هستند، انتخاب میشوند. - محاسبه آمار (Statistics): برای هر آزمون، دو مقدار محاسبه میشود:
requests: تعداد کل سوالات تعریف شده برای آن آزمون (از جدولexam_questions).answers: تعداد کل پاسخنامههای ثبت شده توسط کاربران (از جدولexam_response).
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| branch | Integer | بله | شناسه شعبه (این مقدار معمولاً از توکن احراز هویت استخراج میشود). |
نمونه خروجی (Response)
{
"status": true,
"time": 1732626000,
"data": [
{
"id": 10,
"type": "general",
"title": "آزمون جامع نیمسال اول",
"requests": 25,
"answers": 140
},
{
"id": 12,
"type": "specialized",
"title": "آزمون تخصصی فنی",
"requests": 10,
"answers": 45
}
]
}
PATCH /api/v2/exams/answers/list/follow-up
Route Info
| Method | Endpoint | Controller |
| PATCH | /api/v2/exams/answers/list/follow-up | OfficialController@listExamsAnswersFollowUp |
شرح عملکرد (Functionality)
این متد برای بهروزرسانی وضعیت «پیگیری» (Follow-up) یک رکورد خاص در جدول پاسخنامههای آزمون (exam_response) استفاده میشود. کاربرد اصلی آن احتمالا علامتگذاری پاسخنامهها برای بررسی مجدد یا تغییر وضعیت رسیدگی به آنها توسط مدیریت است.
- بهروزرسانی وضعیت: فیلد
follow_upرکورد مشخص شده باid، به مقدار جدید ارسال شده تغییر میکند.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| id | Integer | بله | شناسه یکتا (Primary Key) رکورد پاسخنامه در جدول exam_response. |
| follow_up | Mixed | بله | مقدار جدید برای وضعیت پیگیری (ممکن است عدد، رشته یا بولی باشد که وضعیت جدید را مشخص میکند). |
نمونه خروجی (Response)
در صورت موفقیت، سرور کد وضعیت 204 No Content را بدون بازگرداندن هیچ دادهای (Body خالی) ارسال میکند.
HTTP/1.1 204 No Content
GET /api/v2/exams/answers/evaluation
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/exams/answers/evaluation | OfficialController@listExamsEvaluation |
شرح عملکرد (Functionality)
این متد وظیفه تحلیل آماری و تجمیع پاسخهای داده شده به یک آزمون خاص را بر عهده دارد. سیستم تمام سوالات فعال آزمون را استخراج کرده و برای هر سوال، پاسخهای کاربران را بر اساس نوع سوال پردازش میکند:
- سوالات تشریحی (Explanatory):
- پاسخهای متنی کاربران عیناً در قالب یک آرایه لیست میشوند تا مدیر بتواند آنها را مطالعه کند.
- سوالات تکگزینهای (Single Choice):
- تعداد دفعات انتخاب هر گزینه محاسبه میشود (هیستوگرام پاسخها).
- میانگین عددی پاسخها (Average) نیز محاسبه میشود (مناسب برای سوالات امتیازدهی یا نظرسنجی عددی).
- سوالات چندگزینهای (Multiple Choice):
- پاسخها که به صورت JSON ذخیره شدهاند (آرایهای از گزینههای انتخاب شده)، باز شده و تعداد انتخاب شدن هر گزینه به صورت جداگانه شمارش میشود.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| exam | Integer | بله | شناسه آزمونی که قصد دریافت تحلیل پاسخهای آن را دارید. |
نمونه خروجی (Response)
{
"status": true,
"time": 1732628000,
"data": [
{
"id": 101,
"type": "single_choice",
"subject": "رضایتمندی",
"title": "از کیفیت دوره چقدر رضایت دارید؟ (1 تا 5)",
"options": ["1", "2", "3", "4", "5"],
"description": null,
"mandatory": 1,
"score": 0,
"order": 1,
"relation": false,
"average": 4,
"counter": 50,
"evaluation": {
"5": 30,
"4": 15,
"3": 5
}
},
{
"id": 102,
"type": "explanatory",
"subject": "نظرات",
"title": "پیشنهاد خود را بنویسید",
"average": 0,
"counter": 12,
"evaluation": [
{"response": "بسیار عالی بود"},
{"response": "زمان کلاس کم بود"}
]
}
]
}
GET /api/v2/salary/annual-obligation
Route Info
| Method | Endpoint | Controller |
| GET | /api/v2/salary/annual-obligation | OfficialController@salaryAnnualObligation |
شرح عملکرد (Functionality)
این متد اطلاعات مربوط به تعهدات مالی و پارامترهای محاسباتی حقوق و دستمزد (مانند پایه حقوق، سقف مالیاتی، حق مسکن و...) را برای یک سال مالی مشخص بازیابی میکند.
- جستجو بر اساس سال: سیستم در جدول
salary_annual_obligationرکوردهایی که فیلدyearآنها با ورودی کاربر مطابقت دارد را جستجو میکند. - بررسی وجود داده:
- اگر اطلاعاتی پیدا شود، لیست رکوردها بازگردانده میشود.
- اگر رکوردی برای سال مورد نظر وجود نداشته باشد، خروجی با
status: falseو پیام "There is no data available" ارسال میشود.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| year | Integer | بله | سال مورد نظر برای دریافت تعهدات مالی (مثلاً 1403). |
نمونه خروجی (Response)
{
"status": true,
"time": 1732629000,
"data": [
{
"id": 5,
"year": 1403,
"min_salary": 70000000,
"housing_allowance": 9000000,
"description": "بخشنامه حقوق سال 1403",
"created_at": "2024-03-20 10:00:00"
}
]
}
GET /api/v2/scheduled/notifications
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /api/v2/scheduled/notifications | OfficialController@listScheduledNotifications | authWithJwt |
تحلیل دقیق عملکرد (Deep Functionality Analysis)
این متد وظیفه استخراج و آمادهسازی لیست اعلانات زمانبندی شده (Scheduled Notifications) را دارد. برخلاف یک کوئری ساده، این تابع عملیات Sanitization (پاکسازی) و Transformation (تغییر فرمت) را روی دادهها انجام میدهد.
مراحل اجرای کد:
- Database Query: اتصال به جدول
scheduled_notificationsو دریافت تمام ستونها.- Scope Filtering: نتایج بر اساس مقدار
branchفیلتر میشوند. این مقدار از آبجکت$requestخوانده میشود (که معمولاً توسط میدلور پس از احراز هویت ست شده است).
- Scope Filtering: نتایج بر اساس مقدار
- Data Transformation Loop (Map): روی تکتک رکوردهای دریافتی یک حلقه اجرا شده و تغییرات زیر اعمال میشود:
- Unsetting Fields: برای امنیت و کاهش حجم Payload، فیلدهای سیستمی زیر حذف میشوند:
created_at(تاریخ ایجاد رکورد)updated_at(تاریخ ویرایش رکورد)branch(شناسه شعبه - چون همه خروجیها متعلق به یک شعبه هستند، تکرار آن غیرضروری است).
- JSON Decoding: فیلدهای زیر که در دیتابیس به صورت رشته (String/Text) ذخیره شدهاند، به آبجکت یا آرایه PHP تبدیل میشوند تا در خروجی JSON نهایی به درستی نمایش داده شوند:
recipientsdevicesmobiles
- Unsetting Fields: برای امنیت و کاهش حجم Payload، فیلدهای سیستمی زیر حذف میشوند:
- Response Formatting: دادههای پردازش شده در قالب استاندارد پاسخ موفقیتآمیز قرار میگیرند.
- Error Handling: کل پروسه داخل بلوک
try-catchقرار دارد. در صورت بروز هرگونه خطا (مثل قطع دیتابیس یا مشکل در Decode کردن JSON)، سیستم کرش نکرده و یک پاسخ استاندارد خطا برمیگرداند.
ورودیها و وابستگیها (Inputs & Dependencies)
| منبع (Source) | پارامتر | نوع | توضیحات فنی |
|---|---|---|---|
| Request / Auth Middleware | branch | Integer/String | این پارامتر به صورت صریح در URL دیده نمیشود اما متد $request->get('branch') برای فیلتر کردن کوئری SQL حیاتی است. این مقدار معمولاً از توکن کاربر استخراج میشود. |
ساختار پاسخها (Response Structures)
✅ پاسخ موفق (Success - 200 OK)
در صورت اجرای موفقیتآمیز، آرایهای شامل وضعیت، زمان سرور و لیست دادهها برگردانده میشود.
{
"status": true,
"time": 1732631000, // Timestamp زمان اجرای درخواست
"data": [
{
"id": 15, // فیلدهای باقیمانده از جدول
"title": "Title from DB", // (فرض بر وجود این فیلد در دیتابیس)
"message": "Message Body", // (فرض بر وجود این فیلد در دیتابیس)
// فیلدهای Decode شده:
"recipients": ["user_group_1", "user_group_2"], // فرمت آرایه
"devices": {"android": true, "ios": false}, // فرمت آبجکت
"mobiles": ["0912xxxxxxx"] // فرمت آرایه
// نکته: created_at, updated_at, branch در اینجا وجود ندارند.
}
]
}
❌ پاسخ خطا (Failure - Exception Captured)
در صورت بروز خطای سیستمی (Exception)، بلوک catch فعال شده و خروجی زیر را تولید میکند:
{
"status": false,
"time": 1732631005,
"message": "HY000 : SQLSTATE[HY000] [2002] Connection refused", // کد خطا + متن خطا
"trace": [ ... ] // آرایه کامل Stack Trace برای دیباگ (فقط در محیط توسعه باید نمایش داده شود)
}
POST /api/v2/scheduled/notifications
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /api/v2/scheduled/notifications | OfficialController@storeScheduledNotifications | authWithJwt |
تحلیل دقیق عملکرد (Deep Functionality Analysis)
این متد برای ذخیرهسازی رکوردها در جدول scheduled_notifications استفاده میشود. برخلاف بسیاری از متدها که از Eloquent Model استفاده میکنند، این متد از DB::table برای درج مستقیم (Raw Insert) استفاده میکند که سربار کمتری دارد.
مراحل اجرای کد:
- Data Serialization (سریالسازی دادهها):
ورودیهای آرایهای یا آبجکت مانندrecipients(گیرندگان)،devices(دستگاهها) وmobiles(شمارههای همراه) قبل از ذخیره شدن در دیتابیس، توسط تابعjson_encodeبه رشته JSON تبدیل میشوند. - Auth Context Injection:
شناسه شعبه (branch) و شناسه اپراتور ثبتکننده (operator->id) مستقیماً از آبجکت$requestاستخراج میشوند. این یعنی این مقادیر توسط میدلورauthWithJwtدر ریکوئست تزریق شدهاند و کاربر نمیتواند آنها را جعل کند. - Database Insert:
دادهها در جدول درج میشوند. نکته مهم این است که هیچگونه Validation (اعتبارسنجی) صریحی در ابتدای تابع دیده نمیشود. فرض بر این است که کلاینت دادهها را کامل ارسال کرده است. اگر پارامترهای اجباری نرسند، در سطح دیتابیس یا هنگام دسترسی به پراپرتیها (مثل$request->recipients) خطا رخ خواهد داد.
پارامترهای ورودی (Input Parameters)
| Parameter | Type | Required | Description |
|---|---|---|---|
| recipients | Array | Yes | آرایهای از شناسههای گیرندگان یا گروههای کاربری (ذخیره به صورت JSON). |
| devices | Object/Array | Yes | تنظیمات دستگاههای هدف (مثلاً {"android": true}). |
| object | String/Mixed | Yes | محتوای اصلی اعلان یا شناسه آبجکت مرتبط. |
| type | String | Yes | نوع اعلان (مثلاً 'system', 'alert', 'news'). |
| period | String/Int | Yes | زمانبندی ارسال (فرمت آن وابسته به لاجیک کرونجاب سیستم است). |
| mobiles | Array | No | لیست شماره موبایلها (اختیاری). |
ساختار پاسخها (Response Structures)
✅ Success (200 OK)
در صورت درج موفقیتآمیز:
{
"status": true,
"time": 1732631200
}
❌ Error (Exception Captured)
در صورت بروز هرگونه خطا (مثلاً خطای دیتابیس یا نبود فیلد اجباری):
{
"status": false,
"time": 1732631205,
"message": "23000 : SQLSTATE[23000]: Integrity constraint violation...",
"trace": [ ... ] // Stack trace کامل خطا
}
PUT /api/v2/scheduled/notifications
Route Info
| Method | Endpoint | Controller | Middleware |
| PUT | /api/v2/scheduled/notifications | OfficialController@updateScheduledNotifications | authWithJwt |
تحلیل دقیق عملکرد (Deep Functionality Analysis)
این متد یک بروزرسانی کامل (Full Update) روی جدول scheduled_notifications انجام میدهد. نکته حیاتی اینجاست که حتی فیلدهای سیستمی مانند branch و operator نیز بازنویسی میشوند.
منطق دقیق کد:
- استخراج وابستگیهای سیستمی (System Dependencies):
متد مقادیرbranchوoperatorرا از آبجکت$requestمیخواند.
⚠️ نکته امنیتی: این مقادیر معمولاً توسط میدلورauthWithJwtتزریق میشوند، اما کد کنترلر کورکورانه آنها را آپدیت میکند ($request->get('operator')->id). این یعنی مالکیت رکورد به "اپراتور و شعبه جاری" تغییر میکند. - تبدیل دادهها (Data Transformation):
فیلدهایrecipientsوdevicesالزاماً باjson_encodeتبدیل میشوند. فیلدmobilesمنطق شرطی دارد: اگر مقدار داشته باشد تبدیل به JSON میشود، در غیر این صورتNULLذخیره میشود. - مدیریت زمان (Timestamping):
چون ازDB::tableاستفاده شده،updated_atخودکار نیست و باCarbon::now()دقیقاً در لحظه آپدیت تنظیم میشود.
پارامترهای پردازش شده (Processed Parameters)
این جدول شامل تمام دادههایی است که در آرایه $update استفاده شدهاند، چه توسط کاربر ارسال شوند و چه توسط سیستم تزریق شوند.
| Key / Variable | Source Type | Required | Logic / Transformation |
|---|---|---|---|
| id | Integer | Yes | در شرط where استفاده میشود. |
| branch | Integer | Yes* | مستقیماً در دیتابیس ذخیره میشود (معمولاً تزریق شده توسط Middleware). |
| operator | Object (User) | Yes* | آبجکت یوزر دریافت شده و ->id آن استخراج و ذخیره میشود. |
| recipients | Array | Yes | توسط json_encode به رشته تبدیل میشود. |
| devices | Object/Array | Yes | توسط json_encode به رشته تبدیل میشود. |
| object | String | Yes | بدون تغییر ذخیره میشود. |
| type | String | Yes | بدون تغییر ذخیره میشود. |
| period | String/Int | Yes | بدون تغییر ذخیره میشود. |
| status | Boolean/Int | Yes | بدون تغییر ذخیره میشود. |
| mobiles | Array/Null | No | اگر وجود داشته باشد json_encode میشود، وگرنه NULL. |
ساختار پاسخها (Response Structures)
✅ Success (200 OK)
{
"status": true,
"time": 1732631500
}
❌ Error (Exception Captured)
{
"status": false,
"time": 1732631505,
"message": "...", // PHP/SQL Error Message
"trace": [ ... ]
}
POST /api/v2/banks/list
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /api/v2/banks/list | AccountingController@banksList | authWithJwt |
تحلیل دقیق عملکرد (Deep Functionality Analysis)
این متد یک کوئری مستقیم روی دیتابیس اجرا میکند:
SELECT * FROM accounting_banks WHERE status = 1
نکات (Fact-Based):
- نوع خروجی: خروجی دقیقاً همان
Collectionبازگردانده شده توسط Query Builder لاراول است (آرایهای از آبجکتهای stdClass). - عدم فیلترینگ: هیچ ستونی پنهان نمیشود (hidden) و هیچ تغییری در نام کلیدها داده نمیشود. تمام ستونهای موجود در جدول دیتابیس به کلاینت ارسال میشوند.
- ستونهای قطعی: تنها ستونی که وجودش در خودِ این تابع قطعی است، ستون
statusاست (چون در شرط where استفاده شده). - ستونهای استنتاجی (از کدهای مجاور): با نگاه به متدهای دیگر همین فایل (مثل
accountIndex)، مشخص میشود که جدولaccounting_banksحداقل دارای ستونهایid،title_faوlogoنیز میباشد، اما این تابع محدود به اینها نیست و همه چیز را برمیگرداند.
ساختار پاسخها (Response Structures)
ساختار خروجی دقیقاً منطبق بر ساختار جدول دیتابیس (Schema) است که در کدهای ارسالی موجود نیست.
✅ Success (200 OK)
[
{
"status": 1,
// سایر ستونهای جدول accounting_banks عیناً در اینجا قرار میگیرند.
// (نام و تعداد فیلدها وابسته به دیتابیس است)
},
...
]
POST /v2/account/bill
Route Info
| Method | Endpoint | Controller |
| POST | /v2/account/bill | AccountingController@accountBill |
شرح عملکرد (Functionality)
این متد وظیفه تولید "صورتحساب" (Statement of Account) را برای یک حساب خاص (گروه، کل، معین یا تفضیلی) بر عهده دارد. این تابع تراکنشهای مالی را از منابع مختلف (اسناد پرداخت، چکها، ارزش افزوده و اسناد افتتاحیه/اختتامیه) جمعآوری کرده، بدهکار/بستانکار بودن را محاسبه نموده و خروجی را جهت نمایش در جداول مالی آماده میکند. نکات فنی و منطقی آن عبارتند از:
- تشخیص سطح حساب (Accounting Titles):
- با استفاده از متد
getAccountingTitles، کد حساب ورودی (id) تحلیل میشود. - طول کد سطح حساب را مشخص میکند: 2 رقم (گروه)، 4 رقم (کل)، 6 رقم (معین) و 9 رقم (تفضیلی).
- وابستگیهای حساب مانند "تفضیلیهای شناور" (Detailed First Preferences) استخراج میشوند تا تراکنشهای مرتبط با آنها نیز در گزارش لحاظ شوند.
- با استفاده از متد
- مدیریت تاریخ و فیلترها:
- اگر بازه تاریخی توسط کلاینت ارسال نشود، سیستم به صورت پیشفرض بازه "3 ماه اخیر" را در نظر میگیرد (مگر برای اپراتورهای خاص با IDهای 34 و 52 که سال 1403 را کامل برمیگرداند).
- تاریخها برای کوئری زدن به دیتابیس به فرمت میلادی (
created_at) و شمسی (برای فیلدهای رشتهای مثلdeadline) تبدیل میشوند.
- منابع دادهای و منطق واکشی (Data Sources): سیستم بر اساس تنظیمات
billدر حساب معین، منابع زیر را پیمایش میکند:- Pays (اسناد دریافت/پرداخت): کوئری اصلی روی جدول
paysبا شروط پیچیده روی تاریخ ایجاد و سررسید (Deadline). - Check Operations (عملیات چک): بررسی وضعیتهای خاص چک (واگذاری، نقد شدن، برگشتی) با کدهای معین خاص (مثل
111202و111203). از متدgetCheckOperationInManualDocumentبرای استخراج جزئیات سند استفاده میشود. - Value Added (ارزش افزوده): محاسبه مالیات بر ارزش افزوده بر اساس "اعلامیهها" (Announcements) و نرخهای ذخیره شده در
ACCOUNTING_VALUE_ADDED. - Marketing: (در کد اشاره شده اما منطق کامل در قطعه کدها نیست).
- Pays (اسناد دریافت/پرداخت): کوئری اصلی روی جدول
- سوابق مالی (Financial Pasts - Opening/Closing):
- سیستم با استفاده از
StaticController::getFinancialPastsاسناد افتتاحیه و اختتامیه سالهای مالی درخواست شده را استخراج میکند. - این اسناد با استفاده از
array_unshiftبه ابتدای لیست تراکنشها اضافه میشوند تا مانده از قبل (Balance) صحیح باشد. - از Redis برای کش کردن مقادیر افتتاحیه/اختتامیه استفاده میشود تا بار روی دیتابیس کاهش یابد.
- سیستم با استفاده از
- محاسبه مانده و تشخیص (Credit/Debit logic):
- متد
StaticController::calculatorCreditDebitبرای هر سطر تعیین میکند که مبلغ باید در ستون بدهکار بنشیند یا بستانکار. این تشخیص بر اساس "ماهیت حساب" (Nature) و نوع تراکنش (Payment/Receive) انجام میشود. - در نهایت جمع کل بدهکار، بستانکار و مانده نهایی محاسبه شده و "تشخیص" حساب (Debtor/Creditor/Neutral) تعیین میشود.
- نتیجه نهایی مانده حساب در Redis با کلید
accounting:account:balance:{code}ذخیره میشود.
- متد
- فرمتدهی خروجی (Formatting):
- جزئیات متنی تراکنش (مثل "بابت فاکتور شماره...") توسط
convertPayDetailsDbToTableتولید میشود. - اگر تراکنش مربوط به یک "رفرنس" (تور/پرواز) باشد، لینک HTML به آن رفرنس تولید میشود.
- جزئیات متنی تراکنش (مثل "بابت فاکتور شماره...") توسط
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| id | Integer/String | بله | کد حساب مورد نظر (میتواند کد گروه، کل، معین یا تفضیلی باشد). |
| type | String | خیر | نوع حساب (اختیاری، معمولا توسط خود کد شناسایی میشود). |
| branch | Integer | بله | شناسه شعبهای که صورتحساب آن درخواست شده است. |
| json | JSON String | بله | آبجکت شامل تنظیمات فیلتر و صفحهبندی: advanced.from: تاریخ شروع (شمسی Y-m-d) advanced.to: تاریخ پایان (شمسی Y-m-d) start: آفست شروع (Pagination) length: طول صفحه |
نمونه خروجی (Response)
{
"Bills": [
{
"serial_id": 15100, // سریال نمایشی سند
"system_serial": 101, // شناسه سیستمی رکورد
"datetime": "1403/01/01 00:00:00",
"credit": 0, // مبلغ بستانکار
"debit": 5000000, // مبلغ بدهکار
"details": {
"title": {
"html": "سند افتتاحیه سال 1403 ...", // توضیحات با فرمت HTML
"text": "سند افتتاحیه سال 1403 ..."
},
"type": {
"subject": "financial_past",
"title": "افتتاحیه"
}
},
"communications": false
},
{
"serial_id": 2050,
"system_serial": 5002,
"datetime": "1403/05/10 12:30:00",
"credit": 1200000,
"debit": 0,
"details": {
"title": {
"html": "سند دریافت وجه نقد | بابت ...",
"text": "سند دریافت وجه نقد | بابت ..."
},
"type": {
"subject": "pay",
"title": "سندمالی"
}
}
}
// ... سایر ردیفها
],
"Total": {
"credit": 1200000,
"debit": 5000000,
"balance": 3800000,
"diagnosis": "Debtor" // وضعیت نهایی: Debtor (بدهکار)، Creditor (بستانکار)، Neutral (تراز)
}
}
POST /v2/account/bill2
Route Info
| Method | Endpoint | Controller |
| POST | /v2/account/bill2 | AccountingController@accountBill2 |
شرح عملکرد دقیق (Deep Logic Analysis)
این متد صورتحساب، برخلاف نسخه ساده قبلی، دارای منطق "تفسیر و تجمیع" است که وابستگی شدیدی به توابع استاتیک کمکی دارد:
- باگ حیاتی (Bug Alert): همچنان تاکید میکنم خط اول تابع حاوی
dd($request->all())است که باید حذف شود. - 1. استانداردسازی زمان (Time Normalization):
ورودیهای تاریخ توسط تابعcheckDatetimeاستاندارد میشوند تا فرمتهای مختلف (با جداکننده یا بدون آن) به یک فرمت واحد Y-m-d تبدیل شوند. این کار برای کوئریهای دقیق روی Redis و دیتابیس حیاتی است. - 2. کشینگ توضیحات (Redis Description Caching):
داخل حلقه پردازش تراکنشها، سیستم ابتدا Redis را چک میکند (Redis::get('accountingpays' . $item->id)).
اگر توضیحات سند قبلاً ساخته نشده باشد، تابع سنگینconvertPayDetailsDbToTableصدا زده میشود و خروجی آن (شامل لینکهای HTML و متن فرمت شده) در Redis ذخیره میشود تا در درخواستهای بعدی سرعت بالا برود. - 3. تجمیع هوشمند کارتخوانها (POS Aggregation Logic):
این مهمترین تفاوت است. اگر نوع پرداختpos(کارتخوان) باشد:- تراکنشها به لیست اصلی
Billsاضافه نمیشوند. - در عوض، در آرایه
$PosTotalبر اساس تاریخ روز جمع زده میشوند. - در انتهای پردازش، یک آیتم واحد برای هر روز ساخته میشود که عنوان "سند مجموع کارتخوانهای..." دارد و ریز تراکنشها داخل فیلد
subsetقرار میگیرند (به نمونه خروجی دقت کنید).
- تراکنشها به لیست اصلی
- 4. محاسبه مانده از قبل (Financial Pasts):
توسط تابعgetFinancialPasts، مانده حساب از ابتدای تاریخ بازه انتخابی محاسبه میشود. این تابع هوشمندانه بررسی میکند که آیا "سند دستی افتتاحیه" وجود دارد یا باید تمام تراکنشهای سالهای قبل را جمع بزند. - 5. کارمزدها (Wage Handling):
اگر تراکنش شامل کارمزد بانکی باشد (wage > 0)، یک ردیف جداگانه در صورتحساب با عنوان "کارمزد نقل و انتقال بانکی" و تایپwageدرج میشود.
ساختار خروجی (Refined Response)
با توجه به فایل جدید، ساختار خروجی شامل فیلد subset برای تراکنشهای تجمیعی است:
{
"Bills": [
{
// نمونه تراکنش معمولی
"serial_id": 15102,
"system_serial": 204,
"datetime": "1403/02/15 10:00:00",
"credit": 0,
"debit": 2500000,
"details": {
"title": {
"html": "سند پرداخت پایا...",
"text": "سند پرداخت پایا..."
},
"type": {
"subject": "pay",
"title": "سندمالی"
}
}
},
{
// نمونه تراکنش تجمیعی (POS) - کشف شده در فایل جدید
"serial_id": "1403021601", // ID ترکیبی از تاریخ
"system_serial": "1403021601",
"datetime": "1403/02/16 00:00:00",
"credit": 50000000, // جمع کل کارتخوانهای آن روز
"debit": 0,
"details": {
"title": {
"html": "سند مجموع کارتخوان های 1403/02/16",
"text": "سند مجموع کارتخوان های 1403/02/16"
},
"type": {
"subject": "pos",
"title": "سند کارتخوان"
}
},
"subset": [ // <--- بخش جدید اضافه شده
{
"id": 5001,
"amount": 20000000,
"description": "تراکنش اول..."
},
{
"id": 5002,
"amount": 30000000,
"description": "تراکنش دوم..."
}
]
}
],
"Total": {
"credit": 50000000,
"debit": 2500000,
"balance": 47500000,
"diagnosis": "Creditor"
}
}
وابستگیهای سیستمی (System Dependencies)
عملکرد صحیح این روت به وجود و صحت توابع زیر در فایلهای Helper وابسته است:
| نام تابع | نقش کلیدی |
|---|---|
| StaticController::getAccountingTitles | تشخیص سطح حساب (کل/معین/تفضیلی) برای تصمیمگیری در مورد نوع کوئری. |
| StaticController::convertPayDetailsDbToTable | تولید HTML لینکدار برای رفرنسها و تشخیص وضعیت چکها. |
| StaticController::int2DateTime | تبدیل اعداد دیتابیسی (مثل 14030215) به رشته نمایشی (1403/02/15). |
POST /v2/currencies/list
Route Info
| Method | Endpoint | Controller |
| POST | /v2/currencies/list | AccountingController@currencies |
شرح عملکرد (Functionality)
این متد وظیفه ارائه لیست ارزهای موجود در سیستم را دارد و دادههای استاتیک دیتابیس را با دادههای لحظهای (نرخ بازار) ترکیب میکند.
- فیلترینگ اولیه (Database Query):
- تنها ارزهایی که وضعیت فعال دارند (
status = 1) انتخاب میشوند. - اگر پارامتر
selectedارسال شود، فقط ارزهایی که تیک "انتخاب شده" (Select) دارند برگردانده میشوند (مخصوص نمایشهای خلاصه).
- تنها ارزهایی که وضعیت فعال دارند (
- ادغام با نرخ بازار (Redis Integration):
- سیستم کل نرخهای بازار را از کلید ردیس
application:market_rateدریافت میکند. - بر اساس فیلد
icon(که به عنوان کد ارز مثل USD, EUR تفسیر میشود)، نرخ لحظهای پیدا میشود. - نکته محاسباتی: نرخ دریافت شده از ردیس در عدد 10 ضرب شده و گرد میشود (احتمالاً جهت تبدیل واحد پولی یا استانداردسازی نمایش).
- سیستم کل نرخهای بازار را از کلید ردیس
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| selected | Boolean / Integer | خیر | اگر ارسال شود (مثلاً 1)، فقط ارزهای منتخب (Selected) را برمیگرداند. در غیر این صورت همه ارزهای فعال نمایش داده میشوند. |
نمونه خروجی (Response)
{
"status": true,
"time": 1715765000, // Unix Timestamp
"data": [ // (فقط یک مثال جهت فهم بهتر میباشد)
{
"id": 1,
"title": "دلار آمریکا",
"icon": "USD",
"symbol": "$",
"select": 1,
"status": 1,
"rate": {
"value": 580000, // (نرخ ردیس * 10)
"datetime": "2024-05-15 10:30:00" // زمان آخرین آپدیت نرخ
}
},
{
"id": 2,
"title": "یورو",
"icon": "EUR",
"symbol": "€",
"select": 0,
"status": 1,
"rate": {
"value": 620000,
"datetime": "2024-05-15 10:30:00"
}
}
]
}
POST /v2/accounts/list
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounts/list | AccountingController@accountsList |
شرح عملکرد (Functionality)
این متد لیست حسابهای بانکی/مالی فعال مربوط به شعبه کاربر جاری را برمیگرداند و اطلاعات حساب را با اطلاعات بانک عامل ادغام میکند.
- فیلترینگ دادهها:
- فقط حسابهای فعال (
status = 1). - فقط حسابهای متعلق به شعبه جاری کاربر (
branch = request->branch).
- فقط حسابهای فعال (
- الحاق اطلاعات بانک (Left Join):
جدول حسابها (accounting_accounts) با جدول بانکها (accounting_banks) الحاق میشود تا نام فارسی بانک و لوگوی آن دریافت شود.
نکته: فیلدtitleدر خروجی، در واقع همانaccounting_banks.title_faاست (مگر اینکه خود جدول اکانت هم فیلد تایتل داشته باشد که باعث همپوشانی میشود).
ساختار خروجی (Response)
توجه: برخلاف سایر متدها، این متد مستقیماً یک آرایه (Array) برمیگرداند و فاقد کلیدهای استاندارد status یا data است.
[
{
"id": 105,
"branch": 2,
"bank": 12, // شناسه بانک در جدول accounting_banks
"account_number": "123-456-789",
"sheba": "IR000000000000000000",
"status": 1,
"title": "بانک ملت", // برگرفته از title_fa جدول بانکها
"logo": "mellat.png", // برگرفته از جدول بانکها
"balance": 5000000, // سایر فیلدهای جدول اکانت...
"pos_device": 1
},
{
"id": 106,
"branch": 2,
"bank": 4,
"title": "بانک پاسارگاد",
"logo": "pasargad.png",
// ...
}
]
GET /v2/accounting/account
Route Info
| Method | Endpoint | Controller |
| GET | /v2/accounting/account | AccountingController@accountIndex |
شرح عملکرد (Functionality)
این متد جزئیات کامل یک حساب تفضیلی/بانکی خاص را برمیگرداند. تفاوت اصلی این متد با لیستگیری، در نرمالسازی (Normalize) دادهها و دستهبندی اطلاعات در آبجکتهای تو در تو است.
- دریافت اطلاعات (Join Query):
اطلاعات از جدولaccounting_accountsدریافت شده و با جدولaccounting_banksجوین میشود تا نام و لوگوی بانک رزرو شود. - ترجمه وضعیتها (Status Mapping):
- نوع حساب (type): اگر مقدار دیتابیس
2باشد، به عنوانcash(صندوق) و در غیر این صورتbankخروجی داده میشود. - واحد پولی (currency): اگر
0باشد،rialsو در غیر این صورتcurrency(ارزی) در نظر گرفته میشود.
- نوع حساب (type): اگر مقدار دیتابیس
- ساختار درختی (Nested Objects):
اطلاعات مربوط به درگاه پرداخت (gateway)، دستگاه پوز (pos)، بانک (bank) و شعبه (branch) در آبجکتهای مجزا دستهبندی میشوند تا پارس کردن آن در فرانتاند سادهتر باشد.
پارامترهای ورودی (Input Parameters)
| نام پارامتر | نوع | الزامی؟ | توضیحات |
|---|---|---|---|
| id | Integer | بله | شناسه یکتای حساب مورد نظر در جدول accounting_accounts. |
نمونه خروجی موفق (Success Response)
{
"status": true,
"time": 1715768000,
"data": {
"id": 15,
"bank": {
"id": 3,
"title": "بانک ملی ایران",
"logo": "melli.png"
},
"account_number": "010555666777", // مپ شده از فیلد number
"card": "6037991122334455",
"sheba": "IR55017000000010555666777",
"check": 1, // وضعیت دستهچک (دارد/ندارد)
"type": "bank", // یا "cash"
"currency": "rials", // یا "currency"
"branch": {
"code": "1245",
"title": "شعبه مرکزی"
},
"pos": {
"code": "998877", // سریال دستگاه پوز
"status": true // آیا متصل است؟
},
"gateway": {
"data": "merchant_id_example", // اطلاعات اتصال درگاه
"status": true
},
"status": 1,
"created_at": "2023-01-01 12:00:00"
}
}
نمونه خروجی خطا (Error Response)
در صورت بروز هرگونه خطا (مانند پیدا نشدن رکورد یا خطای دیتابیس):
{
"status": false,
"time": 1715768005,
"code": 0, // کد خطا
"message": "Attempt to read property \"id\" on null", // متن خطا
"trace": [...] // جزئیات فنی برای دیباگ
}
POST /v2/accounting/account
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/account | AccountingController@accountStore |
شرح عملکرد (Functionality)
این متد وظیفه ثبت یک حساب بانکی یا صندوق جدید در سیستم را بر عهده دارد. برخلاف متد نمایش، این متد دادههای دریافت شده از کلاینت را به فرمت عددی و دیتابیسی تبدیل (Reverse Mapping) میکند.
- تولید سریال (Serial Generation):
شناسه سریال حساب به صورت خودکار توسط تابع کمکیStaticController::getSerialIdبر اساس نوع موجودیت ("account") و شعبه مربوطه تولید میشود. - تبدیل دادهها (Data Mapping):
- نوع حساب: ورودی
cashبه2و سایر مقادیر به1تبدیل میشوند. - ارز: ورودی
rialsبه0و سایر مقادیر به1تبدیل میشوند. - وضعیتها: فیلدهای بولین (مانند
check,pos,online) در صورت وجود یا true بودن به1و در غیر این صورت بهnullتبدیل میشوند.
- نوع حساب: ورودی
- ذخیرهسازی (Database Insert):
دادهها در جدولaccounting_accountsدرج میشوند. تاریخ ایجاد و ویرایش نیز با زمان فعلی سرور تنظیم میشود.
پارامترهای ورودی (JSON Body)
ساختار جیسون ارسالی باید به شکل زیر باشد:
{
"branch": 2, // شناسه شعبه
"bank": 12, // شناسه بانک از لیست بانکها
"account_number": "123456",// شماره حساب
"card": "6037...", // شماره کارت (اختیاری)
"sheba": "IR00...", // شماره شبا (اختیاری)
"type": "bank", // نوع: "bank" یا "cash"
"currency": "rials", // ارز: "rials" یا "currency"
"check": true, // آیا دسته چک دارد؟
"status": 1, // وضعیت فعال/غیرفعال
"branch": { // آبجکت اطلاعات شعبه
"code": "101",
"title": "مرکزی"
},
"pos": { // تنظیمات پوز
"status": true,
"code": "990011" // سریال پوز
},
"gateway": { // تنظیمات درگاه آنلاین
"status": false,
"data": null
}
}
خروجی (Response)
موفقیت (Success):
{
"status": true,
"time": 1715772000
}
خطا (Error):
در صورت بروز خطای سیستمی (مثلاً دیتابیس یا فرمت داده):
{
"status": false,
"time": 1715772005,
"code": 0,
"message": "SQLSTATE[23000]: Integrity constraint violation...",
"trace": [...]
}
PUT /v2/accounting/account
Route Info
| Method | Endpoint | Controller |
| PUT | /v2/accounting/account | AccountingController@accountUpdate |
شرح عملکرد (Functionality)
این متد اطلاعات یک حساب موجود را به طور کامل بازنویسی (Full Update) میکند. شناسه حساب از طریق بدنه درخواست (Body) دریافت میشود.
- هدفگیری رکورد: عملیات آپدیت بر اساس فیلد
idارسال شده در JSON انجام میشود. - بازنویسی کامل: تمام فیلدهای قابل ویرایش (اطلاعات بانکی، نوع حساب، وضعیتها و تنظیمات درگاه/پوز) با مقادیر جدید جایگزین میشوند.
- بهروزرسانی زمان: فیلد
updated_atبه زمان لحظهای سرور تغییر میکند. - نکته مهم: فیلدهای سیستمی و ثابت مانند
serial(شماره سریال سیستمی) وcreated_atوbranch(شناسه اصلی شعبه) در این متد تغییر نمیکنند.
پارامترهای ورودی (JSON Body)
هشدار: تمام فیلدهای زیر الزامی هستند. حتی اگر تغییری نکردهاند باید ارسال شوند، در غیر این صورت ممکن است با خطای سیستمی مواجه شوید.
{
"id": 15, // شناسه حساب (الزامی برای پیدا کردن رکورد)
"branch": { // آبجکت شعبه (الزامی)
"code": "101",
"title": "شعبه مرکزی"
},
"bank": 12,
"account_number": "987654321",
"card": "6037...",
"sheba": "IR99...",
"type": "bank", // یا "cash"
"currency": "rials", // یا "currency"
"check": true,
"status": 1,
"pos": {
"status": true,
"code": "NEW_POS_SERIAL"
},
"gateway": {
"status": false,
"data": null
}
}
خروجی (Response)
موفقیت (Success):
{
"status": true,
"time": 1715775000
}
نکته امنیتی/منطقی:
اگر id ارسال شده وجود نداشته باشد، سیستم همچنان status: true برمیگرداند اما عملاً هیچ تغییری در دیتابیس رخ نداده است (Silent Failure).
خطا (Error):
در صورت ارسال ناقص دادهها (مثلاً حذف آبجکت branch):
{
"status": false,
"time": 1715775005,
"code": 0,
"message": "Trying to access array offset on value of type null",
"trace": [...]
}
DELETE /v2/accounting/account
Route Info
| Method | Endpoint | Controller |
| DELETE | /v2/accounting/account | AccountingController@accountDelete |
شرح عملکرد (Functionality)
این متد وظیفه حذف فیزیکی (Hard Delete) یک حساب از جدول accounting_accounts را دارد.
- نحوه حذف: رکورد منطبق با
idارسالی مستقیماً از دیتابیس پاک میشود. - هشدار مهم: این عملیات غیرقابل بازگشت است. هیچ بررسیای مبنی بر اینکه آیا این حساب دارای تراکنش مالی، چک پاس نشده یا اسناد حسابداری است، در سطح کد انجام نمیشود. (وابسته به تنظیمات دیتابیس).
پارامترهای ورودی (JSON Body)
با وجود اینکه متد از نوع DELETE است، پارامتر id در بدنه درخواست (Body) انتظار میرود:
{
"id": 15 // شناسه حسابی که باید حذف شود
}
خروجی (Response)
موفقیت (Success):
{
"status": true,
"time": 1715779000
}
خطا (Error):
ممکن است در صورت وجود وابستگیهای دیتابیسی (Foreign Keys) خطای Integrity Violation رخ دهد:
{
"status": false,
"time": 1715779005,
"code": "23000",
"message": "SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row...",
"trace": [...]
}
GET /v2/wallet/balance
Route Info
| Method | Endpoint | Controller |
| GET | /v2/wallet/balance | AccountingController@balanceWallet |
شرح عملکرد (Functionality)
این متد وظیفه محاسبه تراز کیف پول (Wallet) را بر عهده دارد. بر اساس کدهای فعلی، این متد صرفاً موجودی کیف پول شعبه (ERP) را محاسبه میکند.
- منطق محاسبه: جمع ستونهای
credit(بستانکار) وdebit(بدهکار) از جدولwallet. - فیلترها:
- رکوردهایی که
statusآنها برابر 2 (احتمالا حذف شده/لغو شده) نباشد. - فیلد
operator_typeبرابر با 'erp' باشد (ثابت در کد). - فیلد
branchبرابر با شناسه ارسالی (یا شناسه شعبه توکن جاری) باشد.
- رکوردهایی که
- تشخیص ماهیت (Diagnosis): سیستم به صورت خودکار وضعیت تراز را به یکی از سه حالت
creditor(بستانکار/مثبت)،debtor(بدهکار/منفی) یاneutral(بیحساب/صفر) تعیین میکند.
پارامترهای ورودی (Query String)
?type=office // (بلااستفاده) به دلیل باگ داخلی نادیده گرفته میشود &id=101 // (اختیاری) شناسه شعبه. اگر ارسال نشود از توکن کاربر خوانده میشود
خروجی (Response)
موفقیت (Success):
ساختار خروجی استاندارد شامل payload (داده اصلی) و meta (اطلاعات متا) است.
{
"payload": {
"credit": 15000000, // مجموع ورودیها
"debit": 5000000, // مجموع خروجیها
"balance": 10000000, // مانده (credit - debit)
"diagnosis": "creditor" // وضعیت: creditor | debtor | neutral
},
"meta": {
"timestamp": 1715780000
}
}
خطا (Error):
{
"status": false,
"time": 1715780005,
"message": "Error message here...",
"trace": [...]
}
POST /v2/wallet/check
Route Info
| Method | Endpoint | Controller |
| POST | /v2/wallet/check | AccountingController@checkWallet |
شرح عملکرد (Functionality)
این متد برای بررسی کفایت موجودی (Balance Check) قبل از انجام تراکنش استفاده میشود. برخلاف متد balance که فقط گزارش میدهد، این متد معمولاً یک پاسخ Boolean یا آبجکت وضعیت برمیگرداند که آیا تراکنش با مبلغ مشخص شده مجاز است یا خیر.
- هدف: جلوگیری از ثبت سفارش/تراکنش برای حسابهای فاقد موجودی.
- منطق: پارامترهای نوع حساب، شناسه و مبلغ درخواستی به متد داخلی
getCheckWalletارسال میشود. - نقص امنیتی: در حال حاضر هیچ اعتبارسنجی روی مبلغ (مثلاً عدم ارسال عدد منفی) انجام نمیشود.
پارامترهای ورودی (JSON Body)
تمامی فیلدها الزامی هستند:
{
"type": "office", // نوع کیف پول (office, user, colleague)
"id": 101, // شناسه مالک کیف پول
"amount": 5000000 // مبلغ تراکنش مورد نظر (به ریال)
}
خروجی (Response)
سناریوی ۱: موجودی کافی است
{
"status": true,
"data": true // یا آبجکتی که تایید میکند موجودی کافی است
}
سناریوی ۲: موجودی ناکافی است
نکته: در این حالت هم status کلی پاسخ true است (چون درخواست بررسی با موفقیت انجام شده)، اما data منفی است.
{
"status": true,
"data": false // یا پیامی مبنی بر کمبود موجودی
}
خطا (Error):
از آنجایی که بلوک try-catch وجود ندارد، در صورت بروز خطا در دیتابیس یا ورودیها، ساختار JSON استاندارد بازگردانده نمیشود و ممکن است با خطای سیستمی (Exception Trace) مواجه شوید.
GET /v2/wallet/transactions
Route Info
| Method | Endpoint | Controller |
| GET | /v2/wallet/transactions | AccountingController@transactionsWallet |
شرح عملکرد (Functionality)
این متد برای دریافت لیست تراکنشهای کیف پول استفاده میشود و دادهها را پس از پردازش سنگین (شامل joinهای متعدد، تبدیل دادهها، تعیین subject/object/operator، لینکدهی رزرو و فاکتور، و ساخت ساختار نهایی) برمیگرداند.
- صفحهبندی (Pagination) به صورت دستی با پارامترهای
startوlengthانجام میشود. - اگر پارامتر
branch != 1باشد، فقط تراکنشهای همان شعبه برگشت داده میشوند. - اگر
branch == 1باشد، تمام تراکنشهای کل سیستم برگردانده میشود (نقص امنیتی جدی). - برای هر رکورد بین 5 تا 8 کوئری دیتابیس زده میشود (N+1 Query) که در مقیاس بزرگ بسیار کند است.
پارامترهای ورودی (Query + Body)
Query Params
?branch=101
- branch: اگر مقدار 1 باشد → هیچ فیلتری اعمال نمیشود و کل دادهها خوانده میشوند.
Body Params (paginate)
{
"paginate": {
"start": 0,
"length": 20
}
}
- start: عدد offset
- length: تعداد آیتم در هر صفحه (اگر صفر باشد → خطای تقسیم بر صفر)
خروجی (Response)
موفق (Success)
{
"items": [
{
"id": 10023,
"subject": "خرید از هاب توسط دفتر ایکس",
"object": "دفتر ایکس (1023)",
"credit": 500000,
"debit": 0,
"description": "متن توضیحات | رفرنس ...",
"operator": {
"id": 4,
"text": "1234 - علی رضایی",
"query": { ... }
},
"service": "دفتر سرویس",
"sub_service": "دفتر زیرسرویس",
"details": { ... },
"confirm_at": "2024-06-15 12:11:05",
"confirm_note": null,
"confirm_by": { ... }
}
],
"meta": {
"timestamp": 1715780023,
"table": {
"total": 200,
"per_page": 20,
"current_page": 1,
"last_page": 10,
"from": 1,
"to": 20
}
}
}
خطا (Error)
در صورت بروز Exception (مثلاً موارد زیر):
- نبودن فیلد paginate
- صفر بودن length
- خراب بودن داده Redis
- وجود نداشتن رزرو/فاکتور/اپراتورهای مورد نیاز
- N+1 Query timeout
{
"error": {
"code": 500,
"message": "Exception message...",
"trace": [ ... ]
}
}
`
POST /v2/accounting/connections/store
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/connections/store | AccountingController@storeConnection |
شرح عملکرد (Functionality)
این متد وظیفه ایجاد یک اتصال (Connection) و گروهبندی چندین رکورد مالی مختلف (مثل سندهای پرداختی، اقلام فاکتور، چکها و سوابق مالی) تحت یک شناسه واحد را دارد.
- ابتدا یک رکورد جدید در جدول
connectionsایجاد میکند.- شماره سریال (
serial) با استفاده از تابع کمکیStaticController::getSerialIdتولید میشود (این تابع آخرین سریال شعبه را میخواند و یکی اضافه میکند).
- شماره سریال (
- سپس روی آرایه ورودی
itemsحلقه میزند و بر اساسtypeهر آیتم، جدول مربوطه را آپدیت میکند:- اگر نوع
pay,wage,manual_documentباشد → جدول pays آپدیت میشود. - اگر نوع
fitemباشد → جدول factor_items آپدیت میشود. - اگر نوع
financial_pastباشد → جدول financial_pasts آپدیت میشود. - اگر نوع
checkباشد → جدول check_operations آپدیت میشود.
- اگر نوع
- در رکوردهای هدف، فیلد
relationshipبرابر با ID کانکشن جدید و فیلدupdated_atبرابر با زمان حال تنظیم میشود.
پارامترهای ورودی (JSON Body)
{
"branch": 101,
"type": "merge_doc", // نوع کانکشن
"id": 500, // شناسه آبجکت اصلی (Object ID)
"items": [
{ "id": 101, "type": "pay" },
{ "id": 205, "type": "check" },
{ "id": 12, "type": "fitem" }
]
}
- branch: شناسه شعبه (جهت تولید سریال).
- items: آرایهای از اشیاء که هر کدام شامل
idرکورد هدف وtypeآن هستند.
خروجی (Response)
موفق (Success)
{
"status": true,
"time": 1715780023
}
خطا (Error)
مدیریت خطای خاصی پیادهسازی نشده است. اگر دیتابیس خطا دهد (مثلاً ID اشتباه باشد یا اتصال قطع شود)، خطای استاندارد لاراول (500) باز میگردد.
POST /v2/accounting/connections/update
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/connections/update | AccountingController@updateConnection |
شرح عملکرد (Functionality)
این متد برای ویرایش یک Connection موجود استفاده میشود. نوع عملیات وابسته به دو آرایه است:
items_add: آیتمهایی که باید به Connection اضافه شوندitems_remove: آیتمهایی که باید از Connection جدا شوند
برای هر آیتم، فیلد relationship در جدول مربوطه یا به مقدار connection_id آپدیت میشود یا null میگردد.
نوع هر آیتم، جدول مقصد را تعیین میکند:
pay,wage→ جدولpaysfitem→ جدولfactor_itemsfinancial_past→ جدولfinancial_pastscheck→ جدولcheck_operations
پارامترهای ورودی (JSON Body)
{
"connection_id": 42,
"items_add": [
{ "type": "pay", "id": 1001 },
{ "type": "check", "id": 55 },
{ "type": "fitem", "id": 990 }
],
"items_remove": [
{ "type": "financial_past", "id": 81 },
{ "type": "pay", "id": 402 }
]
}
فیلدهای اجباری:
connection_id(int)items_add(آرایه آیتمها)items_remove(آرایه آیتمها)
خروجی (Response)
موفق (Success)
{
"status": true,
"time": 1718450000
}
خطا (Error)
اینجا هیچ مدیریت خطا، اعتبارسنجی یا کنترل وجود ندارد. اگر:
connection_idاشتباه باشد،- آیتم حذفشونده قبلاً جدا شده باشد،
- آیتم افزودهشونده وجود نداشته باشد،
- نوع (
type) اشتباه ارسال شود،
باز هم متد بدون هیچ هشدار یا Exception، پاسخ موفق بازمیگرداند. (Silent Failure)
POST /v2/accounting/connections/trash
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/connections/trash | AccountingController@trashConnection |
شرح عملکرد (Functionality)
این متد برای حذف نرم (Soft Delete) یک Connection استفاده میشود. در این عملیات:
- رکورد مربوط به اتصال در جدول
connectionsبا مقدارstatus = 2علامتگذاری میشود. - تمام رکوردهایی که قبلاً به این Connection متصل بودهاند، در جداول زیر از آن جدا میشوند:
paysfactor_itemsfinancial_pastscheck_operations
- فیلد
relationshipدر تمام این جداول برابر با مقدارnullقرار میگیرد.
این عملیات هیچ رکوردی را حذف نمیکند و فقط ارتباط آنها با Connection را قطع میکند.
پارامترهای ورودی (JSON Body)
{
"connection_id": 42
}
- connection_id: شناسه اتصال موردنظر برای حذف نرم.
خروجی (Response)
موفق (Success)
{
"status": true,
"time": 1718450000
}
خطاها (Errors)
برای این مسیر مدیریت خطای اختصاصی پیادهسازی نشده است.
- در صورتی که
connection_idوجود نداشته باشد، دیتابیس هیچ ردیفی را آپدیت نمیکند. - در صورت بروز خطای دیتابیس (DB Exception)، پاسخ خطای استاندارد سرور برگردانده میشود.
POST /v2/accounting/connections/trash
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/connections/trash | AccountingController@trashConnection |
شرح عملکرد (Functionality)
این متد برای حذف نرم (Soft Delete) یک Connection استفاده میشود. در این عملیات:
- رکورد مربوط به اتصال در جدول
connectionsبا مقدارstatus = 2علامتگذاری میشود. - تمام رکوردهایی که قبلاً به این Connection متصل بودهاند، در جداول زیر از آن جدا میشوند:
paysfactor_itemsfinancial_pastscheck_operations
- فیلد
relationshipدر تمام این جداول برابر با مقدارnullقرار میگیرد.
این عملیات هیچ رکوردی را حذف نمیکند و فقط ارتباط آنها با Connection را قطع میکند.
پارامترهای ورودی (JSON Body)
{
"connection_id": 42
}
- connection_id: شناسه اتصال موردنظر برای حذف نرم.
خروجی (Response)
موفق (Success)
{
"status": true,
"time": 1718450000
}
خطاها (Errors)
برای این مسیر مدیریت خطای اختصاصی پیادهسازی نشده است.
- در صورتی که
connection_idوجود نداشته باشد، دیتابیس هیچ ردیفی را آپدیت نمیکند. - در صورت بروز خطای دیتابیس (DB Exception)، پاسخ خطای استاندارد سرور برگردانده میشود.
POST /v2/accounting/connections/view
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/connections/view | AccountingController@viewConnection |
شرح عملکرد (Functionality)
این مسیر برای نمایش جزئیات کامل یک Connection استفاده میشود. در این متد، نوع اتصال بررسی میشود و در صورتی که اتصال از نوع leger_account باشد، عملیات زیر انجام میشود:
- فراخوانی تابع داخلی ledgerAccounts() برای استخراج تمامی آیتمهای مرتبط با Connection.
- گردآوری تراکنشها از جداول مختلف شامل:
- pays
- factor_items
- check_operations
- announcements
- financial_pasts (افتتاحیه)
- تولید آرایه یکپارچه شامل رکوردهای مالی، چکها، رفرنسها، اعلانها و اسناد مرتبط.
- مرتبسازی نهایی نتایج بر اساس تاریخ شمسی.
خروجی شامل لیست نهایی اسناد (Bills) است که برای نمایش جزئیات دفاتر حسابداری استفاده میشود.
پارامترهای ورودی (JSON Body)
{
"connection_id": 42
}
- connection_id: شناسه اتصال موردنظر جهت نمایش اسناد.
خروجی (Response)
موفق (Success)
{
"status": true,
"time": 1718450000,
"data": [
{ ... ledger items ... }
]
}
فیلد data شامل آرایهای از تمام آیتمهای دفتر حساب (Ledger) است. هر آیتم شامل فیلدهای:
- serial_id
- serial
- datetime
- credit
- debit
- description (html/text)
- details (documents/title/type)
- relationship
- communications
خطاها (Errors)
این مسیر مدیریت خطای اختصاصی ندارد.
- در صورتی که
connection_idیافت نشود، مقدار$connectionتهی شده و اجرای کد ممکن است خطای سیستمی ایجاد کند. - در صورت بروز خطای پایگاه داده، خطای عمومی سرور برگردانده میشود.
POST /v2/accounting/connections/merge
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/connections/merge | AccountingController@mergeConnection |
شرح عملکرد (Functionality)
این مسیر برای ادغام دو Connection مورد استفاده قرار میگیرد. عملیات به این صورت انجام میشود:
- تمام رکوردهایی که
relationshipآنها برابرconnection_last_idاست، در جداول مختلف بهconnection_current_idمنتقل میشوند. - این عملیات شامل چهار جدول زیر است:
paysfactor_itemsfinancial_pastscheck_operations
- در هر بهروزرسانی، مقدار
updated_atبرابر با تاریخ و زمان جاری (Carbon) ثبت میشود.
در نهایت، مسیر صرفاً تأیید انجام عملیات را بازمیگرداند و اطلاعات اضافی ارسال نمیشود.
پارامترهای ورودی (JSON Body)
{
"connection_last_id": 14,
"connection_current_id": 7
}
- connection_last_id: شناسه اتصال قدیمی که باید ادغام شود.
- connection_current_id: شناسه اتصال جدید که همه رکوردها به آن منتقل میشوند.
خروجی (Response)
موفق (Success)
{
"status": true,
"time": 1718450000
}
خطاها (Errors)
این مسیر مدیریت خطای اختصاصی ندارد.
- در صورت نبودن شناسهها در دیتابیس، عملیات آپدیت بدون خطا اجرا میشود.
- در صورت بروز خطای دیتابیس، پاسخ خطای عمومی سرور بازگردانده خواهد شد.
POST /v2/accounting/connections/list
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/connections/list | AccountingController@listConnection |
شرح عملکرد (Functionality)
این مسیر برای دریافت لیست Connections فعال استفاده میشود. امکان اعمال دو نوع فیلتر ورودی وجود دارد:
- فیلتر بر اساس مقدار
serial(فیلدsearch) - فیلتر بر اساس شناسه
object(فیلدid)
پس از اعمال فیلترها، فقط رکوردهایی با status = 1 بازگردانده میشوند. در مرحله بعد، برای هر Connection یک ساختار خروجی شامل id، شماره سریال محاسبهشده و عنوان تولید میشود.
شماره سریال نهایی با افزودن 1000 به مقدار اصلی تولید میشود.
پارامترهای ورودی (JSON Body)
همه پارامترها اختیاری هستند.
{
"search": "123", // اختیاری - جستجو بر اساس serial
"id": "45" // اختیاری - فیلتر بر اساس object id
}
خروجی (Response)
موفق (Success)
{
"status": true,
"time": 1718450000,
"data": [
{
"id": 12,
"serial": 1123,
"title": "ارتباط 1123"
},
...
]
}
خطاها (Errors)
این مسیر مدیریت خطای اختصاصی ندارد.
- در صورت عدم وجود هیچ Connection، مقدار
dataآرایه خالی خواهد بود. - در صورت بروز خطای دیتابیس، پاسخ خطای عمومی سرور (5xx) برگردانده میشود.
POST /v2/accounting/account/update
Route Info
| Method | Endpoint | Controller |
| POST | /v2/accounting/account/update | AccountingController@updateAccountInTreeView |
شرح عملکرد (Functionality)
این مسیر برای ایجاد (store) یا ویرایش (update) اطلاعات حسابهای درخت حسابداری مورد استفاده قرار میگیرد. در این متد سه نوع ساختار حسابداری پشتیبانی میشود:
- گروه حسابداری (Group)
- کل حسابداری (General)
- معین حسابداری (Moeen)
نوع عملیات از طریق پارامتر action مشخص میشود:
store: ایجاد رکورد جدیدupdate: ویرایش رکورد موجود
ورودیها در کلید data قرار میگیرند و بر اساس نوع حساب (type) عمل درج یا ویرایش روی یکی از جداول زیر انجام میشود:
accounting_groupsaccounting_generalsaccounting_moeens
در صورت بروز خطا، پیام و Trace کامل استثنا بازگردانده میشود.
پارامترهای ورودی (JSON Body)
ساختار کلی ورودی
{
"action": "store | update",
"id": 12, // فقط در حالت update
"data": {
"type": "group | general | moeen",
"code": "112",
"title_fa": "عنوان فارسی",
"title_en": "English Title",
"nature": 1,
"status": 1,
"...": "سایر فیلدهای ویژه نوع حساب"
}
}
فیلدهای تکمیلی برای نوع General
- group
فیلدهای تکمیلی برای نوع Moeen
- general
- nature_check
- currency
- aggregation
- bill (JSON)
- detailed_first (JSON)
- detailed_second (JSON)
- detailed_third (JSON)
- detailed_fourth (JSON)
خروجی موفق (Success Response)
{
"status": true,
"time": 1718450000
}
خروجی خطا (Error Response)
در صورت بروز خطا (مانند خطای دیتابیس)، ساختار زیر بازگردانده میشود:
{
"status": false,
"time": 1718450000,
"code": 5005,
"message": "Exception message...",
"trace": [ ... ]
}
GET /v2/accounting/account/get
Route Info
| Method | Endpoint | Controller |
| GET | /v2/accounting/account/get | AccountingController@getAccountInTreeView |
شرح عملکرد (Functionality)
این مسیر برای دریافت جزئیات یک حساب درخت حسابداری استفاده میشود. با توجه به پارامتر type که میتواند یکی از سه مقدار group، general یا moeen باشد، اطلاعات از جداول مرتبط استخراج شده و ساختار داده متناسب بازگردانده میشود.
اطلاعات بازگشتی شامل:
- کد حساب (code + public_code)
- عنوان فارسی و انگلیسی
- ماهیت (nature)
- وضعیت (status)
- برای سطوح پایینتر، اطلاعات والد (parent)
- برای معین (moeen): تنظیمات اضافی مانند bill، detailed_first و ...
پارامترهای ورودی (Query Params)
/v2/accounting/account/get?type=TYPE&id=ID
- type (الزامی): یکی از مقادیر
group،generalیاmoeen - id (الزامی): شناسه حساب
خروجی موفق (Success Response)
نمونه پاسخ برای نوع Group
{
"status": true,
"time": 1718449999,
"data": {
"id": 3,
"type": "group",
"code": "11",
"public_code": "11",
"title_fa": "دارایی",
"title_en": "Assets",
"nature": 1,
"status": 1
}
}
نمونه پاسخ برای نوع General
{
"status": true,
"time": 1718449999,
"data": {
"parent": {
"type": "group",
"code": "11",
"public_code": "11",
"id": 3,
"title": { "fa": "دارایی", "en": "Assets" }
},
"id": 18,
"type": "general",
"code": "02",
"public_code": "1102",
"title_fa": "موجودی کالا",
"title_en": "Inventory",
"nature": 1,
"status": 1
}
}
نمونه پاسخ برای نوع Moeen
{
"status": true,
"time": 1718449999,
"data": {
"parent": {
"type": "general",
"code": "02",
"public_code": "1102",
"id": 18,
"title": { "fa": "موجودی کالا", "en": "Inventory" }
},
"id": 51,
"type": "moeen",
"code": "005",
"public_code": "1102005",
"title_fa": "موجودی انبار مرکزی",
"title_en": "Main Warehouse",
"nature": 1,
"nature_check": 1,
"currency": 1,
"aggregation": 0,
"bill": [ ... ],
"detailed_first": null,
"detailed_second": null,
"detailed_third": null,
"detailed_fourth": null,
"status": 1
}
}
خروجی خطا (Error Response)
در صورت بروز خطا مانند عدم یافتن رکورد یا خطای دیتابیس:
{
"status": false,
"time": 1718449999,
"code": 5005,
"message": "Error message...",
"trace": [ ... ]
}
POST /v2/manual-document/update
Route Info
| Method | Endpoint | Controller |
| POST | /v2/manual-document/update | AccountingController@updateManualDocument |
شرح عملکرد (Functionality)
این مسیر برای ایجاد (add) یا ویرایش (update) یک سند دستی (Manual Document) استفاده میشود. روش کار متد بطور خلاصه:
- ابتدا تاریخ ورودی با تنظیمات «پایان دوره مالی» در
office_configمقایسه میشود. - اگر تاریخ سند در بازه بسته مالی باشد، عملیات متوقف و پیام خطا بازگردانده میشود.
- در حالت
action = add:- محاسبه سال مالی از تاریخ سند
- محاسبه سریال جدید سند دستی با
StaticController::getSerialId() - ثبت رکورد جدید در جدول
manual_documents - ثبت رکوردهای مرتبط در جدول
pays
- در حالت
action = update:- بهروزرسانی رکورد سند دستی
- بهروزرسانی موارد موجود در جدول
paysیا افزودن موارد جدید - حذف رکوردهای پرداختی که در
data_deleteآمدهاند
در هر دو حالت، ثبت لاگ در SystemLog انجام میشود.
پارامترهای ورودی (JSON Body)
ساختار کلی
{
"action": "add | update",
"id": 15, // فقط در حالت update
"date": "1403-05-20",
"description": "سند دستی",
"status": 3,
"currency": 1,
"currency_fee": 0,
"sub_serial": null,
"data": [
{
"preference": "colleague-42",
"moeen": "moeen-18",
"credit": "0",
"debit": "250000",
"description": "توضیحات"
}
],
"data_delete": [ ... ] // فقط در حالت update
}
توضیحات پارامترهای کلیدی
- action: تعیین نوع عملیات. مقدار
addبرای ایجاد سند جدید وupdateبرای ویرایش. - data[]: آرایه خطوط حسابداری. هر خط شامل:
- preference: ترکیب نوع و شناسه؛ مثال:
colleague-42 - moeen: کد معین (با یا بدون پیشوند)
- credit / debit: مبالغ با کاراکترهای فارسی/انگلیسی، که به عدد انگلیسی تبدیل میشوند
- description: توضیحات خط سند
- preference: ترکیب نوع و شناسه؛ مثال:
- data_delete: لیست id پرداختهایی که باید حذف شوند.
خروجی موفق (Success Response)
در حالت ایجاد (add)
{
"status": true,
"time": 1718450000,
"message": "ثبت سند با موفقیت انجام شد. شماره: [3125, 3126]"
}
در حالت ویرایش (update)
{
"status": true,
"time": 1718450000,
"message": "ویرایش سند با موفقیت انجام شد."
}
خروجی خطا (Error Response)
خطا در بازه زمانی بسته مالی
{
"status": false,
"time": 1718450000,
"type": "danger",
"code": 5007,
"message": "ثبت و ویرایش سند دستی در بازه زمانی بسته شده امکان پذیر نمیباشد..."
}
خطاهای دیتابیس (Insert/Update)
{
"status": false,
"time": 1718450000,
"code": 5006,
"message": "SQL error message...",
"trace": [ ... ]
}
DELETE /v2/manual-document
Route Info
| Method | Endpoint | Controller |
| DELETE | /v2/manual-document | AccountingController@trashManualDocument |
شرح عملکرد (Functionality)
این مسیر جهت حذف نرم (Soft Delete) یک سند دستی (Manual Document) استفاده میشود. فرآیند عملکرد به صورت زیر است:
- ابتدا تنظیم «پایان دوره مالی بسته» از جدول
office_configخوانده میشود. - تاریخ سند موردنظر از جدول
manual_documentsگرفته شده و با تاریخ بسته مالی مقایسه میشود. - اگر تاریخ سند داخل بازه بسته مالی باشد، حذف انجام نمیشود و پیام خطا باز میگردد.
- در صورت معتبر بودن تاریخ و شناسه:
- فیلد
status=5روی رکورد سند و پرداختهای مرتبط در جدولpaysاعمال میشود. - زمان ویرایش با
Carbon::now()بهروزرسانی میشود. - لاگ عملیات از طریق
SystemLogثبت و وارد صفsnailJobمیشود.
- فیلد
- در نهایت پاسخ HTTP با وضعیت
204 No Contentبازگردانده میشود.
ورودیها (Request Inputs)
Body Parameters (JSON/Form):
{
"id": 1542
}
- id: شناسه سند دستی که باید حذف شود (الزامی)
- branch: شناسه شعبه جاری (به صورت خودکار توسط سیستم/توکن ارسال میشود)
- operator: اطلاعات اپراتور لاگین شده (از JWT)
خروجی موفق (Success Response)
در صورت موفقیت عملیات، پاسخ بدون بدنه با کد 204 بازگردانده میشود:
HTTP 204 No Content
خطاها (Error Responses)
۱) سند داخل دوره مالی بسته شده باشد
{
"status": false,
"time": 1718450000,
"type": "danger",
"code": 5007,
"message": "حذف سند دستی در بازه زمانی بسته شده امکان پذیر نمیباشد. حساب ها تا تاریخ YYYY/MM بسته شده است."
}
۲) عدم ارسال شناسه معتبر
{
"error": {
"code": 1000,
"message": "شناسه سند ارسالی صحیح نمی باشد"
},
"meta": {
"timestamp": 1718450000
}
}
نکات داخلی (Internal Logic Notes)
- تاریخها قبل از مقایسه با تابع
Functions::checkDatetimeنرمالسازی میشوند. - فیلد
status=5برای حذف نرم اسناد و پرداختها استفاده میشود. - لاگ شامل:
- type = TrashManualDocument
- goal = id سند
- by = operator.id
- ip, agent, datetime
POST /v2/manual-document/list
Route Info
| Method | Endpoint | Controller |
| POST | /v2/manual-document/list | AccountingController@listManualDocument |
شرح عملکرد (Functionality)
این مسیر برای لیستگیری، جستجو، فیلتر و صفحهبندی اسناد دستی در جدول manual_documents استفاده میشود. عملکرد متد به صورت زیر است:
- دریافت ورودی JSON از کلید
jsonشامل تنظیمات صفحهبندی و فیلتر. - محاسبه مقدار
startبر اساس مقدار فعلی و طول (length). - اجرای کوئری با شروط:
- فیلتر تاریخ بین
fromوto - فیلتر براساس سال
- فیلتر شماره سند (from_document / to_document)
- فیلتر بر اساس branch
- فیلتر تاریخ بین
- بارگذاری گزارش تراز سند از Redis:
documents:balance_report:{id}
- در صورت نبود داده در Redis، فراخوانی
getManualDocumentDetails(). - ساخت خروجی استاندارد شامل:
- serial و system_serial
- title و description
- status فارسی
- تراز مالی (financial)
پارامترهای ورودی (JSON Body)
Body Parameter: کل ورودی در فیلد json ارسال میشود
{
"start": 0,
"length": 20,
"draw": 1,
"search": {
"from": "1403-01-01",
"to": "1403-01-30",
"year": "",
"from_document": "",
"to_document": ""
}
}
- start: نقطه شروع صفحهبندی
- length: تعداد آیتم در هر صفحه
- draw: شماره درخواست (برای DataTables)
- search.from / search.to: بازه تاریخی
- search.year: فیلتر سال
- from_document / to_document: بازه شماره سند (با تبدیل داخلی -1000)
خروجی موفق (Success Response)
{
"status": true,
"time": 1718450000,
"draw": 1,
"recordsTotal": 152,
"recordsFiltered": 20,
"data": [
{
"serial": 3125,
"system_serial": 125,
"type": "manual",
"title": "سند شماره 3125 | 1403/01/25 | دستی - توضیحات",
"date": "14030125",
"manual_serial": false,
"sub_serial": false,
"description": "توضیحات سند",
"status": "تائید نهایی",
"financial": {
"debit_financial_past": 0,
"credit_financial_past": 0,
"debit_start_period": 0,
"credit_start_period": 0,
"debit_during_period": 1500000,
"credit_during_period": 900000,
"debit_balance": 600000,
"credit_balance": 0
}
}
]
}
خروجی خطا (Error Response)
در صورت بروز خطای دیتابیس یا Exception:
{
"status": false,
"time": 1718450000,
"code": 5005,
"message": "SQL error message...",
"trace": [ ... ]
}
نکات داخلی (Internal Logic Notes)
- شماره سند خروجی =
serial + 1000 - تاریخ خروجی به صورت
YYYYMMDDدر فیلد date - عنوان سند از طریق
getManualDocumentTitle()ساخته میشود - گزارش تراز مالی سند در Redis با کلید
documents:balance_report:{id} - بازه صفحه =
currentPage = start / length
POST /v2/manual-document/view
Route Info
| Method | Endpoint | Controller |
| POST | /v2/manual-document/view | AccountingController@viewManualDocument |
شرح عملکرد (Functionality)
این مسیر برای نمایش جزئیات کامل یک سند دستی (Manual Document) استفاده میشود. پس از دریافت شناسه سند:
- فراخوانی تابع
AccountingController::getManualDocumentDetails(id) - استخراج اطلاعات سند شامل:
- ردیفهای سند (Moeen، طرف حساب، شرح، بدهکار، بستانکار)
- تراز کامل سند شامل:
- debit_during_period
- credit_during_period
- debit_balance
- credit_balance
- دادههای برگشتی شامل کلیدهای
dataوbalanceهستند.
پارامترهای ورودی (Request Inputs)
Body (JSON یا Form-Data):
{
"id": 1524
}
- id: شناسه رکورد در جدول
manual_documents(اجباری)
خروجی موفق (Success Response)
ساختار خروجی شامل:
- data: آرایه کامل آیتمهای سند (بسته به نوع سند متفاوت است: daily_sales, daily_credit_debit, manual, opening, closing)
- balance: تراز نهایی سند
{
"status": true,
"time": 1718450000,
"data": [
{
"moeen": {
"id": 1102,
"title": "بانک ملت"
},
"preference": {
"id": "colleague-5421",
"title": "علی رضایی"
},
"preference_2": false,
"preference_3": false,
"preference_4": false,
"description": "سند دستی 3125 | پرداخت نقدی",
"credit": 0,
"debit": 2500000
}
],
"balance": {
"debit_financial_past": 0,
"credit_financial_past": 0,
"debit_start_period": 0,
"credit_start_period": 0,
"debit_during_period": 2500000,
"credit_during_period": 0,
"debit_finish_period": 2500000,
"credit_finish_period": 0,
"debit_balance": 2500000,
"credit_balance": 0
}
}
خروجی خطا (Error Response)
در صورت وقوع هرگونه Exception، ساختار خطا به شکل زیر بازگردانده میشود:
{
"status": false,
"time": 1718450000,
"code": 5005,
"message": "Error message ...",
"trace": [ ... ]
}
خلاصه منطق داخلی getManualDocumentDetails
بسته به نوع سند:
- daily_sales: محاسبه از جداول فاکتور و پرداختها
- daily_credit_debit: ترکیب اقلام پرداخت و بدهکار/بستانکار
- manual: لیست ردیفهای
paysباgroup = id - opening / closing: استخراج اقلام افتتاحیه/اختتامیه
در پایان:
- ذخیره گزارش تراز در Redis با کلید:
documents:balance_report:{id} - برگشت:
{ data: [...], balance: {...} }
POST /v2/manual-document/get
Route Info
| Method | Endpoint | Controller |
| POST | /v2/manual-document/get | AccountingController@getManualDocument |
شرح عملکرد (Functionality)
این مسیر جهت دریافت تمامی جزئیات یک سند دستی و تمام ردیفهای وابسته به آن استفاده میشود.
فرآیند متد:
- جستجو و بازیابی رکورد موردنظر از جدول
manual_documents. - استخراج آیتمهای مرتبط از جدول
paysبر اساسgroup = id. - محاسبه مجموع:
- تعداد آیتمها (count)
- جمع بدهکار (debit)
- جمع بستانکار (credit)
- استخراج جزئیات هر pay از Redis:
- کلید:
accounting:pays:{pay_id} - درصورت نبود داده: تبدیل داده با
convertPayDetailsDbToTable
- کلید:
- تبدیل شناسههای معین و طرفحساب به ساختار استاندارد با:
Functions::getDetailsItems()
- برگشت ساختار استاندارد «document payload» شامل:
- system_serial
- serial (با +1000)
- type
- date
- title با استفاده از
getManualDocumentTitle - توضیحات، زیر شمارهها، وضعیت و جمع کل
پارامترهای ورودی (JSON Body)
Body:
{
"id": 1524
}
- id: شناسه سند در جدول
manual_documents(اجباری)
خروجی موفق (Success Response)
خروجی شامل سه بخش اصلی است:
- items: لیست کامل پرداختها/دریافتهای سند
- payload: اطلاعات کامل سند
- meta: زمان اجرا
{
"items": [
{
"id": 980211,
"serial": 1452,
"moeen": {
"id": 1102,
"serial": 220,
"group": 4,
"details": { ... },
"type": { "fa": "معین", "en": "moeen" },
"title": {
"fa": { "simple": "بانک ملت", "html": "بانک ملت" }
}
},
"preference": {
"id": "colleague-5421",
"serial": 5421,
"title": "علی رضایی"
},
"preference_2": {
"id": 1452,
"title": 1452
},
"preference_3": false,
"preference_4": false,
"description": "واریز نقدی",
"credit": 0,
"debit": 850000
}
],
"payload": {
"document": {
"serial": 3125,
"system_serial": 1524,
"title": "سند شماره 3125 | دستی - توضیحات"
},
"system_serial": 1524,
"serial": 3125,
"type": "manual",
"title": "سند شماره 3125 | دستی - توضیحات",
"date": "1403/01/25",
"manual_serial": false,
"sub_serial": false,
"description": "توضیحات سند",
"total": {
"count": 4,
"debit": 3200000,
"credit": 2400000
},
"status": 1
},
"meta": {
"timestamp": 1718450000
}
}
خروجی خطا (Error Response)
در صورت بروز Exception:
{
"status": false,
"time": 1718450000,
"code": 5005,
"message": "Error message...",
"trace": [ ... ]
}
نکات داخلی اجرای متد
- کلید Redis برای آیتمها:
accounting:pays:{id} - در صورت عدم وجود داده در Redis:
V2CreditDebitController::convertPayDetailsDbToTable() - تبدیل شناسهها به عنوان و ساختار کامل:
Functions::getDetailsItems(type, id, "autocomplete") - عنوان سند از:
getManualDocumentTitle() - serial خروجی سند = serial + 1000
POST /v2/manual-document/list/{type}
Route Info
| Method | Endpoint | Controller Method | Middleware |
| POST | /v2/manual-document/list/{type} | AccountingController@listTypeManualDocument | authWithJwt |
شرح عملکرد (Functionality)
این مسیر برای دریافت لیست ۵۰تایی اسناد دستی، افتتاحیه یا اختتامیه استفاده میشود. فیلتر جستجو از طریق پارامتر json اعمال میشود و خروجی شامل سریال نمایشی (+1000)، شماره سیستمی، و عنوان نهایی سند است.
- خواندن رشته
jsonو استخراج مقدارsearch.value - اگر مقدار جستجو عدد باشد → جستجو روی
serial = value - 1000 - اگر مقدار جستجو متن باشد → LIKE روی تاریخ و توضیحات
- اعمال فیلتر
branch - محدودسازی نوع به:
manual،opening،closing - مرتبسازی نزولی بر اساس id
- محدودیت خروجی: ۵۰ رکورد
- تولید عنوان نهایی سند با استفاده از:
getManualDocumentTitle(type, serial+1000, description, formattedDate)
پارامترهای مسیر (Path Parameters)
| نام | نوع | توضیح |
| type | string | نوع سند (manual / opening / closing) |
پارامترهای ورودی (JSON Body)
Body:
{
"json": "{\"search\": {\"value\": \"1403/01\"}}",
"branch": 1
}
- json: رشتهای که شامل:
- search.value: مقدار برای اعمال فیلتر عددی یا متنی
- branch: شناسه شعبه
خروجی موفق (Success Response)
خروجی نهایی شامل آرایهای از اسناد فرمتشده است:
{
"status": true,
"time": 1718451600,
"data": [
{
"serial": 3125,
"system_serial": 1524,
"title": "سند شماره 3125 | 1403/01/21 | دستی - هزینه دفتر"
},
{
"serial": 3101,
"system_serial": 1512,
"title": "سند شماره 3101 | افتتاحیه - افتتاح سال 1403"
}
]
}
خروجی خطا (Error Response)
در صورت رخ دادن استثناء عمومی:
{
"status": false,
"time": 1718451600,
"code": 5005,
"message": "Exception message...",
"trace": [ ... ]
}
الگوی ساخت عنوان سند (getManualDocumentTitle)
الگوی خروجی عنوان براساس نوع سند:
- manual → سند شماره X | تاریخ | دستی - توضیحات
- opening → سند شماره X | افتتاحیه - توضیحات
- closing → سند شماره X | اختتامیه - توضیحات
- daily_sales → مکانیزه فروشهای روزانه
- daily_credit_debit → مکانیزه دریافت/پرداخت روزانه
در این Route فقط سه نوع زیر بازگردانده میشود:
- manual
- opening
- closing
POST /v2/manual-document/preferences/list/details
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /v2/manual-document/preferences/list/details | AccountingController@preferenceDetails | authWithJwt |
شرح عملکرد (Functionality)
این مسیر برای دریافت جزئیات کامل معین (Moeen) و تفصیل (Preference) استفاده میشود. ورودی شامل آرایهای از رکوردها است که هر کدام شامل:
moeen: کد معین ۶ رقمیpreference: کد تفصیل ۹ رقمی (pref code)
این متد با منطق زیر کار میکند:
- استخراج کدهای group/general/moeen از کد اصلی ۶ رقمی
- اعتبارسنجی وجود داشتن:
- accounting_groups
- accounting_generals
- accounting_moeens
- بازگرداندن اطلاعات کامل معین شامل:
- id
- serialId
- aggregation
- title + کد کامل
- تشخیص نوع تفصیل از روی ۲ رقم اول کد و استخراج اطلاعات آن از جداول مربوطه:
(accounts / offices / colleague / personnel / references / customers / titles / announcement) - در صورت عدم یافتن هر جزء، پاسخ خطـا ۴۲۲ برگردانده میشود.
پارامترهای ورودی (JSON Body)
Body:
{
"data": [
{
"moeen": "110203",
"preference": 120000145
}
],
"branch": 1
}
- data: آرایهای از آیتمها
- moeen: رشته/عدد با ۶ رقم (گروه + کل + معین)
- preference: کد کامل تفصیل
- branch: شناسه شعبه
خروجی موفق (Success Response)
در صورت موفقیت، خروجی شامل آرایهای از آیتمهای پردازش شده:
{
"items": [
{
"moeen": {
"id": 25,
"serialId": 25,
"details": {
"aggregation": 1
},
"code": "110203",
"text": "110203 - فروش | فروش داخلی"
},
"preference": {
"id": "title-15",
"code": 120000145,
"table": "title",
"text": "هزینه تعمیرات",
"financial": false
}
}
],
"meta": {
"timestamp": 1718453500
}
}
خطاهای اعتبارسنجی (Error Response 422)
اگر هر بخش از گروه، کل یا معین وجود نداشته باشد:
{
"error": {
"code": 1000,
"message": "گروه حسابداری با کد 11 در معین 110203 وجود ندارد"
},
"meta": {
"timestamp": 1718453500
}
}
اگر تفصیل وجود نداشته باشد:
{
"error": {
"code": 1000,
"message": "تفصیل با کد 120000999 وجود ندارد"
},
"meta": {
"timestamp": 1718453500
}
}
ساختار استخراج کد معین (Moeen Code Structure)
برای هر کد ۶ رقمی مثلاً 110203:
- group = 11
- general = 02
- moeen = 03
این ۳ بخش بهترتیب روی جداول زیر اعتبارسنجی میشوند:
- accounting_groups
- accounting_generals
- accounting_moeens
تشخیص نوع تفصیل بر اساس ۲ رقم اول کد
| پیشوند | نوع | جدول مرتبط |
| 10 | حساب بانکی | accounting_accounts |
| 12 | عنوانها | titles |
| 13 | پرسنل / اپراتور | operators |
| 20 | همکار | colleagues |
| 21 | اعلان | announcement |
| 30 | مرکز هزینه | offices |
| 40 | فاکتور (reference) | factors + customers |
| 80 | مشتری | customers |
نمونه خروجی تفصیل برای انواع مختلف
1 — Bank Account (prefix 10)
{
"id": "account-12",
"code": 100000532,
"text": "بانک ملت شعبه مرکزی (1234567890)"
}
2 — Personnel (prefix 13)
{
"id": "personnel-88",
"code": 130001234,
"text": "مهدی احمدی"
}
3 — Office (prefix 30)
{
"id": "office-7",
"code": 300000215,
"text": "مرکز هزینه اداره مرکزی"
}
Meta
تمام پاسخها شامل:
timestamp: زمان Unix- HTTP Status:
- 200 در صورت موفقیت
- 422 در صورت خطا
GET /v2/accounting/closing
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /v2/accounting/closing | AccountingController@showClosingAccount | authWithJwt |
شرح عملکرد (Functionality)
این مسیر اطلاعات مربوط به تاریخ پایان دوره مالی بسته شده را برای دفتر (Branch/Office) فعلی برمیگرداند. دادهها از جدول office_config خوانده میشوند و مقدار کلید END_OF_FINANCIAL_PERIOD_CLOSING_ACCOUNTS بررسی میشود. در صورت وجود مقدار، تنها سال و ماه از آن جدا و بازگردانده میشود.
تابع کمکی Functions::officeConfig($office, $key) وظیفه دارد مقدار تنظیم دفتر را از جدول office_config بخواند. اگر چیزی یافت نشود، مقدار false برمیگردد.
این مسیر برای کنترل دورههای مالی در مسیرهای دیگر (مثل ثبت یا حذف سند) کاربرد دارد تا مانع عملیات بعد از تاریخ بسته شدن شود.
فرآیند محاسباتی (Logic Steps)
- دریافت شناسه شعبه با کلید
branchاز Query String یا Token. - فراخوانی تابع
officeConfig(branch, 'END_OF_FINANCIAL_PERIOD_CLOSING_ACCOUNTS'). - اگر مقدار موجود بود، رشته خروجی مثلاً
14040301به قالب JSON تبدیل میشود:year = 1404month = 03
- در غیر این صورت مقدار
nullبرایpayloadبرگردانده میشود.
پاسخ موفق (Success Response)
{
"payload": {
"year": "1404",
"month": "03"
},
"meta": {
"timestamp": 1733053000
}
}
پاسخ در صورت نبود تنظیمات
اگر مقدار در جدول تنظیمات یافت نشود:
{
"payload": null,
"meta": {
"timestamp": 1733053000
}
}
وابستگی دیتابیس (Database Dependency)
| Table | Key | Description |
| office_config | END_OF_FINANCIAL_PERIOD_CLOSING_ACCOUNTS | تاریخ بسته شدن حسابهای دوره مالی (فرمت: YYYYMMDD) |
توابع کمکی استفاده شده (Helper Functions)
static function officeConfig($office, $key)
{
return DB::table('office_config')
->where('office', $office)
->where('key', strtoupper($key))
->value('value') ?: false;
}
اطلاعات تکمیلی (Meta)
- HTTP Status Code:
200در همه حالات (حتی بدون داده) - Authentication: لازم است (JWT Token)
- Dependency: Redis ندارد – کاملاً DB-driven
POST /v2/accounting/closing
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /v2/accounting/closing | AccountingController@updateClosingAccount | authWithJwt |
شرح عملکرد (Functionality)
این مسیر برای بستن دوره مالی در یک شعبه استفاده میشود. قبل از اعمال بستن دوره، سیستم باید اطمینان پیدا کند که در بازه انتخابشده هیچ سند قطعینشده وجود ندارد. این بررسی شامل ۳ دسته سند اصلی است:
- اسناد فروش (References / Factors)
- اسناد دریافت-پرداخت (Payments)
- اسناد دستی (Manual Documents)
اگر هرکدام از این موارد قطعی نشده باشند، مسیر با خطای 422 و پیام مناسب بازمیگردد. در صورت پاک بودن همه اسناد، مقدار جدید END_OF_FINANCIAL_PERIOD_CLOSING_ACCOUNTS برای شعبه در جدول office_config ثبت یا بهروزرسانی میشود.
پارامترهای ورودی (JSON Body)
{
"year": "1404",
"month": "03",
"branch": 1
}
- year: سال شمسی دورهای که باید بسته شود
- month: ماه شمسی دورهای که باید بسته شود
- branch: شعبه هدف
منطق بررسی اسناد قطعینشده (checkDocumentsForClosingAccount)
این تابع ۳ نوع سند را بررسی میکند:
اسناد فروش (factors)
هر سندی که:
- برای همان شعبه باشد
status != 3باشد (یعنی قطعی نشده باشد)- در سال/ماه مورد نظر ثبت شده باشد
اسناد دریافت/پرداخت (pays)
- اگر created_at داشته باشد: باید ≤ آخر سال میلادی متناظر باشد
- اگر deadline (مخصوص چک) داشته باشد: باید ≤ آخر ماه شمسی متناظر باشد و
type_pay = check - پرداختهایی که داخل سند گروهبندی شدهاند (
group IS NOT NULL) بررسی نمیشوند
اسناد دستی (manual_documents)
- status != 3 → قطعی نشده
- date ≤ YYYYMM31
خروجی تابع:
{
"references": true | false,
"payments": true | false,
"manual_documents": true | false,
"salary": false,
"treasury_bills": false
}
پاسخ خطا (422)
اگر هرگونه سند قطعینشده وجود داشته باشد:
{
"error": {
"code": 1000,
"message": "اسناد فروش روزانه | دارای سند قطعی نشده می باشد. قبل از بستن حساب ها، تمامی اسناد باید قطعی شوند."
},
"meta": {
"timestamp": 1733056000
}
}
*نوع پیام بسته به اولین پرچم فعال در خروجی checkDocumentsForClosingAccount است:
- references → "اسناد فروش روزانه"
- payments → "اسناد دریافت پرداخت"
- manual_documents → "اسناد دستی"
پاسخ موفق (Success Response)
اگر هیچ سندی مانع بستن دوره نباشد:
- اگر مقدار قبلاً در office_config وجود داشته باشد → update
- اگر وجود نداشته باشد → insert
پاسخ:
Status: 204 No Content Body: ""
وابستگی دیتابیس
| Table | Description |
| office_config | ذخیره مقدار پایان دوره مالی (فرمت: YYYYMM) |
| factors | اسناد فروش روزانه |
| pays | اسناد دریافت و پرداخت |
| manual_documents | سندهای دستی |
META
- HTTP Status Codes: 204, 422
- کاملاً منطبق با عملیات واقعی حسابداری
- هیچ داده اضافی در صورت موفقیت بازگشت داده نمیشود
PUT /v2/accounting/closing/definite/{type}
Route Info
| Method | Endpoint | Controller | Middleware |
| PUT | /v2/accounting/closing/definite/{type} | AccountingController@definiteDocumentsInClosingAccount | authWithJwt |
شرح عملکرد (Functionality)
این مسیر برای قطعیکردن دستهای اسناد هنگام بستن دوره مالی استفاده میشود. نوع سند از پارامتر {type} تعیین میشود و شامل موارد زیر است:
- daily_sales: اسناد فروش روزانه (جدول factors)
- daily_credit_debit: اسناد دریافت/پرداخت روزانه (جدول pays)
- manual_documents: اسناد دستی (جدول manual_documents)
- salary: (رزرو – بدون پیادهسازی)
- treasury_bills: (رزرو – بدون پیادهسازی)
هر نوع سند با شرطهای خاص زمانی (شمسی/میلادی) و وضعیت فعلی، به وضعیت 3 (قطعی) بهروزرسانی میشود. این API بخشی از پیوستگی عملیات بستن حساب است و معمولاً پس از بررسی عدم وجود سند باز انجام میشود.
پارامترهای ورودی (Input)
Path Parameter
- {type}: نوع سند هدف (daily_sales/daily_credit_debit/manual_documents/…)
Body (JSON):
{
"year": "1404",
"month": "03",
"branch": 1
}
- year: سال شمسی موردنظر
- month: ماه شمسی موردنظر
- branch: شعبه هدف
منطق پردازش (Logic)
ابتدا سال/ماه شمسی به میلادی تبدیل میشوند:
- miladi.year = سال میلادی
- miladi.month = ماه میلادی
- shamsi.year = سال شمسی
- shamsi.month = ماه شمسی
قطعیکردن اسناد فروش روزانه (type = daily_sales)
- status ∈ [1, 4]
- branch = {branch}
- created_at.year = میلادی
- created_at.month = میلادی
UPDATE factors SET status = 3
قطعیکردن دریافت/پرداخت روزانه (type = daily_credit_debit)
- status ∈ [4, 6]
- group IS NULL
- branch = {branch}
- اگر deadline NULL → created_at ≤ YYYY-12-31 میلادی
- اگر deadline NOT NULL و نوع چک باشد → deadline ≤ YYYYMM31 شمسی
UPDATE pays SET status = 3
قطعیکردن اسناد دستی (type = manual_documents)
- status = 1
- branch = {branch}
- date ≤ YYYYMM31 شمسی
UPDATE manual_documents SET status = 3
موارد رزرو (بهصورت NO-OP)
- salary → بدون تغییر
- treasury_bills → بدون تغییر
return false
پاسخ موفق (Success Response)
اگر عملیات با موفقیت انجام شود (true):
Status: 204 No Content Body: ""
پاسخ خطا (Invalid Type)
اگر نوع سند شناسایی نشود (false):
Status: 422 Unprocessable Entity Body: ""
وابستگی دیتابیس
| Table | Description |
| factors | اسناد فروش روزانه |
| pays | اسناد دریافت/پرداخت (چک + نقد) |
| manual_documents | سندهای دستی |
Meta
- HTTP Status Codes: 204, 422
- نقش کلیدی در فرآیند قطعیسازی قبل از بستن مالی
- همواره نیازمند year + month + branch
GET /v2/accounting/preference
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /v2/accounting/preference | AccountingController@indexMappingPreferences | authWithJwt |
شرح عملکرد (Functionality)
این مسیر برای دریافت لیست نگاشت حسابداری استفاده میشود. اطلاعات جدول mapping_accounting_preferences را بر اساس شعبه، نوع شیء (object_type) و شناسه شیء (object) بهصورت صفحهبندی شده (Paginated) برمیگرداند.
در صورت نبود پارامتر paginate، سیستم بهصورت خودکار مقدار پیشفرض زیر را اعمال میکند:
- length = 30
- start = 0
محاسبه شماره صفحه بر اساس فرمول زیر انجام میشود:
page = (start == 0 ? length : start + length) / length
پارامترهای ورودی (Query Parameters)
- branch شناسه شعبه (اجباری)
- object_type نوع شیء هدف (اختیاری) مثال: customer, colleague, office
- object شناسه شیء هدف (اختیاری)
- paginate[length] تعداد رکورد در هر صفحه (پیشفرض 30)
- paginate[start] شماره شروع (Offset) — پیشفرض 0
پاسخ موفق (Success Response)
پاسخ شامل دادههای صفحهبندیشده لاراول است (object کامل pagination):
{
"payload": {
"current_page": 1,
"data": [
{
"id": 12,
"branch": 1,
"object_type": "customer",
"object": 24,
"moeen": 14,
"created_at": "2024-07-21T10:00:00.000000Z",
"updated_at": "2024-07-21T10:00:00.000000Z"
}
],
"first_page_url": "...",
"from": 1,
"last_page": 4,
"last_page_url": "...",
"next_page_url": "...",
"path": "...",
"per_page": 30,
"prev_page_url": null,
"to": 30,
"total": 110
},
"meta": {
"timestamp": 1733056000
}
}
خطاها (Errors)
- این مسیر خطای اختصاصی ندارد.
- در صورت نبود داده، مقدار
payloadبرابر false خواهد بود.
وابستگی دیتابیس
| Table | Description |
| mapping_accounting_preferences | جدول نگاشت حسابداری برای انواع موجودیتها |
Meta
- HTTP Status: 200
- Pagination: فعال
- فیلتر پویا بر اساس object_type و object
POST /v2/accounting/preference
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /v2/accounting/preference | AccountingController@storeMappingPreferences | authWithJwt |
شرح عملکرد (Functionality)
این API برای ثبت نگاشتهای حسابداری در جدول mapping_accounting_preferences استفاده میشود. ورودی بهصورت مجموعهای از آیتمها در فیلد items ارسال میشود. برای هر آیتم:
- ابتدا بررسی میشود آیا کد (
code) قبلاً در همین شعبه ثبت شده یا خیر. - اگر تکراری باشد → به آرایه
errorافزوده میشود. - اگر جدید باشد → اطلاعات ساختاردهی شده و آماده Insert میشود.
در انتها، اگر هیچ خطایی وجود نداشته باشد، همه رکوردهای جدید به صورت Batch درج میشوند.
ورودی مورد نیاز (JSON Body)
ساختار کلی:
{
"branch": 1,
"items": [
{
"id": "customer-24",
"text": "24 - مشتری عمده",
"code": "110201"
},
{
"id": "office-3",
"text": "301 - دفتر مرکزی",
"code": "880030"
}
]
}
جزئیات فیلدها:
- branch شناسه شعبه (ضروری)
- items: آرایهای از نگاشتها هر آیتم شامل:
- id: ترکیب نوع شیء و شناسه (مثال:
customer-24) - text: رشته شامل کد سیستمی و نام خوانا (مثال:
"24 - مشتری") - code: کد نهایی نگاشت موردنظر (۶ رقمی یا بیشتر)
- id: ترکیب نوع شیء و شناسه (مثال:
ساختاردهی رکورد درجشونده
برای هر آیتم غیرتکراری:
object_typeاز بخش اولid(مثال: customer)objectاز بخش دومid(مثال: 24)system_codeاولین بخشtextقبل از خط تیرهtitleبخش آخرtextبعد از آخرین خط تیرهcodeهمان کد نهایی ارسالشده توسط کاربرbranchاز ورودیupdated_at= زمان فعلی
پاسخ موفق (Success Response)
در صورت عدم وجود خطا و انجام موفق درج:
{
"payload": true,
"error": false,
"meta": {
"timestamp": 1733056000
}
}
پاسخ در صورت وجود خطا برای برخی آیتمها
اگر بخشی از آیتمها Duplicate باشند:
{
"payload": true,
"error": [
{
"message": "این کد قبلا ثبت شده است.",
"code": "110201"
}
],
"meta": {
"timestamp": 1733056000
}
}
خطای عدم ارسال فیلد items
{
"error": {
"message": "The items field is required."
},
"meta": {
"timestamp": 1733056000
}
}
وابستگی دیتابیس
| Table | Description |
| mapping_accounting_preferences | نگاشت حسابداری برای انواع موجودیتها |
Meta
- HTTP Status Code: 200
- Batch Insert: فعال
- Duplicate Check: مبتنی بر code + branch
GET /v2/accounting/preference/{id}
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /v2/accounting/preference/{id} | AccountingController@showMappingPreferences | authWithJwt |
شرح عملکرد (Functionality)
این مسیر برای دریافت یک رکورد واحد از جدول mapping_accounting_preferences بر اساس شناسه ارسال شده در {id} استفاده میشود.
هیچ پردازش اضافی، تغییر ساختار یا اعتبارسنجی ویژه انجام نمیشود؛ فقط رکورد دیتابیس خوانده شده و بهصورت مستقیم بازگردانده میشود.
پارامتر مسیر (Path Parameter)
- {id} شناسه عددی رکورد موردنظر در جدول نگاشتهای حسابداری
پاسخ موفق (Success Response)
اگر رکورد موجود باشد:
{
"payload": {
"id": 12,
"branch": 1,
"object_type": "customer",
"object": 24,
"title": "مشتری عمده",
"system_code": "24",
"code": "110201",
"updated_at": "2024-07-21 10:00:00"
},
"meta": {
"timestamp": 1733056000
}
}
اگر رکورد موجود نباشد، مقدار payload برابر false خواهد بود.
{
"payload": false,
"meta": {
"timestamp": 1733056000
}
}
خطاها (Errors)
- این مسیر هیچ خطای اختصاصی ندارد.
- در صورت عدم یافتن رکورد،
payload = falseبازگردانده میشود.
وابستگی دیتابیس
| Table | Description |
| mapping_accounting_preferences | ذخیرهساز نگاشتهای حسابداری |
Meta
- HTTP Status Code: 200
- Database Method:
find(id) - No pagination
PUT /v2/accounting/preference/{id}
Route Info
| Method | Endpoint | Controller | Middleware |
| PUT | /v2/accounting/preference/{id} | AccountingController@updateMappingPreferences | authWithJwt |
شرح عملکرد (Functionality)
این API برای ویرایش یک نگاشت حسابداری در جدول mapping_accounting_preferences استفاده میشود.
منطق مسیر شامل مراحل زیر است:
- بررسی یکتا بودن
codeدر همان شعبه (بهجز رکورد جاری). - تجزیه
idبهobject_typeوobject. - تجزیه
textبرای استخراج:- system_code → بخش اول متن
- title → آخرین بخش متن
- بهروزرسانی رکورد در دیتابیس.
- بازگردانی رکورد ویرایششده.
ساختار ورودی (JSON Body)
{
"branch": 1,
"id": "customer-24",
"text": "24 - مشتری عمده",
"code": "110201"
}
توضیح فیلدها
- branch شناسه شعبه
- id ساختار
{object_type}-{object}، مثال:- customer-24
- office-3
- text رشتهای شامل کد سیستمی و عنوان قابل نمایش مثال:
"24 - مشتری عمده" - code کد نهایی نگاشت (۶ رقمی یا بیشتر)
اعتبارسنجی
قبل از ذخیره، بررسی میشود که آیا کد جدید در همان شعبه قبلاً استفاده شده است یا خیر:
exists where:
branch = request.branch
code = request.code
id != {id}
اگر رکوردی با همین کد وجود داشته باشد، بهجای بهروزرسانی، خطا بازگردانده میشود.
پاسخ موفق (Success Response)
در صورت بهروزرسانی موفق، رکورد کامل جدید بازگردانده میشود:
{
"payload": {
"id": 12,
"branch": 1,
"object_type": "customer",
"object": 24,
"title": "مشتری عمده",
"system_code": "24",
"code": "110201",
"updated_at": "2024-07-21T10:00:00.000000Z"
},
"meta": {
"timestamp": 1733056000
}
}
پاسخ خطا (Error Response)
اگر کد تکراری باشد:
{
"error": {
"message": "این کد قبلا ثبت شده است."
},
"meta": {
"timestamp": 1733056000
}
}
وابستگی دیتابیس
| Table | Description |
| mapping_accounting_preferences | نگاشت حسابداری برای انواع موجودیتها |
Meta
- HTTP Status Code: 200
- Operation: Update
- Validation: Unique per branch (except current record)
POST /v2/account-history/calculate-daily
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /v2/account-history/calculate-daily | AccountHistoryController@calculateDailyBalanceForYear | authWithJwt |
شرح عملکرد (Functionality)
این API برای محاسبه و ذخیره ماندههای روزانه یک همکار (Colleague) استفاده میشود. فرآیند محاسبه میتواند:
- برای کل یک سال مشخص (جلالی)
- یا برای یک بازه تاریخ خاص
محاسبات امکان اجرا به صورت:
- همزمان (Sync) با بازگردانی نتیجه
- غیرهمزمان (Async) با استفاده از Job در Queue
نتیجههای محاسبه شده در Redis با TTL سی روزه ذخیره میشوند.
ورودی (Request Body)
{
"colleague_id": 24,
"year": 1403, // optional
"from": "1403-01-01", // optional
"to": "1403-12-29", // optional
"async": true // optional, default = false
}
قوانین اعتبارسنجی
- colleague_id الزامی و باید در جدول colleagues موجود باشد.
- year اختیاری، عدد بین 1300 تا 1500.
- from/to تاریخ شمسی در فرمت
Y-m-d. - async مقدار boolean.
- ارسال تاریخهای آینده ممنوع است.
منطق اجرا (Execution Logic)
- اول اعتبارسنجی Laravel انجام میشود.
- اگر تاریخ آینده ارسال شود → خطا.
- اگر async=true → Job با پارامترهای:
- colleague_id
- year
- type=daily
- اگر async=false →
calculateAndStoreDailyBalance()مستقیماً فراخوانی میشود.
مشخصات Job
- timeout: 3600 ثانیه
- tries: 3
- در صورت خطا → fail شدن job
- لاگ کامل برای شروع، پایان و خطا
پاسخ موفق (Async Mode)
{
"payload": null,
"meta": {
"colleague_id": 24,
"year": 1403,
"from": "1403-01-01",
"to": "1403-12-29",
"type": "daily",
"status": "queued",
"timestamp": "2025-12-01T10:15:00+03:30"
}
}
پاسخ موفق (Sync Mode)
{
"payload": {
"1403-01-01": {
"credit": 0,
"debit": 1200000,
"balance": -1200000
},
"1403-01-02": {
"credit": 500000,
"debit": 0,
"balance": 500000
}
},
"meta": {
"colleague_id": 24,
"from": "1403-01-01",
"to": "1403-12-29",
"total_days": 365,
"timestamp": "2025-12-01T10:15:00+03:30"
}
}
پاسخهای خطا (Error Responses)
اعتبارسنجی (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "colleague_id is required"
},
"meta": {
"timestamp": "2025-12-01T10:18:00+03:30"
}
}
تاریخ آینده مجاز نیست (400)
{
"error": {
"code": "FUTURE_DATE_NOT_ALLOWED",
"message": "Cannot calculate for future dates."
},
"meta": {
"colleague_id": 24,
"to": "1404-01-01",
"timestamp": "2025-12-01T10:18:00+03:30"
}
}
خطای داخلی (500)
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Unexpected error..."
},
"meta": {
"colleague_id": 24,
"timestamp": "2025-12-01T10:20:00+03:30"
}
}
Meta
- Redis Key Format:
account_history:daily:{colleagueId}:{year}:{date} - TTL: 30 روز
- Job Class:
CalculateAccountHistoryJob - Service Method:
calculateAndStoreDailyBalance()
POST /v2/account-history/calculate-monthly
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /v2/account-history/calculate-monthly | AccountHistoryController@calculateMonthlyBalanceForYear | authWithJwt |
شرح عملکرد (Functionality)
این API وظیفه محاسبه و ذخیره ماندههای ماهانه برای یک همکار (Colleague) را دارد. محاسبه میتواند بر اساس:
- یک سال مشخص (مثلاً 1402)
- یا یک بازه تاریخ دلخواه (from / to)
محاسبات میتوانند به صورت:
- همزمان (Sync): خروجی محاسبه همان لحظه بازگردانده میشود.
- غیرهمزمان (Async): پردازش در صف اجرا شده و پاسخ سریع برمیگردد.
در حالت async، عملیات توسط CalculateAccountHistoryJob با type = "monthly" انجام میشود.
ورودی (Request Body)
{
"colleague_id": 108,
"year": 1403, // optional
"from": "1403-01-01", // optional
"to": "1403-12-29", // optional
"async": false // optional (default=false)
}
قوانین اعتبارسنجی
- colleague_id الزامی و باید در جدول colleagues وجود داشته باشد.
- year اختیاری و باید بین 1300 تا 1500 باشد.
- from/to تاریخ شمسی معتبر در فرمت
Y-m-d. - async مقدار boolean.
- ارسال تاریخ آینده ممنوع است → خطای FUTURE_DATE_NOT_ALLOWED.
منطق اجرا (Execution Logic)
- اعتبارسنجی Laravel اجرا میشود.
- اگر from یا to بزرگتر از تاریخ امروز باشد → خطا.
- اگر async=true:
- Job با مقادیر:
- colleague_id
- year
- type = monthly
- پاسخ سریع با وضعیت queued برمیگردد.
- Job با مقادیر:
- اگر async=false:
محاسبه مستقیم با:calculateAndStoreMonthlyBalance()انجام میشود.
مشخصات Job
- timeout: 3600 ثانیه
- tries: 3
- در صورت خطا → fail شدن job
- ثبت کامل لاگ از شروع، پایان یا خطا
پاسخ موفق (Async Mode)
{
"payload": null,
"meta": {
"colleague_id": 108,
"year": 1403,
"from": "1403-01-01",
"to": "1403-12-29",
"type": "monthly",
"status": "queued",
"timestamp": "2025-12-01T12:35:00+03:30"
}
}
پاسخ موفق (Sync Mode)
{
"payload": {
"1403-01": { "credit": 0, "debit": 900000, "balance": -900000 },
"1403-02": { "credit": 500000, "debit": 0, "balance": 500000 }
},
"meta": {
"colleague_id": 108,
"year": 1403,
"total_months": 12,
"timestamp": "2025-12-01T12:35:00+03:30"
}
}
پاسخهای خطا (Error Responses)
اعتبارسنجی (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "colleague_id field is required"
},
"meta": {
"timestamp": "2025-12-01T12:40:00+03:30"
}
}
تاریخ آینده مجاز نیست (400)
{
"error": {
"code": "FUTURE_DATE_NOT_ALLOWED",
"message": "Cannot calculate for future dates."
},
"meta": {
"colleague_id": 108,
"to": "1405-01-01",
"timestamp": "2025-12-01T12:40:00+03:30"
}
}
خطای داخلی (500)
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Unexpected error..."
},
"meta": {
"colleague_id": 108,
"timestamp": "2025-12-01T12:41:00+03:30"
}
}
Meta
- Job Class:
CalculateAccountHistoryJob - Service Method:
calculateAndStoreMonthlyBalance - Job Type:
monthly
POST /v2/account-history/calculate-complete
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /v2/account-history/calculate-complete | AccountHistoryController@calculateCompleteHistory | authWithJwt |
شرح عملکرد (Functionality)
این API برای محاسبه کامل ماندهها استفاده میشود؛ یعنی:
- محاسبه ماندههای روزانه
- محاسبه ماندههای ماهانه
محاسبهها میتوانند بر اساس:
- یک سال مشخص (year)
- یا بازهای میان
from / to
روش اجرا:
- همزمان (Sync): هر دو محاسبه انجام شده و پاسخ کامل داده میشود.
- غیرهمزمان (Async): دو Job جداگانه (daily و monthly) در صف Dispatch میشوند.
هر دو محاسبه از طریق سرویس AccountHistoryService انجام میشوند.
ورودی (Request Body)
{
"colleague_id": 108,
"year": 1403, // optional
"from": "1403-01-01", // optional
"to": "1403-12-29", // optional
"async": true // optional (default = true)
}
قوانین اعتبارسنجی
- colleague_id الزامی، integer، موجود در colleagues.
- year اختیاری، عدد 1300 تا 1500.
- from / to تاریخ شمسی معتبر
Y-m-d. - async مقدار boolean (پیشفرض true).
- تاریخ آینده ممنوع → خطای FUTURE_DATE_NOT_ALLOWED.
- در صورت عدم ارسال year و عدم ارسال from/to → خطای INVALID_DATE_RANGE (در سرویس).
منطق اجرا (Execution Logic)
- ابتدا اعتبارسنجی انجام میشود.
- اگر from یا to > امروز باشد → خطا 400.
- اگر async=true:
- دو Job جدا dispatch میشوند:
- type = daily
- type = monthly
- پاسخ سریع با وضعیت queued برمیگردد.
- دو Job جدا dispatch میشوند:
- اگر async=false:
calculateAndStoreDailyBalance()calculateAndStoreMonthlyBalance()- خطای هرکدام → CALCULATION_ERROR
مشخصات Job
- timeout: 3600 ثانیه
- tries: 3
- job جداگانه برای daily و monthly
- در صورت خطا → fail شدن job
- لاگ کامل شروع، پایان، و خطا
پاسخ موفق (Async Mode)
{
"payload": null,
"meta": {
"colleague_id": 108,
"year": 1403,
"from": "1403-01-01",
"to": "1403-12-29",
"jobs": ["daily", "monthly"],
"status": "queued",
"timestamp": "2025-12-01T14:20:00+03:30"
}
}
پاسخ موفق (Sync Mode)
{
"payload": {
"daily": {
"1403-01-01": { "credit": 0, "debit": 1200000, "balance": -1200000 },
"1403-01-02": { "credit": 500000, "debit": 0, "balance": 500000 }
},
"monthly": {
"1403-01": { "credit": 0, "debit": 900000, "balance": -900000 },
"1403-02": { "credit": 500000, "debit": 0, "balance": 500000 }
}
},
"meta": {
"colleague_id": 108,
"year": 1403,
"from": "1403-01-01",
"to": "1403-12-29",
"daily_meta": { "...": "..." },
"monthly_meta": { "...": "..." },
"timestamp": "2025-12-01T14:20:00+03:30"
}
}
پاسخهای خطا (Error Responses)
اعتبارسنجی (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "colleague_id is required"
},
"meta": {
"timestamp": "2025-12-01T14:25:00+03:30"
}
}
تاریخ آینده مجاز نیست (400)
{
"error": {
"code": "FUTURE_DATE_NOT_ALLOWED",
"message": "Cannot calculate for future dates."
},
"meta": {
"colleague_id": 108,
"to": "1404-01-01",
"timestamp": "2025-12-01T14:25:00+03:30"
}
}
خطای محاسباتی یکی از دو عملیات (400)
{
"error": {
"code": "CALCULATION_ERROR",
"message": "Error in one or both calculations",
"details": {
"daily": null,
"monthly": {
"code": "INTERNAL_ERROR",
"message": "..."
}
}
},
"meta": {
"colleague_id": 108,
"timestamp": "2025-12-01T14:26:00+03:30"
}
}
خطای داخلی (500)
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Unexpected exception..."
},
"meta": {
"colleague_id": 108,
"timestamp": "2025-12-01T14:26:00+03:30"
}
}
Meta
- Jobs:
CalculateAccountHistoryJob - Job Types:
daily,monthly - Service Methods:
calculateAndStoreDailyBalance()calculateAndStoreMonthlyBalance()
- همه عملیاتها برای همکار
ID = 108نمونهسازی شدهاند.
GET /v2/account-history/daily
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /v2/account-history/daily | AccountHistoryController@getDailyBalance | authWithJwt |
شرح عملکرد (Functionality)
این API صرفاً برای خواندن سریع اطلاعات محاسبهشده از قبل استفاده میشود.
- این متد هیچ محاسبهای انجام نمیدهد (Calculation Trigger نیست).
- دادهها را مستقیماً از Cache (Redis) بازیابی میکند.
- اگر دادهای برای تاریخ مورد نظر محاسبه نشده باشد (در کش نباشد)، خطای 404 برمیگرداند.
ورودی (Query Parameters)
پارامترها به صورت Query String ارسال میشوند:
?colleague_id=108&year=1403&date=1403-01-05
قوانین اعتبارسنجی (Validation Rules)
- colleague_id: الزامی | integer | باید در جدول
colleaguesموجود باشد. - year: الزامی | integer | بازه 1300 تا 1500.
- date: الزامی | فرمت دقیق
Y-m-d(تاریخ شمسی).
منطق اجرا (Execution Logic)
- ابتدا ورودیها توسط
Validatorبررسی میشوند.- در صورت شکست: بازگشت خطای 400 با کد
VALIDATION_ERROR.
- در صورت شکست: بازگشت خطای 400 با کد
- متد سرویس
getDailyBalanceFromCache($colleagueId, $year, $date)فراخوانی میشود. - سرویس چک میکند آیا کلید مربوطه در Redis وجود دارد یا خیر.
- بررسی نتیجه سرویس:
- اگر خروجی شامل کلید
errorباشد → بازگشت خطای 404 (Not Found). - در غیر این صورت → بازگشت دادهها با وضعیت 200.
- اگر خروجی شامل کلید
- مدیریت خطا (Exception Handling): هرگونه خطا در بلاک
try-catchمنجر به خطای 500 میشود.
پاسخ موفق (200 OK)
{
"payload": {
"credit": 0,
"debit": 1500000,
"balance": -1500000
},
"meta": {
"colleague_id": 108,
"year": 1403,
"date": "1403-01-05",
"source": "cache",
"timestamp": "2025-12-01T15:30:00+03:30"
}
}
پاسخهای خطا (Error Responses)
خطای اعتبارسنجی (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The date does not match the format Y-m-d."
},
"meta": {
"timestamp": "2025-12-01T15:30:00+03:30"
}
}
داده یافت نشد / محاسبه نشده (404)
{
"error": {
"code": "NOT_FOUND",
"message": "No data found for this date. Please calculate first."
},
"meta": {
"timestamp": "2025-12-01T15:31:00+03:30"
}
}
خطای سرور (500)
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Redis connection failed..."
},
"meta": {
"colleague_id": 108,
"timestamp": "2025-12-01T15:32:00+03:30"
}
}
توضیحات فنی (Meta)
- Service Method:
AccountHistoryService::getDailyBalanceFromCache - Redis Key Pattern: معمولاً به فرمت
account_history:daily:{colleagueId}:{year}:{date}میباشد. - این متد Stateless است و فقط خواندن انجام میدهد.
GET /v2/account-history/monthly
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /v2/account-history/monthly | AccountHistoryController@getMonthlyBalance | authWithJwt |
شرح عملکرد (Functionality)
این API برای دریافت مانده حساب ماهانه طراحی شده است و دارای مکانیزم هوشمند (Read-Through Cache) است:
- بررسی کش: ابتدا سعی میکند داده را از Redis بخواند.
- محاسبه خودکار (Auto-Calculation): اگر داده در کش نباشد، سیستم به طور خودکار متد
calculateAndStoreMonthlyBalanceرا اجرا میکند تا دادههای آن سال تولید و ذخیره شوند. - بازخوانی: پس از محاسبه، مجدداً داده را از کش میخواند و برمیگرداند.
توجه: اگر کش خالی باشد، پاسخ این سرویس ممکن است کمی طول بکشد (به اندازه زمان محاسبه مانده سالانه)
ورودی (Query Parameters)
?colleague_id=108&year=1403&month=2
قوانین اعتبارسنجی (Validation Rules)
- colleague_id: الزامی | integer | موجود در جدول
colleagues. - year: الزامی | integer | بازه 1300 تا 1500.
- month: الزامی | integer | بازه 1 تا 12 (نیاز به ارسال صفر قبل عدد نیست، سیستم خودکار Pad میکند).
منطق اجرا (Execution Logic)
- اعتبارسنجی ورودیها.
- خطا → 400 (VALIDATION_ERROR).
- تبدیل ماه ورودی به فرمت دورقمی (مثلاً
2به02). - فراخوانی سرویس
getMonthlyBalanceFromCache. - Logic داخل سرویس:
- ساخت کلید Redis.
- چک کردن وجود کلید. اگر بود → بازگشت (Meta:
cached: true). - اگر نبود → اجرای متد محاسبه (Calculation).
- چک کردن مجدد Redis. اگر بود → بازگشت (Meta:
cached: false, calculated: true). - اگر باز هم نبود → بازگشت آرایه خطا (CACHE_MISS).
- اگر خروجی سرویس حاوی
errorباشد → پاسخ 404. - در غیر این صورت → پاسخ 200.
پاسخ موفق (200 OK)
حالت ۱: داده در کش موجود بود (Cache Hit)
{
"payload": {
"credit": 500000,
"debit": 0,
"balance": 500000
},
"meta": {
"colleague_id": 108,
"year": 1403,
"month": "02",
"cached": true,
"timestamp": "2025-12-01T16:00:00+03:30"
}
}
حالت ۲: داده محاسبه شد (Cache Miss -> Calculated)
{
"payload": {
"credit": 500000,
"debit": 0,
"balance": 500000
},
"meta": {
"colleague_id": 108,
"year": 1403,
"month": "02",
"cached": false,
"calculated": true,
"timestamp": "2025-12-01T16:00:05+03:30"
}
}
پاسخهای خطا (Error Responses)
خطای اعتبارسنجی (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The month must be at least 1."
},
"meta": { ... }
}
خطای عدم یافتن/محاسبه (404)
زمانی رخ میدهد که حتی پس از تلاش برای محاسبه، دادهای در کش ذخیره نشود.
{
"error": {
"code": "CACHE_MISS",
"message": "Balance not found in cache and calculation failed"
},
"meta": {
"colleague_id": 108,
"year": 1403,
"month": "02",
"cached": false,
"timestamp": "..."
}
}
خطای سرور (500)
{
"error": {
"code": "INTERNAL_ERROR",
GET /v2/account-history/all-daily
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /v2/account-history/all-daily | AccountHistoryController@getAllDailyBalances | authWithJwt |
شرح عملکرد (Functionality)
این API برای دریافت یکجای تمام سوابق روزانه یک سال خاص استفاده میشود (مثلاً برای رسم نمودار تغییرات موجودی در طول سال).
- عملکرد دستهای (Batch): به جای درخواستهای متعدد، تمام کلیدهای کش مربوط به یک سال را اسکن و بازیابی میکند.
- مرتبسازی: دادهها به صورت خودکار بر اساس تاریخ (Date) مرتب میشوند (Sort Ascending).
- بدون محاسبه: این متد فقط خواندنی است و محاسبهای انجام نمیدهد. فقط دادههایی که قبلاً محاسبه و کش شدهاند را برمیگرداند.
ورودی (Query Parameters)
?colleague_id=108&year=1403
قوانین اعتبارسنجی (Validation Rules)
- colleague_id: الزامی | integer | موجود در جدول
colleagues. - year: الزامی | integer | بازه 1300 تا 1500.
منطق اجرا (Execution Logic)
- اعتبارسنجی ورودیها (بازگشت 400 در صورت خطا).
- ساخت الگوی جستجو در Redis:
DAILY_BALANCE_KEY{colleagueId}:{year}:*. - استفاده از دستور
Redis::keysبرای یافتن تمام کلیدهای منطبق. - حلقه روی کلیدها:
- دریافت مقدار (`Redis::get`) و دیکود کردن JSON.
- ذخیره نتایج در آرایه با کلید تاریخ (برای دسترسی سریعتر در فرانتاند).
- مرتبسازی آرایه بر اساس کلید (تاریخ) با تابع
ksort. - بازگشت دادهها (حتی اگر لیست خالی باشد، آرایه خالی برمیگردد و خطای 404 نداریم).
پاسخ موفق (200 OK)
توجه کنید که کلیدهای آبجکت payload همان تاریخها هستند.
{
"payload": {
"1403-01-01": {
"credit": 0,
"debit": 100000,
"balance": -100000,
"date": "1403-01-01"
},
"1403-01-02": {
"credit": 50000,
"debit": 0,
"balance": -50000,
"date": "1403-01-02"
},
"1403-05-20": {
"credit": 200000,
"debit": 0,
"balance": 150000,
"date": "1403-05-20"
}
},
"meta": {
"colleague_id": 108,
"year": 1403,
"total_days": 3,
"cached": true,
"timestamp": "2025-12-01T16:15:00+03:30"
}
}
پاسخهای خطا (Error Responses)
خطای اعتبارسنجی (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The year must be between 1300 and 1500."
},
"meta": { ... }
}
خطای سرور (500)
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Redis connection failed"
},
"meta": { ... }
}
توضیحات فنی (Meta)
- Redis Pattern: استفاده از Wildcard (
*) در انتهای کلید. - Performance: این کوئری ممکن است روی دیتابیسهای ردیس بسیار حجیم (میلیونها کلید) کمی کند باشد، اما در اسکیل فعلی قابل قبول است.
- خروجی به صورت
Dictionary(Map) است، نه آرایه ساده؛ تا دسترسی به یک تاریخ خاص در کلاینت (Client-Side) راحتتر باشد (O(1)).
GET /v2/account-history/all-monthly
Route Info
| Method | Endpoint | Controller | Middleware |
| GET | /v2/account-history/all-monthly | AccountHistoryController@getAllMonthlyBalances | authWithJwt |
شرح عملکرد (Functionality)
این API برای دریافت یکجای تمام ماندههای ماهانه یک سال مشخص استفاده میشود.
- کاربرد: مناسب برای نمایش جداول خلاصه وضعیت سالانه یا نمودارهای میلهای ماهانه.
- نحوه خواندن: دادهها را به صورت دستهای (Bulk) با استفاده از الگوی Wildcard از Redis استخراج میکند.
- بدون محاسبه (No Calculation): این متد صرفاً دادههای موجود در کش را میخواند. اگر برای ماهی دادهای محاسبه نشده باشد، در خروجی ظاهر نخواهد شد (Trigger محاسبه ندارد).
ورودی (Query Parameters)
?colleague_id=108&year=1403
قوانین اعتبارسنجی (Validation Rules)
- colleague_id: الزامی | integer | موجود در جدول
colleagues. - year: الزامی | integer | بازه 1300 تا 1500.
منطق اجرا (Execution Logic)
- بررسی ورودیها توسط Validator.
- جستجو در Redis با الگوی:
MONTHLY_BALANCE_KEY{colleagueId}:{year}:*. - استخراج کلیدها و سپس دریافت مقادیر (Values).
- استخراج شماره ماه: سیستم ماه را از انتهای کلید Redis جدا میکند (مثلاً از
...:1403:05مقدار05برداشته میشود). - ذخیره در آرایه خروجی به صورتی که کلید آرایه، شماره ماه باشد.
- مرتبسازی بر اساس ماه (
ksort) تا دادهها به ترتیب زمانی (فروردین تا اسفند) باشند. - بازگشت پاسخ نهایی.
پاسخ موفق (200 OK)
خروجی به صورت Map (Dictionary) است که کلیدهای آن شماره ماه (معمولاً دو رقمی مثل "01") هستند.
{
"payload": {
"01": {
"credit": 1000000,
"debit": 500000,
"balance": 500000
},
"02": {
"credit": 200000,
"debit": 0,
"balance": 700000
},
"12": {
"credit": 0,
"debit": 100000,
"balance": 600000
}
},
"meta": {
"colleague_id": 108,
"year": 1403,
"total_months": 3,
"cached": true,
"timestamp": "2025-12-01T16:30:00+03:30"
}
}
پاسخهای خطا (Error Responses)
خطای اعتبارسنجی (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The selected colleague id is invalid."
},
"meta": { ... }
}
خطای سرور (500)
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Redis connection failed"
},
"meta": { ... }
}
توضیحات فنی (Meta)
- Redis Key Parse: منطق استخراج ماه به صورت
substr($key, strrpos($key, ':') + 1)است. این یعنی ساختار کلید باید دقیقاً با:جدا شده باشد. - Missing Data: اگر ماه خاصی (مثلاً تیرماه) در خروجی نیست، به این معنی است که کاربر هنوز درخواست محاسبه برای آن سال/ماه را نداده است. فرانتاند باید این را مدیریت کند (مثلاً نمایش مقدار 0 یا دکمه "محاسبه").
DELETE /v2/account-history/cache
Route Info
| Method | Endpoint | Controller | Middleware |
| DELETE | /v2/account-history/cache | AccountHistoryController@clearCache | authWithJwt |
شرح عملکرد (Functionality)
این API برای پاکسازی دستی کش (Invalidation) استفاده میشود. زمانی که دادههای زیرساختی تغییر کردهاند و نیاز است محاسبات مجدداً از صفر انجام شوند، این متد صدا زده میشود.
- دامنه پاکسازی: هم کش روزانه و هم کش ماهانه مربوط به همکار و سال ورودی را حذف میکند.
- نتیجه: پس از اجرای این متد، اولین درخواست بعدی به روتهای دریافت اطلاعات (مثل
getMonthlyBalance)، باعث محاسبه مجدد (Recalculation) خواهد شد.
ورودیها (Input Parameters)
پارامترها میتوانند در Query String یا Body (JSON) ارسال شوند.
{
"colleague_id": 108,
"year": 1403
}
قوانین اعتبارسنجی (Validation Rules)
- colleague_id: الزامی | integer | موجود در جدول
colleagues. - year: الزامی | integer | بازه 1300 تا 1500.
منطق اجرا (Execution Logic)
- اعتبارسنجی ورودیها.
- گام اول (روزانه): جستجوی تمام کلیدهای روزانه با الگوی
DAILY_BALANCE_KEY{colleagueId}:{year}:*و حذف آنها. - گام دوم (ماهانه): جستجوی تمام کلیدهای ماهانه با الگوی
MONTHLY_BALANCE_KEY{colleagueId}:{year}:*و حذف آنها. - شمارش تعداد کلیدهای حذف شده.
- ثبت عملیات در Log سیستم (
Log::info). - بازگشت تعداد کلیدهای حذف شده به کلاینت.
پاسخ موفق (200 OK)
{
"payload": {
"cleared_keys": 25
},
"meta": {
"colleague_id": 108,
"year": 1403,
"timestamp": "2025-12-01T17:05:00+03:30"
}
}
cleared_keys مجموع تعداد روزها و ماههای حذف شده است. پاسخهای خطا (Error Responses)
خطای سمت سرویس (500)
اگر اتصال به Redis در حین حذف قطع شود:
{
"error": {
"code": "CACHE_CLEAR_ERROR",
"message": "Redis connection failed..."
},
"meta": { ... }
}
نکات فنی (Technical Notes)
- Non-Atomic Operation: عملیات حذف در دو مرحله (ابتدا روزانه، سپس ماهانه) انجام میشود. اگرچه احتمال آن کم است، اما در صورت بروز خطا در وسط کار، ممکن است نیمی از کش پاک شود و نیمی باقی بماند.
- Race Condition: بین زمانی که دستور
Redis::keysکلیدها را پیدا میکند و زمانی کهRedis::delآنها را پاک میکند، اگر کلید جدیدی (در کسری از ثانیه) اضافه شود، آن کلید جدید پاک نخواهد شد. این موضوع در سیستمهای با نرخ نوشتن بسیار بالا اهمیت دارد اما در اینجا قابل چشمپوشی است.
POST /v2/redis-accounting/create-missing-documents
Route Info
| Method | Endpoint | Controller | Middleware |
| POST | /v2/redis-accounting/create-missing-documents | RedisAccountingController@createMissingDocuments | authWithJwt, shamsiDate |
شرح عملکرد (Functionality)
این API مسئول بررسی وجود اسناد مفقود و سپس ایجاد خودکار آنها در سیستم است. منطق ساخت اسناد بهطور کامل در سرویس redisAccountingService اجرا میشود و این روت فقط فراخوانی و مدیریت پاسخ را انجام میدهد.
- هیچ ورودیای نیاز ندارد.
- عملیات بهصورت Batch اجرا میشود.
- خروجی شامل تعداد کل اسناد پردازششده و تعداد اسناد ایجادشده است.
ورودیها (Input Parameters)
این روت هیچ پارامتر ورودیای در Query یا Body دریافت نمیکند.
منطق اجرا (Execution Logic)
- فراخوانی مستقیم متد
createMissingDocuments()از سرویس. - سرویس عملیات زیر را انجام میدهد:
- شناسایی اسناد مفقود
- ایجاد اسناد جدید
- محاسبه تعداد اسناد پردازششده
- Controller نتیجه سرویس را در ساختار JSON استاندارد برمیگرداند.
- در صورت بروز هرگونه Exception، پاسخ خطای 500 بازگردانده میشود.
پاسخ موفق (200 OK)
{
"success": true,
"data": {
"total_processed": 42,
"created": 42,
"existing": 0
},
"message": "پردازش کامل شد. 42 سند ایجاد شد."
}
پاسخهای خطا (Error Responses)
خطای سمت سرویس (500)
در صورت بروز Exception در سرویس:
{
"success": false,
"message": "خطا در ایجاد اسناد: Redis connection failed"
}
نکات فنی (Technical Notes)
- این روت هیچ عملیات Read ندارد؛ صرفاً اجرای یک Job سنگین است.
- اجرا میتواند بسته به حجم داده چند ثانیه طول بکشد.
- توصیه میشود فقط توسط کاربران سطح بالا (Admins) فراخوانی شود.
POST /v2/redis-accounting/create-missing-documents
Redis Accounting Sync
مستندات فنی سرویس حسابداری - نسخه 2.0
مسیر پردازش داده (Data Flow)
Header Requirements
نمونه پاسخها (Responses)
{
"success": true,
"data": {
"total_scanned": 1540,
"total_processed": 12,
"details": [
"Document #8891 synced from Redis."
]
},
"message": "پردازش کامل شد. 12 سند ایجاد شد."
}
{
"success": false,
"message": "خطا در ایجاد اسناد: Redis connection timed out."
}
POST /v2/redis-accounting/create-missing-documents
Create Missing Documents
این اندپوینت در سرویس Redis Accounting وظیفه دارد همه مدارکی که باید وجود داشته باشند اما در Redis ثبت نشدهاند را شناسایی و ایجاد کند. این عملیات معمولاً در موارد زیر استفاده میشود:
- بازسازی دادههای از دست رفته
- یکپارچهسازی وضعیت حسابها
- رفع inconsistency بین DB و Redis
Request Overview
/v2/redis-accounting/create-missing-documentsRequest Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
organization_id |
integer | yes | شناسه سازمانی که باید مدارک آن بررسی و تکمیل شود |
sync_mode |
string | no | نوع همگامسازی. مقادیر مجاز: full, delta |
dry_run |
boolean | no | اگر true باشد، فقط موارد گمشده شناسایی میشود ولی ایجاد نمیشود |
Request Example
{
"organization_id": 1204,
"sync_mode": "delta",
"dry_run": false
}
Response Schema
| Field | Type | Description |
|---|---|---|
status |
string | وضعیت اجرای عملیات (success یا failed) |
created_count |
integer | تعداد مدارک جدیدی که ایجاد شدهاند |
missing_ids |
array | لیست شناسههایی که گم شده بودند (در حالت dry_run همیشه پر است) |
execution_time_ms |
integer | مدت زمان اجرای فرآیند بر حسب میلیثانیه |
Response Example
{
"status": "success",
"created_count": 32,
"missing_ids": [
"redis:doc:93422",
"redis:doc:93424",
"redis:doc:93455"
],
"execution_time_ms": 128
}
Process Flow
POST /v2/redis-accounting/save-manual-document
Save Manual Document
این اندپوینت یک سند دستی (Manual Document) را از دیتابیس بارگذاری کرده و ردیفهای آن را در Redis ذخیره میکند. این عملیات زمانی استفاده میشود که بخواهیم وضعیت یک سند دستی در Redis بازسازی یا هماهنگسازی شود.
Request Overview
/v2/redis-accounting/save-manual-documentRequest Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
id |
integer | yes | شناسه سند دستی (manual_documents.id) |
Request Example
{
"id": 9234
}
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت انجام عملیات |
message |
string | پیام وضعیت ذخیره |
data.manual_document_id |
integer | شناسه سندی که پردازش شد |
data.success |
boolean | نتیجه واقعی ذخیرهسازی از سرویس |
data.message |
string | پیام بازگشتی از سرویس ذخیرهسازی |
{
"success": true,
"message": "Saved 3 documents to Redis",
"data": {
"success": true,
"message": "Saved 3 documents to Redis",
"manual_document_id": 9234
}
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"id": [
"The id field is required"
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در ذخیره سند: <error details>"
}
Process Flow
POST /v2/redis-accounting/save-document
Save Document
این اندپوینت یک سند حسابداری را در هسته حسابداری Redis ذخیره میکند. عملیات شامل ایجاد Hash سند، ساخت ایندکسهای زمانی و حسابداری، و بهروزرسانی ماندهها در سه سطح گروه/کل/حساب است. این متد ستون فقرات سیستم حسابداری Redis محسوب میشود.
Request Overview
/v2/redis-accounting/save-documentRequest Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
serial |
integer | optional | شماره سریال سند. اگر ارسال نشود، سیستم خودش تولید میکند. |
description |
string | yes | شرح سند |
jalaliDate |
string | yes | تاریخ شمسی با فرمت YYYY-MM-DD (الزامی) |
debtor |
number | optional | مبلغ بدهکار (پیشفرض: 0) |
creditor |
number | optional | مبلغ بستانکار (پیشفرض: 0) |
group |
integer | yes | شناسه گروه حسابداری |
general |
integer | yes | شناسه کل |
account |
integer | yes | شناسه معین |
subsidiary |
integer | yes | شناسه تفضیلی |
datetime |
string | optional | تاریخزمان میلادی (اگر ارسال نشود، now() استفاده میشود) |
Request Example
{
"serial": 120,
"description": "خرید قطعات",
"jalaliDate": "1403-09-01",
"debtor": 250000,
"creditor": 0,
"group": 10,
"general": 101,
"account": 1011,
"subsidiary": 1011003
}
Response (Success)
| Field | Type | Description |
|---|---|---|
status |
boolean | وضعیت نهایی ذخیره سند |
id |
integer | شناسه سند ایجادشده در Redis |
serial |
integer | سریال اختصاص دادهشده |
time |
float | مدت زمان اجرای عملیات (ms) |
{
"status": true,
"serial": 120,
"id": 934221,
"time": 1.78
}
Response (Validation Error)
{
"status": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"jalaliDate": [
"The jalaliDate must match YYYY-MM-DD"
]
}
}
Response (Server Error)
{
"status": false,
"message": "خطا در ذخیره سند",
"trace": [...]
}
Internal Redis Architecture
| Key | Type | Description |
|---|---|---|
accounting:doc:{id} |
Hash | اطلاعات کامل سند ذخیرهشده |
accounting:docs:by_datetime |
SortedSet | مرتبسازی تمام اسناد بر اساس timestamp |
accounting:docs:group:{groupId} |
Set | ایندکس اسناد گروه |
accounting:docs:general:{generalId} |
Set | ایندکس سطح کل |
accounting:docs:account:{accountId} |
Set | ایندکس سطح معین |
accounting:docs:subsidiary:{subsidiaryId} |
Set | ایندکس سطح تفضیلی |
Process Flow
DELETE /v2/redis-accounting/delete-document
Delete Document
این اندپوینت یک سند حسابداری را از هسته Redis حذف میکند. عملیات شامل حذف Hash سند، حذف شناسه از تمامی ایندکسها، و پاکسازی کامل کلیدهای بالانس مرتبط با گروه/کل/معین/تفضیلی است. توجه: در منطق فعلی سیستم، بالانسها recalculate نمیشوند و بهجای آن حذف کامل میگردند.
Request Overview
/v2/redis-accounting/delete-documentRequest Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
id |
integer | yes | شناسه سندی که باید حذف شود |
Request Example
{
"id": 934221
}
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت نهایی حذف سند |
message |
string | پیام موفقیتآمیز |
{
"success": true,
"message": "سند با موفقیت حذف شد"
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"id": [
"The id field is required."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در حذف سند: Internal server error message..."
}
Internal Redis Architecture
| Key | Type | Description |
|---|---|---|
accounting:doc:{id} |
Hash | سند ذخیرهشده که باید حذف شود |
accounting:docs:by_date |
SortedSet | حذف شناسه از ایندکس تاریخ میلادی |
accounting:docs:by_jalali_date |
SortedSet | حذف شناسه از ایندکس تاریخ شمسی |
accounting:docs:group:{groupId} |
Set | ایندکس سطح گروه — حذف شناسه |
accounting:docs:general:{generalId} |
Set | ایندکس سطح کل — حذف شناسه |
accounting:docs:account:{accountId} |
Set | ایندکس سطح معین — حذف شناسه |
accounting:docs:subsidiary:{subsidiaryId} |
Set | ایندکس سطح تفضیلی — حذف شناسه |
balance:group:{groupId} |
Hash | حذف کامل مانده سطح گروه |
balance:general:{generalId} |
Hash | حذف کامل مانده سطح کل |
balance:account:{accountId} |
Hash | حذف کامل مانده سطح معین |
balance:subsidiary:{subsidiaryId} |
Hash | حذف کامل مانده سطح تفضیلی |
Process Flow
DELETE /v2/redis-accounting/clear-all
Clear All Documents
این اندپوینت تمام اسناد حسابداری ذخیرهشده در Redis را حذف میکند. عملیات شامل حذف تمامی Hashها، پاکسازی کامل تمام ایندکسها (Set / Sorted Set)، و حذف همه کلیدهای بالانس در سطوح گروه، کل، معین و تفضیلی است. این عملیات یک عملیات مخرب و غیرقابل بازگشت محسوب میشود.
Request Overview
/v2/redis-accounting/clear-allRequest Body Schema
این اندپوینت هیچ ورودیای نمیپذیرد.
| Field | Type | Required | Description |
|---|---|---|---|
| — | — | — | بدون ورودی |
Request Example
DELETE /v2/redis-accounting/clear-all
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت عملیات |
message |
string | پیام نهایی |
{
"success": true,
"message": "تمام اسناد از Redis حذف شدند"
}
Response (Server Error)
{
"success": false,
"message": "خطا در حذف اسناد: Internal server error..."
}
Internal Redis Architecture
| Key Pattern | Type | Description |
|---|---|---|
accounting:doc:* |
Hash | تمام اسناد ذخیرهشده — همگی حذف میشوند |
accounting:docs:* |
Set / SortedSet | تمام ایندکسها شامل تاریخ، گروه، کل، معین، تفضیلی — حذف کامل |
balance:group:* |
Hash | تمام ماندههای سطح گروه — حذف کامل |
balance:general:* |
Hash | تمام ماندههای سطح کل — حذف کامل |
balance:account:* |
Hash | تمام ماندههای سطح معین — حذف کامل |
balance:subsidiary:* |
Hash | تمام ماندههای سطح تفضیلی — حذف کامل |
Process Flow
GET /v2/redis-accounting/documents/subsidiary
Get Documents by Subsidiary
این اندپوینت لیست اسناد حسابداری مربوط به یک کد تفضیلی را از Redis برمیگرداند. دیتای خروجی شامل اسناد، مانده فعلی، تعداد کل اسناد، و در صورت درخواست، مانده تجمیعی از ابتدای سال تا تاریخ مشخص است. عملیات بر اساس ایندکس Redis accounting:docs:subsidiary:{code} انجام میشود.
Request Overview
/v2/redis-accounting/documents/subsidiaryQuery Parameters Schema
| Field | Type | Required | Description |
|---|---|---|---|
code |
integer | yes | کد تفضیلی که اسناد باید براساس آن فیلتر شوند |
from_year_start |
boolean | optional | اگر true باشد، مانده از ابتدای سال شمسی تا امروز یا تا تاریخ مشخص محاسبه میشود |
to_date |
string (YYYY-MM-DD) | optional | تاریخ پایانی برای محاسبه year_start_balance (پیشفرض: تاریخ امروز) |
Request Example
GET /v2/redis-accounting/documents/subsidiary?code=1011003&from_year_start=true&to_date=1403-09-15
Response (Success)
ساختار خروجی:
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت نهایی درخواست |
data.documents |
array | لیست اسناد؛ توجه: مقدار برگشتی شامل [diffProcess, docs] است |
data.balance |
object | مانده فعلی Redis برای تفضیلی |
data.total_documents |
integer | تعداد کل اسناد برگرداندهشده |
data.year_start_balance |
object | فقط اگر from_year_start=true باشد |
{
"success": true,
"data": {
"documents": [
[
[1733301120.123, 1733301120.125, ...],
[
{
"id": "934221",
"date": "1403-09-01",
"debit": "250000",
"credit": "0",
"group": "10",
"general": "101",
"account": "1011",
"subsidiary": "1011003"
}
]
],
"balance": {
"debit": 250000,
"credit": 0,
"remaining": 250000
},
"total_documents": 1,
"year_start_balance": {
"debit": 250000,
"credit": 0,
"remaining": 250000,
"year_start": "1403-01-01",
"to_date": "1403-09-15"
}
}
}
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"code": [
"The code field is required."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در دریافت اسناد: Internal server error..."
}
Internal Redis Architecture
| Key | Type | Description |
|---|---|---|
accounting:docs:subsidiary:{code} |
Set | لیست شناسه اسناد مرتبط با این تفضیلی |
accounting:doc:{id} |
Hash | اطلاعات کامل سند |
balance:subsidiary:{code} |
Hash | مانده فعلی سندهای مربوط به این تفضیلی |
accounting:docs:by_date |
SortedSet | ایندکس تمام اسناد بر اساس تاریخ |
Process Flow
GET /v2/redis-accounting/documents/account
Get Documents by Account
این اندپوینت لیست اسناد حسابداری مربوط به یک کد معین را از Redis بازیابی میکند. خروجی شامل اسناد، مانده فعلی، تعداد اسناد، و در صورت فعال بودن، ماندهٔ تجمیعی از ابتدای سال است. عملیات بر اساس ایندکس Redis accounting:docs:account:{code} انجام میشود.
Request Overview
/v2/redis-accounting/documents/accountQuery Parameters Schema
| Field | Type | Required | Description |
|---|---|---|---|
code |
integer | yes | کد معین که اسناد براساس آن فیلتر میشوند |
from_year_start |
boolean | optional | اگر true باشد، مانده تجمیعی از ابتدای سال شمسی محاسبه میشود |
to_date |
string (YYYY-MM-DD) | optional | تاریخ پایانی برای محاسبه year_start_balance (پیشفرض: تاریخ امروز) |
Request Example
GET /v2/redis-accounting/documents/account?code=1011&from_year_start=true
Response (Success)
ساختار خروجی:
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت نهایی |
data.documents |
array | خروجی دقیقاً شامل [diffProcess, docs] است diffProcess آرایهای از timestamps docs آرایه سندها |
data.balance |
object | مانده فعلی Redis برای حساب معین |
data.total_documents |
integer | توجه: در سیستم فعلی مقدار آن ۲ است چون count روی آرایه دوبخشی [diffProcess, docs] اعمال شده (این یک نقص منطقی در پیادهسازی است) |
data.year_start_balance |
object | فقط اگر from_year_start=true ارسال میشود |
{
"success": true,
"data": {
"documents": [
[
[1733301120.101, 1733301120.103, ...],
[
{
"id": "2210",
"date": "1403-05-01",
"debit": "500000",
"credit": "0",
"group": "10",
"general": "101",
"account": "1011",
"subsidiary": "1011001"
}
]
],
"balance": {
"debit": 500000,
"credit": 0,
"remaining": 500000
},
"total_documents": 2,
"year_start_balance": {
"debit": 500000,
"credit": 0,
"remaining": 500000,
"year_start": "1403-01-01",
"to_date": "1403-09-15"
}
}
}
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"code": [
"The code field is required."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در دریافت اسناد: Internal server error..."
}
Internal Redis Architecture
| Key | Type | Description |
|---|---|---|
accounting:docs:account:{code} |
Set | لیست شناسه اسناد مربوط به این معین |
accounting:doc:{id} |
Hash | اطلاعات کامل سند |
balance:account:{code} |
Hash | مانده حساب در Redis |
accounting:docs:by_date |
SortedSet | ایندکس سراسری تمام اسناد بر اساس تاریخ برای year_start_balance |
Process Flow
GET /v2/redis-accounting/documents/general
Get Documents by General
این اندپوینت اسناد حسابداری مرتبط با یک کد کل (General Code) را از Redis بازیابی میکند. خروجی شامل: لیست اسناد، مانده فعلی، تعداد اسناد، و اگر درخواست شده باشد، مانده تجمیعی از ابتدای سال است. دادهها از ایندکس Redis با الگو accounting:docs:general:{code} استخراج میشوند.
Request Overview
/v2/redis-accounting/documents/generalQuery Parameters
| Field | Type | Required | Description |
|---|---|---|---|
code |
integer | yes | کد کل هدف |
from_year_start |
boolean | optional | در صورت true بودن، مانده از ابتدای سال شمسی تا تاریخ مشخص محاسبه میشود |
to_date |
string (YYYY-MM-DD) | optional | تاریخ پایانی جهت محاسبه year_start_balance (پیشفرض: امروز) |
Request Example
GET /v2/redis-accounting/documents/general?code=101&from_year_start=true
Response (Success)
ساختار خروجی:
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت نهایی |
data.documents |
array | مقدار برگشتی دقیقاً بهصورت زیر است: [ diffProcess[], documents[] ] diffProcess شامل timestamps مراحل عملکرد Redis است. documents آرایه کامل اسناد میباشد. |
data.balance |
object | مانده فعلی کل در Redis |
data.total_documents |
integer | توجه: مقدار فعلی همیشه ۲ است چون count روی آرایه دوبخشی اعمال میشود (این یک ایراد منطقی در پیادهسازی فعلی API است) |
data.year_start_balance |
object | در صورت درخواست (from_year_start = true) |
{
"success": true,
"data": {
"documents": [
[
[1733301120.201, 1733301120.203, ...],
[
{
"id": "88401",
"date": "1403-03-15",
"debit": "150000",
"credit": "0",
"group": "10",
"general": "101",
"account": "1011",
"subsidiary": "1011001"
}
]
],
"balance": {
"debit": 150000,
"credit": 0,
"remaining": 150000
},
"total_documents": 2,
"year_start_balance": {
"debit": 150000,
"credit": 0,
"remaining": 150000,
"year_start": "1403-01-01",
"to_date": "1403-09-15"
}
}
}
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"code": [
"The code field is required."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در دریافت اسناد: Internal server error..."
}
Internal Redis Architecture
| Key Pattern | Type | Description |
|---|---|---|
accounting:docs:general:{code} |
Set | لیست ID اسناد مرتبط با کد کل |
accounting:doc:{id} |
Hash | اطلاعات کامل سند |
balance:general:{code} |
Hash | مانده فعلی Redis برای کل |
accounting:docs:by_date |
SortedSet | ایندکس سراسری برای year_start_balance |
Process Flow
GET /v2/redis-accounting/documents/group
Get Documents by Group
این اندپوینت وظیفه دارد تمام اسناد حسابداری مرتبط با یک کد گروه (Group Code) را از Redis بازیابی کند. دادهها شامل اسناد، مانده فعلی، تعداد اسناد، و در صورت درخواست، مانده تجمیعی از ابتدای سال شمسی است. استخراج داده از Redis بر اساس الگوی زیر انجام میشود: accounting:docs:group:{code}.
Request Overview
/v2/redis-accounting/documents/groupQuery Parameters
| Field | Type | Required | Description |
|---|---|---|---|
code |
integer | yes | کد گروه موردنظر |
from_year_start |
boolean | optional | اگر true باشد، ماندهٔ تجمیعی از ابتدای سال شمسی محاسبه میشود |
to_date |
string (YYYY-MM-DD) | optional | تاریخ نهایی جهت محاسبه year_start_balance (پیشفرض: امروز) |
Request Example
GET /v2/redis-accounting/documents/group?code=10&from_year_start=true
Response (Success)
ساختار خروجی دقیقاً مطابق پیادهسازی فعلی RedisAccountingService است.
| Field | Type | Description |
|---|---|---|
success |
boolean | True در صورت موفقیت عملیات |
data.documents |
array | آرایه دوبخشی: [ diffProcess[], docs[] ] diffProcess شامل timestamps اجرای مراحل Redis است. docs شامل اطلاعات کامل هر سند است. |
data.balance |
object | ماندهٔ فعلی گروه در Redis |
data.total_documents |
integer | مقدار همیشه ۲ است چون count روی آرایه دوبخشی اعمال شده (نقص منطقی در سیستم فعلی) |
data.year_start_balance |
object | فقط اگر from_year_start=true ارسال میشود |
{
"success": true,
"data": {
"documents": [
[
[1733301001.102, 1733301001.105, ...],
[
{
"id": "88401",
"date": "1403-03-15",
"debit": "150000",
"credit": "0",
"group": "10",
"general": "101",
"account": "1011",
"subsidiary": "1011001"
}
]
],
"balance": {
"debit": 150000,
"credit": 0,
"remaining": 150000
},
"total_documents": 2,
"year_start_balance": {
"debit": 150000,
"credit": 0,
"remaining": 150000,
"year_start": "1403-01-01",
"to_date": "1403-09-15"
}
}
}
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"code": [
"The code field is required."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در دریافت اسناد: Internal server error..."
}
Internal Redis Architecture
| Key Pattern | Type | Description |
|---|---|---|
accounting:docs:group:{code} |
Set | شناسه اسناد مرتبط با این گروه |
accounting:doc:{id} |
Hash | اطلاعات کامل هر سند |
balance:group:{code} |
Hash | مانده فعلی گروه در Redis |
accounting:docs:by_date |
SortedSet | برای year_start_balance استفاده میشود |
Process Flow
GET /v2/redis-accounting/documents/group
Get Documents by Group
این اندپوینت وظیفه دارد تمام اسناد حسابداری مرتبط با یک کد گروه (Group Code) را از Redis بازیابی کند. دادهها شامل اسناد، مانده فعلی، تعداد اسناد، و در صورت درخواست، مانده تجمیعی از ابتدای سال شمسی است. استخراج داده از Redis بر اساس الگوی زیر انجام میشود: accounting:docs:group:{code}.
Request Overview
/v2/redis-accounting/documents/groupQuery Parameters
| Field | Type | Required | Description |
|---|---|---|---|
code |
integer | yes | کد گروه موردنظر |
from_year_start |
boolean | optional | اگر true باشد، ماندهٔ تجمیعی از ابتدای سال شمسی محاسبه میشود |
to_date |
string (YYYY-MM-DD) | optional | تاریخ نهایی جهت محاسبه year_start_balance (پیشفرض: امروز) |
Request Example
GET /v2/redis-accounting/documents/group?code=10&from_year_start=true
Response (Success)
ساختار خروجی دقیقاً مطابق پیادهسازی فعلی RedisAccountingService است.
| Field | Type | Description |
|---|---|---|
success |
boolean | True در صورت موفقیت عملیات |
data.documents |
array | آرایه دوبخشی: [ diffProcess[], docs[] ] diffProcess شامل timestamps اجرای مراحل Redis است. docs شامل اطلاعات کامل هر سند است. |
data.balance |
object | ماندهٔ فعلی گروه در Redis |
data.total_documents |
integer | مقدار همیشه ۲ است چون count روی آرایه دوبخشی اعمال شده (نقص منطقی در سیستم فعلی) |
data.year_start_balance |
object | فقط اگر from_year_start=true ارسال میشود |
{
"success": true,
"data": {
"documents": [
[
[1733301001.102, 1733301001.105, ...],
[
{
"id": "88401",
"date": "1403-03-15",
"debit": "150000",
"credit": "0",
"group": "10",
"general": "101",
"account": "1011",
"subsidiary": "1011001"
}
]
],
"balance": {
"debit": 150000,
"credit": 0,
"remaining": 150000
},
"total_documents": 2,
"year_start_balance": {
"debit": 150000,
"credit": 0,
"remaining": 150000,
"year_start": "1403-01-01",
"to_date": "1403-09-15"
}
}
}
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"code": [
"The code field is required."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در دریافت اسناد: Internal server error..."
}
Internal Redis Architecture
| Key Pattern | Type | Description |
|---|---|---|
accounting:docs:group:{code} |
Set | شناسه اسناد مرتبط با این گروه |
accounting:doc:{id} |
Hash | اطلاعات کامل هر سند |
balance:group:{code} |
Hash | مانده فعلی گروه در Redis |
accounting:docs:by_date |
SortedSet | برای year_start_balance استفاده میشود |
Process Flow
GET /v2/redis-accounting/documents/date-range
Get Documents By Date Range
این اندپوینت اسناد حسابداری را بر اساس یک بازهٔ تاریخ شمسی (Jalali) از Redis استخراج میکند. استخراج اسناد بر اساس ایندکس تاریخ شمسی accounting:docs:by_jalali_date انجام شده و خروجی شامل لیست اسناد، تعداد کل اسناد و اطلاعات معتبر از بازهٔ ارسالشده است.
Request Overview
/v2/redis-accounting/documents/date-rangeQuery Parameters
| Field | Type | Required | Description |
|---|---|---|---|
start_date |
string (YYYY-MM-DD) | yes | تاریخ شمسی شروع بازه |
end_date |
string (YYYY-MM-DD) | yes | تاریخ شمسی پایان بازه |
Request Example
GET /v2/redis-accounting/documents/date-range?start_date=1403-01-01&end_date=1403-03-31
Validation Rules
- start_date: required + regex YYYY-MM-DD
- end_date: required + regex YYYY-MM-DD
- start_date <= end_date
- هر دو تاریخ باید با Jalalian قابل تبدیل باشند
Response (Success)
ساختار خروجی بر اساس منطق RedisAccountingService است.
| Field | Type | Description |
|---|---|---|
success |
boolean | True در صورت موفقیت عملیات |
data.documents |
array | لیست کامل اسناد فلت (بدون diffProcess) |
data.total_documents |
integer | تعداد دقیق اسناد بازگرداندهشده |
data.date_range |
object | جزئیات بازه شمسی و میلادی مبنا |
{
"success": true,
"data": {
"documents": [
{
"id": "88401",
"date": "1403-02-15",
"debit": "450000",
"credit": "0",
"group": "10",
"general": "101",
"account": "1011",
"subsidiary": "1011001"
}
],
"total_documents": 1,
"date_range": {
"start": "1403-01-01",
"end": "1403-03-31",
"start_shamsi": "1403/01/01",
"end_shamsi": "1403/03/31"
}
}
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی - تاریخ باید به صورت شمسی (YYYY-MM-DD) باشد",
"errors": {
"start_date": [
"The start_date format is invalid."
]
}
}
Response (Start > End)
{
"success": false,
"message": "تاریخ شروع باید کوچکتر یا مساوی تاریخ پایان باشد"
}
Response (Server Error)
{
"success": false,
"message": "خطا در دریافت اسناد: Internal server error..."
}
Internal Redis Architecture
| Key Pattern | Type | Description |
|---|---|---|
accounting:docs:by_jalali_date |
Sorted Set | ایندکس اصلی بازه تاریخ؛ score = YYYYMMDD شمسی |
accounting:doc:{id} |
Hash | اطلاعات کامل سند |
Process Flow
GET /v2/redis-accounting/balance
Get Redis Balance
این اندپوینت مانده حساب را بر اساس نوع و کد حساب از Redis بازیابی میکند. ماندهها در کلیدهای balance:{type}:{code} ذخیره میشوند و شامل سه مقدار debit، credit و remaining هستند.
Request Overview
/v2/redis-accounting/balanceQuery Parameters
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | یکی از مقادیر: group، general، account، subsidiary |
code |
integer | yes | کد حساب موردنظر |
Request Example
GET /v2/redis-accounting/balance?type=group&code=10
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | True در صورت موفقیت |
data.debit |
float | مقدار بدهکار |
data.credit |
float | مقدار بستانکار |
data.remaining |
float | debit - credit |
{
"success": true,
"data": {
"debit": 450000,
"credit": 150000,
"remaining": 300000
}
}
No Balance Found
در صورتی که کلید balance:{type}:{code} وجود نداشته باشد، خروجی بدون ارور و به شکل زیر است:
{
"success": true,
"data": {
"debit": 0,
"credit": 0,
"remaining": 0
}
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"type": [
"The type field is required."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در دریافت مانده: Redis connection error..."
}
Internal Redis Architecture
| Key Pattern | Type | Description |
|---|---|---|
balance:{type}:{code} |
Hash | ساختار مانده حساب
|
Process Flow
GET /v2/redis-accounting/stats
Get Redis Statistics
این اندپوینت آمار کلی Redis را در حوزه سیستم حسابداری استخراج میکند. شامل تعداد اسناد، تعداد ایندکسها، تعداد ماندهها و میزان حافظه مصرف شده Redis. این روت برای استفاده داخلی (Ops, Engineering, Monitoring) طراحی شده است.
Request Overview
/v2/redis-accounting/statsAuthentication
این اندپوینت کاملاً محافظتشده است. فقط کاربران احراز هویت شده و دارای JWT معتبر دسترسی دارند.
Request Parameters
بدون ورودی. این روت هیچ query یا body ندارد.
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | True در صورت موفقیت |
data.total_documents |
integer | تعداد کل اسناد ذخیرهشده در Redis (کلیدهای accounting:doc:*) |
data.total_indexes |
integer | تعداد کل ایندکسهای Redis (کلیدهای accounting:docs:*) |
data.total_balances |
integer | تعداد کل کلیدهای مانده (balance:*) |
data.memory_usage |
string | حجم حافظه مصرفشده Redis به صورت human-readable (مثلاً 5.21M) |
{
"success": true,
"data": {
"total_documents": 3412,
"total_indexes": 128,
"total_balances": 904,
"memory_usage": "6.12M"
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در دریافت آمار: Redis connection lost"
}
Internal Architecture Notes
برای استخراج آمار، سرویس داخلی redisAccountingService از دستور Redis::keys استفاده میکند. این عملیات نمایی و غیرمقیاسپذیر است و فقط برای محیطهای داخلی مناسب است (Production-safe only when dataset is controlled).
| Category | Key Pattern | Description |
|---|---|---|
| Documents | accounting:doc:* |
سندهای ذخیرهشده |
| Indexes | accounting:docs:* |
تمام ZSetها و مجموعههای ایندکسی |
| Balances | balance:* |
ماندههای سطوح مختلف |
POST /v2/batch-accounting/process/date-range
Batch Process Documents by Date Range
این اندپوینت پردازش دستهای اسناد حسابداری را در یک بازه تاریخی شمسی انجام میدهد. تمام اسنادی که در تاریخ مشخصشده قرار دارند از دیتابیس فراخوانی شده، در چانکهای کنترلشده پردازش میشوند و دادههای Redis Accounting شامل اسناد و ماندهها آپدیت میگردند. این عملیات برای عملیاتهای Rebuild، Sync و Mass‑Recalculate استفاده میشود.
Request Overview
/v2/batch-accounting/process/date-rangeAuthentication
این اندپوینت کاملاً محافظتشده بوده و فقط کاربران دارای JWT معتبر میتوانند آن را اجرا کنند. در صورت نامعتبر بودن Token، خطاهای استاندارد میدلور AuthWithJWT بازگردانده میشود.
Request Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
start_date |
string (shamsi) | yes | تاریخ شروع بازه (فرمت شمسی YYYY-MM-DD) — توسط middleware اعتبارسنجی میشود |
end_date |
string (shamsi) | yes | تاریخ پایان بازه (فرمت شمسی YYYY-MM-DD) |
batch_size |
integer | no | تعداد پردازش در هر چانک. حداقل 10، حداکثر 1000. پیشفرض: 100 |
Request Example
POST /v2/batch-accounting/process/date-range
Content-Type: application/json
{
"start_date": "1403-01-01",
"end_date": "1403-01-31",
"batch_size": 200
}
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | True در صورت موفقیت |
data.total_processed |
integer | تعداد کل اسناد پردازششده در همه چانکها |
data.total_errors |
integer | تعداد خطاهای رخداده در پردازش Batchها |
data.batch_results |
array | خروجی کامل تمامی Batchها |
data.date_range |
object | محدوده تاریخی پردازششده |
{
"success": true,
"data": {
"success": true,
"total_processed": 283,
"total_errors": 2,
"batch_results": [
{
"processed": 100,
"errors": 0
},
{
"processed": 98,
"errors": 1
},
{
"processed": 85,
"errors": 1
}
],
"date_range": {
"start": "1403-01-01",
"end": "1403-01-31"
}
},
"message": "پردازش کامل شد. 283 سند پردازش شد."
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"start_date": [
"The start_date field format is invalid."
]
}
}
Response (Shamsi Date Middleware Error)
{
"success": false,
"message": "فیلد start_date باید تاریخ شمسی معتبر به فرمت YYYY-MM-DD باشد",
"field": "start_date",
"received_value": "2024-01-01",
"example": "1404-05-27"
}
Response (Server Error)
{
"success": false,
"message": "خطا در پردازش: Redis connection timeout"
}
Internal Processing Architecture
این عملیات تمام اسناد جدول manual_documents را در بازه تاریخی مشخص دریافت میکند، سپس آنها را در چانکهای کنترلشده پردازش میکند. بین هر Batch یک توقف 0.1 ثانیهای برای جلوگیری از فشار Redis انجام میشود.
| Module | Description |
|---|---|
manual_documents |
فیلتر بر اساس تاریخ و وضعیت (status != 5) |
processBatch() |
پردازش هر چانک (ذخیره سند، بروزرسانی ماندهها، آپدیت ایندکسها) |
usleep(100000) |
توقف 0.1s بین Batchها برای جلوگیری از فشار Redis |
Process Flow
POST /v2/batch-accounting/process/month
Process Batch Documents By Month
این اندپوینت اسناد حسابداری را بر اساس یک ماه شمسی مشخص دریافت کرده و آنها را به صورت دستهای (Batch Processing) پردازش میکند. ابتدا از تاریخ ورودی برای تعیین محدودهٔ کامل ماه استفاده میشود سپس عملیات پردازش دستهای بر اساس منطق processBatchByDateRange اجرا میگردد. خروجی شامل تعداد کل اسناد پردازششده، اطلاعات هر Batch و پیام نهایی مربوط به نام فارسی ماه است.
Request Overview
/v2/batch-accounting/process/monthBody Parameters
| Field | Type | Required | Description |
|---|---|---|---|
month_date |
string (YYYY-MM-DD) | yes | تاریخ شمسی داخل ماه مورد نظر. فقط برای تشخیص شماره ماه استفاده میشود. |
batch_size |
integer | no | سایز هر Batch. حداقل 10، حداکثر 1000. مقدار پیشفرض: 100. |
Request Example
POST /v2/batch-accounting/process/month
{
"month_date": "1403-05-01",
"batch_size": 200
}
Validation Rules
- month_date: required + regex YYYY-MM-DD
- batch_size: integer + min=10 + max=1000
- month_date باید با shamsiDate middleware معتبر تشخیص داده شود
Response (Success)
ساختار خروجی بر اساس BatchAccountingService::processBatchByDateRange است.
| Field | Type | Description |
|---|---|---|
success |
boolean | نتیجه نهایی پردازش |
data.total_processed |
integer | تعداد کل اسناد پردازششده |
data.total_errors |
integer | تعداد خطاهای رخداده در Batchها |
data.batches_count |
integer | تعداد کل Batchهای اجراشده |
message |
string | پیام نهایی شامل نام ماه فارسی استخراجشده با getMonthName |
{
"success": true,
"data": {
"success": true,
"total_processed": 450,
"total_errors": 0,
"batches_count": 3
},
"message": "پردازش ماه مرداد کامل شد. 450 سند پردازش شد."
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"month_date": [
"The month_date format is invalid."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در پردازش ماه: Internal server error..."
}
Internal Architecture
| Component | Description |
|---|---|
ShamsiDateHelper::startOfMonth |
استخراج اولین روز ماه شمسی به فرمت YYYY-MM-DD |
ShamsiDateHelper::endOfMonth |
استخراج آخرین روز ماه شمسی |
processBatchByDateRange() |
منطق اصلی Batch Processing روی بازهٔ تاریخ |
getMonthName() |
تبدیل شماره ماه به نام فارسی (فروردین تا اسفند) |
Process Flow
POST /v2/batch-accounting/process/month
Process Batch Documents By Month
این اندپوینت اسناد حسابداری را بر اساس یک ماه شمسی مشخص دریافت کرده و آنها را به صورت دستهای (Batch Processing) پردازش میکند. ابتدا از تاریخ ورودی برای تعیین محدودهٔ کامل ماه استفاده میشود سپس عملیات پردازش دستهای بر اساس منطق processBatchByDateRange اجرا میگردد. خروجی شامل تعداد کل اسناد پردازششده، اطلاعات هر Batch و پیام نهایی مربوط به نام فارسی ماه است.
Request Overview
/v2/batch-accounting/process/monthBody Parameters
Request Example
POST /v2/batch-accounting/process/month
{
"month_date": "1403-05-01",
"batch_size": 200
}
Validation Rules
- month_date: required + regex YYYY-MM-DD
- batch_size: integer + min=10 + max=1000
- month_date باید با shamsiDate middleware معتبر تشخیص داده شود
Response (Success)
ساختار خروجی بر اساس BatchAccountingService::processBatchByDateRange است.
{
"success": true,
"data": {
"success": true,
"total_processed": 450,
"total_errors": 0,
"batches_count": 3
},
"message": "پردازش ماه مرداد کامل شد. 450 سند پردازش شد."
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"month_date": [
"The month_date format is invalid."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در پردازش ماه: Internal server error..."
}
Internal Architecture
Process Flow
POST /v2/batch-accounting/process/year
Process Batch Documents By Year
این اندپوینت اسناد حسابداری را بر اساس یک سال شمسی مشخص پردازش میکند. ابتدا تاریخ شروع سال و پایان سال شمسی محاسبه میشود، سپس عملیات دستهای (Batch Processing) توسط متد processBatchByDateRange اجرا میگردد. نتیجه نهایی شامل تعداد اسناد پردازششده، تعداد خطاها و مجموع Batchهای اجرا شده است.
Request Overview
/v2/batch-accounting/process/yearBody Parameters
| Field | Type | Required | Description |
|---|---|---|---|
year |
integer | yes | سال شمسی بین 1300 تا 1500 برای پردازش اسناد |
batch_size |
integer | no | سایز هر Batch. حداقل 50 و حداکثر 2000. مقدار پیشفرض: 500. |
Request Example
POST /v2/batch-accounting/process/year
{
"year": 1402,
"batch_size": 800
}
Validation Rules
- year: required + integer + min=1300 + max=1500
- batch_size: integer + min=50 + max=2000
- اعتبارسنجی سال و ورودیها قبل از پردازش انجام میشود
Response (Success)
ساختار خروجی بر اساس BatchAccountingService::processBatchByYear است.
| Field | Type | Description |
|---|---|---|
success |
boolean | نتیجه کلی پردازش |
data.total_processed |
integer | تعداد کل اسناد پردازششده در سال |
data.total_errors |
integer | تعداد خطاهای رخداده |
data.batches_count |
integer | تعداد Batchهای اجراشده |
message |
string | پیام نهایی پردازش سال |
{
"success": true,
"data": {
"success": true,
"total_processed": 7800,
"total_errors": 12,
"batches_count": 16
},
"message": "پردازش سال 1402 کامل شد. 7800 سند پردازش شد."
}
Response (Validation Error)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"year": [
"The year must be between 1300 and 1500."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در پردازش سال: Internal server error..."
}
Internal Architecture
| Component | Description |
|---|---|
processBatchByYear() |
محاسبه تاریخ آغاز سال و انتهای سال شمسی سپس فراخوانی processBatchByDateRange |
ShamsiDateHelper::endOfYear |
استخراج آخرین روز سال شمسی بر اساس تاریخ شروع |
processBatchByDateRange() |
پردازش دستهای شامل chunking، sleep بین batchها و محاسبه خروجی |
Process Flow
POST /v2/batch-accounting/process/current-month
Process Batch Documents of Current Month
این اندپوینت ماه جاری شمسی را به صورت خودکار تشخیص داده و اسناد آن را با استفاده از پردازش دستهای (Batch Processing) پردازش میکند. تاریخ روز جاری از طریق ShamsiDateHelper::today() استخراج میشود و سپس عملیات ماه جاری مانند متد processBatchByMonth انجام میگیرد. پیام نهایی شامل نام فارسی ماه جاری است.
Request Overview
/v2/batch-accounting/process/current-monthBody Parameters
| Field | Type | Required | Description |
|---|---|---|---|
batch_size |
integer | no | سایز Batchها. حداقل مقدار در متدهای سال و ماه متفاوت است اما اینجا مقدار پیشفرض 100 است. |
Request Example
POST /v2/batch-accounting/process/current-month
{
"batch_size": 150
}
Validation Rules
- batch_size: integer (در صورت ارسال)
- year/month/day استخراجشده از today() توسط middleware shamsiDate معتبر در نظر گرفته میشود
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | نتیجه پردازش دستهای |
data.total_processed |
integer | تعداد کل اسناد پردازششده در ماه جاری |
data.total_errors |
integer | تعداد خطاهای رخداده در Batchها |
data.batches_count |
integer | تعداد Batchهای اجراشده |
message |
string | پیام شامل نام فارسی ماه جاری (getMonthName) |
{
"success": true,
"data": {
"success": true,
"total_processed": 1120,
"total_errors": 0,
"batches_count": 8
},
"message": "پردازش آذر جاری کامل شد. 1120 سند پردازش شد."
}
Response (Server Error)
{
"success": false,
"message": "خطا در پردازش ماه جاری: Internal server error..."
}
Internal Architecture
| Component | Description |
|---|---|
ShamsiDateHelper::today() |
بازگرداندن تاریخ کامل امروز شمسی به صورت YYYY-MM-DD |
processBatchByMonth() |
محاسبه start/end ماه و ارسال به processBatchByDateRange |
getMonthName() |
استخراج نام فارسی ماه از روی شماره ماه استخراجشده از today() |
processBatchByDateRange() |
منطق اصلی Batch Processing شامل chunking، sleep بین Batchها و محاسبه خروجی |
Process Flow
POST /v2/batch-accounting/process/current-year
Process Batch Documents of Current Year
این اندپوینت سال جاری شمسی را به صورت خودکار تشخیص داده و اسناد آن را از طریق پردازش دستهای (Batch Processing) پردازش میکند. سال جاری از تاریخ کامل امروز، که توسط ShamsiDateHelper::today() تولید میشود، استخراج شده و سپس متد processBatchByYear فراخوانی میشود.
Request Overview
/v2/batch-accounting/process/current-yearBody Parameters
| Field | Type | Required | Description |
|---|---|---|---|
batch_size |
integer | no | سایز Batchها. مقدار پیشفرض 500. |
Request Example
POST /v2/batch-accounting/process/current-year
{
"batch_size": 700
}
Validation Rules
- batch_size: integer (در صورت ارسال)
- تاریخ today() توسط middleware shamsiDate معتبر تشخیص داده میشود
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | نتیجه عملیات پردازش |
data.total_processed |
integer | تعداد کل اسناد پردازششده |
data.total_errors |
integer | خطاهای رخداده در Batchها |
data.batches_count |
integer | مجموع Batchهای اجراشده |
message |
string | پیام نهایی شامل سال استخراجشده از تاریخ امروز |
{
"success": true,
"data": {
"success": true,
"total_processed": 9100,
"total_errors": 3,
"batches_count": 20
},
"message": "پردازش سال 1403 کامل شد. 9100 سند پردازش شد."
}
Response (Server Error)
{
"success": false,
"message": "خطا در پردازش سال جاری: Internal server error..."
}
Internal Architecture
| Component | Description |
|---|---|
ShamsiDateHelper::today() |
بازگرداندن تاریخ امروز به فرمت YYYY-MM-DD |
processBatchByYear() |
محاسبه start/end سال و ارسال آنها به processBatchByDateRange |
ShamsiDateHelper::endOfYear |
تشخیص آخرین روز سال شمسی |
processBatchByDateRange() |
منطق کامل پردازش دستهای شامل chunking، sleep و aggregate خروجی |
Process Flow
POST /v2/batch-accounting/rebuild-indexes
Rebuild All Redis Indexes
این اندپوینت یک عملیات بسیار سنگین و مخرب (destructive) برای بازسازی کامل تمامی ایندکسهای Redis اجرا میکند. قبل از شروع، تمام دادههای موجود در Redis شامل اسناد، ایندکسها و ماندهها حذف میشوند و سپس بر اساس رکوردهای فعال جدول manual_documents، ایندکسها از صفر ساخته میشوند. برای جلوگیری از اجرای اشتباه، فیلد confirm اجباری است.
Request Overview
/v2/batch-accounting/rebuild-indexesBody Parameters
| Field | Type | Required | Description |
|---|---|---|---|
batch_size |
integer | no | حداکثر اندازه هر Batch. باید بین 100 و 1000 باشد. مقدار پیشفرض: 200. |
confirm |
boolean | yes | باید مقدار true/1 باشد (Rule: accepted). جهت جلوگیری از اجرای تصادفی. |
Request Example
POST /v2/batch-accounting/rebuild-indexes
{
"batch_size": 300,
"confirm": true
}
Validation Rules
- batch_size: nullable, integer, min:100, max:1000
- confirm: required, boolean, accepted
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت نهایی بازسازی |
data.total_documents |
integer | تعداد کل اسنادی که از دیتابیس خوانده شدند (status != 5) |
data.processed_count |
integer | تعداد کل اسناد پردازششده |
data.error_count |
integer | تعداد اسناد خطادار |
data.duration_seconds |
float | مدت زمان کامل عملیات بازسازی |
data.performance.documents_per_second |
float | میانگین سرعت پردازش |
data.performance.batch_size |
integer | مقدار نهایی batch_size مورد استفاده |
message |
string | پیام نهایی شامل تعداد اسناد پردازششده و مدت زمان |
{
"success": true,
"data": {
"total_documents": 12850,
"processed_count": 12850,
"error_count": 0,
"duration_seconds": 14.27,
"performance": {
"documents_per_second": 900.32,
"batch_size": 300
}
},
"message": "بازسازی کامل انجام شد. 12850 سند در 14.27 ثانیه پردازش شد."
}
Response (Validation Error: 422)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"confirm": [
"فیلد confirm الزامی است."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در بازسازی: Internal server error..."
}
Internal Architecture
| Component | Description |
|---|---|
redisAccountingService->clearAllDocuments() |
حذف کامل تمام اسناد و ایندکسهای Redis قبل از بازسازی |
manual_documents table |
منبع اصلی دادهها برای بازسازی — فقط رکوردهای status != 5 استخراج میشوند |
chunk($batchSize) |
پردازش اسناد دیتابیس به صورت Batch جهت کنترل مصرف حافظه |
processBatch() |
منطق داخلی ساخت مجدد اسناد، ایندکسها و آپدیت ماندهها |
getTotalDocumentCount() |
برای محاسبه درصد پیشرفت (progress) در هر Batch |
Log::info() |
ثبت کامل پیشرفت و زمان اجرای rebuild |
Process Flow
POST /v2/batch-accounting/optimize-memory
Optimize Redis Memory
این اندپوینت عملیات بهینهسازی حافظه Redis را به صورت کامل و ساختاریافته انجام میدهد. هدف عملیات شامل: - اندازهگیری حافظه فعلی، - حذف کلیدهای غیرمعتبر، - اجرای فشردهسازی AOF از طریق bgrewriteaof, - و ارائه گزارش میزان حافظه آزادشده است. از آنجا که این عملیات میتواند بر عملکرد موقتی Redis اثر بگذارد، معمولاً در محیطهای Production با احتیاط فراخوانی میشود.
Request Overview
/v2/batch-accounting/optimize-memoryBody Parameters
این اندپوینت هیچ ورودی ندارد.
| Field | Type | Required | Description |
|---|---|---|---|
| No parameters | |||
Request Example
POST /v2/batch-accounting/optimize-memory
{}
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت کلی عملیات |
data.initial_memory |
integer | حجم حافظه Redis پیش از عملیات (بر حسب بایت) |
data.final_memory |
integer | حجم حافظه Redis پس از عملیات |
data.saved_memory |
integer | میزان حافظه آزادشده |
data.optimization_percentage |
float | درصد بهبود حافظه (saved_memory / initial_memory × 100) |
message |
string | پیام نهایی شامل درصد بهینهسازی |
{
"success": true,
"data": {
"initial_memory": 183783424,
"final_memory": 119013376,
"saved_memory": 64770048,
"optimization_percentage": 35.25
},
"message": "بهینهسازی کامل شد. 35.25% حافظه آزاد شد."
}
Response (Server Error)
{
"success": false,
"message": "خطا در بهینهسازی: Internal server error..."
}
Internal Architecture
| Component | Description |
|---|---|
getRedisMemoryUsage() |
استخراج مقدار مصرف حافظه Redis پیش از عملیات و پس از عملیات |
Redis::eval() |
اجرای اسکریپت Lua برای شمارش و حذف کلیدهای غیرمعتبر یا کلیدهایی که باید TTL داشته باشند. (در نسخه فعلی فقط بخش اسکلت حذف نوشته شده و قابل گسترش است.) |
Redis::bgrewriteaof() |
عملیات فشردهسازی AOF برای کاهش مصرف فضای دیسک و حافظه |
saved_memory |
اختلاف میان initial_memory و final_memory |
optimization_percentage |
محاسبه درصد بهینهسازی با فرمول دقیق round((saved / initial) × 100, 2) |
Process Flow
GET /v2/batch-accounting/report/comprehensive
Comprehensive Redis Accounting Report
این اندپوینت یک گزارش جامع (Comprehensive Report) از وضعیت کامل سیستم Redis Accounting و Batch Accounting تولید میکند. این گزارش شامل آمار عمومی Redis، تحلیل اسناد ماهانه، بخشهای پرمصرف (Top Subsidiaries)، جمعبندی بالانسها، گزارش حافظه، و متریکهای عملکردی است. این گزارش معمولاً برای تیمهای DevOps، Backend Engineering و تیمهای Performance Monitoring استفاده میشود.
Request Overview
/v2/batch-accounting/report/comprehensiveQuery Parameters
این اندپوینت هیچ پارامتر ورودی ندارد.
| Field | Type | Required | Description |
|---|---|---|---|
| No parameters | |||
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت نهایی عملیات |
data.general_stats |
object | خروجی کامل getRedisStats شامل تعداد اسناد، ایندکسها، بالانسها و حافظه Redis |
data.documents_by_month |
array | آمار تجمیعشده اسناد بر اساس ماه (مثلاً {1403-01: 1280}) |
data.top_subsidiaries |
array | بیشترین سطح فعالیت (Subsidiary Codes با بیشترین سند/بیشترین تراکنش) |
data.balance_summary |
object | خلاصه ماندهها در تمام لایهها (account, general, group, subsidiary) |
data.memory_info |
object | گزارش جزئی مصرف حافظه Redis (مقایسه internal metrics) |
data.performance_metrics |
object | متریکهای وظرفیت و throughput پردازش Batch |
message |
string | پیام خروجی |
{
"success": true,
"data": {
"general_stats": {
"total_documents": 12850,
"total_indexes": 5120,
"total_balances": 740,
"memory_usage": "148 MB"
},
"documents_by_month": {
"1403-01": 1320,
"1403-02": 1408,
"1403-03": 960
},
"top_subsidiaries": [
{ "code": 21012, "documents": 482 },
{ "code": 21007, "documents": 441 }
],
"balance_summary": {
"total_debit": 483900000,
"total_credit": 480200000,
"difference": 3700000
},
"memory_info": {
"used_memory": 183783424,
"peak_memory": 221184000,
"fragmentation_ratio": 1.23
},
"performance_metrics": {
"avg_documents_per_batch": 280,
"avg_processing_time_ms": 14.2,
"throughput_docs_per_second": 905.4
}
},
"message": "گزارش کامل تهیه شد"
}
Response (Server Error)
{
"success": false,
"message": "خطا در تهیه گزارش: Internal server error..."
}
Internal Architecture
| Component | Description |
|---|---|
redisAccountingService->getRedisStats() |
آمار کلیدی Redis شامل کلیدها، ماندهها و حافظه |
getDocumentsByMonth() |
خروجی تحلیلی تعداد اسناد بر اساس سال/ماه شمسی |
getTopSubsidiaries() |
تشخیص پُرتراکنشترین کدهای معین |
getBalanceSummary() |
تجمیع ماندههای کل سیستم |
getDetailedMemoryInfo() |
گزارش چرایی مصرف حافظه: fragmentation, allocator, peaks |
getPerformanceMetrics() |
متریکهای سرعت، حجم Batch، زمانبندیها و throughput |
Process Flow
POST /v2/batch-accounting/preview
Preview Batch Processing (Dry‑Run)
این اندپوینت یک پیشنمایش کامل از عملیات پردازش Batch را ارائه میدهد، بدون اینکه هیچ پردازش واقعی روی Redis یا دیتابیس انجام شود. این پیشنمایش برای تصمیمگیری مهندسی قبل از اجرای پردازشهای سنگین استفاده میشود و شامل: - تعداد اسناد قابل پردازش، - تعداد Batch مورد نیاز، - زمان تخمینی، - تعداد روزهای شمسی در بازه، - و اطلاعات کامل بازه تاریخ میباشد.
Request Overview
/v2/batch-accounting/previewBody Parameters
| Field | Type | Required | Description |
|---|---|---|---|
start_date |
string | yes | تاریخ شمسی شروع بازه به فرمت دقیق YYYY-MM-DD. (اعتبارسنجی regex و همچنین اعتبارسنجی Jalali توسط Middleware) |
end_date |
string | yes | تاریخ پایان بازه، فرمت مشابه start_date |
batch_size |
integer | no | اندازه Batch بین 10 تا 1000. مقدار پیشفرض 100. |
Validation Rules
- start_date: required, string, regex:/^\\d{4}-\\d{2}-\\d{2}$/
- end_date: required, string, regex:/^\\d{4}-\\d{2}-\\d{2}$/
- batch_size: nullable, integer, min:10, max:1000
Response (Success)
| Field | Type | Description |
|---|---|---|
success |
boolean | وضعیت کلی پیشنمایش |
data.total_documents |
integer | تعداد کل اسناد در دیتابیس بین start و end با status != 5 |
data.batch_size |
integer | مقدار نهایی batch_size (ورودی یا پیشفرض) |
data.estimated_batches |
integer | تعداد Batch موردنیاز (ceil(total / batch_size)) |
data.estimated_time_seconds |
float | تخمین زمان کل (0.5 ثانیه برای هر batch) |
data.estimated_time_minutes |
float | زمان تخمینی بر حسب دقیقه |
data.working_days_count |
integer | تعداد روزهای شمسی شامل start→end |
data.date_range |
object | شامل start، end، نسخههای فرمتشده (formatFull)، و اطلاعات بازه |
message |
string | پیام نهایی پیشنمایش: «پیشنمایش: X سند در Y batch پردازش خواهد شد» |
{
"success": true,
"data": {
"total_documents": 1480,
"batch_size": 100,
"estimated_batches": 15,
"estimated_time_seconds": 7.5,
"estimated_time_minutes": 0.13,
"working_days_count": 31,
"date_range": {
"start": "1403-01-01",
"end": "1403-01-31",
"start_formatted": "۱ فروردین ۱۴۰۳",
"end_formatted": "۳۱ فروردین ۱۴۰۳"
}
},
"message": "پیشنمایش: 1480 سند در 15 batch پردازش خواهد شد"
}
Response (Validation Error: 422)
{
"success": false,
"message": "خطا در اعتبارسنجی",
"errors": {
"start_date": [
"فرمت تاریخ نامعتبر است."
]
}
}
Response (Server Error)
{
"success": false,
"message": "خطا در پیشنمایش: Internal server error..."
}
Internal Architecture
| Component | Description |
|---|---|
manual_documents table |
فیلتر اسناد بر اساس بازه تاریخ و حذف status=5 |
ShamsiDateHelper::generateDateRange() |
تولید تمام روزهای شمسی بین start و end |
ShamsiDateHelper::formatFull() |
فرمت تبدیل YYYY-MM-DD به متن کامل مانند «۱ فروردین ۱۴۰۳» |
estimatedBatches |
محاسبه تعداد Batch از طریق ceil(total/batch_size) |
estimatedTime |
زمان تخمینی با مدل 0.5 ثانیه برای هر Batch |
Process Flow
POST /v2/core/offices/list
Core Offices List
این اندپوینت لیست کامل دفاتر (Offices) سیستم را از جدول offices بازیابی کرده و برای استفاده در پنلهای مدیریتی Core Center برگشت میدهد. دسترسی به این اندپوینت کاملاً ویژه است و فقط کاربرانی که Branch آنها دقیقاً مقدار "[0]" باشد میتوانند وارد شوند.
Request Overview
/v2/core/offices/listAuthentication
لازم است هدر زیر حتماً ارسال شود:
Authorization: Bearer <JWT_TOKEN>
میدلور AuthWithJWT موارد زیر را احراز میکند:
- توکن وجود داشته باشد
- توکن با HS256 و کلید
env('JWT_SECRET_KEY')معتبر باشد - UUID در توکن وجود داشته باشد
- کاربر در جدول مربوط به typ (operators / customers / colleague_auth) یافت شود
- وضعیت کاربر status = 1 باشد
سپس میدلور group, level, operator را به درخواست اضافه میکند.
Authorization (Core Access)
میدلور CoreAccess سطح دسترسی را کنترل میکند.
| Condition | Allow? |
|---|---|
$request->get('operator')->branch == "[0]" |
✔ Access Granted |
| Anything Else | ✘ Access Denied |
در صورت عدم دسترسی خروجی زیر برگردانده میشود:
{
"status": false,
"time": 1710000000,
"error": {
"code": 5005,
"message": "Access to the [core center] requires special access."
},
"support": {
"phone": "021-91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
Body Parameters
این اندپوینت هیچ پارامتر ورودی ندارد.
| Field | Type | Required | Description |
|---|---|---|---|
| No body parameters | |||
Response (Success)
خروجی شامل تمام رکوردهای جدول offices است.
| Field | Type | Description |
|---|---|---|
status |
boolean | همیشه true در حالت موفق |
time |
integer | UNIX timestamp |
data |
array[object] | نتیجه خام DB::table('offices')->get() |
{
"status": true,
"time": 1710000000,
"data": [
{
"id": 1,
"title_fa": "دفتر مرکزی",
"brand_fa": "ایرپلاس",
"city": "تهران",
"status": 1,
...
},
...
]
}
Response (JWT Error)
نمونه خروجی اگر توکن ارسال نشود:
{
"status": false,
"time": 1710000000,
"error": {
"code": 1005,
"message": "Token not provided!"
},
"support": { ... }
}
Process Flow
POST /v2/core/accommodations/list
Core Accommodations List
این اندپوینت لیست اقامتگاهها (هتلها) را با پشتیبانی از فیلترهای پیشرفته، صفحهبندی پویا، بارگذاری دادههای تکمیلی از Redis، اطلاعات جغرافیایی از دیتابیس، و استخراج Supplier Mapping برگشت میدهد. این سرویس صرفاً در اختیار کاربرانی قرار میگیرد که دسترسی Core داشته باشند (operator.branch == "[0]").
Request Overview
/v2/core/accommodations/listAuthentication & Access Control
مانند سایر سرویسهای هسته Core:
- authWithJwt: بررسی JWT، بازیابی اپراتور از DB، بررسی status=1
- core: اجازه فقط اگر
$request->operator->branch == "[0]"
در صورت عدم دسترسی:
{
"status": false,
"time": 1710000000,
"error": {
"code": 5005,
"message": "Access to the [core center] requires special access."
},
"support": { ... }
}
Request Body
ورودی با کلید json در Body ارسال میشود و توسط کد json_decode تبدیل میشود.
| Field | Type | Required | Description |
|---|---|---|---|
draw |
integer | yes | شماره سریال درخواست (برای DataTables) |
start |
integer | yes | نقطه شروع صفحهبندی. اگر 0 باشد: مقدار برابر length قرار داده میشود اگر >0 باشد: مقدار += length |
length |
integer | yes | تعداد رکورد در هر صفحه |
advanced.country |
string | no | فیلتر کشور |
advanced.state |
string | no | فیلتر استان |
advanced.city |
string | no | فیلتر شهر |
advanced.rate |
string | no | فیلتر تعداد ستاره |
advanced.r |
string | no | جستجوی آزاد روی id، عنوان فارسی، عنوان انگلیسی، آدرس |
Database Query Logic
کوئری اصلی روی جدول hotels اعمال میشود:
- فیلتر کشور، استان، شهر، rate در صورت مقدار داشتن
- فیلتر full-text (جزیی) روی:
- id
- en_title
- fa_title
- address
- where status = 1
- OrderBy id DESC
- paginate با page =
$Data->start / $Data->length
paginate(
$perPage = length,
columns = "*",
pageName = "hotels",
currentPage = (start / length)
)
Enrichment Pipeline (per-item)
برای هر accommodation مراحل زیر اجرا میشود:
| Step | Description |
|---|---|
| 1. Load Country (Redis → DB fallback) | تلاش برای خواندن countries:{id} از Redis در صورت نبود: خواندن از DB و ذخیره در Redis |
| 2. Load City Info | join cities + states خروجی شامل title_fa و group_by (عنوان استان) |
| 3. Supplier Mapping | خواندن mapping از جدول mapping_colleagues سپس دریافت colleague از جدول colleagues |
| 4. Media (Logo) | Media::where('related_id', $request->get('id')) **نکته:** این مقدار از request->id دریافت میشود، نه id اقامتگاه — رفتار دقیقاً مطابق کد ثبت شد. |
Response Schema
| Field | Type | Description |
|---|---|---|
status |
boolean | true |
time |
integer | UNIX timestamp |
draw |
integer | echo همان draw ورودی |
recordsTotal |
integer | result->total() از paginate |
recordsFiltered |
integer | مقدار ورودی length (طبق کد، نه تعداد واقعی فیلتر شده) |
data |
array | لیست اقامتگاهها با اطلاعات کامل enrich شده |
Sample Successful Response
{
"status": true,
"time": 1710000000,
"draw": 3,
"recordsTotal": 1295,
"recordsFiltered": 20,
"data": [
{
"id": 554,
"title": "Tehran Grand Hotel",
"title_fa": "هتل بزرگ تهران",
"rate": 5,
"country": {
"id": 102,
"fa_name": "ایران",
"en_name": "Iran",
"fa_nationality": "ایرانی",
"en_nationality": "Iranian"
},
"state": "تهران",
"city": "تهران",
"location": "35.7000,51.4000",
"address": "خیابان ولیعصر ...",
"logo": "/media/accommodations/554/logo.png",
"status": 1,
"supplier": {
"system_serial": 88,
"serial": "C-4401"
}
}
]
}
Process Flow
POST /v2/core/accommodation/store
Core Accommodation Store
این اندپوینت یک اقامتگاه جدید (Hotel / Accommodation) را در سیستم ثبت میکند. دادهها از کلید data در بدنه درخواست دریافت شده و بهصورت کامل روی جدول hotels درج میشوند. سپس عملیاتهای جانبی شامل ذخیره لوگو و گالری رسانه، ثبت Mapping در جدول mapping_accommodations، و اجرای سه Job (UpdateRedis، SystemLog و RunCron) در صف انجام میشود.
Request Overview
/v2/core/accommodation/storeAccess Control
دسترسی فقط در صورتی مجاز است که:
- JWT معتبر باشد (middleware: authWithJwt)
- operator.branch == "[0]" باشد (middleware: core)
Request Body Schema
تمام ورودیها داخل کلید data ارسال میشوند.
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | yes | نوع اقامتگاه (hotel, hostel, …) |
| title_fa | string | yes | نام فارسی |
| title_en | string | yes | نام انگلیسی |
| rate | integer | no | تعداد ستاره — اگر مقدار نداشته باشد 0 ذخیره میشود |
| country | integer | yes | ID کشور |
| state | integer | yes | ID استان |
| city | integer | yes | ID شهر |
| location | string | no | لوکیشن جغرافیایی (latitude,longitude) |
| address | string | yes | آدرس کامل |
| description | string | no | شرح |
| board | array | no | مثلاً: ["BB","HB","FB"] — در صورت خالی null |
| possibilities | array | no | امکانات — در صورت خالی null |
| details | object | yes | جزئیات کامل (json_encode ذخیره میشود) |
| site | string | no | وبسایت |
| string | no | ایمیل | |
| mobile | string | no | موبایل مدیر/هتل |
| phone | string | no | تلفن ثابت |
| leader_name | string | no | نام مسئول |
| leader_mobile | string | no | موبایل مسئول |
| confirm_status | integer | yes | وضعیت تأیید (0/1) |
| priority | integer | yes | اولویت در نمایش |
| logo | string|null | no | مسیر عکس لوگو — اگر null باشد ذخیره نمیشود |
| media | array | no | لیست تصاویر گالری — هر مورد path است |
| mapping | object | no | اتصال به سرویسهای بیرونی (tport, sepehr, snapptrip, ...) |
Core Database Insertion Logic
عملیات اصلی درج:
- insertGetId روی جدول
hotels - json_encode برای: board، possibilities، details
- پر کردن created_at و updated_at با Carbon::now()
- اگر لوگو وجود داشته باشد → درج در Media
- اگر media آرایه داشته باشد → درج تکتک در Media
- اگر mapping موجود باشد → درج در mapping_accommodations
id = DB::table('hotels')->insertGetId([...])
Media Insertion Rules
هر فایل مسیر مانند
"uploads/hotels/abc.jpg"
به شکل زیر پردازش میشود:
- استخراج extension بر اساس آخرین "."
- نوع برای لوگو:
logo - نوع برای گالری:
image
Mapping Insertion
شیء mapping میتواند شامل کلیدهای زیر باشد. هر مقدار خالی (null / "") → در DB مقدار null ذخیره میشود.
- tport
- sepehr
- snapptrip
- alibaba
- eghamat24
- iranhotelonline
در جدول mapping_accommodations فیلد accommodation = ID درج شده ذخیره میشود.
Background Jobs
در ادامه سه Job اجرا میشود:
| Job | Queue | Delay | Description |
|---|---|---|---|
| UpdateRedis | fastJob | +1 min | آپدیت کش Redis برای hotels و hotels_details |
| SystemLog | snailJob | +10 min | ثبت عملیات StoreAccommodation با user-agent و IP |
| RunCron | fastJob | +1 min | اجرای فرآیند بروزرسانی اقامتگاه |
Response (Success)
{
"status": true,
"time": 1710000000,
"message": "1391 | Accommodation added!"
}
Response (Error)
{
"status": false,
"code": "1003",
"message": "SQLSTATE... (error message)",
"trace": [ ... ]
}
Flowchart
POST /v2/core/accommodation/update
Core Accommodation Update
این اندپوینت برای بهروزرسانی اطلاعات یک اقامتگاه موجود استفاده میشود. رفتار اندپوینت در دو حالت انجام میشود:
method = "update" → بهروزرسانی کامل رکورد اصلی (hotels)، ثبت لوگو و رسانه، بهروزرسانی یا ایجاد Mapping، و اجرای Jobها.
method = "board" → فقط آپدیت فیلد Board و اجرای Jobهای مرتبط.
Request Overview
/v2/core/accommodation/updateAccess Control
- JWT معتبر
- operator.branch == "[0]"
Request Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer | yes | ID اقامتگاه |
| method | string | yes | مقدار: update یا board |
| data | object | yes | شیء اصلی دادههای ورودی |
در حالت method = update، ساختار data شامل تمام فیلدهای زیر است:
| Field | Type | Description |
|---|---|---|
| type | string | نوع اقامتگاه |
| title_fa | string | نام فارسی |
| title_en | string | نام انگلیسی |
| rate | int | ستاره |
| country | int | ID کشور |
| state | int | ID استان |
| city | int | ID شهر |
| location | string | موقعیت جغرافیایی |
| address | string | آدرس کامل |
| description | string | توضیحات |
| board | array | BB/HB/FB … یا خالی |
| possibilities | array | امکانات هتل |
| details | object | JSON کامل جزئیات |
| site | string | وبسایت |
| string | ایمیل | |
| mobile | string | موبایل |
| phone | string | تلفن ثابت |
| leader_name | string | نام مدیر |
| leader_mobile | string | موبایل مدیر |
| confirm_status | int | تأیید/عدمتأیید |
| priority | int | اولویت نمایش |
| logo | string|null | مسیر عکس لوگو |
| media | array | گالری تصاویر |
| mapping | object|null | اطلاعات Supplier Mapping |
Update Logic (method = "update")
- آپدیت کامل رکورد در جدول
hotelsبا Query Builder - board و possibilities در صورت خالی بودن → null
- details همیشه json_encode میشود
- updated_at ← Carbon::now()
Media Update Rules
- اگر logo جدید ارسال شده باشد:
- extract extension
- چک تکراری بودن در Media
- در صورت عدم وجود → create
- media[]:
- برای هر آیتم extension استخراج میشود
- چک وجود رکورد
- در صورت نبودن → create
Mapping Logic
- اگر mapping وجود داشته باشد:
- اگر رکورد mapping_accommodations وجود داشته باشد → update
- در غیر اینصورت → insert
- کلیدهای پشتیبانیشده:
tport, sepehr, snapptrip, alibaba, eghamat24, iranhotelonline
Update Logic (method = "board")
- فقط فیلد board آپدیت میشود
- null در صورت خالی بودن
- updated_at آپدیت میشود
Background Jobs
| Job | Queue | Delay | Scope |
|---|---|---|---|
| UpdateRedis | fastJob | none | method = update → hotels و hotels_details method = board → فقط hotels_details |
| SystemLog | snailJob | 10 minutes | update → نوع UpdateAccommodation board → نوع BoardAccommodation |
Response (Success)
{
"status": true,
"time": 1710000000,
"message": "1402 | Accommodation updated!"
}
Response (Error)
{
"status": false,
"code": "1003",
"message": "(Exception message)",
"trace": [...]
}
Flowchart
DELETE /v2/core/accommodation/delete
Core Accommodation Delete
این اندپوینت برای حذف کامل یک اقامتگاه (Hotel / Accommodation) از سیستم استفاده میشود. عملیات حذف فقط رکورد اصلی جدول hotels را پاک میکند و هیچ عمل پاکسازی روی Media یا Mapping انجام نمیدهد. پس از حذف، یک SystemLog با تأخیر ۱۰ دقیقه به صف snailJob ارسال میشود.
Request Overview
/v2/core/accommodation/deleteAccess Control
- JWT معتبر (authWithJwt)
- اجازه دسترسی فقط در صورت
operator.branch == "[0]"(middleware: core)
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer | yes | ID اقامتگاه برای حذف |
پارامتر id باید بهصورت QueryParam یا BodyParam (بسته به ساختار درخواست) ارسال شود.
Delete Logic
- حذف رکورد اصلی اقامتگاه از جدول
hotels - هیچگونه عملیات پاکسازی روی:
- Media (لوگو، گالری)
- mapping_accommodations
- Redis یا Cache
- پس از حذف، یک SystemLog با نوع
DeleteAccommodationثبت و به صف ارسال میشود.
SystemLog Payload
{
"type": "DeleteAccommodation",
"data": null,
"goal": ,
"by": operator.id,
"agent": HTTP_USER_AGENT,
"ip": client_ip,
"datetime": now()
}
Response (Success)
{
"status": true,
"time": 1710000000,
"message": "1402 | Accommodation deleted!"
}
Response (Error)
{
"status": false,
"code": "1003",
"message": "(Exception message)",
"trace": [...]
}
Flowchart
GET /v2/core/accommodation/view
Core Accommodation View
این اندپوینت اطلاعات کامل یک اقامتگاه را بر اساس شناسه ارسالشده بازگردانی میکند. دادههای هتل، اطلاعات کشور/استان/شهر، مدیا (لوگو + گالری)، جزئیات، امکانات، برد، اطلاعات مدیریت و Mapping Supplierها بهصورت کامل تجمیع و استانداردسازی میشود.
Request Overview
/v2/core/accommodation/viewAccess Control
- JWT معتبر
- middleware core → شرط:
operator.branch == "[0]"
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer | yes | ID اقامتگاه برای مشاهده |
Lookup Logic
- دریافت رکورد اصلی از جدول
hotels. - اگر
countryمقدار null باشد → مقدار پیشفرض 118. - واکشی اطلاعات:
countries(با nationality)statescities
title.faوtitle.en- icon → ISO
- واکشی Mapping از جدول
mapping_accommodations:- اگر رکورد وجود داشته باشد: id, accommodation, status حذف میشود
- هر مقدار null → false
- اگر رکورد نبود → خروجی:
false
- واکشی Media از جدول
media:- type = logo → ذخیره در logo
- type != logo → ورود به آرایه media[]
- اگر هیچ رسانهای وجود نداشت:
- logo = null
- media = []
- Decode کردن:
detailsboardpossibilities
- تجمیع نهایی در آرایه
$returnو بازگشت.
Response (Success)
{
"status": true,
"time": 1710000000,
"data": {
"title": {
"fa": "...",
"en": "..."
},
"type": "...",
"rate": 4,
"country": {
"id": 118,
"icon": "IR",
"title": { "fa": "...", "en": "..." },
"nationality": { "fa": "...", "en": "..." }
},
"state": { "id": 1, "title": { "fa": "...", "en": "..." } },
"city": { "id": 2, "title": { "fa": "...", "en": "..." } },
"phone": "...",
"mobile": "...",
"email": "...",
"site": "...",
"address": "...",
"location": "...",
"logo": "... or false",
"media": ["..."] or false,
"details": {...},
"board": [...],
"possibilities": [...],
"leader_name": "...",
"leader_mobile": "...",
"description": "...",
"confirm_status": 1,
"priority": 10,
"mapping": {
"tport": true|false,
"sepehr": true|false,
"snapptrip": true|false,
"alibaba": true|false,
"eghamat24": true|false,
"iranhotelonline": true|false
},
"status": 1
}
}
Response (Error)
{
"status": false,
"code": "1003",
"message": "(Exception message)",
"trace": [...]
}
Flowchart
POST /v2/core/accommodation/supplier/store
Core Accommodation Supplier Store
این اندپوینت برای ایجاد یک Supplier جدید (نوع colleague) و اتصال آن به یک اقامتگاه استفاده میشود. این عملیات شامل ایجاد رکورد جدید در جدول colleagues با سریال اختصاصی و سپس ثبت ارتباط در mapping_colleagues است.
Request Overview
/v2/core/accommodation/supplier/storeAccess Control
- JWT معتبر
- در middleware core، فقط زمانی اجازه داده میشود که
operator.branch == "[0]"
Request Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
| data | object | yes | اطلاعات کامل Supplier |
| accommodation_id | integer | yes | ID اقامتگاه که Supplier به آن متصل میشود |
Structure of data
| Field | Type | Description |
|---|---|---|
| title.fa | string | نام فارسی Supplier |
| title.en | string | نام انگلیسی Supplier |
| logo | string|null | آدرس یا مسیر لوگو |
Insert Logic
1. ایجاد رکورد colleague
- رکورد در جدول
colleaguesدرج میشود. - branch ثابت و برابر 2 است.
- serial با فراخوانی:
StaticController::getSerialId('colleague', 2)تولید میشود.
- foundation = 1
- category = 5
- type = 1
- فیلدهای office و office_en از:
data.title.fadata.title.en
- فیلد logo مقداردهی میشود.
- created_at و updated_at هر دو با
Carbon::now()->toDateTimeString()مقدار میگیرند.
2. درج در mapping_colleagues
- colleague = ID رکورد ایجادشده
- accommodation = مقدار ارسالشده در
accommodation_id
Serial Generation Logic (colleague)
فراخوانی زیر انجام میشود:
StaticController::getSerialId('colleague', 2)
منطق:
- جدول:
colleagues - Query: بر اساس branch = 2
- اگر رکورد وجود داشته باشد → serial آخر + 1
- اگر هیچ رکوردی نباشد → مقدار اولیه = 2
Response (Success)
{
"status": true,
"time": 1710000000,
"message": "1023 | Supplier stored!"
}
Response (Error)
{
"status": false,
"code": "1003",
"message": "(Exception message)",
"trace": [...]
}
Flowchart
POST /v2/core/airlines/list
Core Airlines List
این اندپوینت لیست خطوط هوایی را با قابلیت فیلترینگ پیشرفته، Pagination سفارشی و Pull اطلاعات کشور از Redis یا Database باز میگرداند. منطق Pagination به صورت DataTables‑Style انجام شده و ساختار پاسخ دقیقاً با نیازهای فرانت هماهنگ است.
Request Overview
/v2/core/airlines/listAccess Control
- JWT معتبر
- شرط middleware core →
operator.branch == "[0]"
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| json | string (JSON object) | yes | پارامترهای کامل DataTables شامل start, length, draw و فیلترهای advanced |
| lang.id | string|integer | optional | شناسه زبان انتخابشده (در این روت استفادهی عملی ندارد) |
Structure Inside json
| Field | Type | Description |
|---|---|---|
| start | integer | مقدار اولیه offset |
| length | integer | تعداد رکوردهای هر صفحه |
| draw | integer | شاخص درخواست DataTables |
| advanced.country | string | فیلتر بر اساس کشور |
| advanced.iata | string | فیلتر بر اساس IATA |
| advanced.icao | string | فیلتر بر اساس ICAO |
| advanced.r | string | فیلتر جستجو روی en_title, fa_title, description |
Pagination Logic
- اگر start = 0 باشد → start = length
- در غیر این صورت → start = start + length
- مقدار currentPage = start / length
- Query با paginate اجرا میشود
Filtering Logic
تمام فیلترها داخل یک Closure در where قرار گرفتهاند:
- country → اگر مقدار خالی نباشد، where('country')
- iata → اگر مقدار خالی نباشد، where('iata')
- icao → اگر مقدار خالی نباشد، where('icao')
- r → applied on:
- en_title LIKE %r%
- fa_title LIKE %r%
- description LIKE %r%
- status = 1
- ORDER BY id DESC
Country Fetch Logic (Redis + DB Fallback)
- ابتدا تلاش میشود country از Redis با کلید
countries:{country_id}خوانده شود. - اگر Redis مقدار نداشت:
- query روی جدول countries (status=1, id, iso, fa_name, en_name, fa_nationality, en_nationality)
- نتیجه در Redis ذخیره میشود
- country نهایی در خروجی فقط fa_name است (در صورت نبود → همان country_id)
Final Output Mapping
برای هر airline:
{
"id": ...,
"title": airline.en_title,
"title_fa": airline.fa_title,
"icao": airline.icao,
"iata": airline.iata,
"country": resolved_country_fa_name,
"description": airline.description,
"logo": airline.logo,
"status": airline.status
}
Response (Success)
{
"status": true,
"time": 1710000000,
"draw": ,
"recordsTotal": ,
"recordsFiltered": ,
"data": [...]
}
Response (Error)
{
"status": false,
"code": "1003",
"message": "(Exception message)",
"trace": [...]
}
Flowchart
POST /v2/core/airports/list
Core Airports List
این اندپوینت برای دریافت لیست فرودگاهها با فیلترهای پیشرفته، Pagination مشابه DataTables، و غنیسازی دادهها (Country از Redis/DB و City از DB) استفاده میشود. خروجی کاملاً استاندارد و سازگار با سیستمهای مدیریت داده (DataTables‑Compatible Response) است.
Request Overview
/v2/core/airports/listAccess Control
- JWT معتبر
- middleware core فقط وقتی اجازه میدهد که:
operator.branch == "[0]"
Request Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
| json | string (JSON) | yes | حاوی پارامترهای DataTables (start, length, draw, advanced filters) |
| lang.id | string|integer | optional | شناسه زبان (در این متد استفاده عملی ندارد) |
Structure Inside json
| Field | Type | Description |
|---|---|---|
| start | integer | Offset اولیه |
| length | integer | اندازه هر صفحه |
| draw | integer | شاخص درخواست DataTables |
| advanced.country | string | فیلتر کشور |
| advanced.state | string | فیلتر استان |
| advanced.city | string | فیلتر شهر |
| advanced.iata | string | فیلتر IATA |
| advanced.type | string | فیلتر نوع فرودگاه |
| advanced.r | string | جستجوی آزاد روی title, title_fa, description, address |
Pagination Logic
- اگر start = 0 → start = length
- در غیر این صورت → start = start + length
- page = (start / length)
- کوئری paginate با pageName = "airports"
Filtering Logic
تمام فیلترها داخل Closure:
- country → where('country')
- state → where('state')
- city → where('city')
- iata → where('iata')
- type → where('type')
- r → applied on:
- title LIKE %r%
- title_fa LIKE %r%
- description LIKE %r%
- address LIKE %r%
- status = 1
- ORDER BY id DESC
Country & City Enrichment
Country (Redis → DB Fallback)
- Redis key:
countries:{country_id} - اگر موجود نبود:
- اطلاعات از DB خوانده میشود (جدول countries)
- در Redis ذخیره میشود
- در خروجی: فقط fa_name (در صورت نبود → id اصلی)
City Resolution
Lookup مستقیم DB:
- جدول cities با join روی states
- خروجی شامل:
- city.title_fa → نمایش نام فارسی شهر
- state.fa_name → نمایش استان
- اگر City یافت نشد → فیلدهای state و city از DB اصلی airport (city/state ID) استفاده میشود.
Final Output Mapping
{
"id": airport.id,
"title": airport.title,
"title_fa": airport.title_fa,
"iata": airport.iata,
"country": resolved_country,
"state": resolved_state_name,
"city": resolved_city_name,
"location": airport.location,
"description": airport.description,
"logo": airport.logo,
"image": airport.image,
"status": airport.status
}
Response (Success)
{
"status": true,
"time": 1710000000,
"draw": ,
"recordsTotal": ,
"recordsFiltered": ,
"data": [...]
}
Response (Error)
{
"status": false,
"code": "1003",
"message": "(Exception message)",
"trace": [...]
}
Flowchart
POST /v2/core/socket/send
Core Socket: Send Message
این اندپوینت یک پیام را از طریق سیستم Broadcasting لاراول ارسال میکند. پیام به Event TradeOperation پاس داده میشود و با toOthers() فقط به کلاینتهای دیگر (غیر از فرستنده) Broadcast میشود.
Request Overview
/v2/core/socket/sendAccess Control
- JWT معتبر لازم است
- میانافزار core تنها وقتی اجازه میدهد که:
operator.branch == "[0]"
Request Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
| message | string | yes | متن پیام که باید از طریق WebSocket (Broadcast) ارسال شود |
Broadcast Logic
پس از دریافت message:
- یک Event از نوع
TradeOperationساخته میشود:
broadcast(new TradeOperation($message))
- فقط به دیگر کلاینتها ارسال میشود (نه فرستنده):
->toOthers()
- Broadcasting باید در کانالهای Laravel Echo/Pusher/SocketServer پیکربندی شده باشد
Response (Success)
{
"status": "Message Sent!"
}
Response (Error)
در این نسخه از کد هندلکردن Exception وجود ندارد، اما در صورت بروز خطا در سطح سیستم (مثلاً عدم کارکرد Broadcast Driver)، لاراول پاسخ خطا برمیگرداند.
Flowchart
GET /v2/core/changelogs
Core System Changelogs
این اندپوینت وظیفه ارائه نسخهبندی کامل تغییرات سیستم (Change Log) را بر اساس ساختار SemVer توسعهیافته (major.minor.patch) بر عهده دارد. دادهها از جدول change_logs دریافت میشوند، سپس در قالب گروهبندیشده بر اساس نسخه، بازگردانده میشوند.
Request Overview
/v2/core/changelogsAccess Control
- توکن JWT معتبر
- دسترسی فقط برای branch = "[0]" (براساس middleware core)
Database Query Logic
دریافت تمامی لاگها از جدول change_logs با مرتبسازی دقیق نسخه:
- orderBy major DESC
- orderBy minor DESC
- orderBy patches DESC
- orderBy created_at ASC (برای نمایش ترتیب وقوع تغییرات در هر نسخه)
DB::table('change_logs')
->orderBy('major', 'DESC')
->orderBy('minor', 'DESC')
->orderBy('patches', 'DESC')
->orderBy('created_at')
->get();
Version Grouping Logic
هر رکورد داخل گروه مخصوص نسخه قرار میگیرد. کلید گروه نسخه برابر است با: major . minor . patches بدون نقطه (مثلاً 231 → نسخه 02.03.01).
نسخه نهایی با صفرهای سمت چپ فرمت میشود:
- major → دو رقمی
- minor → دو رقمی
- patch → دو رقمی
$changeLogs[$major.$minor.$patch]['version']
= sprintf("%02d", major)
. '.' . sprintf("%02d", minor)
. '.' . sprintf("%02d", patch);
ساختار داده هر آیتم
[
"title" => row.title,
"content" => row.content,
"created_at" => CalendarUtils::strftime('Y/m/d', strtotime(row.created_at)),
"tag" => row.tag
]
Response (Success)
{
"status": true,
"time": 1710000000,
"data": {
"020301": {
"version": "02.03.01",
"data": [
{
"title": "Fix Accounting Issues",
"content": "Detail text ...",
"created_at": "1403/07/12",
"tag": "fix"
},
...
]
},
...
}
}
Response (Error)
در این متد try/catch وجود ندارد؛ هر خطای دیتابیس یا رانتایم بهصورت Exception خام از طرف لاراول برگشته میشود (HTTP 500).
Flowchart
GET /v2/core/accommodation/view
Core Accommodation: View Details
این اندپوینت برای دریافت جزئیات کامل یک هتل در سیستم Core استفاده میشود. دادهها از جداول اصلی (hotels، countries، states، cities)، جدول mapping_accommodations و جدول media بارگذاری شده و سپس در یک ساختار استاندارد و نرمالسازی شده بازگردانده میشوند.
Request Overview
/v2/core/accommodation/viewAccess Control
- نیازمند JWT معتبر
- در این روت middleware
coreوجود ندارد → برای تمام کاربران معتبر قابل مشاهده است
Request Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer | yes | شناسه هتل در جدول hotels |
Hotel Record Loading
جزئیات اولیه هتل از جدول hotels بارگذاری میشود:
$item = DB::table('hotels')
->where('id', request->id)
->first();
- اگر مقدار country برابر null باشد، مقدار پیشفرض 118 جایگزین میشود
Country Resolution
کشور با شرطهای زیر لود میشود:
- status = 1
- fa_nationality != null
[
"id" => country.id,
"icon" => country.iso,
"title" => [
"fa" => country.fa_name,
"en" => country.en_name
],
"nationality" => [
"fa" => country.fa_nationality,
"en" => country.en_nationality
]
]
State Resolution
$state = [
"id" => state.id,
"title" => [
"fa" => state.title_fa,
"en" => state.title_en
]
];
City Resolution
$city = [
"id" => city.id,
"title" => [
"fa" => city.title_fa,
"en" => city.title_en
]
];
Mapping Resolution
اگر رکورد فعال وجود داشته باشد، فیلدهای null به false تبدیل میشوند؛ در غیر این صورت مقدار نهایی false است.
{
"tport": value or false,
"sepehr": value or false,
"snapptrip": value or false,
"alibaba": value or false,
"eghamat24": value or false,
"iranhotelonline": value or false
}
Media Resolution
همه مدیاها با شرطهای زیر لود میشوند:
ساختار خروجی نهایی مدیا:
{
"logo": "/path/to/logo.jpg" or false,
"media": ["/path/a.jpg","/path/b.jpg"] or false
}
Final Response Mapping
ساختار کلی:
{
"title": {
"fa": item.fa_title,
"en": item.en_title
},
"type": item.type,
"rate": item.rate,
"country": country_object,
"state": state_object,
"city": city_object,
"phone": item.phone,
"mobile": item.mobile,
"email": item.email,
"site": item.site,
"address": item.address,
"location": item.location,
"logo": logo or false,
"media": media_list or false,
"details": json_decode(item.details),
"board": json_decode(item.board),
"possibilities": json_decode(item.possibilities),
"leader_name": item.leader_name,
"leader_mobile": item.leader_mobile,
"description": item.description,
"confirm_status": item.confirm_status,
"priority": item.priority,
"mapping": mapping_object or false,
"status": item.status
}
Response (Success)
{
"status": true,
"time": 1710000000,
"data": { ... }
}
Response (Error)
{
"status": false,
"code": "1003",
"message": "Exception message",
"trace": [...]
}
Flowchart
POST /v2/core/system/report
Core System: Add System Report
این اندپوینت برای ثبت گزارش سیستمی (System Report) در بخش Core استفاده میشود. دادهها به متد Visa::AddSystemReport() ارسال شده و پس از ثبت، یک کد رهگیری (tracking_code) تولید و برگردانده میشود.
Request Overview
/v2/core/system/reportAccess Control
- JWT معتبر لازم است
- در این روت هیچ محدودیت branch یا middleware core وجود ندارد
- اطلاعات operator از داخل JWT توسط سیستم تزریق میشود:
$request->get('operator')
Request Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
| data | mixed | yes | داده خام گزارش. محتوای آن ۱۰۰٪ وابسته به Visa::AddSystemReport() است. |
| operator | object | auto | به صورت خودکار از JWT استخراج میشود؛ شامل اطلاعات کاربری. از این object فقط id استفاده میشود. |
Core Processing Logic
ورودیها مستقیماً وارد سرویس ثبت گزارش میشوند:
$insert = Visa::AddSystemReport(
$request->get('data'),
$request->get('operator')->id
);
- پارامتر اول: محتوای گزارش (data)
- پارامتر دوم: ID اپراتور ارسال کننده گزارش
- خروجی متد شامل کلید
Codeاست که نقش tracking_code را دارد
Response (Success)
کد وضعیت HTTP: 201 Created
{
"payload": {
"tracking_code": "ABC123456"
},
"meta": {
"timestamp": 1710000000
}
}
Response (Error)
در این متد هیچ try/catch وجود ندارد، بنابراین هرگونه Exception از طرف Visa::AddSystemReport یا دیتابیس → خطای 500 بازگشت داده میشود.
Flowchart
GET /v2/charter (Multi‑Mode Charter Loader)
Charter: Get Charter(s) Details
این اندپوینت، هسته اصلی بازیابی اطلاعات چارترهاست. بسته به ورودی، میتواند یک چارتر واحد را برگرداند، یا چندین چارتر را بهصورت آرایه، و همچنین بسته به مقدار action میتواند چهار سطح مختلف از دادهها را برگرداند: all / list / base / calculations / capacity. این Endpoint از کلاس پیچیده CharterResource استفاده میکند که شامل غنیسازی بسیار سنگین دیتاست.
Request Overview
/v2/charterBehavior Summary
- اگر
idیک عدد باشد → فقط یک چارتر را لود میکند - اگر
idآرایه باشد → چندین چارتر را لود میکند - تمامی رکوردها از جدول
chartersخوانده میشوند - داده نهایی با
new CharterResource()تبدیل میشود
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer | array | yes | شناسه یک چارتر یا چند چارتر |
| action | string | optional | نوع خروجی را تعیین میکند:
all | list | base | calculations | capacity
|
| item_id | integer | optional | فقط در action = capacity |
Single Charter Mode
if (is_numeric($request->id)) {
$charter = DB::table('charters')->where('id', $request->id)->first();
}
اگر یافت نشود:
{
"status": false,
"time": 1710000000,
"message": "item not found!"
}
Multiple Charter Mode
$charters = DB::table('charters')->whereIn('id', $request->id)->get();
همه رکوردها map میشوند:
$charters->map(fn($item) => new CharterResource($item))
CharterResource Overview
این کلاس بسیار بزرگ مسئول تبدیل رکورد دیتابیس به ساختارهای کامل، شامل اطلاعات اپراتور، محاسبات، قوانین، آیتمها، ظرفیت رزرو، ویژگیها، و دهها نوع داده متفاوت دیگر است.
Modes (action)
- all: کاملترین مود. شامل همه موارد: items, calculations, rules, schedule, reservation و …
- list: نسخه سبک برای لیستگیری
- base: اطلاعات پایه + قوانین + آیتمها
- calculations: فقط آیتمها و محاسبات مالی
- capacity: فقط ظرفیتهای رزرو (با item_id اختیاری)
Shared Data Loaders (Resource Level)
- اپراتور: جدول
operators - آیتمها: جدول
charter_items - محاسبات: جدول
{table}_calculation - قوانین استرداد:
charter_refund_rules - قوانین عمومی:
charter_public_rules - ظرفیت رزرو:
ReservationController::capacityItemCharter() - اطلاعات کلی:
getInformation() - نوتیفیکشن زمانبندی شده: جدول
scheduled_notifications
sortItems() Logic
این تابع یکی از سنگینترین بخشهاست و بسته به method/submethod آیتم دیتا را enrich میکند:
- method = route (aircraft, train, bus, ship)
- method = accommodation
- method = service
- method = custom
جزئیات شامل:
- لینککردن airline، airports، aircraft
- محاسبه duration از روی دقیقه → فرمت HH:MM
- لوکآپ شرکتها، supplierها و شهرها
- normalize کردن vehicle و company
sortCalculations() Logic
این قسمت شامل:
- لود مالیاتها: جدول charter_taxes
- لود خدمات: charter_services + charter_service_categories
- محاسبات مبتنی بر type و subtype
- rule mapping برای:
commission / markup / citizenshipاز جدول charter_financial_handling
- financial.base = قیمتها
- financial.staircases
- financial.taxes
- financial.commissions
- financial.markups
- financial.citizenship
getInformation() Logic
بسته به type → title و blocks متفاوت تولید میکند. مقادیر در Redis کش میشوند تحت کلید:
charter:{id}:information:title
- tour → origin, destination, start, end
- route → origin, destination, start
- accommodation → destination, start, end (+ hotel title)
- service → اطلاعات سرویس مثل بیمه/ویزا/CIP
Response Structure (Single Charter, action=all)
{
"status": true,
"time": 1710000000,
"data": {
"id": 123,
"serial": 10000 + serial,
"title": { fa, en },
"type": "...",
"subtype": "...",
"capacity": ...,
"buy": ...,
"deadline": ...,
"information": {...},
"reservation": {...},
"calculations": [...],
"rules": { refund: [...], public: [...] },
"operator": {...},
"items": [...],
"details": {...},
"plan": ...,
"slug": ...,
"hub": ...,
"schedule": {...} | false,
"status": ...
}
}
Error Response
{
"status": false,
"time": 1710000000,
"message": "item not found!"
}
Flowchart
POST /v2/charter
Charter: Create Inventory (Store)
این اندپوینت قلب تپنده سیستم تعریف موجودی (Inventory) است. وظیفه آن دریافت یک الگوی زمانی و قیمتی، و تبدیل آن به صدها رکورد فیزیکی در دیتابیس است. این متد از یک Replication Engine داخلی استفاده میکند تا تاریخهای پرواز یا رزرو هتل را بر اساس الگوهای هفتگی، دورهای یا تاریخهای خاص تولید کرده و تمام وابستگیهای مالی و قانونی را در قالب تراکنشهای اتمیک ذخیره کند.
Request Overview
/v2/charterKey Features & Behavior
- Poly-morphic Creation: پشتیبانی همزمان از پرواز (Route) و هتل (Accommodation) با یک ساختار واحد.
- Auto-Inversion: تولید خودکار مسیر برگشت (Outbound) با جابجایی مبدا/مقصد و محاسبه اختلاف روز.
- Smart Replication: تولید انبوه رکوردها بر اساس روزهای هفته (شمسی) یا بازههای زمانی.
- Financial Complexity: مدیریت پلکانی قیمتها، مارکآپ (Markup)، کمیسیون و قوانین ملیت.
- Data Integrity: استفاده از تراکنش دیتابیس برای هر تاریخ (Fail-safe per date).
Payload Schema (Root Level)
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | Yes | نوع موجودی:
route | accommodation
|
| subtype | string | Cond. | برای route الزامی است:
aircraft | train | bus
|
| branch | integer | Yes | شناسه شعبه ایجاد کننده |
| items | array | Yes | اطلاعات فیزیکی (شماره پرواز، ترمینال، هتل) و مسیر برگشت |
| repeat | object | No | تنظیمات موتور تولید تاریخ (اگر نرسد، فقط یک رکورد ثبت میشود) |
| calculations | array | Yes | آرایهای از کلاسهای نرخی، قیمت پایه، مالیات و قوانین مالی |
| rules | object | No | قوانین استرداد (Refund) و قوانین عمومی (Public Rules) |
Logic 1: Route & Object Resolution
سیستم ابتدا دادههای ورودی را نرمالسازی میکند:
IF type == 'route':
IF subtype == 'aircraft':
Origin/Dest IDs -> Look up 'airports' table -> Fetch City Name
ELSE (train/bus):
Origin/Dest IDs -> Used directly as City Name
IF type == 'accommodation':
Object -> Hotel ID -> Look up 'hotels' table -> Fetch City Name
Origin = Destination = City Name
Start/End Time -> Set strictly to 00:00:00 / 23:59:59
Logic 2: Outbound Handling (Round Trip)
اگر در اولین آیتم آرایه items، کلید outbound وجود داشته باشد:
- مسیر برگشت به عنوان یک موجودیت جداگانه اما وابسته (Linked) ساخته میشود.
- جای
originوdestinationتعویض (Swap) میشود. - پارامتر
diffمحاسبه میشود: فاصله زمانی بین رفت و برگشت (برای استفاده در تولید تاریخهای بعدی).
Logic 3: The Replication Engine
متغیر کلیدی $charterDates بر اساس آبجکت repeat پر میشود. این بخش هوشمند سیستم است:
Type: Dates (Manual)
{ "type": "dates", "dates": ["2024-01-01", "2024-01-05"] }
// دقیقاً برای همین تاریخها رکورد تولید میشود.
Type: Weekly (Pattern)
{
"type": "weekly",
"days": [1, 3, 5], // 1=شنبه, 3=دوشنبه ...
"from": "2024-01-01",
"to": "2024-03-01"
}
// حلقه روی بازه زمانی میچرخد و روزهای هفته شمسی را چک میکند.
Type: Periodic (Interval)
{
"type": "periodic",
"repeat_day": 2, // یک روز در میان
"from": "...", "to": "..."
}
Logic 4: Financial Calculations
دادههای مالی در جداول جداگانه بر اساس type ذخیره میشوند. همچنین جداول واسط زیر پر میشوند:
| Table | Description |
|---|---|
| charter_taxes | مالیاتها به تفکیک بزرگسال، کودک و نوزاد. |
| charter_financial_handling | مدیریت سه نوع داده: Markup, Commission, Citizenship rules. |
| charter_accommodation_rooms | (فقط هتل) نگاشت شماره اتاقها و طبقات به کلاس نرخی. |
| mapping_accommodations | (فقط هتل) اتصال شناسه هتل لوکال به شناسه چارتر تولید شده (Airplus ID). |
Execution Flowchart
Generate List of Dates
Calc / Tax / Markup / Rooms
Response Example
// Success
{
"status": true,
"time": 1715432100
}
// Error (Exception handled)
{
"status": false,
"time": 1715432105,
"message": "SQL Error: Column 'x' not found...",
"trace": [...]
}
PUT /v2/charter
Charter: Update Inventory (Operation)
این اندپوینت مسئول مدیریت تغییرات یک چارتر ثبتشده است. این متد یک Action Dispatcher داخلی دارد که بر اساس مقدار action درخواست را به یکی از سه رفتار مستقل هدایت میکند: base، calculations و copy. این عملیاتها اتمیک و تحت تراکنش انجام میشوند و سیستم از Capacity Protection برای جلوگیری از تناقض ظرفیت و فروش استفاده میکند.
Request Overview
Key Features & Behavior
- Action-Based Logic: سه مسیر عملیاتی مستقل (Base, Calculations, Copy).
- Capacity Protection: ظرفیت جدید نمیتواند کمتر از تعداد رزروهای قطعی/موقت باشد.
- Transactional: هر عملیات کامل در یک تراکنش انجام میشود.
- Deep Cloning: در حالت Copy تمام جداول وابسته با Serial جدید تکثیر میشوند.
- Financial Sync: مالیات، کمیسیون، قوانین استرداد و اتاقها برای هر کلاس نرخی مدیریت میشود.
Payload Schema (Root Level)
| Field | Type | Required | Description |
|---|---|---|---|
| action | string | Yes | مقدار ثابت: base یا calculations یا copy |
| id | int | Cond. | شناسه چارتر (برای base و copy الزامی است) |
| main_id | int | Cond. | شناسه چارتر (در حالت calculations الزامی است) |
| items | array | Cond. | برای calculations مورد نیاز است |
Logic 1: Base Operation
این حالت برای ویرایش اطلاعات عمومی چارتر استفاده میشود.
| Field | Type | Description |
|---|---|---|
| status | bool | فعال/غیرفعال |
| capacity | int | ظرفیت کلی (چک ظرفیت اعمال میشود) |
| commission | float | کمیسیون پایه |
| get_passenger | bool | نیاز به اطلاعات مسافر |
| notification | object | { mobile, text, date, time } |
// Capacity Guard Example
$cap = CharterHelper::capacityItemCharter($id);
if ($new_capacity < $cap['reserved_total']) {
throw new Exception("New capacity cannot be lower than sold seats.");
}
Logic 2: Calculations Operation
مدیریت کلاسهای نرخی، قیمتها، ظرفیت اتاقها، مالیاتها و قوانین کنسلی.
create: ایجاد کلاس جدیدupdate: ویرایش قیمت/ظرفیت (با چک فروش)delete: حذف کلاس (تنها اگر فروش نداشته باشد)
{
"action": "calculations",
"main_id": 150,
"items": [
{
"mode": "update",
"id": 455,
"price": 3200000,
"capacity": 12,
"taxes": [{ "amount": 10000, "description": "Tax" }],
"rules": [{ "percent": 30, "from_time": 0, "to_time": 12 }]
}
]
}
Logic 3: Copy Operation
عملیات کپی یک چارتر شامل:
- تولید Serial جدید
- کپی جدول اصلی Charter
- کپی Charter Items
- کپی Calculations + Taxes + Rules
- کپی Mapping ها و وابستگیها
{
"action": "copy",
"id": 150
}
Execution Flowchart
Response Example
// Success
{
"status": true,
"data": { "id": 150, "status": true }
}
// Error
{
"status": false,
"message": "Capacity cannot be less than sold seats"
}
`
PATCH /v2/charter (updateCharter)
Charter: Update Charter (Status / Sell)
این اندپوینت جهت تغییر وضعیت کلی چارتر (Status) و یا مدیریت وضعیت فروش (Sell) برای همکاران و HUB طراحی شده است. این متد برخلاف ویرایش کامل، عملیات سبکتری انجام میدهد اما قوانین سختگیرانهای برای حذف (Delete) دارد.
Request Overview
/v2/charterAccess Control
- دسترسی معتبر JWT
- ارسال پارامتر
actionالزامی است.
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer | yes | شناسه رکورد چارتر |
| action | string | yes | مقادیر مجاز: status یا sell |
| data | object | yes | حاوی فیلدهای مرتبط با اکشن انتخابی |
Logic: Action "status"
برای تغییر وضعیت چارتر (فعال/غیرفعال/حذف) استفاده میشود.
- بررسی وضعیت پایان یافته: اگر وضعیت فعلی چارتر
3باشد، امکان ویرایش وجود ندارد (Error: چارتر مورد نظر در وضعیت پایان یافته می باشد). - بررسی حذف (Status 4):
- فراخوانی متد
capacityItemCharter. - اگر
capacity.totalباcapacity.capacityبرابر نباشد (یعنی رزرو قطعی، موقت یا گارانتی وجود دارد): - جلوگیری از حذف → Error Code 1000.
- فراخوانی متد
- در غیر این صورت وضعیت آپدیت میشود.
| data.field | Type | Description |
|---|---|---|
| status | integer | وضعیت جدید (مثلاً 4 برای حذف) |
Logic: Action "sell"
برای کنترل سوئیچهای فروش استفاده میشود.
- اگر
data.colleagueارسال شود → فیلدcolleague_sellآپدیت میشود. - اگر
data.hubارسال شود → فیلدhub_sellآپدیت میشود. - پس از موفقیت، کش مربوط به عنوان چارتر در Redis پاک میشود.
| data.field | Type | Description |
|---|---|---|
| colleague | boolean | فعال/غیرفعالسازی فروش همکار |
| hub | boolean | فعال/غیرفعالسازی فروش HUB |
Response (Success)
در صورت موفقیت، هیچ محتوایی برگردانده نمیشود (HTTP 204).
HTTP/1.1 204 No Content
Response (Error)
// Error: Deletion failed due to existing reservations
{
"error": {
"code": 1000,
"message": "بر روی این چارتر رزرو انجام شده است و امکان حذف وجود ندارد."
}
}
// Error: Charter Not Found
{
"error": {
"code": 1000,
"message": "چارتر مورد نظر یافت نشد."
}
}
Flowchart
DELETE /v2/charter
Charter: Delete (Soft Deactivate)
این اندپوینت مسئول حذف نرم (Soft Delete) یا غیرفعالسازی یک چارتر است. توجه داشته باشید که این متد رکورد را از دیتابیس پاک نمیکند، بلکه وضعیت (Status) آن را به مقدار 2 تغییر میدهد (که معمولاً به معنای غیرفعال یا بایگانی است).
Request Overview
/v2/charterAccess Control
- دسترسی معتبر JWT
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer | yes | شناسه چارتری که باید غیرفعال شود |
Logic Details
- عملیات مستقیم دیتابیس: این متد از Eloquent Model استفاده نمیکند و مستقیماً روی جدول
chartersکوئری آپدیت میزند. - تغییر وضعیت: مقدار فیلد
statusبرای رکورد مورد نظر به عدد2تغییر مییابد. - مدیریت خطا: کلیه عملیات داخل بلوک
try-catchقرار دارد. در صورت بروز خطا (مثلاً مشکل دیتابیس)، متن خطا و Trace برگردانده میشود.
Response (Success)
{
"status": true,
"time": 1710000000
}
Response (Exception Error)
{
"status": false,
"time": 1710000000,
"message": "SQLSTATE[...]: Integrity constraint violation...",
"trace": [...]
}
Flowchart
GET /v2/charter/list
Charter: List & Search
این اندپوینت جهت دریافت لیست چارترها با قابلیت فیلتر کردن پیشرفته، جستجو بر اساس تاریخ (با منطق متفاوت برای اقامتگاه و پرواز)، مسیرهای دوطرفه و وضعیتهای مختلف استفاده میشود.
Request Overview
/v2/charter/listAccess Control
- دسترسی معتبر JWT
- کاربران با
branch_id = 1به همه رکوردها دسترسی دارند؛ سایرین فقط رکوردهای شعبه خود را میبینند.
Request Parameters (Query String)
کلیه پارامترها به صورت Query Param ارسال میشوند.
| Parameter | Type | Description |
|---|---|---|
| serial | string | فیلتر دقیق بر اساس شماره سریال |
| type | string | نوع چارتر (مثلاً accommodation, flight) |
| action | string | اگر مقدار list ارسال شود، ترتیب نمایش آیتمها در صفحه جاری معکوس میشود. |
| paginate[length] | integer | تعداد آیتم در هر صفحه (پیشفرض: 30) |
| paginate[start] | integer | آفست شروع (پیشفرض: 0) |
| Advanced Filters (آرایه advanced) | ||
| advanced[id] | integer | جستجو بر اساس ID (سیستم به صورت خودکار ۱۰,۰۰۰ واحد از این عدد کم میکند) |
| advanced[from] | date | تاریخ شروع بازه جستجو |
| advanced[to] | date | تاریخ پایان بازه جستجو |
| advanced[origin] | integer | شناسه مبدا |
| advanced[destination] | integer | شناسه مقصد |
| advanced[roundtrip] | boolean | حالت دوطرفه (اگر true باشد، مسیر برگشت هم جستجو میشود) |
| advanced[route] | string | فیلتر بر اساس subtype |
| advanced[status] | string | active: وضعیتهای 1 و 3all: همه وضعیتها (غیر از 1) |
Complex Logic Details
۱. منطق تاریخ (Date Logic)
- حالت عادی (General): جستجو میکند که
startچارتر دقیقاً بین بازهfromوtoباشد. - حالت اقامتگاه (Accommodation): اگر
type=accommodationباشد، سیستم تداخل زمانی (Overlap) را بررسی میکند:- تاریخ شروع چارتر در بازه باشد.
- یا تاریخ پایان چارتر در بازه باشد.
- یا چارتر کل بازه درخواستی را پوشش دهد (شروع چارتر <= شروع درخواست و پایان چارتر >= پایان درخواست).
۲. جستجوی دوطرفه (Roundtrip)
اگر advanced[roundtrip] برابر true باشد و مبدا و مقصد مشخص شده باشند، کوئری به صورت OR اجرا میشود:
(مبدا = Origin AND مقصد = Dest) یا (مبدا = Dest AND مقصد = Origin).
۳. صفحهبندی و مرتبسازی
- مرتبسازی پیشفرض: بر اساس تاریخ شروع (ASC) و سپس شناسه (ASC).
- اگر
action=listباشد، آیتمهای فچ شده در صفحه جاری، توسط کالکشن لاراول معکوس (Reverse) میشوند (نمایش از آخر به اول در همان صفحه).
Response (Success)
{
"items": [
{
"id": 10050,
"serial": "CHR-123",
"type": "flight",
"origin": "THR",
"destination": "MHD",
"start": "2024-05-20 10:00:00",
"status": 1,
// ... other resource fields
}
],
"meta": {
"timestamp": 1715000000,
"table": {
"total": 150
}
}
}
Flowchart
Else: Check Start Date
Else: (A→B)
GET /v2/charter/communications
Charter: List Communications
این اندپوینت لیست "ارتباطات" (Communications) تعریف شده بین چارترها را برمیگرداند. این جدول معمولاً برای تعریف مسیرهای متصل (Connecting Flights) یا ارتباط بین یک چارتر اصلی و چارترهای وابسته استفاده میشود. خروجی شامل دو بخش اصلی src (چارتر مبدا) و dst (چارتر مقصد/متصل) است.
Request Overview
/v2/charter/communicationsAccess Control
- دسترسی معتبر JWT
Request Parameters
| Parameter | Type | Description |
|---|---|---|
| origin | integer | شناسه شهر مبدا. سیستم ابتدا چارترهایی که origin آنها برابر این مقدار است را پیدا کرده و سپس در جدول ارتباطات جستجو میکند. |
| destination | integer | شناسه شهر مقصد (واسط). توجه: سیستم چارترهایی که origin (مبدا) آنها برابر با این مقدار است را پیدا میکند (منطق پرواز کانکشن: پرواز دوم از اینجا شروع میشود). |
| main_id | integer | شناسه مستقیم چارتر اصلی (Source Main ID) |
| item_id | integer | شناسه آیتم/کلاس چارتر اصلی (Source Item ID) |
| destination_main_id | integer | شناسه چارتر مقصد (Destination Main ID) |
| destination_item_id | integer | شناسه آیتم/کلاس چارتر مقصد (Destination Item ID) |
| paginate[length] | integer | تعداد آیتم در صفحه |
| paginate[start] | integer | آفست شروع |
Logic Details
۱. فیلتر هوشمند مبدا و مقصد
این متد مستقیماً روی جدول charter_communications جستجو نمیکند، بلکه ابتدا شناسههای معتبر را از جدول charters استخراج میکند:
- اگر
originارسال شود: لیست ID چارترهایی که از این شهر شروع میشوند را میگیرد و در فیلدmain_idجستجو میکند. - اگر
destinationارسال شود: لیست ID چارترهایی که از این شهر شروع میشوند (به عنوان لگ دوم سفر) را میگیرد و در فیلدcommunication_main_idجستجو میکند.
۲. غنیسازی دادهها (Data Enrichment)
پس از دریافت لیست خام ارتباطات، برای هر رکورد توابع زیر صدا زده میشوند تا جزئیات کامل برگردانده شود:
getCommunicationsCharter: اطلاعات کلی پرواز/قطار (مبدا، مقصد، زمان).getCommunicationsCalculation: اطلاعات کلاس نرخی (Business/Economy) یا نوع قطار (4 تخته و ...).
Response Structure
{
"items": {
"communication": [
{
"id": 501,
"title": "Tehran to Mashhad (Connection)",
"src": {
"main": {
"id": 100,
"type": "route",
"subtype": "aircraft",
"information": { "origin": 1, "destination": 2, ... },
"calculations": [...]
},
"item": {
"class": { "iata": "Y", "title_en": "Economy", ... }
},
"type": "system" // source_type
},
"dst": {
"main": { ... }, // اطلاعات چارتر دوم
"item": { ... }, // اطلاعات کلاس چارتر دوم
"type": "charter" // destination_type
}
}
]
},
"meta": {
"timestamp": 1715000000,
"table": { "total": 10, ... }
}
}
Flowchart
GET /v2/charter/reservation/{type}
Charter: List Reservations & Reports
این اندپوینت لیست رزروها را بر اساس type (نوع گزارش) فیلتر میکند. این متد قلب تپنده گزارشگیری سیستم چارتر است و حالتهای مختلفی از جمله رزروهای قطعی، موقت (لاگین شده)، استردادی و نمای گرافیکی "Plan" (مخصوص هتل) را پوشش میدهد.
Request Overview
/v2/charter/reservation/{type}Access Control
- دسترسی معتبر JWT
Request Parameters
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| type | string (enum) | نوع لیست درخواستی. مقادیر مجاز:
|
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| id | integer | شناسه چارتر اصلی (Main Charter ID) |
| search[from] | date (Y-m-d) | تاریخ شروع بازه گزارش |
| search[to] | date (Y-m-d) | تاریخ پایان بازه گزارش |
| search[report_type] | string | فقط برای type=definite: نحوه فیلتر تاریخ:
|
Logic Details
۱. منطق فیلتر Definite (رزروهای قطعی)
در این حالت، سیستم رزروهایی که refund_id ندارند و حذف نشدهاند را برمیگرداند. منطق تاریخ بر اساس report_type متغیر است:
- check_in:
checkin_date BETWEEN from AND to - check_out:
checkout_date BETWEEN from AND to - guests: رزروهایی که بازه اقامتشان با بازه انتخابی همپوشانی دارد (مهمانان مقیم).
checkin <= to AND checkout > from
۲. منطق Temporary (رزروهای موقت)
این بخش رکوردهایی را از جدول charter_temporary_reservation میخواند که هنوز منقضی نشدهاند.
شرط انقضا: created_at + duration > NOW()
۳. منطق Plan (نمای تقویم اقامتگاه)
این حالت پیچیدهترین بخش است و دادهها را از ۴ منبع تجمیع میکند. اگر نوع چارتر accommodation نباشد، خطای 409 برمیگرداند.
- Reservations: رزروهای قطعی در بازه زمانی.
- Warranties: گارانتیهای تعریف شده.
- Locks: قفلهای موقت (Temporary Reservations) فعال.
- Disable Rooms: اتاقهای غیرفعال شده (خرابی و ...).
POST /v2/charter/reservation
Charter: Insert Bulk Reservations
این اندپوینت برای ایجاد یک یا چند رزرو به صورت همزمان طراحی شده است. ورودی اصلی آن آرایهای از مسافران است. سیستم ابتدا تمام مسافران را اعتبارسنجی کرده، سپس ظرفیت را بررسی میکند و در نهایت رزروها را ایجاد میکند. این متد بین چارترهای مسیرمحور (مانند پرواز) و اقامتگاهی (هتل) تمایز قائل میشود.
Request Overview
/v2/charter/reservationAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
| main_id | integer | (الزامی) شناسه چارتر اصلی |
| item_id | integer | (الزامی) شناسه آیتم محاسباتی (کلاس پروازی یا نوع اتاق) |
| checkin_date | string (Y-m-d) | (فقط برای اقامتگاه) تاریخ ورود |
| checkout_date | string (Y-m-d) | (فقط برای اقامتگاه) تاریخ خروج |
| passengers | array[object] | (الزامی) لیست مسافرانی که باید برایشان رزرو ثبت شود. |
ساختار آبجکت Passenger
| Field | Type | Description |
|---|---|---|
| identity.id | string | کد ملی برای مسافر ایرانی |
| identity.nationality | string | کد ۲ حرفی ISO کشور (مثلاً 'IR') |
| passport.id | string | شماره پاسپورت برای مسافر خارجی |
| fullname.first_name.en | string | نام (انگلیسی) |
| fullname.last_name.en | string | نام خانوادگی (انگلیسی) |
| birth | string | تاریخ تولد (Y-m-d) |
| mobile / email | string | اطلاعات تماس (اختیاری) |
| status | string | (اختیاری) اگر مقدار آن refund باشد، رزرو مستقیماً به عنوان استرداد ۱۰۰٪ ثبت میشود. |
| financial | object | (اختیاری) برای ثبت قیمت به صورت دستی. اگر ارسال نشود، قیمت به صورت خودکار محاسبه میشود. |
|
||
Logic Details
۱. اعتبارسنجی گسترده
پیش از هر اقدامی، سیستم تمام مسافران موجود در آرایه passengers را بررسی میکند. در صورت وجود اولین خطا، عملیات متوقف و کد خطای 422 Unprocessable Entity بازگردانده میشود.
- ملیت: باید یک کد معتبر دو حرفی باشد (کدهای ۳ حرفی به ۲ حرفی تبدیل میشوند). (خطای 1019)
- کد ملی / پاسپورت: برای ایرانیان الگوریتم کد ملی و برای خارجیها فرمت پاسپورت بررسی میشود. (خطای 1011)
- اطلاعات تماس: فرمت شماره موبایل و ایمیل بررسی میشود. (خطای 1013, 1014)
- تاریخ تولد: باید با فرمت
Y-m-dباشد. (خطای 1021)
۲. بررسی ظرفیت
پس از اعتبارسنجی، سیستم تعداد مسافران بزرگسال و کودک را شمارش کرده و با ظرفیت باقیمانده آیتم مقایسه میکند.
اگر ظرفیت کافی نباشد، عملیات متوقف و کد خطای 409 Conflict به همراه پیام "ظرفیت تکمیل است" (کد 1008) بازگردانده میشود.
۳. تفاوت منطق Route و Accommodation
هسته اصلی این متد بر اساس نوع چارتر عمل میکند:
- نوع Route (پرواز، قطار و...):
- برای هر مسافر در آرایه یک رزرو جداگانه ایجاد میشود.
- قبل از ایجاد، بررسی میشود که آیا مسافر قبلاً برای همین چارتر رزرو فعال دارد یا خیر (جلوگیری از ثبت تکراری). در صورت تکراری بودن، یک آیتم خطا در خروجی نهایی قرار میگیرد. - نوع Accommodation (هتل):
- تمام مسافران در آرایه به عنوان مهمانان یک رزرو واحد (یک اتاق) در نظر گرفته میشوند.
- ابتدا بررسی میشود که آیا برای بازهcheckin_dateتاcheckout_dateاتاق خالی وجود دارد یا خیر. در غیر این صورت، خطای409 Conflict(کد 1026) بازگردانده میشود. - پس از ایجاد رزرو، سیستم به صورت خودکار یک اتاق در دسترس را برای تمام شبهای اقامت به این رزرو اختصاص میدهد.
۴. محاسبه مالی و ثبت
برای هر رزرو، اگر آبجکت financial در اطلاعات مسافر ارسال شده باشد، قیمتها به صورت دستی ثبت میشوند. در غیر این صورت، سیستم با استفاده از financialCalculation قیمت نهایی را بر اساس قوانین قیمتگذاری (پلهای، کارمزد، مارکاپ) محاسبه میکند. در نهایت، برای هر رزرو موفق، یک PNR محلی و یک شناسه یکتا تولید و در دیتابیس ذخیره میشود.
Response Structure
پاسخ موفق
در صورت موفقیت، کد 201 Created به همراه آرایهای از نتایج بازگردانده میشود. این آرایه میتواند شامل آیتمهای موفق و ناموفق (مثلاً مسافر تکراری) باشد.
{
"items": [
{
"status": true,
"pnr": {
"local": "XG7H5A2B", // PNR مشترک برای این بچ
"original": "K9L4M1N0", // Slug یکتای این رزرو
"id": 10845 // شناسه رزرو + 10000
},
// این بخش فقط برای رزرو اقامتگاه وجود دارد
"accommodation_rooms": [
{
"reservation_id": 10845,
"room_id": 10021,
"date": "2025-12-25",
"number": "205"
}
]
},
{
"status": false,
"item_id": 45,
"details": "0012345678",
"code": 1010,
"message": "برای این مسافر قبلا این آیتم خریداری شده است.",
"solution": "..."
}
],
"meta": { "timestamp": 1715000000 }
}
Flowchart
Nationality → ID/Passport → Contact → Birth Date
Required (ADT+CHD) vs. Available Balance
Insert DB Record → Handle Refund Status → For Accommodation, Assign Room per Night
PUT /v2/charter/reservation
Charter: Update Reservation(s)
این اندپوینت دو قابلیت مجزا اما مرتبط را فراهم میکند. بسته به پارامتر apply_all، میتوان یک رزرو خاص را با تمام جزئیاتش بهروزرسانی کرد، یا فقط اطلاعات مالی (مبلغ) را برای تمام رزروهای فعال یک چارتر به صورت یکجا تغییر داد. این متد برای اصلاح اطلاعات مسافر یا اعمال تغییرات قیمت کلی بسیار کاربردی است.
Request Overview
/v2/charter/reservationAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
ساختار بدنه درخواست بسته به مقدار فیلد apply_all متفاوت است.
| Field | Type | Description | Mode |
|---|---|---|---|
| main_id | integer | (الزامی) شناسه چارتر اصلی | هر دو |
| apply_all | boolean | اگر true باشد، حالت بهروزرسانی گروهی فعال میشود. در غیر این صورت (یا عدم وجود)، حالت تکی فعال است. |
هر دو |
| reserve_id | integer | (الزامی در حالت تکی) شناسه رزرواسیون مورد نظر برای ویرایش. | تکی |
| passenger | object | (الزامی در حالت تکی) آبجکت کامل و بهروز شده اطلاعات مسافر. | تکی |
| financial | object | (الزامی) شامل کلید payable برای بهروزرسانی مبلغ. |
هر دو |
| mobile | string | شماره موبایل جدید (اختیاری). | تکی |
| string | ایمیل جدید (اختیاری). | تکی | |
| guarantor | string | شناسه ضامن (در صورت وجود). این فیلد اطلاعات warranty را تنظیم میکند. |
تکی |
Logic Details
این متد بر اساس فیلد apply_all به دو شاخه منطقی اصلی تقسیم میشود:
۱. حالت بهروزرسانی تکی (apply_all: false)
این حالت پیشفرض است و برای ویرایش جزئیات یک رزرو مشخص به کار میرود.
- یافتن رزرو: ابتدا سیستم با استفاده از
main_idوreserve_idرزرو مورد نظر را در جدول مربوطه جستجو میکند. اگر یافت نشود، خطای422با پیام "آیتم یافت نشد" بازگردانده میشود. - بررسی وضعیت: یک شرط حیاتی وجود دارد: رزرو فقط در صورتی قابل ویرایش است که وضعیت (
status) آن1(خرید قطعی) یا3(استفاده شده) باشد. در غیر این صورت، خطای422با پیام "آیتم در وضعیت خرید قطعی نمی باشد" بازگردانده میشود. - آمادهسازی دادهها:
- آبجکت
passengerبه صورت JSON در فیلد مربوطه ذخیره میشود. - مبلغ نهایی از
financial.payableاستخراج و در فیلدamountذخیره میشود. - فیلدهای
mobileوemailبهروزرسانی میشوند. - اگر فیلد
guarantorارسال شده باشد، فیلدwarranty_typeبه 'colleague' و فیلدwarrantyبا مقدارguarantorپر میشود.
- آبجکت
- عملیات پایگاه داده: اطلاعات جدید در رکورد مربوطه در پایگاه داده جایگزین میشود.
۲. حالت بهروزرسانی گروهی (apply_all: true)
این حالت برای تغییر یکپارچه قیمت تمام رزروهای یک چارتر استفاده میشود.
- هدفگیری رزروها: سیستم تمام رکوردهایی را که
main_idآنها با مقدار ارسالی برابر است و استرداد نشدهاند (refund_id IS NULL) انتخاب میکند. این شرط تضمین میکند که رزروهای کنسل شده تحت تأثیر قرار نگیرند. - آمادهسازی دادهها: تنها فیلدهایی که برای بهروزرسانی آماده میشوند عبارتند از:
amountکه ازfinancial.payableگرفته میشود.updated_atکه با زمان فعلی تنظیم میشود.
- عملیات پایگاه داده: یک دستور
UPDATEگروهی بر روی تمام رکوردهای انتخاب شده اجرا میشود و مبلغ آنها را تغییر میدهد.
Response Structure
پاسخ موفق
در صورت موفقیتآمیز بودن هر یک از دو حالت، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز میگرداند که به معنای انجام موفقیتآمیز عملیات بدون نیاز به بازگرداندن محتوا است.
پاسخهای خطا
- کد
422 Unprocessable Entity: در صورتی که رزرو یافت نشود یا وضعیت آن برای ویرایش مناسب نباشد. - کد
400 Bad Request: در صورت بروز هرگونه خطای پیشبینی نشده در سرور. این پاسخ شامل جزئیات خطا (trace) برای اشکالزدایی است.
Flowchart
Find reservation by `reserve_id`
passenger, amount, mobile, email, warranty...
Prepare data: `amount`, `updated_at`
WHERE main_id = ? AND refund_id IS NULL
DELETE /v2/charter/reservation
Charter: Soft Delete Reservation(s)
این اندپوینت برای حذف نرم (soft delete) یک یا چند رزرو به صورت همزمان طراحی شده است. عملیات حذف به صورت فیزیکی رکوردها را از پایگاه داده پاک نمیکند، بلکه وضعیت (status) آنها را به 2 (حذف شده) تغییر داده و فیلد deleted_at را با زمان فعلی پر میکند. این متد همچنین رزرو اتاقهای مرتبط با چارترهای اقامتی را نیز مدیریت میکند.
Request Overview
/v2/charter/reservationAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
| type | string | (الزامی) نوع چارتر (مثلاً 'route' یا 'accommodation'). این فیلد برای تعیین جدول صحیح رزروها در پایگاه داده استفاده میشود. |
| reserves_id | array[integer] | (الزامی) آرایهای از شناسههای رزروهایی که باید حذف شوند. |
Logic Details
فرآیند حذف در دو مرحله کلیدی انجام میشود تا از هماهنگی دادهها اطمینان حاصل شود:
۱. بهروزرسانی جدول اصلی رزروها
ابتدا، سیستم با استفاده از پارامتر type، نام جدول مربوط به رزروها (مثلاً charter_reservation_route) را از طریق متد getTableCharter به دست میآورد. سپس یک دستور UPDATE گروهی اجرا میکند که تمام رکوردهای موجود در آرایه reserves_id را پیدا کرده و فیلدهای زیر را در آنها بهروزرسانی میکند:
statusبه مقدار 2 تغییر میکند.deleted_atبا تاریخ و زمان فعلی سرور پر میشود.
۲. بهروزرسانی جدول اتاقهای اقامتی
بلافاصله پس از مرحله اول، یک دستور UPDATE دیگر بر روی جدول charter_reservation_accommodation_rooms اجرا میشود. این عملیات نیز تمام رکوردهایی را که reservation_id آنها در آرایه reserves_id وجود دارد، پیدا کرده و آنها را نیز به صورت نرم حذف میکند (status=2 و deleted_at=now()).
نکته مهم: این مرحله دوم همیشه اجرا میشود، حتی اگر نوع چارتر route باشد. در این حالت، چون هیچ رکوردی در جدول اتاقها با شناسههای رزرو پرواز مطابقت ندارد، این دستور تأثیری نخواهد داشت، اما وجود آن برای مدیریت صحیح رزروهای اقامتی ضروری است.
Response Structure
پاسخ موفق
اگر هر دو عملیات بهروزرسانی با موفقیت انجام شوند، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز میگرداند. این نشان میدهد که درخواست با موفقیت پردازش شده و نیازی به بازگرداندن محتوا نیست.
پاسخ خطا
در صورت بروز هرگونه استثنا (Exception) در حین اجرای عملیات پایگاه داده، سرور با کد 400 Bad Request پاسخ میدهد. بدنه پاسخ شامل یک آبجکت خطا با جزئیاتی مانند کد خطا، پیام و ردپای آن (trace) برای کمک به فرآیند اشکالزدایی خواهد بود.
Flowchart
type and reserves_id (array)typeUPDATE charter_reservation_[type]SET status = 2, deleted_at = NOW()WHERE id IN (reserves_id) UPDATE charter_reservation_accommodation_roomsSET status = 2, deleted_at = NOW()WHERE reservation_id IN (reserves_id) PATCH /v2/charter/reservation/undo
Charter: Undo Reservation Deletion
این اندپوینت برای بازگردانی یک رزرو که قبلاً به صورت نرم (soft delete) حذف شده است، استفاده میشود. عملیات اصلی، تغییر وضعیت (status) رزرو از 2 (حذف شده) به 1 (قطعی) و پاک کردن مقدار فیلد deleted_at است. نکته بسیار مهم در این فرآیند، بررسی مجدد ظرفیت خالی قبل از بازگردانی است که دارای منطق متفاوتی برای مسافران نوزاد و غیرنوزاد میباشد.
Request Overview
/v2/charter/reservation/undoAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
| main_id | integer | (الزامی) شناسه چارتر اصلی. این شناسه برای تشخیص نوع چارتر (مثلاً 'route' یا 'accommodation') و پیدا کردن جدول صحیح رزروها به کار میرود. |
| reserves_id | integer | (الزامی) شناسه رزرو مشخصی که باید بازگردانی شود. (توجه: با وجود نام جمع، این فیلد فقط یک شناسه را میپذیرد). |
Logic Details
فرآیند بازگردانی با یک بررسی ظرفیت حیاتی همراه است تا از فروش بیش از حد جلوگیری شود.
۱. یافتن رزرو و بررسی اولیه
ابتدا سیستم با استفاده از main_id و reserves_id، رکورد رزرو حذف شده را از جدول مربوطه پیدا میکند. اگر رکوردی یافت نشود، یک خطای عمومی رخ داده و پاسخ 400 بازگردانده میشود.
۲. بررسی ظرفیت (Capacity Check)
این مهمترین بخش منطق است. سیستم متد ReservationController::capacityItemCharter را فراخوانی میکند تا ظرفیت باقیمانده واقعی را محاسبه کند. این متد با کسر کردن رزروهای قطعی، گارانتی و قفلهای موقت از ظرفیت کل، عدد نهایی را به دست میآورد. سپس، یک شرط بر اساس نوع مسافر اعمال میشود:
- برای مسافران بزرگسال و کودک (Non-Infant):
سیستم بررسی میکند که آیا ظرفیت باقیمانده>= 0است. از آنجایی که ظرفیت نمیتواند منفی باشد، این شرط همیشه درست است. این یعنی بازگردانی رزرو بزرگسال/کودک به بررسی ظرفیت نیازی ندارد و همیشه موفقیتآمیز خواهد بود. فرض بر این است که ظرفیت این مسافر قبلاً اشغال شده بوده و اکنون صرفاً به حالت فعال بازمیگردد. - برای مسافران نوزاد (Infant):
سیستم بررسی میکند که آیا ظرفیت باقیمانده>= 1است. این بدان معناست که برای بازگرداندن یک رزرو نوزاد، باید حداقل یک جای خالی وجود داشته باشد. این منطق نشان میدهد که نوزادان ممکن است ظرفیت متفاوتی (مثلاً ظرفیت صندلی پرواز) را اشغال کنند که باید مجدداً بررسی شود.
۳. عملیات پایگاه داده
- در صورت موفقیتآمیز بودن بررسی ظرفیت: سیستم یک دستور
UPDATEاجرا کرده و مقادیر زیر را برای رزرو مورد نظر تنظیم میکند:statusبه 1 (قطعی)updated_atبه زمان فعلیdeleted_atبه NULL
- در صورت شکست در بررسی ظرفیت (فقط برای نوزاد): عملیات متوقف شده و یک خطای مشخص به کاربر بازگردانده میشود.
Response Structure
پاسخ موفق
اگر بازگردانی با موفقیت انجام شود، سرور یک پاسخ خالی با کد وضعیت 204 No Content ارسال میکند.
پاسخهای خطا
- کد
400 Bad Requestباcode: 1008: این خطا فقط زمانی رخ میدهد که یک رزرو نوزاد در حال بازگردانی باشد اما ظرفیت آیتم تکمیل شده باشد.
متن خطا: "ظرفیت این آیتم تکمیل شده است" - کد
400 Bad Request(عمومی): در صورت بروز هر خطای پیشبینی نشده دیگر، مانند یافت نشدن رزرو، یک پاسخ خطای عمومی شامل جزئیات استثنا (trace) برای اشکالزدایی بازگردانده میشود.
Flowchart
SET status = 1
SET updated_at = NOW()
SET deleted_at = NULL
(e.g., Cannot read property of null)
with trace
PUT /v2/charter/reservation/transfer
Charter: Transfer Reservations
این اندپوینت یک قابلیت مدیریتی قدرتمند برای انتقال یک یا چند رزرو از یک چارتر/آیتم به چارتر/آیتم دیگر فراهم میکند. فرآیند انتقال تنها در صورتی انجام میشود که چارتر مقصد ظرفیت کافی برای پذیرش تمام رزروهای درخواستی را داشته باشد. این عملیات برای جابجایی مسافران بین پروازها یا اتاقهای مختلف بسیار کاربردی است.
Request Overview
/v2/charter/reservation/transferAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
| goal | object | (الزامی) آبجکتی که اطلاعات چارتر و آیتم مقصد را مشخص میکند. |
| goal.main_id | integer | شناسه چارتر مقصد. |
| goal.item_id | integer | شناسه آیتم (مانند اتاق یا پرواز) در چارتر مقصد. |
| items | array[integer] | (الزامی) آرایهای شامل شناسههای رزروهایی که باید به مقصد جدید منتقل شوند. |
Logic Details
منطق اصلی این اندپوینت بر پایه یک بررسی ظرفیت و سپس اجرای یک سری بهروزرسانیهای متوالی استوار است.
۱. بررسی ظرفیت مقصد
اولین و مهمترین گام، فراخوانی متد ReservationController::capacityItemCharter برای چارتر و آیتم مقصد (goal.main_id و goal.item_id) است. این متد ظرفیت کل را محاسبه کرده و تعداد رزروهای قطعی، گارانتی و قفلهای موقت را از آن کم میکند تا ظرفیت باقیمانده واقعی (balance) را به دست آورد.
۲. تصمیمگیری بر اساس ظرفیت
سپس، سیستم ظرفیت باقیمانده ($capacity['balance']) را با تعداد رزروهایی که قرار است منتقل شوند (count($request->items)) مقایسه میکند.
- اگر ظرفیت کافی باشد (
balance >= count(items)):
عملیات انتقال ادامه مییابد. سیستم برای هر شناسه رزرو در آرایهitems، یک دستورUPDATEمجزا در پایگاه داده اجرا میکند. در هر دستور، فیلدهای زیر برای رکورد رزرو مربوطه بهروزرسانی میشوند:main_idبه شناسه چارتر مقصد تغییر میکند.item_idبه شناسه آیتم مقصد تغییر میکند.updated_atبا زمان فعلی سرور تنظیم میشود.
نکته: بهروزرسانیها به صورت تکی در یک حلقه (loop) انجام میشوند، نه به صورت یک دستور گروهی (bulk update).
- اگر ظرفیت کافی نباشد:
عملیات متوقف شده و یک خطای422 Unprocessable Entityبه کاربر بازگردانده میشود تا اعلام کند که مقصد فضای کافی برای پذیرش این تعداد رزرو را ندارد.
Response Structure
پاسخ موفق
در صورتی که ظرفیت کافی باشد و تمام عملیاتهای بهروزرسانی با موفقیت انجام شوند، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز میگرداند.
پاسخهای خطا
- کد
422 Unprocessable Entity: این خطا زمانی رخ میدهد که ظرفیت باقیمانده در چارتر مقصد کمتر از تعداد رزروهایی باشد که برای انتقال درخواست شدهاند.
-code: 1000
-message: "ظرفیت چارتر مورد نظر تکمیل می باشد." - کد
400 Bad Request: در صورت بروز هرگونه استثنا (Exception) در حین اجرای منطق (مانند خطای پایگاه داده)، این پاسخ به همراه جزئیات کامل خطا (trace) برای اشکالزدایی بازگردانده میشود.
Flowchart
goal (main_id, item_id) and items (array of IDs)UPDATE reservation_tableSET main_id = goal.main_id,item_id = goal.item_idWHERE id = item_id Message: "ظرفیت چارتر مورد نظر تکمیل می باشد."
PATCH /character/reservation/refund
Charter: Process Reservation Refund
این اندپوینت برای پردازش بازپرداخت (Refund) برای یک یا چند رزرو قطعی (`status=1`) طراحی شده است. فرآیند شامل محاسبه جریمه (به صورت درصدی یا مبلغ ثابت)، بهروزرسانی اطلاعات مالی رزرو، ثبت رکورد بازپرداخت در جدول مجزا، و در نهایت تغییر وضعیت رزرو به "مسترد شده" (`status=3`) است. این عملیات به صورت دستهای (batch) برای لیستی از رزروها قابل اجراست.
Request Overview
/v2/charter/reservation/refundAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
| type | string | (الزامی) نوع چارتر را مشخص میکند تا جداول پایگاه داده مربوطه هدف قرار گیرند. مقادیر مجاز: "accommodation" یا "route". |
| items | array[integer] | (الزامی) آرایهای از شناسههای رزروهایی که باید مسترد شوند. |
| financial | object | (الزامی) آبجکتی شامل جزئیات جریمه کنسلی. |
| financial.value_type | string | (الزامی) نوع محاسبه جریمه. مقادیر مجاز: "percent" (درصدی از مبلغ قابل پرداخت) یا "currency" (مبلغ ثابت). |
| financial.value | number | (الزامی) مقدار جریمه. اگر value_type برابر percent باشد، این عدد بین 0 تا 100 است. |
| financial.description | string | (الزامی) توضیحی برای دلیل استرداد و جریمه. |
Logic Details
منطق این اندپوینت در دو فاز اصلی اجرا میشود: فاز اعتبارسنجی و آمادهسازی، و فاز اجرای تغییرات در پایگاه داده.
فاز ۱: اعتبارسنجی و محاسبه جریمه (در یک حلقه)
سیستم در یک حلقه، هر شناسه رزرو ارسال شده در آرایه items را پردازش میکند:
- دریافت اطلاعات رزرو: ابتدا رزرو مربوط به
itemفعلی از پایگاه داده خوانده میشود.- اگر رزرو یافت نشود: عملیات فوراً متوقف شده و خطای
422با کد1000و پیام "آیتم [ID+10000] یافت نشد" بازگردانده میشود.
- اگر رزرو یافت نشود: عملیات فوراً متوقف شده و خطای
- بررسی وضعیت رزرو: وضعیت (
status) رزرو بررسی میشود.- اگر وضعیت برابر
1(قطعی) نباشد: عملیات متوقف شده و خطای422با کد1000و پیام "آیتم [ID+10000] در وضعیت خرید قطعی نمی باشد" بازگردانده میشود.
- اگر وضعیت برابر
- محاسبه جریمه (Penalty):
- اطلاعات مالی فعلی رزرو از فیلد
financial(که به صورت JSON ذخیره شده) استخراج میشود. - بر اساس
request->financial['value_type']:- اگر
percentباشد:penalty = (original_payable * value) / 100 - اگر
currencyباشد:penalty = value
- اگر
- اطلاعات مالی فعلی رزرو از فیلد
- آمادهسازی دادههای جدید: یک آبجکت اطلاعاتی جدید برای این رزرو ساخته شده و به صورت موقت در یک آرایه به نام
$queryInsertنگهداری میشود. این آبجکت شامل اطلاعاتی است که باید در جدولcharter_refundsدرج شود، از جمله آبجکت مالی جدید که در آن مبلغ قابل پرداخت (payable) به صورتoriginal_payable - penaltyمحاسبه شده است.
نکته مهم: اعتبارسنجی به صورت متوالی انجام میشود. در صورت بروز خطا برای هر یک از آیتمها، کل فرآیند متوقف شده و هیچ تغییری در پایگاه داده اعمال نمیگردد.
فاز ۲: اجرای تغییرات در پایگاه داده
اگر فاز اول برای تمام آیتمها بدون خطا به پایان برسد و حداقل یک آیتم معتبر برای استرداد وجود داشته باشد، سیستم وارد این فاز میشود:
- سیستم روی آرایه
$queryInsert(که در فاز اول آماده شده) حلقه میزند. - درج رکورد استرداد: برای هر آیتم، یک رکورد جدید در جدول
charter_refundsبا اطلاعات محاسبه شده درج میشود و شناسه (ID) رکورد جدید دریافت میگردد ($refundId). - بهروزرسانی رزرو اصلی: رزرو اصلی در جدول
charter_reservationsبهروزرسانی میشود:- فیلد
statusبه3(مسترد شده) تغییر میکند. - فیلد
refund_idبا شناسه رکورد استرداد ($refundId) پر میشود.
- فیلد
- عملیات ویژه برای اقامتگاه: اگر نوع چارتر
accommodationباشد، یک عملیات اضافی انجام میشود:- رکورد(های) مرتبط با این رزرو در جدول
charter_reservation_accommodation_roomsنیز حذف نرم (soft delete) میشوند (statusبه2تغییر کرده وdeleted_atتنظیم میشود). این کار باعث آزاد شدن اتاقهای اختصاص داده شده به این رزرو میشود.
- رکورد(های) مرتبط با این رزرو در جدول
Response Structure
پاسخ موفق
در صورتی که تمام عملیات با موفقیت انجام شود (حتی اگر هیچ آیتم معتبری برای استرداد وجود نداشته باشد)، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز میگرداند.
پاسخهای خطا
- کد
422 Unprocessable Entity: این خطا در یکی از دو حالت زیر در فاز اعتبارسنجی رخ میدهد:
-code: 1000,message: "آیتم [ID] یافت نشد." (زمانی که شناسه رزرو نامعتبر است)
-code: 1000,message: "آیتم [ID] در وضعیت خرید قطعی نمی باشد." (زمانی که رزرو قبلاً کنسل، حذف یا مسترد شده است) - پاسخ استثنا (Exception): در صورت بروز هرگونه خطای پیشبینی نشده در حین اجرای منطق (مانند خطای پایگاه داده)، یک پاسخ با ساختار سفارشی بازگردانده میشود که برای اشکالزدایی مفید است. (معمولاً با کد وضعیت 500 یا 400)
{ "status": false, "time": 1670154000, "message": "Error message details...", "trace": [...] }
Flowchart
type, items, financial2. Prepare new financial data
3. Add to temporary `$queryInsert` array
For each item in `$queryInsert`
2. Update `charter_reservations`: `status=3`, `refund_id=new_id`
PATCH /v2/charter/reservation/refund/undo
Charter: Undo Reservation Refund
این اندپوینت برای لغو عملیات استرداد یک رزرو خاص و بازگرداندن آن به وضعیت "قطعی" (`status = 1`) استفاده میشود. قبل از بازگردانی، سیستم ظرفیت آیتم مربوطه (پرواز یا اتاق) را بررسی میکند. یک منطق خاص برای نوزادان (infant) وجود دارد که بازگردانی رزرو آنها تنها در صورت وجود حداقل یک ظرفیت خالی امکانپذیر است، در حالی که برای بزرگسالان و کودکان این بررسی ظرفیت اعمال نمیشود.
Request Overview
/v2/charter/reservation/refund/undoAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
| main_id | integer | (الزامی) شناسه چارتر اصلی. این فیلد برای تعیین نوع چارتر (route یا accommodation) و انتخاب جدول صحیح از پایگاه داده استفاده میشود. |
| reserve_id | integer | (الزامی) شناسه رزروی که عملیات استرداد آن باید لغو شود. |
Logic Details
فرآیند لغو استرداد شامل سه گام اصلی است: دریافت اطلاعات، بررسی ظرفیت، و اجرای بهروزرسانیها.
۱. دریافت اطلاعات رزرو
با استفاده از main_id و reserve_id ارسالی، سیستم ابتدا رکورد کامل رزرو مورد نظر را از جدول مربوطه (charter_route_reservations یا charter_accommodation_reservations) استخراج میکند. اگر رزرو یافت نشود، یک استثنا (Exception) رخ داده و عملیات متوقف میشود.
۲. بررسی ظرفیت (Capacity Check)
این مهمترین بخش منطق است.
- متد
ReservationController::capacityItemCharterفراخوانی میشود تا ظرفیت باقیمانده آیتمی که رزرو در اصل به آن تعلق داشته، محاسبه گردد. - منطق شرطی بر اساس سن مسافر:
- اگر مسافر بزرگسال یا کودک باشد (
passenger_age_title != 'infant'): سیستم بررسی ظرفیت را نادیده میگیرد و فرض میکند که بازگرداندن رزرو همیشه امکانپذیر است. در واقع، این مسافران فضایی را اشغال نکرده بودند که اکنون نیاز به بازپسگیری آن باشد. - اگر مسافر نوزاد باشد (
passenger_age_title == 'infant'): سیستم بررسی میکند که آیا ظرفیت باقیمانده آیتم ($capacity['total']) حداقل1است یا خیر. این بدان معناست که برای بازگرداندن یک رزرو نوزاد، باید حتماً یک جای خالی وجود داشته باشد.
- اگر مسافر بزرگسال یا کودک باشد (
۳. اجرای عملیات
- در صورت وجود ظرفیت کافی (یا عدم نیاز به بررسی):
- بهروزرسانی رزرو اصلی: در جدول رزروها، رکورد مربوط به
reserve_idبهروزرسانی میشود:statusبه1(قطعی) بازمیگردد.refund_idبهNULLتغییر میکند تا ارتباط با رکورد استرداد قطع شود.
- بهروزرسانی رکورد استرداد: در جدول
charter_refunds، رکوردی که قبلاً برای این رزرو ثبت شده بود، وضعیتش به2تغییر میکند. این کار به معنای "باطل شدن" یا "لغو شدن" آن رکورد استرداد است.
نکته: رکورد استرداد حذف نمیشود، بلکه وضعیت آن تغییر میکند تا سوابق عملیات حفظ شود.
- بهروزرسانی رزرو اصلی: در جدول رزروها، رکورد مربوط به
- در صورت عدم وجود ظرفیت کافی (فقط برای نوزادان):
- عملیات متوقف شده و یک خطای
400 Bad Requestبا کد1008بازگردانده میشود که نشاندهنده تکمیل بودن ظرفیت است.
- عملیات متوقف شده و یک خطای
Response Structure
پاسخ موفق
در صورتی که عملیات با موفقیت انجام شود، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز میگرداند.
پاسخهای خطا
- کد
400 Bad Request: این خطا در یکی از دو حالت زیر رخ میدهد:
- **کمبود ظرفیت برای نوزاد:**code: 1008message: "ظرفیت آیتم مورد نظر تکمیل شده است." (یا پیام مشابه از تابع `staticGetErrorDetails`)
- **خطای عمومی یا استثنا (Exception):**- اگر
reserve_idنامعتبر باشد یا هر خطای دیگری در پایگاه داده رخ دهد. - ساختار پاسخ شامل جزئیات کامل خطا برای اشکالزدایی خواهد بود.
Flowchart
main_id, reserve_id(Only if `passenger_age_title` is 'infant')
2. Update refund record: `status=2`
2. Update refund record: `status=2`
DELETE /v2/charter/reservation/temporary
Charter: Soft Delete Temporary Reservation
این اندپوینت برای حذف نرم (soft-delete) یک رزرو موقت از سیستم استفاده میشود. رزروهای موقت در جدول جداگانهای به نام charter_temporary_reservation نگهداری میشوند. این عملیات رکورد را به طور کامل از پایگاه داده حذف نمیکند، بلکه فقط ستون status آن را به مقدار 2 (به معنای لغو شده یا حذف شده) تغییر میدهد تا در پردازشهای بعدی نادیده گرفته شود.
Request Overview
/v2/charter/reservation/temporaryAccess Control
- دسترسی معتبر JWT
Request Parameters
شناسه رزرو موقت باید به عنوان یک پارامتر کوئری (Query Parameter) در URL ارسال شود.
| Field | Type | Location | Description |
|---|---|---|---|
| id | integer | Query | (الزامی) شناسه یکتای رزرو موقت در جدول charter_temporary_reservation که قصد حذف نرم آن را دارید. |
Example URL
/v2/charter/reservation/temporary?id=451 Logic Details
فرآیند این اندپوینت بسیار ساده و سرراست است و در یک بلوک try...catch برای مدیریت خطاهای احتمالی پایگاه داده محصور شده است.
۱. اجرای عملیات حذف نرم (Soft Delete)
- سیستم با استفاده از کوئریبیلدر لاراول، یک دستور
UPDATEبر روی جدولcharter_temporary_reservationاجرا میکند. - این دستور، رکوردی را که مقدار ستون
idآن باidارسال شده در درخواست برابر است، پیدا میکند. - سپس مقدار ستون
statusآن رکورد را به2بهروزرسانی میکند. - این عملیات به صورت اتمیک انجام میشود. اگر رکوردی با شناسه مورد نظر یافت نشود، هیچ خطایی رخ نمیدهد و عملیات بدون هیچ تغییری در پایگاه داده به پایان میرسد و پاسخ موفقیتآمیز بازگردانده میشود.
۲. مدیریت خطا
- اگر در حین اجرای کوئری
UPDATEهرگونه خطای پایگاه داده (مانند قطع ارتباط، خطای سینتکس و...) رخ دهد، اجرای کد به بلوکcatchمنتقل میشود. - در این حالت، یک پاسخ خطا شامل پیام دقیق استثنا (Exception) و ردپای آن (Trace) برای اهداف اشکالزدایی (Debugging) بازگردانده میشود.
Response Structure
برخلاف بسیاری از اندپوینتهای RESTful که در پاسخ به متد DELETE موفق، کد وضعیت 204 No Content را برمیگردانند، این اندپوینت همیشه یک بدنه پاسخ (JSON) با کد وضعیت 200 OK باز میگرداند. وضعیت موفقیت یا شکست عملیات از طریق فیلد status در بدنه JSON مشخص میشود.
پاسخ موفق
در صورتی که کوئری UPDATE بدون خطا اجرا شود (حتی اگر هیچ رکوردی برای بهروزرسانی پیدا نشود)، پاسخ زیر بازگردانده میشود.
- Status Code:
200 OK - Body:
{ "status": true, "time": 1733284200 }
پاسخ خطا
در صورت بروز هرگونه استثنا (Exception) در سطح پایگاه داده، پاسخ زیر بازگردانده میشود.
- Status Code:
200 OK - Body:
{ "status": false, "time": 1733284205, "message": "SQLSTATE[...]: Base table or view not found: ... (or any other DB error)", "trace": [ // ... Stack trace for debugging ... ] }
Flowchart
Query Parameter:
idUPDATE charter_temporary_reservation
SET status = 2
WHERE id = ?{"status": true, "time": ...}{"status": false, "message": ..., "trace": ...}POST /v2/charter/reservation/temporary
Charter: Create Temporary Reservation (Lock)
این اندپوینت برای ایجاد یک "رزرو موقت" یا "قفل" روی ظرفیت یک آیتم چارتر (مانند صندلی پرواز یا اتاق هتل) برای یک مدت زمان مشخص (به دقیقه) طراحی شده است. هدف اصلی آن جلوگیری از فروش همزمان یک ظرفیت توسط چند کاربر است. منطق این اندپوینت بر اساس نوع چارتر (`type`) به دو شاخه اصلی تقسیم میشود: اقامتگاهی (`accommodation`) و غیر اقامتگاهی (مانند مسیر `route`).
Request Overview
/v2/charter/reservation/temporaryAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
ساختار بدنه درخواست شامل اطلاعات عمومی قفل و دو شیء کلیدی requester و capacity است که ساختار capacity بسته به نوع چارتر متفاوت است.
| Field | Type | Description |
|---|---|---|
| main_id | integer | (الزامی) شناسه چارتر اصلی (از جدول charters). برای تشخیص نوع چارتر استفاده میشود. |
| item_id | integer | (الزامی) شناسه آیتم خاص چارتر (مثلاً شناسه یک پرواز یا یک نوع اتاق). |
| branch | integer | (الزامی) شناسه شعبه رزرو کننده. این مقدار از درخواست دریافت میشود. |
| operator | object | (تزریق شده) این شیء توسط میدلور authWithJwt به درخواست اضافه میشود. شناسه اپراتور (operator.id) برای ثبت رزرو کننده استفاده میشود. |
| requester | object | (الزامی) شیء حاوی اطلاعات درخواست کننده نهایی. ساختار: { "type": string, "id": integer } |
| duration | integer | (الزامی) مدت زمان اعتبار قفل به دقیقه. پس از این زمان، قفل به صورت خودکار منقضی میشود. |
| description | string | (اختیاری) توضیحات مربوط به این رزرو موقت. |
| checkin_date | string | (شرطی) تاریخ ورود با فرمت YYYY-MM-DD. فقط برای چارترهای اقامتگاهی الزامی است. |
| checkout_date | string | (شرطی) تاریخ خروج با فرمت YYYY-MM-DD. فقط برای چارترهای اقامتگاهی الزامی است. |
| capacity | object | (الزامی) شیء حاوی تعداد ظرفیت درخواستی. ساختار آن بسته به نوع چارتر تغییر میکند. |
ساختار شیء `capacity`
- برای چارترهای غیر اقامتگاهی (`route`, ...):
{
"capacity": {
"adult": 2,
"child": 1,
"infant": 0
}
}
- برای چارترهای اقامتگاهی (`accommodation`):
{
"capacity": {
"room": 3
}
}
Logic Details
پس از دریافت درخواست، سیستم ابتدا نوع چارتر را با کوئری زدن به جدول charters از روی main_id تشخیص میدهد. سپس منطق بر اساس نوع چارتر اجرا میشود.
۱. منطق برای چارتر اقامتگاهی (Accommodation)
این حالت پیچیدهتر است و نیازمند بررسی دقیق ظرفیت اتاقها در بازه زمانی مشخص است.
- فراخوانی تابع کمکی: متد
ReservationController::getAccommodationRoomsبا پارامترهایitem_id،checkin_dateوcheckout_dateفراخوانی میشود. - عملکرد
getAccommodationRooms:- تمام اتاقهای فیزیکی مرتبط با آیتم (`calc_id`) را از جدول
charter_accommodation_roomsاستخراج میکند. - برای هر اتاق، در تمام طول بازه زمانی درخواستی (از `checkin` تا یک روز قبل از `checkout`) بررسی میکند که آیا اتاق در آن تاریخ خاص آزاد است یا خیر.
- یک اتاق در یک تاریخ خاص "اشغال" محسوب میشود اگر:
- در یک رزرو قطعی (`charter_reservation_accommodation_rooms`) ثبت شده باشد.
- تحت یک گارانتی فعال (`charter_warranties`) باشد.
- در یک رزرو موقت دیگر (قفل) که هنوز منقضی نشده (`charter_temporary_reservation`) قرار داشته باشد.
- در نهایت، لیستی از اتاقهایی را برمیگرداند که در تمام روزهای بازه درخواستی آزاد هستند (`available_all_dates`).
نکته مهم: کد کنترلر فعلی فقط از خروجی
available_all_datesاستفاده میکند و قابلیتهای پیچیدهتر تابع (مانند ترکیب اتاقهای مختلف برای یک اقامت) را به کار نمیگیرد. - تمام اتاقهای فیزیکی مرتبط با آیتم (`calc_id`) را از جدول
- بررسی ظرفیت: سیستم تعداد اتاقهای کاملاً آزاد (`count($charterRooms['available_all_dates'])`) را با تعداد اتاقهای درخواستی (`$request->capacity['room']`) مقایسه میکند.
- در صورت وجود ظرفیت کافی:
- به تعداد اتاقهای درخواستی، یک حلقه تکرار میشود.
- در هر تکرار، یک رکورد رزرو موقت مجزا برای یکی از اتاقهای آزاد ایجاد میشود. یعنی اگر ۳ اتاق درخواست شود، ۳ رکورد مجزا در جدول
charter_temporary_reservationدرج خواهد شد که هر کدام به یک `room_id` متفاوت اشاره دارند. - این رکوردها به صورت دستهای (Bulk Insert) در پایگاه داده ذخیره میشوند.
- در صورت عدم وجود ظرفیت کافی: عملیات متوقف شده و یک پاسخ خطا با پیام "اتاق خالی به تعداد درخواست شده در بازه زمانی مورد نظر وجود ندارد." بازگردانده میشود.
۲. منطق برای چارتر غیر اقامتگاهی (Route)
این حالت بسیار سادهتر است و مبتنی بر اعتماد به درخواستدهنده است.
- عدم بررسی ظرفیت: در این حالت، API هیچگونه بررسی ظرفیتی انجام نمیدهد و فرض میکند که ظرفیت لازم قبلاً در سمت کلاینت (UI) بررسی شده است.
- مقادیر
adult،childوinfantاز شیءcapacityبه دادههای آماده برای درج اضافه میشوند. - یک رکورد واحد در جدول
charter_temporary_reservationبا استفاده از `insertGetId` درج میشود. - شناسه رکورد درج شده (
$id) با عدد20,000جمع شده و در پاسخ بازگردانده میشود. این یک قانون تجاری برای متمایز کردن شناسههای رزرو موقت است.
Response Structure
مشابه اندپوینت قبلی، این API نیز در همه موارد (موفقیت یا شکست) کد وضعیت 200 OK را به همراه یک بدنه JSON برمیگرداند.
پاسخ موفق (چارتر اقامتگاهی)
- Status Code:
200 OK - Body:
{ "status": true, "time": 1733285100 }
پاسخ موفق (چارتر غیر اقامتگاهی)
- Status Code:
200 OK - Body:
{ "status": true, "time": 1733285105, "data": 20123 }توجه: مقدار
dataهمان شناسه رکورد درج شده در پایگاه داده به علاوه20,000است.
پاسخ خطا (ظرفیت ناکافی در چارتر اقامتگاهی)
- Status Code:
200 OK - Body:
{ "status": false, "time": 1733285110, "message": "اتاق خالی به تعداد درخواست شده در بازه زمانی مورد نظر وجود ندارد." }
پاسخ خطا (استثنای عمومی)
- Status Code:
200 OK - Body:
{ "status": false, "time": 1733285115, "message": "An error occurred ...", "trace": [ ... ] }
Flowchart
2. Insert single record & Get ID
POST /v2/charter/reservation/plan/update
Charter: Update Accommodation Reservation Room Plan
این اندپوینت برای تغییر چینش اتاقهای تخصیص داده شده به یک رزرو اقامتگاهی موجود استفاده میشود. کاربرد اصلی آن این است که به اپراتور اجازه میدهد اتاق فیزیکی (`room_id`) مربوط به یک یا چند تاریخ (`date`) خاص از یک رزرو (`reservation_id`) را تغییر دهد. برای مثال، اگر یک رزرو برای سه شب در اتاق ۱۰۱ ثبت شده، میتوان با استفاده از این اندپوینت، شب دوم اقامت را به اتاق ۱۰۵ منتقل کرد.
نکته مهم تجاری: شناسههای ورودی (id و room_id) شناسههای عمومی هستند و سیستم به صورت داخلی عدد 10,000 را از آنها کم میکند تا به شناسههای واقعی در پایگاه داده دسترسی پیدا کند.
Request Overview
/v2/charter/reservation/plan/updateAccess Control
- دسترسی معتبر JWT
Request Body (JSON)
بدنه درخواست باید شامل یک فیلد data باشد که آرایهای از اشیاء است. هر شیء در این آرایه نشاندهنده یک تغییر در تخصیص اتاق برای یک تاریخ مشخص است.
| Field | Type | Description |
|---|---|---|
| data | array of objects | (الزامی) آرایهای از دستورات بهروزرسانی. هر شیء در این آرایه باید شامل فیلدهای زیر باشد. |
ساختار شیء در آرایه `data`
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه عمومی رزرو. سیستم به صورت خودکار مقدار 10,000 را از این عدد کم میکند تا reservation_id واقعی را به دست آورد. |
| date | string | (الزامی) تاریخ مشخصی که تخصیص اتاق برای آن باید تغییر کند. فرمت باید YYYY-MM-DD باشد. |
| room_id | integer | (الزامی) شناسه عمومی اتاق جدید. سیستم به صورت خودکار مقدار 10,000 را از این عدد کم میکند تا شناسه واقعی اتاق در جدول charter_accommodation_rooms را به دست آورد. |
Example Request Body
در مثال زیر، برای رزرو با شناسه عمومی 10123، اتاق تخصیص داده شده برای تاریخ 2025-10-20 به اتاقی با شناسه عمومی 10055 تغییر میکند. همچنین برای همین رزرو، اتاق تاریخ 2025-10-21 به اتاق 10056 منتقل میشود.
{
"data": [
{
"id": 10123,
"date": "2025-10-20",
"room_id": 10055
},
{
"id": 10123,
"date": "2025-10-21",
"room_id": 10056
}
]
}
Logic Details
فرآیند در یک بلوک try...catch برای مدیریت خطاها اجرا میشود.
۱. اعتبارسنجی ورودی
- سیستم ابتدا بررسی میکند که آیا فیلد
dataدر درخواست وجود دارد و خالی نیست (isset($request->data) && $request->data). - اگر
dataوجود نداشته باشد یا یک آرایه خالی باشد، یک خطای400 Bad Requestبا پیام مشخص بازگردانده میشود.
۲. پردازش حلقه بهروزرسانی
- اگر اعتبارسنجی موفقیتآمیز باشد، سیستم بر روی هر شیء (`$data`) در آرایه
request->dataیک حلقه اجرا میکند. - در هر تکرار، یک دستور
UPDATEدر پایگاه داده بر روی جدولcharter_reservation_accommodation_roomsاجرا میشود. - شرطهای بهروزرسانی (WHERE Clause):
'reservation_id'باید برابر با$data['id'] - 10000باشد.'date'باید برابر با$data['date']باشد.
این دو شرط با هم، رکورد دقیق مربوط به یک شب خاص از یک رزرو خاص را هدف قرار میدهند.
- مقادیر جدید (UPDATE Clause):
- ستون
room_idبه مقدار$data['room_id'] - 10000بهروزرسانی میشود. - ستون
updated_atبا تاریخ و زمان فعلی پر میشود.
- ستون
۳. مدیریت خطا
- اگر در هر مرحله از اجرای کوئریهای پایگاه داده خطایی (مانند عدم وجود رکورد، خطای کلید خارجی و...) رخ دهد، اجرای کد به بلوک
catchمنتقل شده و یک پاسخ خطای400 Bad Requestبا جزئیات کامل استثنا (Exception) بازگردانده میشود.
Response Structure
پاسخ موفق
در صورتی که تمام دستورات بهروزرسانی با موفقیت اجرا شوند، سرور یک پاسخ 200 OK با ساختار زیر بازمیگرداند.
- Status Code:
200 OK - Body:
{ "payload": true, "meta": { "timestamp": 1733286000 } }
پاسخ خطا (ورودی نامعتبر)
اگر فیلد data در درخواست ارسال نشود یا خالی باشد.
- Status Code:
400 Bad Request - Body:
{ "error": { "message": "The data field is required." }, "meta": { "timestamp": 1733286005 } }
پاسخ خطا (استثنای عمومی)
در صورت بروز هرگونه خطای پایگاه داده یا خطای دیگر در حین اجرا.
- Status Code:
400 Bad Request - Body:
{ "error": { "code": "23000", "message": "SQLSTATE[23000]: Integrity constraint violation: ... (or any other DB error)", "trace": [ // ... Stack trace for debugging ... ] } }
Flowchart
- Execute `UPDATE charter_reservation_accommodation_rooms`
- `SET room_id = (item.room_id - 10000)`
- `WHERE reservation_id = (item.id - 10000) AND date = item.date`
GET /v2/charter/financial
Charter: Get Financial Report
این اندپوینت یک گزارش مالی جامع و تجمیعشده برای یک چارتر خاص (با شناسه main_id) تولید میکند. هدف اصلی آن، ارائه یک دید کلی از وضعیت فروش، درآمد، هزینهها و بدهیها با سه دستهبندی مجزا است:
- بر اساس کلاس/آیتم (Classes): تفکیک مالی بر اساس هر آیتم (مانند کلاس پروازی Y یا اتاق دو تخته).
- بر اساس متعهدین (Pledgers): تفکیک مالی برای رزروهایی که توسط یک همکار خاص تعهد شدهاند.
- بر اساس گارانتیکنندگان (Warranties): تفکیک مالی برای رزروهایی که تحت گارانتی یک شخص یا شرکت خاص (ثبت شده در سیستم) هستند.
Request Overview
/v2/charter/financialAccess Control
- دسترسی معتبر JWT
Query Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه اصلی چارتر (main_id) که گزارش مالی برای آن درخواست شده است. |
Example Request
GET /v2/charter/financial?id=451
Logic Details
فرآیند تولید گزارش در چندین مرحله و با تجمیع دادهها از جداول مختلف انجام میشود.
۱. شناسایی جداول و واکشی دادههای اولیه
- انتخاب دینامیک جداول: ابتدا تابع کمکی
getTableCharter(id)فراخوانی میشود تا بر اساس نوع چارتر (پروازی یا اقامتی)، نام جداول صحیح (مانندcharter_flight_reservationsوcharter_flight_calculations) مشخص شود. - واکشی رزروها: تمام رکوردهای رزرو (که حذف نرم نشدهاند) مربوط به
main_idاز جدول رزروها (`reserves`) خوانده میشوند. - واکشی قیمت خرید: تمام رکوردهای مربوط به آیتمهای چارتر از جدول محاسبات (`calculations`) خوانده میشوند. این رکوردها حاوی اطلاعات قیمت خرید برای هر آیتم هستند.
۲. ساخت نقشه قیمت خرید (Buy Price Map)
- یک آرایه انجمنی (map) با نام
$buyPriceساخته میشود. - سیستم روی رکوردهای `calculations` حلقه میزند و برای هر `item_id` (که در اینجا `calculation->id` است)، یک ورودی در این نقشه ایجاد میکند.
- هر ورودی شامل قیمت خرید (`buy_adult`, `buy_child`, `buy_infant`) و عنوان آیتم است.
- پردازش عنوان:
- اگر عنوان آیتم یک حرفی باشد (مانند کلاس پروازی 'Y')، تابع
Functions::getClassNameفراخوانی میشود تا نام کامل و خوانای کلاس پروازی به دو زبان فارسی و انگلیسی تولید شود. - در غیر این صورت (برای آیتمهای غیر پروازی)، همان عنوان ثبت شده در دیتابیس استفاده میشود.
- اگر عنوان آیتم یک حرفی باشد (مانند کلاس پروازی 'Y')، تابع
۳. حلقه اصلی و تجمیع دادهها
سیستم یک حلقه بر روی تمام رزروهای واکشی شده (`reserves`) اجرا میکند و در هر تکرار، عملیات زیر را انجام میدهد:
- تجمیع بر اساس کلاس/آیتم (Grouping by Class):
- دادههای مالی هر رزرو در آرایه
$return['classes']بر اساسitem_idآن رزرو تجمیع میشود. - درون هر
item_id، دادهها مجدداً بر اساس رده سنی مسافر (`passenger_age_title` که به حروف کوچک تبدیل شده: `adl`, `chd`, `inf`) دستهبندی میشوند. - برای هر رده سنی، مقادیر زیر محاسبه و جمع زده میشوند:
count: تعداد مسافران.buy: مجموع قیمت خرید (از نقشه$buyPriceخوانده میشود).payable: مجموع مبلغ قابل پرداخت (از فیلد JSON `financial` در رکورد رزرو).taxes,commissions,markups: مجموع مالیات، کمیسیون و مارکاپ. این مقادیر با استفاده از تابع کمکیReservationController::priceHandleمحاسبه میشوند که میتواند مقادیر درصدی یا ثابت را بر اساس مبلغ قابل پرداخت محاسبه کند.
- یک جمع کل نیز برای هر
item_id(بدون تفکیک سنی) در فیلدfinancialنگهداری میشود.
- دادههای مالی هر رزرو در آرایه
- تجمیع بر اساس گارانتی/تعهد (Grouping by Warranty/Pledger):
- سیستم بررسی میکند که آیا رزرو دارای
warranty_typeوwarrantyاست. - اگر
warranty_typeبرابر با `colleague` باشد:- این رزرو یک "تعهد" (Pledger) است.
- اطلاعات همکار از جدول `colleagues` واکشی میشود.
- دادههای مالی رزرو در آرایه
$return['pledgers']با کلید شناسه همکار (`colleague->id`) تجمیع میشود. ساختار تجمیع دقیقا مشابه تجمیع بر اساس کلاس است (با تفکیک سنی و جمع کل).
- اگر
warranty_typeبرابر با `system` باشد:- این رزرو یک "گارانتی" (Warranty) است.
- اطلاعات گارانتیکننده از جدول `charter_warranties` و سپس با join به جدول `colleagues` (برای یافتن مشخصات گارانتیکننده) واکشی میشود.
- دادههای مالی رزرو در آرایه
$return['warranties']با کلید شناسه گارانتیکننده تجمیع میشود.
- سیستم بررسی میکند که آیا رزرو دارای
۴. خروجی نهایی
- در نهایت، ساختار داده تجمیع شده
$returnدر فیلدpayloadیک پاسخ200 OKقرار گرفته و به کاربر بازگردانده میشود. - فیلد
changesدر ساختار خروجی مقداردهی اولیه میشود اما در کد فعلی، هیچ دادهای در آن قرار نمیگیرد و همیشه یک آرایه خالی است.
Response Structure
پاسخ موفق
در صورت موفقیت، سرور یک پاسخ 200 OK با ساختار پیچیده زیر بازمیگرداند. این ساختار شامل سه بخش اصلی `classes`, `pledgers`, و `warranties` است.
- Status Code:
200 OK - Body:
{ "payload": { "classes": { "1234": { // Key is the item_id "title": { "en": "Y | Economy/Coach", "fa": "Y | اکونومی - Economy/Coach" }, "age": { "adl": { "count": 10, "buy": 10000000, "payable": 12000000, "taxes": 1200000, "commissions": 600000, "markups": 0 }, "chd": { "count": 2, "buy": 1600000, "payable": 2000000, "taxes": 200000, "commissions": 100000, "markups": 0 }, "inf": { "count": 1, "buy": 100000, "payable": 150000, "taxes": 15000, "commissions": 0, "markups": 0 } }, "financial": { "count": 13, "buy": 11700000, "payable": 14150000, "taxes": 1415000, "commissions": 700000, "markups": 0 } } // ... other classes }, "pledgers": { "56": { // Key is the colleague_id "title": { "fa": "آژانس همکار - علی رضایی", "en": "Partner Agency - Ali Rezaei" }, "age": { "adl": { "count": 2, "buy": 2000000, "payable": 2400000, "taxes": 240000, "commissions": 120000, "markups": 0 } // ... chd, inf }, "financial": { "count": 2, "buy": 2000000, "payable": 2400000, "taxes": 240000, "commissions": 120000, "markups": 0 } } // ... other pledgers }, "warranties": { // Structure is identical to "pledgers", key is guarantor's colleague_id }, "changes": [] // Always an empty array in the current implementation }, "meta": { "timestamp": 1733288400 } }
پاسخ خطا
در صورت بروز هرگونه خطا (مانند عدم یافتن چارتر یا خطای دیتابیس) سرور یک پاسخ 400 Bad Request بازمیگرداند.
- Status Code:
400 Bad Request - Body:
{ "message": "Error details...", "trace": [ // ... Stack trace for debugging ... ] }
Helper Functions
دو تابع کمکی نقش کلیدی در منطق این اندپوینت دارند:
Functions::getClassName($class, $lang)
این تابع یک کد کلاس پروازی تک حرفی (مانند 'Y', 'C') را به نام کامل و خوانای آن در زبانهای مختلف (en, fa, ar) تبدیل میکند.
ReservationController::priceHandle($price, $values)
این تابع برای محاسبه مقدار پولی مالیات، کمیسیون یا مارکاپ استفاده میشود. ورودی آن مبلغ پایه (`price`) و آرایهای از قوانین (`values`) است. هر قانون میتواند بر اساس درصد (`percent`) یا یک مبلغ ثابت (`currency`) باشد. تابع مجموع مقادیر محاسبه شده را بازمیگرداند.
0 && $values) {
foreach ($values as $value) {
if ($value && isset($value['value'])) { // Direct value structure
if ($value['value'] > 0) {
if ($value['value_type'] == 'percent') {
$return += ($price * $value['value']) / 100;
} else if ($value['value_type'] == 'currency') {
$return += $value['value'];
}
}
} else { // Nested structure
foreach ($value as $item) {
if (isset($item['value']) && $item['value']) {
if ($item['value_type'] == 'percent') {
$return += ($price * $item['value']) / 100;
} else if ($item['value_type'] == 'currency') {
$return += $item['value'];
}
}
}
}
}
}
return $return;
}
?>
Flowchart
2. Fetch all reservations (`reserves`)
3. Fetch all calculation items (`calculations`)
Use `getClassName` for titles.
GET /v2/charter/financial/completion
Charter: Get Completion Financial Report
این اندپوینت برای ارائه یک گزارش تکمیل مالی و تحلیلی از یک چارتر طراحی شده است. این گزارش فراتر از تجمیع ساده دادههای فروش رفته و با محاسبه شاخصهای کلیدی عملکرد (KPIs) مانند هزینه کل خرید ظرفیت (Paid)، سود یا زیان (Profit)، و هزینه ظرفیت فروختهنشده (Burned)، یک دید ۳۶۰ درجه از وضعیت مالی چارتر ارائه میدهد.
خروجی این اندپوینت در چهار بخش اصلی سازماندهی شده است:
- Classes: گزارش دقیق مالی به تفکیک هر آیتم (کلاس پروازی/نوع اتاق) به همراه محاسبات سود و زیان.
- Pledgers: گزارش تجمیعی فروش برای رزروهای تعهد شده توسط همکاران.
- Warranties: گزارش تجمیعی فروش برای رزروهای گارانتی شده.
- Total: یک شیء جامع که کل شاخصهای مالی چارتر را در خود جمعبندی میکند.
Request Overview
/v2/charter/financial/completionAccess Control
- دسترسی معتبر JWT
Query Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه اصلی چارتر (main_id) که گزارش برای آن درخواست شده است. |
Example Request
GET /v2/charter/financial/completion?id=451
Logic Details
منطق این اندپوینت در دو فاز اصلی اجرا میشود: فاز اول تجمیع دادههای اولیه و فاز دوم محاسبه شاخصهای مالی پیشرفته.
فاز اول: واکشی و تجمیع دادههای اولیه
- شناسایی جداول و واکشی دادهها: مانند اندپوینت قبلی، ابتدا جداول مربوطه شناسایی شده و تمام رزروها (`reserves`) و آیتمهای محاسباتی (`calculations`) واکشی میشوند.
- ساخت نقشه قیمت خرید (`$buyPrice`): یک نقشه از قیمتهای خرید برای هر آیتم و رده سنی ساخته میشود. همچنین ظرفیت کل چارتر (`total.capacity`) با جمع کردن ظرفیت تمام آیتمها محاسبه میشود.
- حلقه اصلی روی رزروها: سیستم روی تمام رزروها حلقه میزند و دادهها را تجمیع میکند:
- تجمیع در `classes`: دادههای مالی هر رزرو (شامل
count,payable,buy,taxes,commissions,markups) بر اساس `item_id` و رده سنی در `return['classes']` جمع میشوند. - تجمیع در `total`: مقادیر کلیدی هر رزرو (
count,payable,buy,taxes, و ...) مستقیماً به شیء `return['total']` نیز اضافه میشوند تا جمع کل چارتر به دست آید. - تجمیع در `pledgers` و `warranties`: اگر رزرو دارای گارانتی یا تعهد باشد، دادههای فروش آن (تعداد و مبلغ قابل پرداخت) در دستهبندی مربوطه نیز تجمیع میشود.
- تجمیع در `classes`: دادههای مالی هر رزرو (شامل
فاز دوم: محاسبه شاخصهای سود و زیان ( حلقه دوم)
پس از اتمام حلقه اول و تجمیع دادههای پایه، یک حلقه دوم روی آرایه تجمیعشده return['classes'] اجرا میشود تا محاسبات پیشرفته برای هر آیتم انجام شود:
- محاسبه هزینه کل آیتم (Paid):
- فرمول:
(Total Buy Price / Items Sold) * Total Capacity - توضیح: این مقدار، هزینه واقعی است که شرکت برای کل ظرفیت یک آیتم (چه فروخته شده و چه نشده) پرداخت کرده است. این محاسبه بر اساس میانگین قیمت خرید صندلی/اتاقهای فروخته شده انجام میشود.
- فرمول:
- محاسبه هزینه سوختشده (Burned):
- فرمول:
Average Buy Price * (Total Capacity - Items Sold) - توضیح: این مقدار نشاندهنده هزینه ظرفیت فروختهنشده یا "سوختشده" است. این یکی از مهمترین شاخصها برای ارزیابی عملکرد فروش است.
- فرمول:
- محاسبه سود/زیان (Profit):
- فرمول:
Total Payable - Total Item Cost (Paid) - توضیح: این مقدار، تفاوت بین کل درآمد حاصل از فروش یک آیتم و کل هزینه پرداخت شده برای آن آیتم است. اگر مثبت باشد سود و اگر منفی باشد زیان را نشان میدهد.
- فرمول:
- تشخیص وضعیت مالی (Diagnosis):
- بر اساس مقدار `profit`، یک وضعیت متنی تعیین میشود:
creditor: بستانکار (سودده)debtor: بدهکار (زیانده)neutral: سربهسر
- بر اساس مقدار `profit`، یک وضعیت متنی تعیین میشود:
- تجمیع نهایی در `total`: مقادیر محاسبه شده (`paid`, `burned`, `profit`) برای هر آیتم، به مقادیر متناظر در شیء `return['total']` اضافه میشوند تا جمعبندی نهایی برای کل چارتر به دست آید.
محاسبه نهایی
- در انتها، یک `diagnosis` نهایی برای کل چارتر بر اساس مقادیر `total.payable` و `total.paid` محاسبه و در `return['total']` قرار میگیرد.
Response Structure
پاسخ موفق
پاسخ موفق 200 OK حاوی یک شیء `payload` با ساختاری بسیار غنی است که شامل بخش `total` در کنار سه بخش دیگر میباشد.
- Status Code:
200 OK - Body:
{ "payload": { "classes": { "1234": { // Key is the item_id "title": { "en": "Y | Economy/Coach", "fa": "Y | اکونومی - Economy/Coach" }, "age": { "adl": { "count": 10, "payable": 12000000, "buy": 10000000, "buy_per": 1000000, "taxes": 1080000, "commissions": 600000, "markups": 0 }, "chd": { "count": 2, "payable": 2000000, "buy": 1600000, "buy_per": 800000, "taxes": 180000, "commissions": 100000, "markups": 0 }, "inf": { "count": 1, "payable": 150000, "buy": 100000, "buy_per": 100000, "taxes": 0, "commissions": 0, "markups": 0 } }, "financial": { "count": 13, "capacity": 20, "payable": 14150000, "buy": 11700000, "burned": 6300000, // <-- New Field "paid": 18000000, // <-- New Field "profit": -3850000, // <-- New Field "diagnosis": "debtor", // <-- New Field "taxes": 1260000, "commissions": 700000, "markups": 0 } } // ... other classes }, "pledgers": { /* ... Same structure as previous endpoint ... */ }, "warranties": { /* ... Same structure as previous endpoint ... */ }, "changes": [], "total": { // <-- New Object "count": 13, "capacity": 20, "payable": 14150000, "buy": 11700000, "burned": 6300000, "profit": -3850000, "diagnosis": "debtor", "paid": 18000000, "taxes": 1260000, "commissions": 700000, "markups": 0 } }, "meta": { "timestamp": 1733290800 } }
پاسخ خطا
در صورت بروز خطا، پاسخ 400 Bad Request با جزئیات استثنا بازگردانده میشود.
- Status Code:
400 Bad Request - Body:
{ "message": "Error details...", "trace": [ // ... Stack trace for debugging ... ] }
Helper Functions
این اندپوینت از همان توابع کمکی اندپوینت قبلی برای ترجمه نام کلاس و محاسبه مقادیر درصدی/ثابت استفاده میکند.
Functions::getClassName($class, $lang)
ReservationController::priceHandle($price, $values)
Flowchart
1. Fetch `reserves` & `calculations`.
2. Build `$buyPrice` map.
3. Loop `reserves` to populate initial `classes`, `pledgers`, `warranties`, and `total` (buy, payable, counts).
1. Loop through aggregated `classes`.
2. For each class, calculate:
- `paid` (Total Item Cost)
- `burned` (Unsold Cost)
- `profit` (Payable - Paid)
- `diagnosis` (creditor/debtor)
3. Add these results to `total` object.
Calculate final `diagnosis` for the `total` object.
PATCH /v2/charter/financial/completion
Charter: Finalize Completion Financial Report
این اندپوینت برای نهاییسازی (Finalization) گزارش مالی تکمیلی یک چارتر طراحی شده است. پس از اینکه یک چارتر به پایان میرسد و تمام دادههای فروش و هزینهها مشخص میشود، این اندپوینت فراخوانی میشود تا یک "اسنپشات" (Snapshot) از وضعیت مالی نهایی (شامل سود، زیان، هزینه سوختشده و...) تهیه و آن را در پایگاه داده ثبت کند. این عمل معادل "بستن گزارش مالی" برای آن چارتر است و معمولاً پس از آن، امکان تغییر در رزروها یا دادههای مالی مرتبط با آن چارتر محدود یا مسدود میشود.
Request Overview
/v2/charter/financial/completionAccess Control
- دسترسی معتبر JWT
- (توصیه میشود) دسترسی مبتنی بر نقش (Role-Based Access) فقط برای مدیران مالی یا ادمینهای سیستم.
Request Body
برای مشخص کردن اینکه کدام چارتر باید نهایی شود، شناسه اصلی آن باید در بدنه درخواست ارسال گردد.
| Field | Type | Description |
|---|---|---|
| main_id | integer | (الزامی) شناسه اصلی چارتر (main_id) که گزارش مالی آن باید نهایی و ثبت شود. |
Example Request
{
"main_id": 451
}
Logic Details
نکته مهم: کد ارائه شده صرفاً یک ساختار اولیه است. منطق واقعی این اندپوینت بسیار پیچیدهتر بوده و شامل مراحل زیر خواهد بود:
- اعتبارسنجی ورودی: سیستم بررسی میکند که
main_idدر بدنه درخواست وجود داشته و معتبر است. - بررسی وضعیت چارتر:
- ابتدا چارتر با
main_idمشخص شده از پایگاه داده واکشی میشود. - سیستم بررسی میکند که آیا این چارتر قبلاً نهایی شده است یا خیر (مثلاً با چک کردن یک فیلد
finalized_atدر جدول اصلی چارتر). اگر قبلاً نهایی شده باشد، خطای409 Conflictبازگردانده میشود تا از ثبت مجدد جلوگیری شود.
- ابتدا چارتر با
- تولید دادههای گزارش:
- سیستم به صورت داخلی منطق اندپوینت
GET /v2/charter/financial/completionرا فراخوانی میکند تا گزارش مالی کامل و تحلیلی (شامل `profit`, `burned`, `paid` و ...) را برایmain_idمورد نظر تولید کند.
- سیستم به صورت داخلی منطق اندپوینت
- ذخیرهسازی اسنپشات (Snapshot):
- کل شیء گزارش تولید شده (که خروجی اندپوینت GET است) به فرمت JSON تبدیل میشود.
- این رشته JSON در یک جدول جدید و اختصاصی به نام
charter_financial_snapshotsیا مشابه آن، به همراهmain_id، شناسه کاربری که عملیات را انجام داده (finalized_by)، و تاریخ/زمان فعلی (finalized_at) ذخیره میشود. این کار تضمین میکند که یک نسخه بایگانیشده و غیرقابل تغییر از گزارش نهایی همیشه در دسترس است.
- بهروزرسانی وضعیت چارتر اصلی:
- پس از ذخیره موفقیتآمیز اسنپشات، رکورد مربوط به چارتر در جدول اصلی (مثلاً
charter_main) بهروزرسانی شده و فیلدfinalized_atآن با تاریخ و زمان فعلی پر میشود. این کار به عنوان یک پرچم (flag) عمل کرده و از نهاییسازی مجدد جلوگیری میکند.
- پس از ذخیره موفقیتآمیز اسنپشات، رکورد مربوط به چارتر در جدول اصلی (مثلاً
- پاسخ نهایی: در صورت موفقیت تمام مراحل، یک پاسخ
204 No Contentبازگردانده میشود که به کلاینت اعلام میکند عملیات با موفقیت انجام شده است.
Response Structure
پاسخ موفق
یک پاسخ موفق به این معنی است که گزارش مالی با موفقیت تولید و بایگانی شده است. طبق استاندارد REST، برای عملیات PATCH یا PUT که منجر به بهروزرسانی موفقی شده اما بدنه پاسخی برای برگرداندن ندارد، از کد وضعیت 204 استفاده میشود.
- Status Code:
204 No Content - Body: (Empty)
پاسخهای خطا
خطاهای مختلفی ممکن است در این فرآیند رخ دهد:
- Status Code:
400 Bad Request
دلیل: فیلدmain_idدر بدنه درخواست ارسال نشده یا نوع داده آن صحیح نیست.
{ "message": "The main id field is required." } - Status Code:
404 Not Found
دلیل: چارتری باmain_idارسال شده در سیستم وجود ندارد.
{ "message": "Charter not found." } - Status Code:
409 Conflict
دلیل: این گزارش مالی قبلاً نهایی شده است و امکان نهاییسازی مجدد وجود ندارد.
{ "message": "Financial report for this charter has already been finalized." }
PHP Implementation
کد زیر یک ساختار اولیه برای این اندپوینت است. منطق اصلی که در بخش "Logic Details" تشریح شد، باید در بلوک try پیادهسازی شود.
public function setCompletionFinancialCharter(Request $request)
{
// Validate that 'main_id' exists in the request body.
$validated = $request->validate([
'main_id' => 'required|integer|exists:charter_main,id',
]);
$mainId = $validated['main_id'];
try {
// 1. Check if the charter is already finalized.
$charter = DB::table('charter_main')->where('id', $mainId)->first();
if ($charter->finalized_at) {
return response()->json([
"message" => "Financial report for this charter has already been finalized."
], 409); // 409 Conflict
}
// 2. Generate the financial completion report data internally.
// This would call the same logic as the getCompletionFinancialCharter() method.
$reportData = $this->generateFinancialData($mainId); // Hypothetical internal method
// 3. Store the snapshot.
DB::table('charter_financial_snapshots')->insert([
'charter_main_id' => $mainId,
'snapshot_data' => json_encode($reportData),
'finalized_by' => auth()->id(), // Get user ID from JWT
'created_at' => now(),
'updated_at' => now()
]);
// 4. Update the main charter record to mark it as finalized.
DB::table('charter_main')->where('id', $mainId)->update([
'finalized_at' => now()
]);
// 5. Return success response.
return response('', 204);
} catch (Exception $exception) {
return response()->json([
"message" => $exception->getMessage(),
"trace" => $exception->getTrace()
], 400);
}
}
POST /v2/charter/warranty
Charter: Manage Warranties (Batch Operation)
این اندپوینت به عنوان یک ابزار مدیریتی جامع برای گارانتیهای یک چارتر عمل میکند. با استفاده از متد POST، این اندپوینت قادر است در یک فراخوانی، لیستی از عملیات ایجاد، بهروزرسانی و حذف را بر روی گارانتیها انجام دهد. این رویکرد دستهای، نیاز به ارسال درخواستهای متعدد را از بین برده و مدیریت گارانتیها را بسیار کارآمد میکند.
این اندپوینت به خصوص برای چارترهای اقامتگاهی، دارای منطقهای اعتبارسنجی پیچیدهای است تا از تداخل گارانتی با رزروهای موجود جلوگیری کند.
Request Overview
/v2/charter/warrantyAccess Control
- دسترسی معتبر JWT
Request Body Structure
بدنه درخواست از سه بخش اصلی تشکیل شده است: main_id برای شناسایی چارتر، آرایه warranties برای عملیات ایجاد و بهروزرسانی، و آرایه deleted برای عملیات حذف.
| Field | Type | Description |
|---|---|---|
| main_id | integer | (الزامی) شناسه اصلی چارتر (charter_main.id) که گارانتیها به آن تعلق دارند. |
| warranties | array of objects | (الزامی) آرایهای از اشیاء گارانتی. هر شیء میتواند یک گارانتی جدید برای ایجاد یا یک گارانتی موجود برای بهروزرسانی باشد. ساختار داخلی آن در جدول زیر توضیح داده شده است. |
| deleted | array of integers | (اختیاری) آرایهای از شناسههای گارانتی (charter_warranties.id) که باید حذف شوند. |
Structure of a Warranty Object (inside `warranties` array)
| Field | Type | Description |
|---|---|---|
| id | integer | string | شناسه عملیات: - برای ایجاد (Create): این فیلد را ارسال نکنید یا یک شناسه موقت رشتهای (مثلاً UUID) ارسال کنید. - برای بهروزرسانی (Update): شناسه عددی گارانتی موجود را ارسال کنید. |
| item_id | integer | شناسه آیتم چارتر (کلاس پرواز یا نوع اتاق) که گارانتی به آن تعلق دارد. |
| guarantor | object | شیء حاوی اطلاعات گارانتیکننده (همکار). مثال: {"id": 12} |
| warranty | object | شیء حاوی جزئیات ظرفیت و قیمت گارانتی شده برای هر رده سنی (adult, child, infant). هر رده شامل number (تعداد) و price است. |
| sank_cost | numeric | (اختیاری) هزینه سوخت شده (Sunk Cost). |
| service_price | numeric | (اختیاری) قیمت خدمات اضافی. |
| start_date | string | (اختیاری، برای اقامتگاهی) تاریخ شروع بازه گارانتی. فرمت: YYYY-MM-DD. |
| end_date | string | (اختیاری، برای اقامتگاهی) تاریخ پایان بازه گارانتی. فرمت: YYYY-MM-DD. |
| room_id | array of objects | (اختیاری، برای اقامتگاهی) آرایهای از اتاقهای گارانتی شده. هر شیء شامل room_id و number (شماره اتاق) است. برای عملیات بهروزرسانی، اتاقهای جدید در این آرایه قرار میگیرند. |
| deleted_room_id | array of objects | (اختیاری، فقط برای بهروزرسانی اقامتگاهی) آرایهای از اتاقهایی که باید از گارانتی حذف شوند. ساختار مشابه room_id. |
| status | integer | وضعیت گارانتی (مثلاً 1 برای فعال). |
Example Request (Create, Update & Delete)
{
"main_id": 451,
"warranties": [
{
"id": "temp-12345", // CREATE new warranty
"item_id": 101,
"guarantor": { "id": 25 },
"warranty": {
"adult": { "number": 10, "price": 1200000 },
"child": { "number": 5, "price": 900000 }
},
"status": 1
},
{
"id": 88, // UPDATE existing warranty
"guarantor": { "id": 26 },
"warranty": {
"adult": { "number": 2, "price": 2500000 },
"child": { "number": 0, "price": 0 }
},
"start_date": "2025-12-10",
"end_date": "2025-12-15",
"room_id": [ // Add this new room
{ "room_id": 305, "number": "305" }
],
"deleted_room_id": [ // Remove this old room
{ "room_id": 301, "number": "301" }
],
"status": 1
}
],
"deleted": [92, 95] // DELETE these two warranties
}
Logic Details
منطق این اندپوینت به سه بخش اصلی تقسیم میشود که به ترتیب اجرا میشوند:
1. پردازش آرایه `warranties` (ایجاد و بهروزرسانی)
سیستم روی هر آیتم در آرایه warranties حلقه میزند و بر اساس وجود یا عدم وجود id عددی، تصمیم به ایجاد یا بهروزرسانی میگیرد.
- عملیات ایجاد (Create):
- شرط:
idوجود ندارد یا یک رشته است. - اعتبارسنجی حیاتی (برای اقامتگاهی): قبل از درج، سیستم بررسی میکند که آیا هیچ یک از اتاقهای مشخص شده (
room_id) در بازه زمانی (start_date,end_date) قبلاً در جدولcharter_reservation_accommodation_roomsرزرو شدهاند یا خیر. - در صورت تداخل: اگر حتی یک رزرو پیدا شود، عملیات متوقف شده و خطای
409 Conflictبا پیام "اتاق های انتخاب شده دارای رزرو می باشند" بازگردانده میشود. - درج داده: در صورت عدم تداخل، رکورد جدید در
charter_warrantiesو رکوردهای مرتبط درcharter_warranty_accommodation_rooms(در صورت وجود) درج میشوند.
- شرط:
- عملیات بهروزرسانی (Update):
- شرط:
idیک مقدار عددی است. - رکورد اصلی در
charter_warrantiesبا دادههای جدید بهروز میشود. - مدیریت اتاقها (برای اقامتگاهی):
- افزودن اتاق جدید: برای هر اتاق در آرایه
room_id، سیستم ابتدا بررسی میکند که آیا این اتاق هماکنون رزرو شده یا قبلاً به این گارانتی لینک شده است یا خیر. تنها در صورتی که هیچکدام از این دو شرط برقرار نباشد، اتاق جدید به گارانتی اضافه میشود. - حذف اتاق: برای هر اتاق در آرایه
deleted_room_id، لینک آن از جدولcharter_warranty_accommodation_roomsحذف میشود.
- افزودن اتاق جدید: برای هر اتاق در آرایه
- شرط:
2. پردازش آرایه `deleted` (حذف)
- شرط: آرایه
deletedوجود دارد و خالی نیست. - اعتبارسنجی حیاتی: قبل از حذف، سیستم بررسی میکند که آیا هیچ یک از شناسههای گارانتی موجود در آرایه
deleted، در جدول رزروها (charter_reservations) به عنوانwarrantyثبت شدهاند یا خیر. - در صورت وجود رزرو: اگر گارانتی مورد نظر برای ثبت حتی یک رزرو استفاده شده باشد، قابل حذف نیست. عملیات متوقف شده و خطای
409 Conflictبا پیام "آیتم های انتخاب شده دارای رزرو می باشند" بازگردانده میشود. این کار از نقض یکپارچگی دادهها جلوگیری میکند. - حذف داده: در صورت عدم وجود رزرو، رکوردهای گارانتی از جدول
charter_warrantiesو تمام لینکهای اتاق مرتبط با آن ازcharter_warranty_accommodation_roomsحذف میشوند.
Response Structure
پاسخ موفق
اگر تمام عملیاتها (ایجاد، بهروزرسانی، حذف) بدون خطا انجام شوند، پاسخ 204 No Content بازگردانده میشود.
- Status Code:
204 No Content
پاسخهای خطا (Conflict)
- Status Code:
409 Conflict
سناریو ۱: تلاش برای گارانتی کردن اتاقی که قبلاً رزرو شده است.
سناریو ۲: تلاش برای حذف گارانتی که دارای رزروهای فعال است.{ "error": { "code": 1000, "message": "اتاق های انتخاب شده دارای رزرو می باشند." } }
{ "error": { "code": 1000, "message": "آیتم های انتخاب شده دارای رزرو می باشند." } }
GET /v2/charter/warranty
Charter: List Warranties
این اندپوینت برای بازیابی لیستی کامل از تمام گارانتیهای مرتبط با یک چارتر مشخص (main_id) استفاده میشود. به ازای هر گارانتی، اطلاعات تکمیلی شامل جزئیات گارانتیکننده (همکار) و لیست اتاقهای اختصاصدادهشده (برای چارترهای اقامتگاهی) نیز بازگردانده میشود. این اندپوینت از یک مکانیزم کش (Caching) با استفاده از Redis برای بهینهسازی و افزایش سرعت دریافت اطلاعات همکاران بهره میبرد.
Request Overview
/v2/charter/warrantyAccess Control
- دسترسی معتبر JWT
Request Parameters (Query String)
این اندپوینت اطلاعات مورد نیاز خود را از طریق Query String دریافت میکند.
| Parameter | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه اصلی چارتر (charter_main.id) که گارانتیهای آن باید لیست شوند. |
Example Request
GET /v2/charter/warranty?id=451
Logic Details
فرآیند پردازش درخواست به شرح زیر است:
- واکشی گارانتیهای اصلی:
- ابتدا تمام رکوردها از جدول
charter_warrantiesکهmain_idآنها باidارسال شده در درخواست برابر است، واکشی میشوند. - نتایج بر اساس
idبه صورت نزولی (DESC) مرتب میشوند تا جدیدترین گارانتیها در ابتدای لیست قرار گیرند.
- ابتدا تمام رکوردها از جدول
- پردازش و غنیسازی هر گارانتی (Mapping):
سیستم بر روی هر رکورد گارانتی واکشیشده، یک سری عملیات برای تکمیل اطلاعات انجام میدهد:
- واکشی اطلاعات گارانتیکننده (Guarantor):
- ابتدا تلاش میشود اطلاعات همکار (
colleague) از کش Redis با کلیدی مانندcolleagues:{id}خوانده شود. - در صورت عدم وجود در کش (Cache Miss): یک کوئری به جدول
colleaguesدر پایگاه داده زده میشود تا اطلاعات همکار استخراج شود. - سپس اطلاعات به دست آمده در Redis ذخیره میشود تا در درخواستهای بعدی با سرعت بیشتری در دسترس باشد (Cache Write).
- ابتدا تلاش میشود اطلاعات همکار (
- واکشی اتاقهای گارانتیشده (برای اقامتگاهی):
- یک کوئری مجزا به جدول
charter_warranty_accommodation_roomsزده میشود تا تمام اتاقهایی که به شناسه گارانتی فعلی (warranty_id) متصل هستند، استخراج شوند.
- یک کوئری مجزا به جدول
- ساختاردهی پاسخ نهایی:
- اطلاعات جمعآوری شده در یک ساختار JSON استاندارد و قابل فهم برای فرانتاند قالببندی میشود. فیلدهای پایگاه داده مانند
reserve_adultوper_adultدر یک شیء تو در تویwarrantyقرار میگیرند. - اگر هیچ اتاقی برای گارانتی یافت نشود، فیلد
room_idمقدارfalseخواهد گرفت.
- اطلاعات جمعآوری شده در یک ساختار JSON استاندارد و قابل فهم برای فرانتاند قالببندی میشود. فیلدهای پایگاه داده مانند
- واکشی اطلاعات گارانتیکننده (Guarantor):
Response Structure
پاسخ موفق (Success)
در صورت موفقیت، پاسخ 200 OK به همراه یک ساختار JSON استاندارد حاوی لیست گارانتیها بازگردانده میشود.
- Status Code:
200 OK
{
"status": true,
"time": 1733271000, // Unix Timestamp
"data": [
{
"id": 99,
"item_id": 102, // شناسه آیتم (مثلا نوع اتاق)
"guarantor": { // اطلاعات کامل همکار از کش یا دیتابیس
"id": 25,
"title_fa": "آژانس مسافرتی بهار",
"title_en": "Bahar Agency",
"first_name": "علی",
"last_name": "رضایی",
"credit_amount": 50000000,
"status": 1
},
"warranty": {
"adult": {
"number": 2,
"price": 2500000
},
"child": {
"number": 1,
"price": 1800000
},
"infant": {
"number": 0,
"price": 0
}
},
"room_id": [ // لیست اتاق های این گارانتی
{
"id": 210,
"warranty_id": 99,
"room_id": 305
},
{
"id": 211,
"warranty_id": 99,
"room_id": 306
}
],
"sank_cost": 0,
"service_price": 150000,
"start_date": "2025-12-10",
"end_date": "2025-12-15",
"status": 1
},
{
"id": 88,
"item_id": 101, // شناسه آیتم (مثلا کلاس پروازی)
"guarantor": { ... },
"warranty": { ... },
"room_id": false, // این گارانتی اقامتگاهی نیست یا اتاقی ندارد
"sank_cost": 500000,
"service_price": 0,
"start_date": null,
"end_date": null,
"status": 1
}
]
}
پاسخ خطا (Error)
در صورت بروز هرگونه استثناء (Exception) در سرور، یک پاسخ با ساختار زیر و کد وضعیت (معمولا 200 OK به دلیل مدیریت خطا در کد) بازگردانده میشود.
توجه: نمایش trace خطا در محیط پروداکشن توصیه نمیشود.
{
"status": false,
"time": 1733271000,
"message": "Error message from the exception.",
"trace": [ ... ] // Stack trace of the error
}
GET /v2/charter/services/list
Charter: List Service Categories & Services
این اندپوینت برای دریافت لیست دستهبندیهای خدمات چارتر بههمراه سرویسهای زیرمجموعه هر دسته استفاده میشود. دادهها از جداول charter_service_categories و charter_services بارگذاری شده و فقط مواردی که status = 1 دارند بازگردانده میشوند.
Request Overview
/v2/charter/services/listAccess Control
- JWT معتبر لازم است
- هیچ محدودیت branch یا core middleware وجود ندارد
- اطلاعات اپراتور از طریق JWT تزریق میشود:
$request->get('operator')
Query Parameters
این اندپوینت هیچ پارامتر ورودی (Query / Body) ندارد.
Response Structure
خروجی موفق شامل کلیدهای زیر است:
| Field | Type | Description |
|---|---|---|
| status | boolean | وضعیت موفقیت درخواست |
| time | integer | Unix timestamp زمان تولید پاسخ |
| data | array | آرایهای از دستهبندیها به همراه سرویسهای هر دسته |
Category Object Schema
| Field | Type | Description |
|---|---|---|
| id | integer | شناسه دستهبندی |
| title.fa | string | عنوان فارسی |
| title.en | string | عنوان انگلیسی |
| services | array | لیست سرویسهای زیرمجموعه (در صورت نبود، آرایه خالی) |
Service Object Schema
| Field | Type | Description |
|---|---|---|
| id | integer | شناسه سرویس |
| title | string | عنوان سرویس |
| type | string | نوع سرویس |
| description | string|null | توضیحات سرویس (میتواند null باشد) |
Core Processing Logic
$categories = DB::table('charter_service_categories')
->select('id','title_fa','title_en')
->where('status', 1)
->get();
foreach ($categories as $category) {
$services = DB::table('charter_services')
->select('id','title','type','description')
->where('category', $category->id)
->where('status', 1)
->get();
}
- ابتدا دستهها با
status=1واکشی میشوند - برای هر دسته، سرویسهای فعال لود میشوند
- اگر سرویس وجود نداشته باشد → آرایه خالی برگردانده میشود
- خروجی نهایی شامل تمام دستهها + سرویسهایشان است
Response (Success)
{
"status": true,
"time": 1710000000,
"data": [
{
"id": 1,
"title": {
"fa": "عنوان فارسی",
"en": "English Title"
},
"services": [
{
"id": 100,
"title": "VIP Lounge",
"type": "lounge",
"description": "Access to VIP lounge"
}
]
}
]
}
Response (Error)
در صورت Exception (به دلیل وجود try/catch):
{
"status": false,
"time": 1710000000,
"message": "Exception message here",
"trace": [ ... ]
}
Flowchart
GET /v2/charter/flight-rate
Charter: Get Approved Flight Rate
این اندپوینت نرخ مصوب (Approved Flight Rate) میان دو فرودگاه را بر اساس جدول approved_flight_rate بازیابی میکند. سیستم ابتدا دقیقاً مسیر origin → destination را جستجو میکند و در صورت نبود، مسیر destination → origin را نیز بررسی میکند. در صورت نیافتن رکورد، خروجی با مقدارهای پیشفرض (0) برگردانده میشود.
Request Overview
/v2/charter/flight-rateAccess Control
- دسترسی فقط با JWT معتبر
- هیچ شرط branch، نقش یا core middleware وجود ندارد
- ورودیها مستقیماً از Query Params خوانده میشوند
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| origin | integer | yes | ID فرودگاه مبدا |
| destination | integer | yes | ID فرودگاه مقصد |
Database Table Reference
اطلاعات نرخهای مصوب از جدول زیر خوانده میشود:
| Column | Type | Description |
|---|---|---|
| id | integer | Primary Key |
| origin | integer | ID فرودگاه مبدا |
| destination | integer | ID فرودگاه مقصد |
| least | integer | کمترین نرخ مصوب |
| most | integer | بیشترین نرخ مصوب |
| updated_at | datetime | زمان آخرین بروزرسانی نرخ |
Core Processing Logic
$result = DB::table('approved_flight_rate')
->where(function ($q) use ($request) {
$q->where('origin', $request->get('origin'))
->where('destination', $request->get('destination'));
})
->orWhere(function ($q) use ($request) {
$q->where('origin', $request->get('destination'))
->where('destination', $request->get('origin'));
})
->first();
- ابتدا رکورد exact-match برای origin → destination جستجو میشود
- در صورت عدم وجود، مسیر معکوس destination → origin بررسی میشود
- اگر رکورد یافت شود: مقادیر واقعی least/most برگشت داده میشود
- اگر یافت نشود: least = 0 و most = 0 و updated_at = now()
Response (Success — Found)
در صورتی که رکورد واقعی در جدول موجود باشد:
{
"status": true,
"time": 1710000000,
"data": {
"origin": 1,
"destination": 2,
"least": 850000,
"most": 1350000,
"updated_at": "2024-09-12 16:40:55"
}
}
Response (Success — Not Found)
اگر ریتی برای این مسیر ثبت نشده باشد:
{
"status": true,
"time": 1710000000,
"data": {
"origin": 1,
"destination": 2,
"least": 0,
"most": 0,
"updated_at": "2025-02-12 14:10:21"
}
}
Response (Error)
در صورت بروز Exception:
{
"status": false,
"time": 1710000000,
"message": "SQLSTATE[HY000]: ...",
"trace": [ ... ]
}
Flowchart
GET /v2/charter/pledger
Charter: List Branch Pledgers
این اندپوینت لیست ضامنین (Pledgers) یک شعبه را از جدول charter_pledgers به همراه اطلاعات همکار (colleague) بازیابی میکند. دادهها با join روی جدول colleagues enrich شده و خروجی نهایی یک آرایه items با ساختار title.fa و title.en بازمیگرداند.
Request Overview
/v2/charter/pledgerQuery Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| branch | integer | yes | شناسه شعبهای که باید لیست ضامنین آن واکشی شود |
Database Tables
Table: charter_pledgers
| Column | Type | Description |
|---|---|---|
| id | integer | شناسه ضامن |
| colleague_id | integer | شناسه همکار ضامن |
| branch | integer | شناسه شعبه |
| status | tinyint | 1=فعال، 0=غیرفعال |
Table: colleagues
| Column | Type | Description |
|---|---|---|
| id | integer | شناسه همکار |
| first_name | string | نام |
| last_name | string | نام خانوادگی |
| office | string | نام دفتر به فارسی |
| office_en | string | نام دفتر به انگلیسی |
| status | tinyint | باید 1 باشد تا همکار معتبر محسوب شود |
Core Processing Logic
$pledgers = DB::table('charter_pledgers')
->select(
'charter_pledgers.id',
'colleagues.id as colleague_id',
'colleagues.first_name',
'colleagues.last_name',
'colleagues.office',
'colleagues.office_en',
'colleagues.status'
)
->where('charter_pledgers.branch', $request->get('branch'))
->where('charter_pledgers.status', 1)
->where('colleagues.status', 1)
->leftJoin('colleagues', 'charter_pledgers.colleague_id', 'colleagues.id')
->orderBy('charter_pledgers.id', 'DESC')
->get();
- فقط ضامنین فعال branch مدنظر انتخاب میشوند
- leftJoin تضمین میکند اگر colleague حذف شده باشد، رکورد pledger حذف نشود (اما فیلتر status=1 جلوی آن را میگیرد)
- مرتبسازی DESC بر اساس آخرین ضامنهای ثبتشده انجام میشود
Output Mapping Logic
$return = $pledgers->map(function ($pledger) {
return [
'id' => $pledger->id,
'colleague_id' => $pledger->colleague_id,
'title' => [
'fa' => $pledger->office .
($pledger->last_name != ''
? ' ' . $pledger->first_name . ' ' . $pledger->last_name
: ""),
'en' => $pledger->office_en
]
];
});
ساختار title.fa مطابق الگوی زیر ساخته میشود:
| Field | Example | Description |
|---|---|---|
| office | آژانس هرمس | نام دفتر |
| first_name | علی | نام همکار (در صورت وجود) |
| last_name | کاظمی | نام خانوادگی همکار (در صورت وجود) |
| Final Output Example → آژانس هرمس علی کاظمی | ||
Response (Success)
{
"items": [
{
"id": 18,
"colleague_id": 92,
"title": {
"fa": "آژانس هرمس علی کاظمی",
"en": "Hermes Agency"
}
}
],
"meta": { "timestamp": 1710000000 }
}
Response (Error)
{
"message": "SQLSTATE[HY000]: ...",
"trace": [...]
}
Flowchart
POST /v2/charter/pledger
pledger: storePledgerCharter
این اندپوینت یک ضامن جدید (Pledger) را برای یک شعبه در جدول charter_pledgers ثبت میکند. در صورت موفقیت مقدار خاصی برنمیگرداند و تنها وضعیت 201 Created داده میشود.
/v2/charter/pledgerRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| branch | integer | yes | شناسه شعبهای که ضامن برای آن ثبت میشود |
| colleague_id | integer | yes | شناسه همکار ضامن |
Controller Logic
public function storePledgerCharter(Request $request)
{
try {
$insert = [
'branch' => $request->get('branch'),
'colleague_id' => $request->colleague_id,
];
DB::table('charter_pledgers')->insert($insert);
return response('', 201);
} catch (Exception $exception) {
return response()->json([
"message" => $exception->getMessage(),
"trace" => $exception->getTrace()
], 400);
}
}
Table: charter_pledgers
| Column | Type | Description |
|---|---|---|
| id | integer | Primary Key (auto-increment) |
| branch | integer | شناسه شعبه |
| colleague_id | integer | شناسه همکار ضامن |
| status | tinyint | ۱ = فعال، ۰ = غیرفعال |
Success Response
Status: 201 Created Body: (empty)
Error Response
Status: 400 Bad Request
{
"message": "SQLSTATE[23000]: ...",
"trace": [...]
}
Flowchart
POST /v2/charter/accommodation/rooms
rooms: getCharterAccommodationRooms
این اندپوینت برای دریافت لیست اتاقهای اقامتگاه (Accommodation Rooms) مرتبط با یک محاسبه calculation_id استفاده میشود. مقدار calculation_id ابتدا اصلاح شده و از جدول مرتبط واکشی میگردد. فقط زمانی پاسخ معتبر برگردانده میشود که چارتر از نوع accommodation باشد.
/v2/charter/accommodation/roomsRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| calculation_id | integer | yes | شناسه محاسبه (calculation) که باید حداقل بزرگتر از 10000 باشد. مقدار واقعی با کم کردن 10000 استخراج میشود. |
Controller Logic
static function getCharterAccommodationRooms(Request $request)
{
try {
if (isset($request->calculation_id) && $request->calculation_id) {
$calculationId = $request->calculation_id - 10000;
$calculation = DB::table('charter_calculations_accommodation')
->select('main_id')
->find($calculationId);
if ($calculation) {
$charter = DB::table('charters')
->select('type')
->find($calculation->main_id);
if ($charter && $charter->type == 'accommodation') {
$accommodationRooms = DB::table('charter_accommodation_rooms')
->where('calc_id', $calculationId)
->get();
return response()->json([
'items' => $accommodationRooms,
'meta' => ['timestamp' => time()]
]);
} else {
return response()->json([
'error' => ['message' => 'charter not found.'],
'meta' => ['timestamp' => time()]
], 400);
}
} else {
return response()->json([
'error' => ['message' => 'calculation not found.'],
'meta' => ['timestamp' => time()]
], 400);
}
} else {
return response()->json([
'error' => ['message' => 'The calculation_id field is required.'],
'meta' => ['timestamp' => time()]
], 400);
}
} catch (Exception $exception) {
return response()->json([
'error' => [
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
'trace' => $exception->getTrace()
]
], 400);
}
}
Possible Errors
| Error | HTTP Code | Description |
|---|---|---|
| The calculation_id field is required. | 400 | اگر calculation_id ارسال نشده باشد |
| calculation not found. | 400 | شناسه محاسبه معتبر نیست یا یافت نشد |
| charter not found. | 400 | محاسبه یافت شد اما چارتر وابسته از نوع accommodation نیست |
| Exception | 400 | بروز خطای داخلی همراه با message، code و trace |
Success Response
Status: 200 OK
{
"items": [
{
"id": 12,
"calc_id": 1033,
"room_title": "...",
"capacity": 3,
"price": 1250000,
...
},
...
],
"meta": {
"timestamp": 1730000000
}
}
Error Response
Status: 400 Bad Request
{
"error": {
"message": "calculation not found."
},
"meta": {
"timestamp": 1730000000
}
}
Flowchart
POST /v2/charter/accommodation/rooms/access
Charter: Access Accommodation Rooms
این اندپوینت برای «از دسترس خارج کردن» یا «بازگرداندن به دسترس» یک اتاق اقامتگاهی استفاده میشود. رفتار اندپوینت بر اساس ارسال یا عدم ارسال start_date و end_date دو حالت مختلف دارد: در حالت بازهدار روی تاریخها عمل میکند و در حالت بدون بازه روی وضعیت عمومی اتاق.
/v2/charter/accommodation/rooms/accessRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| room_id | integer | yes | شناسه اتاق (باید مقدار واقعی با room_id - 10000 بهدست آید) |
| delete | boolean | no | اگر true باشد: عملیات «حذف» (بازگشت از حالت disable) اگر false یا ارسال نشود: اعمال disable |
| start_date | string (Y-m-d) | conditional | تاریخ شروع بازه؛ اگر همراه end_date ارسال شود عملیات روی بازه انجام میشود |
| end_date | string (Y-m-d) | conditional | تاریخ پایان بازه؛ یک روز از آن کم میشود (subDay()) |
Controller Logic
static function accessCharterAccommodationRooms(Request $request)
{
try {
$roomId = $request->room_id - 10000;
$delete = false;
if (isset($request->delete) && $request->delete) {
$delete = filter_var($request->delete, FILTER_VALIDATE_BOOLEAN);
}
if ((isset($request->start_date) && $request->start_date) && isset($request->end_date) && $request->end_date) {
$startDate = Carbon::createFromFormat('Y-m-d', $request->start_date);
$endDate = Carbon::createFromFormat('Y-m-d', $request->end_date)->subDay();
$period = CarbonPeriod::create($startDate, $endDate);
if ($delete) {
foreach ($period as $date) {
$dateString = $date->toDateString();
DB::table('charter_reservation_accommodation_rooms')
->where([
['type', '=', 'disable'],
['room_id', '=', $roomId],
['date', '=', $dateString],
])
->delete();
}
} else {
$allReservations = DB::table('charter_reservation_accommodation_rooms')
->where('date', '>=', $startDate->toDateString())
->where('date', '<=', $endDate->toDateString())
->where('room_id', $roomId)
->exists();
if (!$allReservations) {
foreach ($period as $date) {
$dateString = $date->toDateString();
DB::table('charter_reservation_accommodation_rooms')->insertGetId([
'type' => 'disable',
'room_id' => $roomId,
'date' => $dateString,
]);
}
} else {
return response()->json([
'error' => [
'message' =>
'این اتاق در بازه درخواستی شما دارای رزرو میباشد و از دسترس خارج کردن اتاق امکان پذیر نمی باشد.',
],
'meta' => ['timestamp' => time()]
], 400);
}
}
} else {
if ($delete) {
DB::table('charter_accommodation_rooms')
->where('id', $roomId)
->update(['status' => 1]);
} else {
$allReservations = DB::table('charter_reservation_accommodation_rooms')
->where('date', '>', Carbon::today())
->where('room_id', $roomId)
->exists();
if (!$allReservations) {
DB::table('charter_accommodation_rooms')
->where('id', $roomId)
->update(['status' => 2]);
} else {
return response()->json([
'error' => [
'message' =>
'این اتاق در بازه درخواستی شما دارای رزرو می باشد و از دسترس خارج کردن اتاق امکان پذیر نمی باشد.',
],
'meta' => ['timestamp' => time()]
], 400);
}
}
}
return response()->json([
"payload" => true,
'meta' => ['timestamp' => time()]
]);
} catch (Exception $exception) {
return response()->json([
'error' => [
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
'trace' => $exception->getTrace()
]
], 400);
}
}
Possible Errors
| Error Message | HTTP Code | Description |
|---|---|---|
| این اتاق در بازه درخواستی شما دارای رزرو میباشد و از دسترس خارج کردن اتاق امکان پذیر نمی باشد. | 400 | وقتی برای بازهٔ ارسالی قبلاً رزرو وجود دارد |
| این اتاق در بازه درخواستی شما دارای رزرو می باشد و از دسترس خارج کردن اتاق امکان پذیر نمی باشد. | 400 | وقتی بدون بازه، رزرو آینده وجود دارد (نسخه دوم پیام با فاصله متفاوت) |
Success Response
Status: 200 OK
{
"payload": true,
"meta": {
"timestamp": 1730000000
}
}
Flowchart
GET /v2/academy/categories/view
Academy: View Categories
این اندپوینت برای دریافت لیست دستهبندیهای فعال آکادمی استفاده میشود. تنها رکوردهایی که status = 1 دارند واکشی میشوند و دادهها در ساختار استاندارد شامل فیلدهای id، slug، flag، title، description و status بازگردانده میشوند.
/v2/academy/categories/viewRequest
این اندپوینت هیچ پارامتر اجباری یا اختیاری دریافت نمیکند.
Controller Logic
public function viewCategories(Request $request)
{
$resultCategories = DB::table('academy_categories')->where("status", 1)->get();
$categories = [];
foreach ($resultCategories as $category) {
$categories[] = [
"id" => $category->id,
"slug" => $category->slug,
"flag" => $category->flag,
"title" => [
"fa" => $category->title_fa,
"en" => $category->title_en
],
"description" => $category->description,
"status" => $category->status
];
}
return [
"status" => true,
"time" => time(),
"data" => $categories
];
}
Response Structure
| Field | Type | Description |
|---|---|---|
| status | boolean | همیشه true در صورت اجرای موفق |
| time | integer (timestamp) | زمان پاسخ |
| data | array | لیست دستهبندیها |
Category Object
| Field | Type | Description |
|---|---|---|
| id | integer | شناسه دستهبندی |
| slug | string | اسلاگ یکتا |
| flag | string | فلگ/برچسب دستهبندی |
| title.fa | string | عنوان فارسی |
| title.en | string | عنوان انگلیسی |
| description | string | توضیحات دستهبندی |
| status | integer | وضعیت دستهبندی (فقط 1 بازگردانده میشود) |
Example Response
Status: 200 OK
{
"status": true,
"time": 1730000000,
"data": [
{
"id": 4,
"slug": "frontend",
"flag": "fe",
"title": {
"fa": "فرانتاند",
"en": "Frontend"
},
"description": "دستهبندی آموزشهای تخصصی فرانتاند",
"status": 1
},
...
]
}
Flowchart
GET /v2/academy/courses/view
Academy: View Courses
این اندپوینت لیست دورههای آموزشی آکادمی را با امکان فیلتر بر اساس دستهبندی بازمیگرداند. دادهها شامل اطلاعات کامل دوره، اطلاعات دستهبندی مرتبط، وضعیت کاربر در دوره (progress)، تعداد کل مراحل و وضعیت نمایش (featured) است. تنها دورههایی که وضعیت آنها 1 یا 3 باشد در خروجی قرار میگیرند.
/v2/academy/courses/view?category_id= (optional)Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| category_id | integer | no | اگر ارسال شود فقط دورههای مربوط به آن دستهبندی نمایش داده میشوند |
Controller Logic
// Filter: category_id (optional)
// Only show courses with status IN (1,3)
// Join with academy_categories for category details
// For each course: find progress, total steps, progress completed
$resultCourses = DB::table('academy_courses')
->select([...])
->where(function ($q) use ($request) {
if ($request->has('category_id') && $request->get('category_id')) {
$q->where('academy_courses.category', $request->get('category_id'));
}
})
->whereIn("academy_courses.status", [1, 3])
->leftJoin("academy_categories", "academy_categories.id", "academy_courses.category")
->get();
foreach ($resultCourses as $course) {
$resultCompleted = DB::table('academy_course_completed')
->select('completed', 'current_step')
->where('course', $course->id)
->where('student', $request->get('operator')->id)
->first();
if (!$resultCompleted) {
$stepCompleted = 0;
$currentStep = 1;
} else {
$stepCompleted = $resultCompleted->completed;
$currentStep = $resultCompleted->current_step;
}
$courseCount = DB::table('academy_course_steps')
->where('course', $course->id)
->count();
$courses[] = [
"id" => $course->id,
"slug" => $course->slug,
"branch" => !is_null($course->branch) ? $course->branch : false,
"price" => !is_null($course->price) ? $course->price : 'free',
"necessary" => !is_null($course->necessary) ? json_decode($course->necessary) : false,
"title" => [...],
"category" => [...],
"progress" => [
"current_step" => (int)$currentStep,
"completed" => (int)$stepCompleted
],
"duration" => $course->duration,
"total_steps" => $courseCount,
"featured" => $course->featured,
"description" => $course->description,
"status" => $course->status,
"created_at" => $course->created_at,
"updated_at" => $course->updated_at
];
}
return [
"status" => true,
"time" => time(),
"data" => $courses
];
Course Object Structure
| Field | Type | Description |
|---|---|---|
| id | integer | شناسه دوره |
| slug | string | اسلاگ یکتا |
| branch | integer | false | شناسه شعبه یا false اگر مقدار نداشته باشد |
| price | integer | "free" | قیمت دوره یا "free" اگر مقدار نداشته باشد |
| necessary | array | false | پیشنیازهای دوره (json_decode) یا false |
| title.fa | string | عنوان فارسی دوره |
| title.en | string | عنوان انگلیسی دوره |
| category.* | object | اطلاعات کامل دستهبندی مرتبط |
| progress.current_step | integer | مرحله فعلی کاربر در دوره |
| progress.completed | integer | تعداد مراحل تکمیلشده توسط کاربر |
| duration | integer | مدت زمان دوره (به واحد دقیقه) |
| total_steps | integer | تعداد کل مراحل دوره |
| featured | integer (0/1) | وضعیت برجسته بودن دوره |
| description | string | توضیحات دوره |
| status | integer | 1 یا 3 (فعال / نمایشی) |
| created_at | datetime | تاریخ ایجاد |
| updated_at | datetime | تاریخ بهروزرسانی |
Example Response
Status: 200 OK
{
"status": true,
"time": 1730000000,
"data": [
{
"id": 21,
"slug": "advanced-react",
"branch": false,
"price": "free",
"necessary": false,
"title": {
"fa": "ریاکت پیشرفته",
"en": "Advanced React"
},
"category": { ... },
"progress": { "current_step": 1, "completed": 0 },
"duration": 180,
"total_steps": 12,
"featured": 1,
"description": "دوره کامل React برای توسعهدهندگان پیشرفته",
"status": 1,
"created_at": "2024-04-02 11:30:00",
"updated_at": "2024-04-20 19:12:00"
}
]
}
Flowchart
GET /v2/academy/course/steps/view
Academy: View Course Steps
این اندپوینت اطلاعات کامل یک دوره آموزشی بههمراه تمام مراحل (steps) آن را برمیگرداند. همچنین وضعیت پیشرفت کاربر (current_step و completed) را بهروزرسانی و بازگردانی میکند. منطق این API بر اساس آخرین مرحله (آخرین رکورد step) داده دریافت شده بنا شده است و اطلاعات دوره نیز عمداً از همان آخرین step استخراج میشود (طبق رفتار فعلی سیستم).
/v2/academy/course/steps/viewQuery Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| course_id | integer | yes | شناسه دورهای که میخواهید مراحل آن را مشاهده کنید |
| step_id | integer | no | اگر ارسال شود فقط یک مرحله خاص فیلتر میشود |
| current_step | integer | no | مرحله جدیدی که کاربر به آن رسیده؛ اگر برابر total_steps باشد → مقدار completed یک افزایش مییابد |
Controller Logic
// 1. Fetch all steps of this course (optional filtering by step_id)
// 2. LEFT JOIN course + category info inside each step row
// 3. Load or create progress record in academy_course_completed
// 4. Update progress (completed / current_step)
// 5. Build steps[]
// 6. Build final course object based on the LAST step (intentional behavior)
$resultCourseSteps = DB::table('academy_course_steps')
->select([... course + step + category fields ...])
->where('academy_course_steps.course', $request->get('course_id'))
->where(function ($q) {
if ($request->has('step_id')) $q->where('academy_course_steps.id', $request->get('step_id'));
})
->leftJoin("academy_courses", ...)
->leftJoin("academy_categories", ...)
->orderBy('order')->orderBy('id')
->get();
$resultStep = DB::table('academy_course_completed')
->where('course', $request->get('course_id'))
->where('student', $request->get('operator')->id)
->first();
$courseCount = total number of steps in academy_course_steps;
// Progress Update Logic
if (!$resultStep) create new record with defaults;
else update based on current_step rules;
// Build steps[]
foreach (... each step ...) { ... }
// IMPORTANT: course info is taken from LAST step (as user confirmed)
$return = [
"id" => $step->course_id,
...
"steps" => $steps
];
return [...];
Step Object Structure
| Field | Type | Description |
|---|---|---|
| id | integer | شناسه مرحله |
| title.fa | string | عنوان فارسی مرحله |
| title.en | string | عنوان انگلیسی مرحله |
| subtitle.fa | string | زیرعنوان فارسی |
| subtitle.en | string | زیرعنوان انگلیسی |
| content | string | محتوای مرحله (HTML / Markdown) |
| order | integer | ترتیب نمایش مرحله |
| status | integer | وضعیت فعال/غیرفعال |
| created_at | datetime | تاریخ ساخت |
| updated_at | datetime | آخرین بهروزرسانی |
Course Object Structure (Extracted From Last Step)
| Field | Type | Description |
|---|---|---|
| id | integer | شناسه دوره (از آخرین step) |
| slug | string | اسلاگ دوره |
| branch | integer | false | شناسه شعبه یا false |
| price | integer | "free" | قیمت دوره |
| necessary | array | false | پیشنیازها |
| title.fa | string | عنوان فارسی دوره |
| title.en | string | عنوان انگلیسی دوره |
| category.* | object | اطلاعات دستهبندی |
| progress.current_step | integer | مرحله فعلی |
| progress.completed | integer | تعداد مراحل تکمیلشده |
| duration | integer | مدت زمان دوره |
| total_steps | integer | تعداد کل مراحل |
| featured | integer | 1 اگر دوره ویژه است |
| created_at | datetime | تاریخ ساخت |
| updated_at | datetime | آخرین تغییر |
| steps | array | آرایهی تمام مراحل |
Important Note
اطلاعات دوره از **آخرین step** استخراج میشود. این رفتار در کد وجود دارد و توسط کاربر (علیرضا) تأیید شدهاست. اگر steps خالی باشد، `$step` undefined شده و سیستم خطا خواهد داد.
Example Response
Status: 200 OK
{
"status": true,
"time": 1730000000,
"data": {
"id": 21,
"slug": "advanced-react",
"branch": false,
"price": "free",
"necessary": false,
"title": { "fa": "ریاکت پیشرفته", "en": "Advanced React" },
"category": {
"id": 3,
"flag": "frontend",
"title": { "fa": "فرانتاند", "en": "Frontend" }
},
"progress": { "current_step": 2, "completed": 1 },
"total_steps": 12,
"duration": 180,
"featured": 1,
"created_at": "2024-04-02 11:30:00",
"updated_at": "2024-04-20 19:12:00",
"status": 1,
"steps": [
{
"id": 10,
"title": { "fa": "آشنایی", "en": "Introduction" },
"subtitle": { "fa": "مقدمه", "en": "Intro" },
"content": "...",
"order": 1,
"status": 1,
"created_at": "2024-04-02 11:30:00",
"updated_at": "2024-04-02 11:30:00"
},
...
]
}
}
Flowchart
POST /v2/academy/course/steps/update
Academy: Update / Store / Delete Course Steps
این اندپوینت سه عملیات روی مراحل دوره (Course Steps) انجام میدهد: ایجاد (store)، ویرایش (update) و حذف (delete). نوع عملیات از طریق پارامتر action مشخص میشود. همه عملیاتها روی جدول academy_course_steps انجام میشود و در صورت خطا، پاسخ استاندارد شامل پیام خطا بازگردانده میشود.
/v2/academy/course/steps/updateBody Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| action | string | yes | مشخصکننده عملیات. یکی از مقادیر: update، store، delete. |
| data.course | integer | yes (store/update) | شناسه دوره |
| data.step_id | integer | yes (update/delete) | شناسه مرحله |
| data.title_fa | string | yes (store/update) | عنوان فارسی مرحله |
| data.title_en | string | null | no | اگر رشته خالی ارسال شود → مقدار null ذخیره میشود (دقیقاً مطابق کد). |
| data.subtitle_fa | string | yes | زیرعنوان فارسی |
| data.subtitle_en | string | null | no | اگر رشته خالی باشد تبدیل به null میشود. |
| data.content | string | yes | محتوای مرحله (HTML یا Markdown) |
| data.order | integer | yes | ترتیب مرحله در دوره |
Controller Logic
action = $request->get('action');
data = $request->data;
try {
// ---------- UPDATE ----------
if (action == 'update') {
update academy_course_steps where id = data['step_id']
set:
course = data['course']
title_fa = data['title_fa']
title_en = data['title_en'] == '' ? null : data['title_en']
subtitle_fa = data['subtitle_fa']
subtitle_en = data['subtitle_en'] == '' ? null : data['subtitle_en']
content = data['content']
order = data['order']
updated_at = now()
return status=true, time
// ---------- STORE ----------
} else if (action == 'store') {
insert into academy_course_steps:
course = data['course']
title_fa = data['title_fa']
title_en = data['title_en'] == '' ? null : data['title_en']
subtitle_fa = data['subtitle_fa']
subtitle_en = data['subtitle_en'] == '' ? null : data['subtitle_en']
content = data['content']
order = data['order']
created_at = now()
updated_at = now()
return status=true, time
// ---------- DELETE ----------
} else if (action == 'delete') {
delete from academy_course_steps where id = data['step_id']
return status=true, time
}
} catch (Exception $e) {
return status=false, time, message=$e->getMessage();
}
Important Notes
- هر سه عملیات خروجی یکسان دارند:
{"status": true, "time": ...} - رشتههای خالی
""در فیلدهایtitle_enوsubtitle_enبهnullتبدیل میشوند. - عملیات حذف، Soft Delete نیست — رکورد کاملاً حذف میشود.
- در صورت بروز Exception، پیام خطا همان مقدار واقعی Exception بازگردانی میشود.
Example Requests
1. Update Step
POST /v2/academy/course/steps/update
{
"action": "update",
"data": {
"step_id": 12,
"course": 4,
"title_fa": "ویرایش شده",
"title_en": "",
"subtitle_fa": "ساب جدید",
"subtitle_en": "",
"content": "
New HTML
",
"order": 3
}
}
2. Store New Step
POST /v2/academy/course/steps/update
{
"action": "store",
"data": {
"course": 4,
"title_fa": "مرحله جدید",
"title_en": "New Step",
"subtitle_fa": "ساب",
"subtitle_en": "",
"content": "
Hello
",
"order": 5
}
}
3. Delete Step
POST /v2/academy/course/steps/update
{
"action": "delete",
"data": {
"step_id": 12
}
}
Flowchart
GET /v2/mail/servers/list
Mail: Get Server List
این اندپوینت لیستی از سرورهای ایمیل فعال (Status = 1) را برای یک شعبه (Branch) خاص بازیابی میکند.
خروجی شامل اطلاعات پیکربندی سرویس و محدودیتهای ماهانه هر سرور است که برای مدیریت ارسال ایمیلها استفاده میشود.
Request Overview
/v2/mail/servers/listAccess Control
- دسترسی معتبر JWT (احراز هویت شده)
Query Parameters
| Field | Type | Description |
|---|---|---|
| branch | mixed | (فیلتر) شناسه شعبه مورد نظر برای فیلتر کردن سرورهای ایمیل. |
Example Request
GET /v2/mail/servers/list?branch=10
Logic Details
فرآیند پردازش در متد mailServer به صورت زیر است:
- دریافت ورودی: پارامتر
branchاز درخواست دریافت میشود. - کوئری دیتابیس: به جدول
mail_serversکوئری زده میشود با دو شرط:branchبرابر با پارامتر ورودی باشد.statusبرابر با1باشد (فقط سرورهای فعال).
- تغییر ساختار (Transformation): روی نتایج حلقه زده میشود تا ساختار خروجی استاندارد شود:
- یک شیء تو در تو به نام
serviceایجاد میشود که شامل نام سرویس (از فیلدservice) و شناسه سرویس (از فیلدservice_id) است. - سایر فیلدها مثل
monthly_limitو تاریخها مستقیماً منتقل میشوند.
- یک شیء تو در تو به نام
- ارسال پاسخ: لیست نهایی در قالب استاندارد با وضعیت
trueبازگردانده میشود.
Response Structure
پاسخ موفق
در صورت موفقیت، لیست سرورها در آرایه data قرار میگیرد.
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670154800, "data": [ { "id": 5, "service": { "title": "SendGrid", "id": 101 }, "monthly_limit": 5000, "created_at": "2023-01-10 12:00:00", "updated_at": "2023-05-15 14:30:00" }, { "id": 8, "service": { "title": "SMTP Internal", "id": 202 }, "monthly_limit": 10000, "created_at": "2023-02-20 09:00:00", "updated_at": "2023-02-20 09:00:00" } ] }
پاسخ خطا
در صورت بروز هرگونه خطا (Exception) در طول پردازش، کد وضعیت 400 بازگردانده میشود.
- Status Code:
400 Bad Request - Body:
{ "status": false, "error": "Error message description..." }
Flowchart
Table: `mail_servers`
Where `branch` == Request input
AND `status` == 1
For each server:
Format `service` object {title, id}
GET /v2/mail/address/list
Mail: Get Address List
این اندپوینت لیستی از آدرسهای ایمیل تعریف شده روی یک سرور خاص را برمیگرداند.
نکته کلیدی در این اندپوینت، سیستم دسترسی است: تنها آدرسهایی بازگردانده میشوند که شناسه اپراتور درخواستدهنده در لیست کاربران مجاز آن آدرس (`users` JSON column) وجود داشته باشد. همچنین آدرس نهایی با ترکیب نام کاربری و دامنه سرور ساخته میشود.
Request Overview
/v2/mail/address/listAccess Control
- دسترسی معتبر JWT (شناسه اپراتور از توکن/درخواست استخراج میشود).
Query Parameters
| Field | Type | Description |
|---|---|---|
| server | integer | (الزامی) شناسه سرور ایمیل (`mail_servers.id`) که آدرسها باید از آن خوانده شوند. |
| operator | object/id | (ضمنی) آبجکت اپراتور که معمولاً توسط میدلور احراز هویت به درخواست تزریق میشود (برای بررسی دسترسی). |
Example Request
GET /v2/mail/address/list?server=5
Logic Details
مراحل پردازش در متد mailAddress به شرح زیر است:
- دریافت اطلاعات پایه:
- شناسه اپراتور (`operator->id`) از درخواست استخراج میشود.
- اطلاعات سرور ایمیل بر اساس پارامتر `server` از جدول `mail_servers` واکشی میشود (جهت دسترسی به دامنه سرور).
- جستجو در دیتابیس (فیلترینگ امنیتی):
کوئری به جدول `mail_address` زده میشود با شروط زیر:server: منطبق با شناسه سرور درخواستی باشد.status: برابر با1(فعال) باشد.users: این فیلد یک آرایه JSON است. سیستم با استفاده از دستورJSON_CONTAINSبررسی میکند که آیا شناسه اپراتور جاری در لیست کاربران مجاز این ایمیل وجود دارد یا خیر.
- آمادهسازی خروجی (Data Mutation):
- فیلدهای داخلی و حساس (`users` و `status`) از شیء نتیجه حذف میشوند.
- آدرس کامل ایمیل ساخته میشود: مقدار فیلد `address` (نام کاربری) با کاراکتر `@` و `domain` (از جدول سرور) ترکیب میشود.
مثال: `info` + `@` + `example.com` = `info@example.com`
Response Structure
پاسخ موفق
لیست آدرسهای مجاز به همراه آدرس کامل ساخته شده بازگردانده میشود.
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670154900, "data": [ { "id": 12, "server": 5, "address": "support@mydomain.com", // <-- Full address constructed dynamically "name": "Support Team", // Other fields from DB "created_at": "2023-01-01 10:00:00", "updated_at": "2023-01-01 10:00:00" }, { "id": 14, "server": 5, "address": "sales@mydomain.com", "name": "Sales Department", "created_at": "2023-02-01 11:00:00", "updated_at": "2023-02-01 11:00:00" } ] }
پاسخ خطا
در صورت بروز خطا (مثلاً سرور یافت نشود یا خطای دیتابیس)، پاسخ 400 بازگردانده میشود.
- Status Code:
400 Bad Request - Body:
{ "status": false, "error": "Trying to get property 'id' of non-object", // Example if server not found "trace": [...] }
Flowchart
Get `mail_servers` row by ID.
(Retrieve Domain)
From `mail_address` WHERE:
1. `server` matches
2. `status` = 1
3. `JSON_CONTAINS(users, operator_id)`
1. Remove `users`, `status`.
2. `address` = `address` + '@' + `server.domain`.
POST /v2/mail/address/store
Mail: Create Address
این اندپوینت برای ایجاد یک حساب ایمیل جدید استفاده میشود.
این متد یک فرآیند دو مرحلهای دارد: ابتدا درخواست ساخت اکانت را به سرویسدهنده خارجی (External API) ارسال میکند و در صورت موفقیت، اطلاعات آن را در دیتابیس محلی ذخیره کرده و به اپراتور درخواستدهنده تخصیص میدهد.
Request Overview
/v2/mail/address/storeAccess Control
- دسترسی معتبر JWT (الزامی برای شناسایی اپراتور سازنده).
Body Parameters
| Field | Type | Description |
|---|---|---|
| service_id | string/int | (الزامی) شناسه سرویس در سیستم خارجی (Third-party) که برای فراخوانی API آن استفاده میشود. |
| server | integer | (الزامی) شناسه رکورد سرور در دیتابیس داخلی (`mail_servers.id`) برای اتصال ایمیل به سرور. |
| address | string | (اختیاری) نام کاربری ایمیل (بدون @domain). اگر ارسال نشود، سیستم به طور خودکار بر اساس نام لاتین اپراتور (`f.lastname`) آن را تولید میکند. |
Logic Details
فرآیند متد mailAddressStore به شرح زیر است:
- تعیین نام کاربری (Address Generation):
- اگر پارامتر
addressارسال شده باشد، همان استفاده میشود. - اگر ارسال نشده باشد، سیستم حرف اول
first_name_enو کلlast_name_enاپراتور را با نقطه ترکیب کرده و کوچک (lowercase) میکند. (مثال: `a.rezayi`).
- اگر پارامتر
- فراخوانی سرویس خارجی:
- یک درخواست
POSTبه آدرسbaseUrl/{service_id}/accountsارسال میشود. - پارامتر ارسالی:
{ "name": mailAddress }. - هدر: توکن Bearer داخلی سیستم (`$this->token`).
- یک درخواست
- بررسی پاسخ و ذخیرهسازی:
- اگر پاسخ سرویس خارجی موفق (`status == 'success'`) باشد:
یک رکورد در جدولmail_addressدرج میشود شامل:server: شناسه سرور داخلی.address: نام کاربری ایمیل.title: نام کامل فارسی اپراتور.users: آرایه JSON حاوی شناسه اپراتور سازنده (به عنوان مالک).avatarوservice_id.
- اگر پاسخ ناموفق باشد، پیام خطای سرویس خارجی بازگردانده میشود.
- اگر پاسخ سرویس خارجی موفق (`status == 'success'`) باشد:
Response Structure
پاسخ موفق (Created)
حساب کاربری هم در سرویس خارجی و هم در دیتابیس داخلی ایجاد شده است.
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670155000, "data": "a.username" // The created mail address }
پاسخ ناموفق (API Reject)
سرویس خارجی درخواست ساخت اکانت را رد کرده است (مثلاً نام کاربری تکراری است).
- Status Code:
200 OK - Body:
{ "status": false, "time": 1670155005, "message": "User already exists or invalid name." // Error from external API }
پاسخ خطا (Exception)
- Status Code:
400 Bad Request - Body:
{ "status": false, "error": "Http request failed...", "trace": [...] }
Flowchart
POST /accounts
(Payload: name)
Set `users` = [operator_id]
Set `title`, `avatar` from operator
GET /v2/mail/{type}/list
Mail: Get Mailbox List
این اندپوینت چندمنظوره برای دریافت لیست ایمیلها بر اساس دستهبندیهای مختلف استفاده میشود.
بسته به پارامتر type در آدرس، سیستم میتواند صندوق ورودی، صندوق ارسال، پیشنویسها، یا ایمیلهای فیلتر شده بر اساس تگ و لیبل را بازگرداند.
Request Overview
/v2/mail/{type}/listAccess Control
- دسترسی معتبر JWT.
Path Parameters
| Field | Description |
|---|---|
| type | نوع صندوق یا فیلتر مورد نظر. مقادیر مجاز:
|
Query Parameters
| Field | Type | Description |
|---|---|---|
| address_id | integer | (الزامی) شناسه آدرس ایمیل در دیتابیس (`mail_address.id`) که قصد دریافت ایمیلهای آن را دارید. |
| service_id | integer | (فقط برای Inbox) جهت سینک کردن ایمیلها و ارسال به جاب صف (SnailJob). |
Logic Details
منطق این متد بر اساس type به سه شاخه اصلی تقسیم میشود:
- حالت Inbox (صندوق ورودی):
- ابتدا یک جاب (Job) برای همگامسازی ایمیلها با سرویسدهنده خارجی (Liara Mail) در صف
snailJobدیسپچ میشود (با تاخیر ۱۰ دقیقه). - از جدول
mail_inboxکوئری گرفته میشود (شرط:recipientوstatus=1). - تعداد پیوستها (Attachments) محاسبه شده و خروجی فرمتدهی میشود.
- ابتدا یک جاب (Job) برای همگامسازی ایمیلها با سرویسدهنده خارجی (Liara Mail) در صف
- حالت Sent (صندوق ارسال):
- از جدول
mail_sentکوئری گرفته میشود (شرط:fromوstatus=1). - لیست گیرندگان (`recipients`) در خروجی قرار میگیرد.
- از جدول
- حالتهای فیلتر (Type/Tag/Label):
- سیستم ابتدا تشخیص میدهد که
typeدرخواستی جزو کدام دسته است (Type, Tag یا Label). - سپس هر دو جدول
mail_inboxوmail_sentرا جستجو میکند. - این یعنی اگر شما دنبال تگ
importantباشید، هم ایمیلهای دریافتی و هم ایمیلهای ارسالی که این تگ را دارند بازگردانده میشوند.
- سیستم ابتدا تشخیص میدهد که
Response Structure
پاسخ موفق
خروجی آرایهای از اشیاء ایمیل است. بسته به نوع (دریافتی/ارسالی) فیلد sender یا recipients وجود خواهد داشت.
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670156000, "data": [ { "id": 105, "direction": "inbox", "sender": "client@gmail.com", "sender_name": "John Doe", "subject": "Project Update", "avatar": false, "attachment": 2, // Count of attachments "type": false, "tag": "starred", "label": "work", "created_at": "2023-05-20 10:00:00", "read_at": null }, { "id": 88, "direction": "sent", "recipients": "boss@company.com", // Specific to sent items "subject": "Weekly Report", "attachment": 0, "type": false, "tag": false, "label": "finance", "created_at": "2023-05-19 14:00:00" } ] }
Flowchart
Sync Mail (10m delay)
Where recipient = ID
Where from = ID
Query `mail_inbox` AND `mail_sent`
Where key = {type}
Calculate Attachments Count
Standardize Fields
POST /v2/mail/{type}/update
Mail: Update Attributes
این اندپوینت یک متد عمومی و داینامیک برای ویرایش ویژگیهای یک ایمیل است.
با استفاده از این متد میتوان وضعیتهایی مانند "خوانده شده" (`read_at`)، برچسبها (`label`)، تگها (`tag`) یا هر ستون دیگری از ایمیل را در صندوق ورودی یا ارسالی تغییر داد.
Request Overview
/v2/mail/{type}/updateAccess Control
- دسترسی معتبر JWT.
Path Parameters
| Field | Description |
|---|---|
| type | نوع صندوق پستی که ایمیل در آن قرار دارد (نام جدول را تعیین میکند).
|
Body Parameters
| Field | Type | Description |
|---|---|---|
| mail_id | integer | (الزامی) شناسه منحصربهفرد ایمیل (`id`) که قصد ویرایش آن را دارید. |
| key | string | (الزامی) نام ستون (Column) در دیتابیس که باید تغییر کند. مثال: `read_at`, `tag`, `label`, `type`. |
| value | mixed | (الزامی) مقدار جدیدی که باید در ستون انتخاب شده قرار گیرد. |
Logic Details
منطق این متد بسیار مستقیم است:
- تشخیص جدول هدف: سیستم نام جدول را با الحاق پیشوند
mail_به پارامترtypeآدرس میسازد (مثلاً `mail_inbox`). - اجرای آپدیت: یک کوئری Update روی جدول انتخاب شده اجرا میشود که در آن رکوردی با شناسه
mail_idپیدا شده و مقدار ستونkeyباvalueجایگزین میشود. - موارد استفاده معمول:
- تغییر وضعیت خواندن: ارسال `key: read_at` و `value: (timestamp)`.
- ستارهدار کردن: ارسال `key: tag` و `value: starred`.
- تغییر لیبل: ارسال `key: label` و `value: work`.
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670157000 }
پاسخ خطا
در صورت بروز خطا (مثلاً نام ستون اشتباه باشد):
- Status Code:
400 Bad Request - Body:
{ "status": false, "error": "SQLSTATE[42S22]: Column not found...", "trace": [...] }
Flowchart
Table = "mail_" + {type}
UPDATE [Table]
SET [key] = [value]
WHERE id = [mail_id]
POST /v2/mail/attachments/download
Mail: Download Attachment
این اندپوینت وظیفه دریافت لینک دانلود فایلهای پیوست ایمیل را بر عهده دارد.
برای ایمیلهای ورودی (Inbox)، از استراتژی Lazy Download استفاده میشود: فایل ابتدا روی سرور ایمیل خارجی است؛ اگر اولین بار باشد که درخواست میشود، فایل دانلود شده، در S3 ذخیره میشود و لینک آن در دیتابیس ثبت میگردد. در درخواستهای بعدی، لینک مستقیم S3 بازگردانده میشود.
Request Overview
/v2/mail/attachments/downloadAccess Control
- دسترسی معتبر JWT.
Body Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه رکورد پیوست در جدول لوکال (`mail_attachment.id`). |
| service_id | integer | (الزامی برای Inbox) شناسه سرویس ایمیل جهت ارتباط با API خارجی. |
| mail_id | string | (الزامی برای Inbox) شناسه پیام در سرور خارجی (Message ID). |
| attach_id | string | (الزامی برای Inbox) شناسه پیوست در سرور خارجی. |
| branch | string | (الزامی) نام یا شناسه برنچ جهت ساختن مسیر ذخیرهسازی فایل در S3 (مثلاً: `uploads/{branch}/mailbox/...`). |
Logic Details
منطق پردازش به شرح زیر است:
- بازیابی رکورد: ابتدا رکورد پیوست از جدول
mail_attachmentبر اساسidخوانده میشود. - بررسی جهت (Direction):
- اگر Sent (ارسالی) باشد: فایل قبلاً هنگام ارسال آپلود شده است. لینک موجود در ستون
storageمستقیماً بازگردانده میشود. - اگر Inbox (دریافتی) باشد:
- بررسی کش (Cache Check): سیستم چک میکند آیا فیلد
storageمقدار دارد (Not Null)؟- بله: فایل قبلاً دانلود شده است. لینک S3 بازگردانده میشود.
- خیر (دانلود اولیه):
- یک درخواست به API سرویسدهنده ایمیل ارسال میشود (با استفاده از `service_id`, `mail_id`, `attach_id`).
- محتوای فایل (Base64) دریافت میشود.
- فایل در S3 (فضای ابری) آپلود میشود.
- لینک فایل جدید در ستون
storageرکورد دیتابیس آپدیت میشود تا در دفعات بعدی دانلود نشود. - لینک نهایی به کاربر ارسال میشود.
- بررسی کش (Cache Check): سیستم چک میکند آیا فیلد
- اگر Sent (ارسالی) باشد: فایل قبلاً هنگام ارسال آپلود شده است. لینک موجود در ستون
Response Structure
پاسخ موفق
- Status Code:
200 OK(یا بازگشت آرایه موفق) - Body:
{ "status": true, "time": 1670158000, "link": "https://storage.bucket.com/uploads/main/mailbox/2023/05/file.pdf" }
Flowchart
(Existing Link)
Get Base64 Content
Update DB `storage`
GET /v2/mail/sent/get
Mail: Get Sent Details
این اندپوینت برای دریافت جزئیات کامل یک ایمیل ارسال شده استفاده میشود.
برخلاف لیستها که اطلاعات خلاصهای ارائه میدهند، این متد متن کامل بدنه ایمیل (`content`)، لیست کامل گیرندگان (CC/BCC) و آرایه کامل فایلهای پیوست را باز میگرداند.
Request Overview
/v2/mail/sent/getAccess Control
- دسترسی معتبر JWT.
Query Parameters
| Field | Type | Description |
|---|---|---|
| mail_id | integer | (الزامی) شناسه منحصربهفرد ایمیل در جدول `mail_sent`. |
Logic Details
فرآیند واکشی اطلاعات به صورت زیر است:
- واکشی ایمیل اصلی: سیستم در جدول
mail_sentبه دنبال رکوردی میگردد کهidآن برابر ورودی باشد وstatus=1(فعال) باشد. - واکشی پیوستها: همزمان، تمام رکوردهای جدول
mail_attachmentکهmail_idآنها منطبق است و جهت (`direction`) آنها برابر باsentاست، استخراج میشوند. - فرمتدهی دادهها:
- فیلد
tagبه صورت بولین برگردانده میشود (اگر تگ داشته باشدtrue، در غیر این صورتfalse). - فیلد
labelاگر موجود باشد مقدار رشتهای آن (مثلا "work") و اگر نباشدfalseبرگردانده میشود. - آرایه
attachmentحاوی آبجکتهای کامل فایلهای پیوست است.
- فیلد
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670160000, "data": { "id": 88, "direction": "sent", "from": "support@mydomain.com", "recipients": "client@gmail.com", "cc": "manager@mydomain.com", "bcc": null, "avatar": false, "attachment": [ { "id": 10, "name": "invoice.pdf", "size": "1024kb", "storage": "https://s3.bucket..." } ], "subject": "Invoice #12345", "content": "Please find attached...", "service_id": 1, "type": false, "tag": true, // Boolean: has tag or not "label": "finance", // String or false "created_at": "2023-06-15 09:30:00" } }
Flowchart
Where ID = request.id
AND Status = 1
Where mail_id = request.id
AND direction = 'sent'
Convert Tag to Bool
Handle Label Nulls
Attach Attachments Array
POST /v2/mail/sent/store
Mail: Store & Send Email
این اندپوینت وظیفه ذخیرهسازی و ارسال ایمیل را بر عهده دارد.
عملکرد این متد وابسته به پارامتر type است: اگر outbox باشد، ایمیل علاوه بر ذخیره شدن در دیتابیس، با استفاده از تنظیمات SMTP پویا (Dynamic SMTP) بلافاصله ارسال میشود. در غیر این صورت (مثلاً پیشنویس)، صرفاً ذخیره میشود.
Request Overview
/v2/mail/sent/storeAccess Control
- دسترسی معتبر JWT.
Body Parameters
| Field | Type | Description |
|---|---|---|
| from | integer | (الزامی) شناسه آدرس فرستنده (ID از جدول `mail_address`). جهت واکشی تنظیمات SMTP استفاده میشود. |
| type | string | (الزامی) نوع عملیات. مقدار outbox باعث ارسال واقعی ایمیل میشود. |
| to | string/array | (الزامی) آدرس گیرنده(ها). |
| subject | string | (الزامی) موضوع ایمیل. |
| content | html/string | (الزامی) متن بدنه ایمیل. |
| cc | string/array | (اختیاری) گیرندگان رونوشت. |
| bcc | string/array | (اختیاری) گیرندگان رونوشت مخفی. |
| attachments | array[string] | (اختیاری) آرایهای از مسیر فایلهای آپلود شده (روی دیسک Liara) جهت پیوست. |
Logic Details
منطق پردازش به شرح زیر است:
- ذخیره اولیه (Persist): اطلاعات اولیه ایمیل (گیرندگان، موضوع، متن) در جدول
mail_sentدرج شده وmail_idدریافت میشود. - پردازش پیوستها (DB):
- اگر آرایه
attachmentsارسال شده باشد، سیستم روی آنها حلقه میزند. - متادیتای هر فایل (mime, size, url) از دیسک ابری (Liara) استخراج میشود.
- اطلاعات در جدول
mail_attachmentبا جهتsentذخیره میشود.
- اگر آرایه
- منطق ارسال (SMTP): اگر
type == 'outbox'باشد:- واکشی تنظیمات: بر اساس
from(شناسه آدرس)، اطلاعات سرور (Host, Port, User, Pass) و برند (Brand/Title, Signature) از جداولmail_address,mail_servers,officesاستخراج میشود. - کانفیگ پویا (Dynamic Config): تنظیمات
mail.mailers.smtpدر لاراول به صورت Runtime با اطلاعات واکشی شده جایگزین میشود. - ارسال:
- امضا (Signature) در صورت وجود به انتهای متن اضافه میشود.
- هدر
Fromبا ترکیب آدرس+دامنه و عنوان+برند تنظیم میشود. - فایلهای پیوست مجدداً از دیسک خوانده شده و به ایمیل ضمیمه میشوند (`$message->attach`).
- ایمیل ارسال میگردد.
- واکشی تنظیمات: بر اساس
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670165000 }
Flowchart
Get Meta from Disk
Insert `mail_attachment`
(Join address, servers, offices)
Config::set('mail.smtp', ...)
Add Signature + Attachments
DELETE /v2/mail/sent/trash
Mail: Move Sent to Trash
این اندپوینت برای انتقال یک ایمیل ارسال شده به "زبالهدان" (Trash) استفاده میشود.
این عملیات یک حذف نرم (Soft Delete) است؛ رکورد فیزیکی از دیتابیس پاک نمیشود، بلکه فیلد type آن به trash تغییر میکند تا در لیستهای عادی نمایش داده نشود.
Request Overview
/v2/mail/sent/trashAccess Control
- دسترسی معتبر JWT.
Body/Query Parameters
| Field | Type | Description |
|---|---|---|
| mail_id | integer | (الزامی) شناسه ایمیل مورد نظر در جدول `mail_sent`. |
Logic Details
منطق پردازش بسیار ساده است:
- بهروزرسانی وضعیت: کوئری آپدیت روی جدول
mail_sentاجرا میشود. - شرط: رکوردی که
idآن برابر باmail_idورودی باشد. - تغییر: مقدار ستون
typeبه رشتهtrashتغییر مییابد.
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670168000 }
Flowchart
SET type = 'trash'
WHERE id = request.mail_id
GET /v2/mail/inbox/get
Mail: Get Inbox Details
این اندپوینت برای دریافت جزئیات کامل یک ایمیل ورودی (Inbox) استفاده میشود.
اطلاعات شامل متن کامل ایمیل، فایلهای پیوست، امتیاز اسپم و متادیتای فرستنده است.
Request Overview
/v2/mail/inbox/getAccess Control
- دسترسی معتبر JWT.
Query Parameters
| Field | Type | Description |
|---|---|---|
| mail_id | integer | (الزامی) شناسه منحصربهفرد ایمیل در جدول `mail_inbox`. |
Logic Details
فرآیند واکشی اطلاعات به صورت زیر است:
- واکشی ایمیل: جستجو در جدول
mail_inboxبا شرطid = mail_idوstatus = 1(فعال). - واکشی پیوستها: جستجو در جدول
mail_attachmentبا شرطmail_idو جهتdirection = 'inbox'. - آمادهسازی دادهها:
- مقدار
avatarهمیشهfalseبازگردانده میشود. - مقادیر
tagوlabelدر صورتnullبودن در دیتابیس، به مقدارfalseتبدیل میشوند. - شامل اطلاعات تحلیل اسپم (`spam_score`, `spam_reason`) است.
- مقدار
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670170000, "data": { "id": 105, "direction": "inbox", "sender": "example@domain.com", "sender_name": "John Doe", "reply_to": "reply@domain.com", "subject": "Weekly Report", "avatar": false, "attachment": [ { "id": 12, "mail_id": 105, "file_name": "report.pdf", "url": "https://storage..." } ], "content": "<html>...</html>", "service_id": 1, "type": "text/html", "tag": false, "label": "Work", "spam_score": 0.1, "spam_reason": "Pass", "created_at": "2023-10-10 10:00:00", "read_at": null } }
Flowchart
WHERE id = request.mail_id
AND status = 1
FROM `mail_attachment`
WHERE mail_id AND direction='inbox'
Handle Nulls (tag/label)
Set Avatar False
GET /v2/mail/inbox/get
Mail: Get Inbox Details
این اندپوینت برای دریافت جزئیات کامل یک ایمیل ورودی (Inbox) استفاده میشود.
اطلاعات شامل متن کامل ایمیل، فایلهای پیوست، امتیاز اسپم و متادیتای فرستنده است.
Request Overview
/v2/mail/inbox/getAccess Control
- دسترسی معتبر JWT.
Query Parameters
| Field | Type | Description |
|---|---|---|
| mail_id | integer | (الزامی) شناسه منحصربهفرد ایمیل در جدول `mail_inbox`. |
Logic Details
فرآیند واکشی اطلاعات به صورت زیر است:
- واکشی ایمیل: جستجو در جدول
mail_inboxبا شرطid = mail_idوstatus = 1(فعال). - واکشی پیوستها: جستجو در جدول
mail_attachmentبا شرطmail_idو جهتdirection = 'inbox'. - آمادهسازی دادهها:
- مقدار
avatarهمیشهfalseبازگردانده میشود. - مقادیر
tagوlabelدر صورتnullبودن در دیتابیس، به مقدارfalseتبدیل میشوند. - شامل اطلاعات تحلیل اسپم (`spam_score`, `spam_reason`) است.
- مقدار
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670170000, "data": { "id": 105, "direction": "inbox", "sender": "example@domain.com", "sender_name": "John Doe", "reply_to": "reply@domain.com", "subject": "Weekly Report", "avatar": false, "attachment": [ { "id": 12, "mail_id": 105, "file_name": "report.pdf", "url": "https://storage..." } ], "content": "<html>...</html>", "service_id": 1, "type": "text/html", "tag": false, "label": "Work", "spam_score": 0.1, "spam_reason": "Pass", "created_at": "2023-10-10 10:00:00", "read_at": null } }
Flowchart
WHERE id = request.mail_id
AND status = 1
FROM `mail_attachment`
WHERE mail_id AND direction='inbox'
Handle Nulls (tag/label)
Set Avatar False
DELETE /v2/mail/inbox/trash
Mail: Move Inbox to Trash
این اندپوینت برای انتقال یک ایمیل ورودی (Inbox) به "زبالهدان" (Trash) استفاده میشود.
مشابه اندپوینت بخش ارسالیها، این عملیات نیز یک حذف نرم (Soft Delete) است و رکورد فیزیکی از دیتابیس پاک نمیشود؛ تنها فیلد type آن به trash تغییر میکند.
Request Overview
/v2/mail/inbox/trashAccess Control
- دسترسی معتبر JWT.
Body/Query Parameters
| Field | Type | Description |
|---|---|---|
| mail_id | integer | (الزامی) شناسه ایمیل مورد نظر در جدول `mail_inbox`. |
Logic Details
مراحل پردازش:
- بهروزرسانی دیتابیس: کوئری آپدیت روی جدول
mail_inboxاجرا میشود. - شرط: رکوردی که
idآن برابر باmail_idورودی باشد. - تغییر وضعیت: مقدار ستون
typeبه رشتهtrashتغییر مییابد تا از صندوق ورودی اصلی مخفی شود.
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670175000 }
Flowchart
SET type = 'trash'
WHERE id = request.mail_id
DELETE /v2/mail/trash/delete
Mail: Permanent Delete (Trash)
این اندپوینت برای حذف فیزیکی و دائمی یک ایمیل استفاده میشود.
معمولاً زمانی فراخوانی میشود که کاربر بخواهد آیتمی را از پوشه "زبالهدان" (Trash) به طور کامل پاک کند. برخلاف اندپوینتهای قبلی که فقط برچسب (Type) را تغییر میدادند، این متد رکورد را از دیتابیس حذف میکند (Hard Delete).
Request Overview
/v2/mail/trash/deleteAccess Control
- دسترسی معتبر JWT.
Body/Query Parameters
| Field | Type | Description |
|---|---|---|
| mail_id | integer | (الزامی) شناسه ایمیل مورد نظر برای حذف. |
| type | string | (الزامی) مشخصکننده جدول مبدا. مقادیر مورد انتظار:
|
Logic Details
مراحل پردازش به شرح زیر است:
- انتخاب جدول پویا: نام جدول هدف با ترکیب پیشوند
mail_و پارامترtypeساخته میشود (مثلاًmail_inboxیاmail_sent). - حذف رکورد: دستور
DELETEروی جدول انتخاب شده با شرطid = mail_idاجرا میشود. - نکته امنیتی/فنی: از آنجا که نام جدول به صورت پویا ساخته میشود، فرانتاند باید دقیقاً مقدار
inboxیاsentرا ارسال کند. ارسال مقادیر اشتباه ممکن است منجر به خطای SQL یا تلاش برای حذف از جداول نادرست شود.
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{ "status": true, "time": 1670180000 }
پاسخ خطا
- Status Code:
400 Bad Request(در صورت بروز خطا در دیتابیس)
Flowchart
Table = "mail_" + request.type
(e.g., mail_inbox / mail_sent)
DELETE FROM {Table}
WHERE id = request.mail_id
GET /v2/modules/lottery/registrations
Module: Lottery Registrations List
این اندپوینت برای دریافت لیست ثبتنامکنندگان در ماژول قرعهکشی (Lottery) استفاده میشود.
این سرویس لیست فاکتورهایی را برمیگرداند که شامل آیتم خدمات با شناسه خاص (۱۷) هستند و محاسبات مالی سنگینی (سود، زیان، تراز دریافتی و پرداختی) برای هر ردیف انجام میدهد.
Request Overview
/v2/modules/lottery/registrationsAccess Control
- دسترسی معتبر JWT.
- دسترسی به `branch` ارسال شده.
Query Parameters
توجه: پارامترها به صورت آرایه `json` و پارامتر مجزای `branch` ارسال میشوند.
| Field | Type | Description |
|---|---|---|
| branch | integer | (الزامی - سطح بالا) شناسه شعبه مورد نظر. |
| json[length] | integer | تعداد رکورد در هر صفحه (Pagination Limit). |
| json[start] | integer | آفست شروع (Pagination Offset). |
| json[draw] | integer | شمارنده درخواست (جهت هماهنگی DataTables). |
| json[advanced][r] | integer | (اختیاری) فیلتر جستجو بر اساس شماره سریال فاکتور (سیستم به طور خودکار ۱۰,۰۰۰ واحد برای تطبیق با دیتابیس از آن کم میکند). |
Logic Details
فرآیند تولید گزارش به شرح زیر است:
- تنظیم صفحهبندی: منطق صفحهبندی لاراول (`paginate`) با محاسبه `currentPage` بر اساس `start` و `length` تنظیم میشود.
- فیلترینگ سختگیرانه (Hardcoded):
factors.operator = 1: فقط فاکتورهای مربوط به اپراتور با شناسه ۱ (احتمالاً سیستم یا ادمین اصلی).factor_items.details->id = 17: شناسه ۱۷ در JSON جزئیات، نشاندهنده سرویس "قرعهکشی" است.product = 'service': نوع آیتم باید خدمت باشد.- وضعیت فاکتور نباید ۲ (ابطال) یا ۵ (احتمالاً پیشنویس حذف شده) باشد.
- محاسبات مالی (Financial Analysis): برای هر رکورد یافت شده، تابع استاتیک
TradeController::financialفراخوانی میشود. این تابع موارد زیر را محاسبه میکند:- مبالغ کل: جمع خرید، فروش، مرجوعی و جریمهها.
- اسناد مالی: بررسی جدول `pays` برای دریافتها، پرداختها و اسناد تجمیعی (Aggregation).
- تراز (Balance): محاسبه مانده بدهکاری/بستانکاری مسافر (`BalanceReceived`) و تامینکننده (`BalancePaid`).
- سود (Profit): محاسبه دقیق سود با کسر تخفیفات و کارمزدها.
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body: شامل آرایه `data` با کلیدهای `snake_case` (به دلیل تنظیمات کنترلر).
{
"status": true,
"time": 1715000000,
"draw": 1,
"recordsTotal": 50,
"data": [
{
"id": 101, // شناسه آیتم فاکتور
"system_id": 5050, // شناسه فاکتور
"serial": 1001, // سریال خام دیتابیس
"details": { // جزئیات سرویس قرعهکشی
"id": 17,
"name": "Lottery Name"
},
"customer_id": 200,
"sale": 5000000,
"discount": false, // یا آبجکت تخفیف
"status": 1,
"description": "توضیحات فاکتور",
"financial": { // خروجی تابع Financial با فرمت snake_case
"operator": {
"title": "Admin User",
"id": 1
},
"leader": {
"id": 200,
"title_fa": "علی محمدی",
"mobile": "0912..."
},
"financial": {
"sum_passenger": 1,
"sum_buy": 4000000,
"sum_sale": 5000000,
"sum_receive": 5000000,
"balance_received": 0, // تراز مالی مسافر (0 یعنی تسویه)
"balance_paid": -4000000, // تراز مالی تامین کننده
"profit": 1000000, // سود خالص
"percent_profit": 25
},
"suppliers": { ... }, // لیست تامینکنندگان
"serial_id": 11001 // سریال نمایشی (10000 + serial)
}
}
]
}
Flowchart
Join `factors` & `factor_items`
Filter: Branch, Operator=1, ItemID=17
(TradeController::financial)
Calc Sums, Balances, Profit
POST /v2/trade/tickets
Trade: Tickets & Vouchers Data
این اندپوینت وظیفه استخراج اطلاعات کامل بلیتها، واچرها و فاکتورها را برای نمایش یا چاپ بر عهده دارد.
این سرویس منطق پیچیدهای برای گردآوری اطلاعات مسافران، تامینکنندگان، وضعیت آیتمها (عادی یا استردادی) و تنظیمات نمایش قیمت دارد.
Request Overview
/v2/trade/ticketsAccess Control
- دسترسی عمومی (Public) یا محدود شده (در کد میدلوری مشخص نشده اما احتمالا نیاز به توکن دارد).
- نیاز به ارسال `branch` معتبر.
Body Parameters
| Field | Type | Description |
|---|---|---|
| branch | integer | (الزامی) شناسه شعبه برای فیلتر کردن فاکتورها. |
| id | mixed | (الزامی) شناسه سند مورد نظر. رفتار این فیلد دوگانه است:
|
| lang[id] | string | (اختیاری) کد زبان (پیشفرض: `fa`). |
Logic Details
منطق پردازش این سرویس شامل مراحل زیر است:
- واکشی رفرنس (Factor): اطلاعات پایه فاکتور با Join به جداول `operators` و `customers` دریافت میشود.
- بررسی قابلیت چاپ:
- اگر
print == 0باشد، خطای "عدم قابلیت چاپ" برمیگرداند. - اگر
status == 5باشد، خطای وضعیت همراه با توضیحات فاکتور برمیگرداند.
- اگر
- تعیین حالت نمایش (Print Mode):
1: مشاهده بدون قیمت.2: مشاهده با قیمت.3: عدم مشاهده بلیت و واچر (مسدود).
- پردازش آیتمها (Factor Items):
- بررسی وضعیت آیتم: آیتمهای `refund` همیشه بررسی میشوند، اما آیتمهای `online` بر اساس فیلد JSON `Status` بررسی میشوند.
- استخراج مسافران:
- اگر سرویس هتل (`accommodation`) باشد، لیست `roommate` از JSON استخراج میشود.
- اطلاعات کامل مسافر از `customers` واکشی میشود.
- کشینگ (Redis): اطلاعات کشور (`countries`) برای هر مسافر در Redis کش میشود تا فشار بر دیتابیس کاهش یابد.
- استخراج تامینکننده: اطلاعات `colleagues` با استفاده از Redis کش و بازیابی میشود.
- ساختاردهی خروجی: آیتمها بر اساس نوع (`product`/`byproduct`) و شناسه مسافر گروهبندی میشوند. آیتمهای استردادی (`refund`) به صورت تو در تو داخل آیتم اصلی قرار میگیرند.
- اطلاعات تماس (Branding): اگر فیلد `colleague_auth` مقدار داشته باشد، اطلاعات آژانس همکار (لوگو، آدرس، تلفن) جایگزین اطلاعات پیشفرض میشود.
Response Structure
نکته مهم: در کد فعلی، حتی در صورت موفقیتآمیز بودن عملیات و بازگشت دیتا، مقدار status برابر با false و کد 5008 برگردانده میشود (احتمالاً یک استاندارد داخلی یا لگاسی کد).
{
"status": false, // ! توجه: طبق کد موجود فالس برمیگرداند
"code": 5008,
"data": [
{
"confirmation": 1,
"serial_id": 100500,
"track_code": "09-1200", // فرمت: ماه-سریال
"print": {
"id": 1,
"title": { "fa": "مشاهده بدون قیمت", "en": "view without price" }
},
"internal": true,
"contact_information": {
"logo": "url...",
"address": "Tehran...",
"phone": "021..."
},
"operator": {
"first_name": "Admin",
"last_name": "User",
"mobile": "0912..."
},
"leader": {
"firstname_fa": "علی",
"lastname_fa": "علوی",
"mobile": "0912..."
},
"data": {
// آرایهای از آیتمها گروهبندی شده بر اساس شناسه آیتم/مسافر
"route_123": [
{
"serial_id": 50,
"action": "route",
"passenger": { ... }, // آبجکت کامل مسافر
"sell": false, // یا مبلغ اگر print=2 باشد
"route": { ... } // جزئیات پرواز
}
]
},
"created": "2024-05-10 10:00:00"
}
// ممکن است شامل آبجکتهای خطا هم باشد اگر یکی از فاکتورها مشکل داشته باشد
// { "status": false, "code": 5006, "message": "..." }
]
}
Flowchart
- Check Item Status
- Extract Passengers (Redis Cache)
- Extract Suppliers (Redis Cache)
- Handle Refunds
POST /v2/trade/contract
Trade: Contract Generation
این اندپوینت وظیفه تولید متن کامل قرارداد را بر عهده دارد.
سیستم با استفاده از قالبهای HTML ذخیره شده در دیتابیس و ترکیب آنها با اطلاعات پویای فاکتور (مسافران، خدمات، قیمتها)، خروجی نهایی قرارداد را برای نمایش یا چاپ آماده میکند.
Request Overview
/v2/trade/contractAccess Control
- نیاز به احراز هویت.
- بررسی دسترسی به `branch` ارسال شده.
Body Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شماره سریال نمایشی فاکتور. سیستم به صورت داخلی `ReferenceExtension` را از این عدد کم میکند تا سریال واقعی را در دیتابیس بیابد. |
| branch | integer | (الزامی) شناسه شعبه. |
| lang[id] | string | (الزامی) کد زبان برای انتخاب متن و قالب قرارداد (مثلاً `fa`). |
Logic Details
فرآیند تولید قرارداد شامل مراحل زیر است:
- واکشی اطلاعات پایه: جستجوی فاکتور با اتصال (Join) به جداول
operatorsوcustomersبر اساس سریال و شعبه. - اعتبارسنجیها:
- اگر
print == 0باشد، خطای "عدم قابلیت چاپ" (5006) برمیگرداند. - اگر
status == 5باشد، خطای وضعیت (5001) همراه با توضیحات فاکتور برمیگرداند.
- اگر
- انتخاب قالب (Template Engine):
- سیستم قالبی را از جدول
pagesانتخاب میکند کهtypeآن برابر باcontract_{route}باشد (مثلاًcontract_internal).
- سیستم قالبی را از جدول
- پردازش هوشمند آیتمها:
- محاسبه مجموع ارقام با استفاده از
ApiTradeController::financial. - مدیریت استرداد (Refund Logic): اگر آیتمی از نوع
refundباشد، آیتم اصلی متناظر با آن از لیست خدمات قرارداد حذف میشود تا سرویس کنسل شده نمایش داده نشود.
- محاسبه مجموع ارقام با استفاده از
- تولید جداول HTML: دو جدول به صورت پویا در کد ساخته میشوند:
%passengers-table%: لیست نام، کدملی و تاریخ تولد مسافران.%services-table%: لیست شرح خدمات باقیمانده.
- جایگذاری متغیرها: متغیرهایی مانند
%leader%,%total%,%company%در متن قالب جایگزین میشوند.
Response Structure
در صورت موفقیت، کد HTML قرارداد در فیلد contract و متادیتای مورد نیاز در data قرار میگیرد.
{
"status": true,
"time": 1715001200,
"contract": "... HTML CONTENT ...",
"data": {
"confirmation": "2024-05-10 12:30:00", // زمان تایید یا null
"serial_id": 1500, // شناسه اصلی دیتابیس
"internal": true, // وضعیت داخلی/خارجی بودن مسیر
"leader": {
"id": 105,
"firstname_en": "Ali",
"lastname_en": "Rezaei",
"firstname_fa": "علی",
"lastname_fa": "رضایی",
"mobile": "09121234567"
},
"slug": "xYz123",
"track_code": "02-2500", // فرمت: ماه - (سریال + اکستنشن)
"created": "2024-05-10 10:00:00"
}
}
پاسخهای خطا
// خطای عدم وجود یا وضعیت نامعتبر
{
"status": false,
"code": 5001,
"message": "قرارداد یافت نشد"
}
// خطای غیرقابل چاپ بودن
{
"status": false,
"code": 5006,
"message": "رفرنس مورد نظر قابلیت چاپ ندارد."
}
Flowchart
From `pages` table
1. Calculate Financials
2. Filter Refunds (Hide Cancelled Items)
3. Build HTML Tables (Pass/Service)
4. Replace %Placeholders%
POST /v2/trade/confirmation
Trade: Contract Confirmation
این اندپوینت جهت تایید نهایی قرارداد (فاکتور) توسط اپراتور استفاده میشود.
با فراخوانی این سرویس، فیلد تاییدیه در دیتابیس با زمان جاری مقداردهی شده و در صورت درخواست، پیامک اطلاعرسانی حاوی لینک قرارداد دیجیتال برای مسافر (سرگروه) ارسال میشود.
Request Overview
/v2/trade/confirmationAccess Control
- نیاز به احراز هویت (احتمالاً JWT با توجه به سایر متدها).
- دسترسی به `branch` ارسال شده.
Body Parameters
| Field | Type | Description |
|---|---|---|
| serial_id | integer | (الزامی) شناسه فاکتور (Contract ID) که باید تایید شود. |
| branch | integer | (الزامی) شناسه شعبه جهت فیلترینگ امنیتی. |
| notices | boolean | (اختیاری) اگر `true` ارسال شود، سیستم اقدام به ارسال پیامک اطلاعرسانی به مسافر میکند. |
Logic Details
فرآیند تایید شامل مراحل زیر است:
- یافتن فاکتور: جستجو در جدول
factorsبر اساسserial_idوbranch. - ثبت تاییدیه: بهروزرسانی ستون
confirmationبا زمان جاری سرور (`Carbon::now`). - ارسال اعلان (Notification Logic): اگر پارامتر
noticesارسال شده باشد:- شماره موبایل مسافر (Customer) از جدول `customers` استخراج میشود.
- اطلاعات شعبه (نام فارسی و تلفن) از جدول `offices` استخراج میشود.
- ساخت پیام: متنی حاوی تایید قرارداد، لینک کوتاه (`mmah.ir/c/{slug}`)، تاریخ شمسی و تلفن پشتیبانی ایجاد میشود.
- صفبندی: جاب
SendNotificationبه صف اولویت بالا (`fastJob`) ارسال میشود.
Response Structure
پاسخ موفق
- Status Code:
200 OK - Body:
{
"status": true,
"time": 1715001200
}
پاسخ خطا
- Status Code:
404 Not Found(حتی در صورت خطای داخلی سرور کد 404 برگردانده میشود). - Body:
{
"status": false,
"code": 5007,
"message": "خطا در ارسال اطلاعات.",
"trace": [ ... ]
}
Flowchart
(ID, Slug, CustomerID)
SET confirmation = NOW()
1. Get Customer Mobile
2. Get Branch Info
3. Build Msg (Link+Date)
POST /v2/credit-card
Credit Card: Issue Request
این اندپوینت وظیفه احراز هویت پیشرفته و صدور کارت اعتباری برای مسافر را بر عهده دارد.
در این فرآیند، اطلاعات هویتی و مالکیتی سیمکارت از طریق سرویسهای شاهکار و ثبتاحوال (Jibit) استعلام شده و در صورت تأیید، یک کارت بانکی مجازی با الگوریتم استاندارد بانکی تولید و ۵۰,۰۰۰ ریال از کیف پول اپراتور کسر میگردد.
Request Overview
/v2/credit-cardAccess Control
- نیاز به توکن احراز هویت (JWT).
- اپراتور باید دسترسی به شعبه (`branch`) ارسال شده را داشته باشد.
Body Parameters
| Field | Type | Description |
|---|---|---|
| passenger | integer | (الزامی) شناسه مسافر (Customer ID) که باید در سیستم فعال باشد و نام/نام خانوادگی فارسی داشته باشد. |
| branch | integer | (الزامی) شناسه شعبه صادرکننده (جهت تولید پیششماره کارت). |
| mobile | string | (اختیاری) شماره موبایل جهت استعلام مالکیت. اگر ارسال نشود، از شماره موبایل پروفایل مسافر استفاده میشود. |
| identity_code | string | (اختیاری) کدملی جهت استعلام. اگر ارسال نشود، از کدملی پروفایل مسافر استفاده میشود. |
| operator | object | (تزریق سیستمی) آبجکت اپراتور که معمولاً توسط میدلور به ریکوئست اضافه میشود (جهت ثبت در تراکنش کیف پول). |
Logic Details
فرآیند صدور کارت دارای منطق پیچیده اعتبارسنجی به شرح زیر است:
- اعتبارسنجی اولیه:
- مسافر باید وجود داشته باشد، وضعیت آن فعال (`status=1`) باشد و نام/نامخانوادگی فارسی پر شده باشد.
- فرمت شماره موبایل و کد ملی (الگوریتم چکدیجیت) بررسی میشود.
- بررسی تکراری نبودن: اگر مسافر از قبل کارت فعال (وضعیت ۱ یا ۳) داشته باشد، خطا بازگردانده میشود.
- استعلام تطابق هویتی (Jibit Identity Check):
این مرحله تنها در صورتی اجرا میشود که مسافر قبلاً احراز نشده باشد (`identity_check IS NULL`).- سرویس
getIdentitySimilarityفراخوانی میشود. - اگر درصد تشابه نام و نام خانوادگی کمتر از 90% باشد، خطا بازگردانده میشود.
- نتیجه استعلام در دیتابیس مشتری (`identity_check` و `identity_data`) کش میشود.
- سرویس
- استعلام مالکیت سیمکارت (Shahkar):
- سرویس
getMatchingServiceبرای تطابق کدملی و موبایل فراخوانی میشود. - در صورت عدم تطابق مالکیت، فرآیند متوقف شده و خطا برمیگردد.
- سرویس
- تولید شماره کارت (Luhn Algorithm):
- پیششماره (BIN): بر اساس کد شعبه ساخته میشود.
اگر شعبه >= 90 باشد:990+branch+10
در غیر این صورت:9900+branch+10 - ساختار: [BIN] + [AccountType:1] + [PassengerID:9digits]
- ارقام باقیمانده تصادفی تولید شده و رقم آخر (Check Digit) با فرمول Luhn محاسبه میشود.
- CVV2 تصادفی و انقضا ۳ ساله تنظیم میشود.
- پیششماره (BIN): بر اساس کد شعبه ساخته میشود.
- عملیات مالی و ذخیرهسازی:
- ثبت کارت در جدول
credit_cards. - کسر مبلغ 50,000 ریال از کیف پول اپراتور با عنوان "بابت استعلام هویت مسافر".
- ارسال پیامک اطلاعرسانی به مسافر از طریق صف
fastJob.
- ثبت کارت در جدول
Response Structure
پاسخ موفق
- Status Code:
201 Created - Body: Empty (بدون محتوا).
پاسخهای خطا (نمونه)
{
"error": {
"code": 1000, // یا کدهای خطای جیبیت
"message": "اطلاعات هویتی مسافر با اطلاعات هویتی موجود در سیستم ثبت احوال مطابقت ندارد."
},
"meta": {
"timestamp": 1715001200
}
}
کدهای وضعیت HTTP:
422 Unprocessable Entity: خطای اعتبارسنجی ورودی، نقص اطلاعات پروفایل یا عدم تطابق هویتی.500 Internal Server Error: خطای سیستمی یا خطای اتصال به جیبیت.
Flowchart
Check Name Similarity
Match Mobile & NationalCode
Calc BIN + Luhn Check Digit
1. Insert Card
2. Deduct Wallet (50k)
3. Queue SMS
GET /v2/credit-card/index
Credit Card: List & Index
این اندپوینت وظیفه واکشی و نمایش لیست کارتهای اعتباری صادر شده را بر عهده دارد.
مهمترین ویژگی این بخش، اعمال لایهی امنیتی روی شماره کارتها (Masking) جهت حفاظت از دادههای حساس و همچنین فیلترینگ هوشمند بر اساس دسترسی شعبه (Branch Access) میباشد.
Request Overview
/v2/credit-card/indexAccess Control
- نیاز به توکن احراز هویت (JWT).
- دادهها بر اساس پارامتر
branchفیلتر میشوند (ایزولهسازی دادههای شعب).
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| branch | integer | (الزامی) شناسه شعبه درخواستکننده. اگر کاربر ادمین مرکزی نباشد، لیست فقط محدود به رکوردهای این شعبه میشود. |
| paginate[length] | integer | (الزامی) تعداد رکورد مورد نظر در هر صفحه (Limit). |
| paginate[start] | integer | (الزامی) آفست (Offset) شروع رکوردها. سیستم از این عدد برای محاسبه شماره صفحه فعلی استفاده میکند. |
Logic Details
منطق پردازش لیست شامل مراحل امنیتی و محاسباتی زیر است:
- کنترل سطح دسترسی شعبه (Branch Filtering):
- سیستم بررسی میکند که آیا
branchارسال شده برابر با 1 (دفتر مرکزی) است یا خیر. - اگر
branch != 1باشد، شرطwhereJsonContains('branch', $branch)به کوئری اضافه میشود. این یعنی یک کارت ممکن است به چند شعبه دسترسی داشته باشد (ساختار JSON).
- سیستم بررسی میکند که آیا
- غنیسازی دادهها (Data Enrichment):
- جدول
credit_cardsبا جدولcustomersبه صورت Left Join ترکیب میشود. - هدف: استخراج نام و نام خانوادگی فارسی مسافر (`first_name_fa`, `last_name_fa`) جهت نمایش در لیست.
- جدول
- محاسبه صفحهبندی (Pagination Logic):
- لاراول به صورت پیشفرض با
pageکار میکند، اما ورودی دیتاتیبل معمولاًstartوlengthاست. - فرمول تبدیل:
$page = ($start == 0 ? $length : $start + $length) / $length.
- لاراول به صورت پیشفرض با
- لایه امنیت و تغییر فرمت (Security Transformation):
- ماسک کردن شماره کارت: تابع
Functions::creditCardSecurityفراخوانی میشود.
الگوریتم: نمایش ۴ رقم اول +********+ نمایش ۴ رقم آخر.
نتیجه:6219********1234. - مدیریت مقادیر خالی: فیلدهای
withdrawal_limitوblocked_amountبررسی میشوند؛ اگرnullباشند، مقدارfalseبرگردانده میشود تا در فرانتاند خطای تایپ رخ ندهد.
- ماسک کردن شماره کارت: تابع
Response Structure
پاسخ موفق
{
"items": [
{
"id": 105,
"status": 1, // 1: Active, 0: Inactive
"card": {
"number": "6219********4321", // Masked for security
"withdrawal_limit": false, // false if null
"blocked_amount": 500000
},
"passenger": {
"id": 2045,
"first_name": "علی",
"last_name": "محمدی"
}
}
// ... more items
],
"meta": {
"timestamp": 1715005000,
"table": {
"total": 50,
"per_page": 10,
"current_page": 1,
"last_page": 5,
"from": 1,
"to": 10
}
}
}
مدیریت خطا
در صورت بروز خطای دیتابیس یا منطقی، ساختار زیر برای دیباگ برگردانده میشود:
{
"error": {
"code": "HY000",
"message": "SQLSTATE[HY000]: General error: ...",
"file": "/app/Http/Controllers/V2/CreditCards.php",
"line": 85
},
"meta": { "timestamp": 1715005123 }
}
Flowchart
whereJsonContains('branch', id)
Left Join Customers table
Apply Pagination
1. Mask Card Number (****)
2. Format Response JSON
GET /v2/credit-card
Credit Card: Show Details
این اندپوینت برای مشاهده جزئیات یک کارت اعتباری خاص استفاده میشود.
ویژگی منحصربهفرد این متد، حساسیت به مالکیت (Ownership Awareness) است. سیستم بررسی میکند که آیا درخواستکننده (Operator) همان صاحب کارت است یا خیر. اگر مالک باشد، اطلاعات محرمانه (CVV2، تاریخ انقضا و شماره کامل) نمایش داده میشود؛ در غیر این صورت، دادهها ماسک میشوند.
Request Overview
/v2/credit-cardAccess Control
- نیاز به توکن احراز هویت (JWT).
- دسترسی به اطلاعات کامل (CVV2/Expire) فقط مخصوص صاحب کارت است.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| id | integer | (اختیاری*) شناسه منحصربهفرد کارت اعتباری. (معمولاً یا این فیلد یا passenger_id باید ارسال شود). |
| passenger_id | integer | (اختیاری*) شناسه مسافر. برای یافتن کارت فعال یک مسافر خاص استفاده میشود. |
* توجه: اگرچه پارامترها اختیاری (Optional) تعریف شدهاند، اما برای دریافت خروجی معتبر باید حداقل یکی از آنها ارسال شود تا کوئری نتیجهای برگرداند.
Logic Details & Security
منطق پردازش شامل مراحل زیر است:
- جستجو و اتصال (Query & Join):
- جستجو در جدول
credit_cardsبر اساسidیاpassenger_id. - اتصال (Left Join) به جدول
customersبرای دریافت نام مسافر. - دریافت اولین رکورد منطبق (
first()).
- جستجو در جدول
- بررسی مالکیت (Ownership Check):
- سیستم شناسه کاربری که لاگین کرده (
$request->operator->id) را با شناسه صاحب کارت (passenger_id) مقایسه میکند.
- سیستم شناسه کاربری که لاگین کرده (
- سطحبندی نمایش دادهها (Visibility Logic):
- حالت ۱: کاربر مالک کارت است
- شماره کارت: کامل نمایش داده میشود (از دیتابیس خوانده میشود).
- فیلدهای حساس:
cvv2وexpire_dateبه پاسخ اضافه میشوند.
- حالت ۲: کاربر مالک نیست (مثلاً ادمین یا اپراتور دیگر)
- شماره کارت: توسط تابع
Functions::creditCardSecurityماسک میشود. - فیلدهای حساس:
cvv2وexpire_dateارسال نمیشوند.
- شماره کارت: توسط تابع
- حالت ۱: کاربر مالک کارت است
Response Structure
حالت اول: پاسخ به مالک کارت (Owner View)
{
"payload": {
"id": 105,
"status": 1,
"card": {
"number": "6219861012345678", // Full Number
"withdrawal_limit": false,
"blocked_amount": false,
"cvv2": "452", // Visible
"expire_date": "1405/02" // Visible
},
"passenger": {
"id": 2045,
"first_name": "علی",
"last_name": "محمدی"
}
},
"meta": { "timestamp": 1715005000 }
}
حالت دوم: پاسخ به سایرین (Admin/Public View)
{
"payload": {
"id": 105,
"status": 1,
"card": {
"number": "6219********5678", // Masked Number
"withdrawal_limit": false,
"blocked_amount": false
// CVV2 and Expire Date are OMITTED
},
"passenger": {
"id": 2045,
"first_name": "علی",
"last_name": "محمدی"
}
},
"meta": { "timestamp": 1715005000 }
}
خطای ۴۲۲ (پیدا نشد)
اگر با پارامترهای ارسالی کارتی پیدا نشود:
{
"error": {
"code": 1000,
"message": "کارت اعتباری پیدا نشد."
},
"meta": { ... }
}
Flowchart
Show Full Number
Add CVV2 & Expire
Mask Number (****)
Hide Sensitive Data
POST /v2/articles/{id}/views
Article: Increment Views
این اندپوینت وظیفه افزایش شمارنده بازدید یک مقاله خاص را بر عهده دارد.
این متد معمولاً زمانی فراخوانی میشود که کاربر وارد صفحه جزئیات مقاله (Single Page) میشود. عملیات افزایش بازدید به صورت مستقیم روی دیتابیس (Atomic) انجام شده و تعداد جدید بازدیدها بلافاصله برگردانده میشود.
Request Overview
/v2/articles/{id}/viewsAccess Control
- نیاز به توکن احراز هویت (JWT) دارد (طبق تعریف گروه Middleware).
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه منحصربهفرد مقاله (Article ID) که در آدرس URL قرار میگیرد. |
Logic Details
روند پردازش به شرح زیر است:
- جستجوی رکورد (Find or Fail):
- از متد
Article::findOrFail($id)استفاده میشود. - اگر مقالهای با این شناسه وجود نداشته باشد، لاراول به صورت خودکار یک خطای 404 Not Found استاندارد پرتاب میکند و ادامه کد اجرا نمیشود.
- از متد
- افزایش اتمیک (Atomic Increment):
- از متد
increment('views')استفاده میشود. - این دستور مستقیماً یک کوئری
UPDATE articles SET views = views + 1 WHERE id = ...به دیتابیس ارسال میکند. این روش از Race Condition جلوگیری کرده و پرفورمنس بالایی دارد (نیازی به خواندن، ویرایش در PHP و ذخیره مجدد نیست).
- از متد
- بازگشت پاسخ:
- تعداد بازدیدهای جدید (آپدیت شده) همراه با برچسب زمان بازگردانده میشود.
Response Structure
پاسخ موفق (200 OK)
{
"status": true,
"time": 1715006000,
"views": 1543 // تعداد بازدید جدید پس از افزایش
}
خطای یافت نشد (404 Not Found)
اگر شناسه مقاله در دیتابیس موجود نباشد (توسط findOrFail):
{
"message": "No query results for model [App\\Models\\Article] 9999"
// یا ساختار استاندارد خطای لاراول بسته به تنظیمات Exception Handler
}
Flowchart
UPDATE `views` = `views` + 1
(New View Count)
RESOURCE /v2/articles
Article Resource Management
این بخش شامل ۵ اندپوینت استاندارد برای مدیریت کامل مقالات (Articles) میباشد.
ویژگیهای کلیدی این کنترلر شامل فیلترینگ پیشرفته در لیستگیری (بر اساس دستهبندی، تگ و مکان) و مدیریت روابط متا تگها (PageMetatags) هنگام ساخت و ویرایش است.
1. List Articles (Index)
/v2/articlesپارامترهای فیلترینگ (Query Params)
| Parameter | Type | Description |
|---|---|---|
| categories | string (JSON) | آرایهای از دستهبندیها به صورت رشته JSON. مثال: ["tech", "news"]از منطق orWhereJsonContains استفاده میکند. |
| tags | string (JSON) | آرایهای از تگها به صورت رشته JSON. مثال: ["laravel", "api"] |
| place | mixed | شناسه (ID) یا نام مستعار (Slug) یک مکان. منطق خاص: ابتدا ID مکان را از جدول articles_places پیدا میکند، سپس دستهبندیهای مرتبط را از articles_categories مییابد و مقالات را بر اساس ستون places فیلتر میکند. |
| sortById | boolean | اگر ارسال شود، ترتیب DESC (نزولی) بر اساس ID اعمال میشود. در غیر این صورت پیشفرض ASC است. |
| limit | integer | محدودیت تعداد رکوردها قبل از صفحهبندی (توجه: نتیجه نهایی همچنان Paginate شده است). |
مثال پاسخ (Response)
{
"status": true,
"time": 1715008000,
"data": [
{ "id": 10, "title": "...", "views": 150, ... } // ArticleResource Object
],
"links": {
"first": "...",
"last": "...",
"prev": null,
"next": "..."
}
}
2. Create Article (Store)
/v2/articlesیک مقاله جدید ایجاد میکند. همزمان اگر آرایه metatags ارسال شود، رکوردهای مربوطه در جدول page_metatags ساخته میشوند.
فیلد operator به صورت خودکار از توکن کاربر احراز هویت شده برداشته میشود.
بدنه درخواست (Body Parameters)
| Field | Type | Description |
|---|---|---|
| title | string | (الزامی) عنوان مقاله |
| slug | string | (الزامی) آدرس یکتا |
| branch | integer | (الزامی) شناسه شعبه |
| categories | array | لیست دستهبندیها (در دیتابیس JSON میشود) |
| tags | array | لیست تگها (در دیتابیس JSON میشود) |
| sub_title | string | عنوان فرعی |
| body | text | متن اصلی مقاله |
| summary | text | خلاصه مقاله |
| thumbnail | string | آدرس تصویر شاخص |
| published_at | datetime | تاریخ انتشار (پیشفرض: زمان حال) |
| score | integer | امتیاز مقاله |
| metatags | array[obj] | لیست متا تگها برای سئو. ساختار هر آبجکت: { "key": "description", "value": "text..." } |
3. Show Article (Show)
/v2/articles/{id}جزئیات کامل یک مقاله را برمیگرداند. از Route Model Binding لاراول استفاده میکند (اگر پیدا نشود ۴۰۴ میدهد).
4. Update Article
/v2/articles/{id}اطلاعات مقاله را ویرایش میکند.
منطق هوشمند متاتگها: در آرایه metatags، اگر آیتمی دارای id باشد، آن رکورد در دیتابیس آپدیت میشود. اگر فاقد ID باشد، به عنوان یک متاتگ جدید برای این مقاله ایجاد میشود.
Item has "id"?
YES →
PageMetatag::find($id)->update() NO →
PageMetatag::create()5. Delete Article
/v2/articles/{id}مقاله را حذف میکند. متاتگهای وابسته (اگر Cascade در دیتابیس تنظیم شده باشد) حذف میشوند، در غیر این صورت فقط رکورد مقاله حذف میشود.
{
"status": true,
"time": 1715009000
}
RESOURCE /v2/categories
Category Resource Management
این بخش شامل مدیریت کامل دستهبندیها (Categories) است.
این کنترلر به طور پیشفرض در متد لیستگیری، فقط دستهبندیهای اصلی (بدون والد) را برمیگرداند. همچنین از فیلترهای مکان و نوع پشتیبانی میکند.
1. List Categories (Index)
/v2/categoriesنکته مهم: این اندپوینت دارای فیلتر سختگیرانه whereNull('main') است. یعنی فقط دستهبندیهایی که فیلد main آنها خالی است (دستهبندیهای والد) در خروجی ظاهر میشوند.
پارامترهای فیلترینگ (Query Params)
| Parameter | Type | Description |
|---|---|---|
| branch | mixed | (معمولاً خودکار) فیلتر بر اساس شعبه. |
| type | string | فیلتر بر اساس نوع دستهبندی (مثلاً article, video و...). |
| place | mixed | شناسه (ID) یا نام مستعار (Slug) مکان. سیستم ابتدا شناسه مکان را از جدول articles_places پیدا کرده و سپس دستهبندیهایی که ستون places آنها برابر با آن شناسه باشد را فیلتر میکند. |
| limit | integer | محدودیت تعداد آیتمها در هر صفحه. |
مثال پاسخ
{
"status": true,
"time": 1715011000,
"data": [
{
"id": 5,
"title": "تکنولوژی",
"slug": "tech",
"image": "https://...",
"main": null, // همیشه null است در این لیست
"places": "[1, 2]" // JSON String
}
],
"links": { ... }
}
2. Create Category (Store)
/v2/categoriesیک دستهبندی جدید ایجاد میکند.
بدنه درخواست (Body Parameters)
| Field | Type | Description |
|---|---|---|
| title | string | (الزامی) عنوان دستهبندی |
| slug | string | (الزامی) آدرس یکتا |
| main | integer|null | شناسه دستهبندی والد (اگر زیرمجموعه است). اگر خالی باشد، دسته اصلی محسوب میشود. |
| image | string | آدرس تصویر |
| description | text | توضیحات |
| places | array | لیستی از مکانهای مرتبط. (در دیتابیس به صورت JSON ذخیره میشود). |
| branch | integer | (از طریق توکن/ریکوئست) شناسه شعبه. |
3. Show Category
/v2/categories/{id}مشاهده جزئیات یک دستهبندی خاص.
4. Update Category
/v2/categories/{id}ویرایش اطلاعات دستهبندی. ورودیها دقیقاً مشابه متد Store هستند.
{
"status": true,
"time": 1715012000
}
5. Delete Category
/v2/categories/{id}حذف کامل دستهبندی.
Index Logic Flowchart
1. Branch = Request->branch
2. Main IS NULL (Root Categories)
(by ID or Slug)
↓
Filter:
whereIn('places', [ID])RESOURCE /v2/tags
Tag Resource Management
این بخش شامل مدیریت کامل تگها (Tags) است که برای برچسبگذاری روی مقالات و سایر محتواها استفاده میشود.
این کنترلر از ساختار استاندارد Resource در لاراول پیروی میکند.
1. List Tags (Index)
/v2/tagsلیست تگها را به صورت صفحهبندی شده (۱۵ آیتم در هر صفحه) برمیگرداند.
مثال پاسخ
{
"status": true,
"time": 1715015000,
"data": [
{
"id": 1,
"title": "Laravel",
"slug": "laravel",
"description": "PHP Framework"
}
],
"links": {
"first": "http://.../v2/tags?page=1",
"last": "http://.../v2/tags?page=5",
"prev": null,
"next": "http://.../v2/tags?page=2"
}
}
2. Create Tag (Store)
/v2/tagsیک تگ جدید ایجاد میکند. پارامتر branch به صورت خودکار از درخواست (احتمالاً از توکن کاربر یا میدلور) دریافت و ثبت میشود.
بدنه درخواست (Body Parameters)
| Field | Type | Description |
|---|---|---|
| title | string | (الزامی) عنوان تگ |
| slug | string | (الزامی) آدرس یکتا (معمولاً انگلیسی) |
| description | text | توضیحات تگ (اختیاری) |
مثال پاسخ موفق
{
"status": true,
"time": 1715015100,
"data": {
"id": 12,
"title": "New Tag",
"slug": "new-tag",
"description": "..."
}
}
3. Show Tag
/v2/tags/{id}مشاهده جزئیات یک تگ خاص بر اساس ID.
4. Update Tag
/v2/tags/{id}ویرایش اطلاعات تگ. فیلدها مشابه متد Store هستند.
{
"status": true,
"time": 1715015200
}
5. Delete Tag
/v2/tags/{id}حذف تگ از سیستم.
RESOURCE /v2/landing_pages
Landing Page Management
این بخش شامل مدیریت کامل صفحات فرود (Landing Pages) است.
این صفحات معمولاً برای کمپینهای خاص یا صفحات ایستا استفاده میشوند و شامل مدیریت سئو (Meta Tags) به صورت تودرتو هستند.
1. List Landing Pages (Index)
/v2/landing_pagesتوجه: در این متد از صفحهبندی (Pagination) استفاده نشده و تمام رکوردهای موجود در جدول برگردانده میشوند.
مثال پاسخ
{
"status": true,
"time": 1715016000,
"data": [
{
"id": 1,
"title": "Black Friday Sale",
"slug": "black-friday",
"image": "https://...",
"published_at": "2024-11-20 00:00:00"
},
{
"id": 2,
"title": "Norouz Campaign",
"slug": "norouz-1404",
"image": "https://...",
"published_at": "2025-03-20 00:00:00"
}
]
}
2. Create Landing Page (Store)
/v2/landing_pagesایجاد صفحه فرود جدید به همراه متاتگهای سئو.
بدنه درخواست (Body Parameters)
| Field | Type | Description |
|---|---|---|
| title | string | (الزامی) عنوان صفحه |
| slug | string | (الزامی) آدرس یکتا |
| body | text | محتوای HTML یا متن صفحه |
| image | string | لینک تصویر شاخص |
| published_at | datetime | تاریخ انتشار |
| meta_tag | string | (احتمالاً متاتگ کلی یا توضیحات متا) |
| metatags | array | آرایهای از اشیاء برای جدول page_metatags. ساختار: [{"key": "keywords", "value": "test"}] |
رفتار سیستمی: متاتگهای ارسالی در آرایه metatags با تایپ landing_page در دیتابیس ذخیره میشوند.
3. Show Landing Page
/v2/landing_pages/{id}دریافت جزئیات کامل یک صفحه فرود.
4. Update Landing Page
/v2/landing_pages/{id}ویرایش اطلاعات صفحه فرود و مدیریت هوشمند متاتگها.
منطق آپدیت متاتگها (Metatags Logic)
هنگام ارسال آرایه metatags، سیستم روی هر آیتم پیمایش میکند:
- اگر
idداشته باشد: متاتگ موجود با آن ID را پیدا کرده وkeyوbody(مقدار value) آن را آپدیت میکند. - اگر
idنداشته باشد: یک متاتگ جدید ایجاد میکند.
(نکته کدنویسی: طبق کد موجود، متاتگهای جدیدی که در متد آپدیت ساخته میشوند، تایپarticleمیگیرند، در حالی که در متد Store تایپlanding_pageبود).
Update key & value
(type='article')
5. Delete Landing Page
/v2/landing_pages/{id}حذف صفحه فرود (و احتمالاً متاتگهای وابسته اگر Cascade در دیتابیس تنظیم شده باشد).
RESOURCE /v2/page_metatags
Page Metatag Management (Direct)
این بخش شامل مدیریت مستقیم و مستقل متاتگهای سئو (Meta Tags) است.
معمولاً متاتگها همراه با مقاله یا صفحه فرود ویرایش میشوند، اما این اندپوینتها برای اصلاحات سریع یا مدیریت سیستمی کاربرد دارند.
1. List All Metatags (Index)
/v2/page_metatagsهشدار عملکردی: این متد از PageMetatag::all() استفاده میکند و تمام رکوردهای جدول را بدون صفحهبندی برمیگرداند. در صورت زیاد بودن دادهها، پاسخ سنگین خواهد بود.
مثال پاسخ
{
"status": true,
"time": 1715018000,
"data": [
{
"id": 101,
"page_id": 5,
"type": "article",
"key": "keywords",
"body": "news, tech, ai",
"details": null,
"status": 1,
"created_at": "...",
"updated_at": "..."
}
]
}
2. Create Metatag (Store)
/v2/page_metatagsایجاد یک متاتگ جدید و انتساب آن به یک صفحه یا مقاله.
بدنه درخواست (Body Parameters)
| Field | Type | Description |
|---|---|---|
| page_id | integer | (الزامی) شناسه صفحهای که این متاتگ به آن تعلق دارد. |
| type | string | (الزامی) نوع صفحه والد (مثلاً article یا landing_page). |
| key | string | (الزامی) نام ویژگی متا (مثلاً description, keywords, og:title). |
| body | text | (الزامی) مقدار محتوای متا (Content Value). |
| details | text/json | جزئیات اضافی (اختیاری). |
| status | integer | وضعیت فعال/غیرفعال (مثلاً 1 یا 0). |
3. Show Metatag
/v2/page_metatags/{id}مشاهده جزئیات یک متاتگ خاص.
4. Update Metatag
/v2/page_metatags/{id}ویرایش کامل یک متاتگ. ورودیها دقیقاً مشابه متد Store هستند.
{
"status": true,
"time": 1715018200
}
5. Delete Metatag
/v2/page_metatags/{id}حذف متاتگ از دیتابیس.
RESOURCE /v2/places
Article Places Management
این بخش مربوط به مدیریت مکانهای نمایش (Places) مقالات است.
مکانها احتمالاً نواحی خاصی در سایت هستند (مثل "اسلایدر اصلی"، "اخبار فوری") که مقالات یا دستهبندیها به آنها منتسب میشوند.
1. List Places (Index)
/v2/placesلیست مکانها را برمیگرداند. برای هر مکان، دستهبندیهای متصل به آن نیز (که در جدول articles_categories تعریف شدهاند) واکشی و ضمیمه میشوند.
فیلترها و منطق کوئری
- فیلتر Branch: سیستم مکانهایی را برمیگرداند که `branch` آنها برابر با برنچ کاربر جاری باشد یا برابر با
0(سراسری) باشد. - فیلتر Type: اگر پارامتر
typeدر URL ارسال شود، نتایج بر اساس آن فیلتر میشوند.
ساختار پاسخ (Different Response Structure)
توجه کنید که آرایه اصلی در کلید items قرار دارد.
{
"items": [
{
"id": 1,
"title": "Home Slider",
"type": "slider",
"branch": 0,
"status": 1,
"categories": [
{
"id": 5,
"title": "Top News",
"places": "[1, 2]"
}
]
}
],
"meta": {
"total": 1,
"time": 1715020000
}
}
2. Create Place (Store)
/v2/placesایجاد یک مکان جدید در جدول articles_places.
بدنه درخواست (Body Parameters)
| Field | Type | Description |
|---|---|---|
| title | string | (الزامی) عنوان مکان |
| type | string | نوع مکان (مثلاً slider, sidebar) |
| status | integer | وضعیت (1 فعال، 0 غیرفعال) |
| branch | integer | شناسه شعبه (معمولاً از توکن یا ورودی گرفته میشود) |
3. Show Place
/v2/places/{id}نمایش جزئیات یک مکان خاص.
در اینجا نیز کوئری دوم اجرا میشود تا تمام دستهبندیهایی که این مکان در فیلد places (JSON) آنها وجود دارد، پیدا شده و به خروجی اضافه شوند.
4. Update Place
/v2/places/{id}ویرایش اطلاعات مکان. پارامترها مشابه متد Store است.
{
"status": true,
"time": 1715020500
}
5. Delete Place
/v2/places/{id}حذف رکورد از جدول articles_places.
RESOURCE /v2/media
Media Management (File Storage)
این ماژول وظیفه مدیریت فایلها و رسانههای سیستم را بر عهده دارد.
برخلاف ذخیرهسازی محلی، این کنترلر مستقیماً با فضای ذخیرهسازی ابری (Liara Object Storage) در ارتباط است. فایلها بر اساس نوع، شعبه، سال و ماه در پوشهبندیهای منظم ذخیره شده و متادیتای آنها (شامل سایز، مسیر و ارتباط با سایر موجودیتها) در دیتابیس نگهداری میشود.
1. Upload File (Store)
/v2/mediaBody Parameters
| Field | Type | Description |
|---|---|---|
| file | File | (الزامی) فایل باینری جهت آپلود (تصویر، سند و...). |
| branch | integer | (الزامی) شناسه شعبه (جهت ساختار پوشهبندی). |
| type | string | (الزامی) نوع فایل (مثلاً `slider`, `avatar`, `document`) که نام پوشه اصلی را تعیین میکند. |
| path | string | (اختیاری) پیشوند مسیر ذخیرهسازی. اگر ارسال نشود، پیشفرض uploads در نظر گرفته میشود. |
| related_id | integer | (اختیاری) شناسه رکورد مرتبط (مثلاً ID هتل یا مقاله). |
| related_category | string | (اختیاری) دستهبندی رکورد مرتبط (مثلاً `articles`). |
| operator | object | (تزریق سیستمی) آبجکت اپراتور (از طریق میدلور Auth) برای ثبت در فیلد operator_id. |
Upload Logic Details
- دریافت و آمادهسازی فایل:
- استخراج پسوند (Extension) و نام اصلی فایل.
- نام فایل در دیتابیس بدون پسوند ذخیره میشود.
- ساخت مسیر ذخیرهسازی (Path Generation):
مسیر فایل به صورت دینامیک بر اساس تاریخ جاری و ورودیها ساخته میشود:{base_path}/{branch}/{type}/{Year}/{Month}/{ext}/- اگر پارامتر
pathارسال شود، به عنوانbase_pathاستفاده میشود. - در غیر این صورت،
uploadsبه عنوان پیشفرض قرار میگیرد.
- اگر پارامتر
- ذخیرهسازی فیزیکی (Liara Disk):
- استفاده از
Storage::disk('liara')->putFileAs(...). - فایل با نام اصلی + پسوند در مسیر ساخته شده آپلود میشود.
- سپس سایز واقعی فایل آپلود شده از دیسک ابری استعلام میشود (
size()).
- استفاده از
- ثبت در دیتابیس:
- رکورد جدید با اطلاعات کامل (شامل مسیر کامل
path، سایز، نوع و ارتباطات) در جدولmediaایجاد میشود.
- رکورد جدید با اطلاعات کامل (شامل مسیر کامل
Flowchart (Upload Process)
(Ext, Name)
base/branch/type/Y/m/ext/
putFileAs() & get Size()
2. List Media (Index)
/v2/mediaلیست فایلهای آپلود شده به صورت صفحهبندی شده (۱۵ تایی).
فیلترهای جستجو (Query Params)
operator_id: شناسه آپلود کننده.related_id: شناسه رکورد مرتبط.related_category: دستهبندی مرتبط.type: نوع فایل.branch: شعبه.status: وضعیت فایل.sortById: اگر ارسال شود (true)، مرتبسازی نزولی (جدیدترینها) اعمال میشود.
3. Update Media Status
/v2/media/{id}توجه: در متد آپدیت، فایل فیزیکی یا مسیر آن تغییر نمیکند. تنها میتوان وضعیت (Status) فایل را تغییر داد.
{
"status": 0 // غیرفعال کردن فایل
}
4. Delete Media
/v2/media/{id}عملیات حذف دومرحلهای:
- ابتدا با استفاده از مسیر ذخیره شده در دیتابیس (`path`)، فایل فیزیکی از دیسک Liara حذف میشود (`Storage::disk('liara')->delete`).
- سپس رکورد از دیتابیس پاک میشود.
GET /v2/travel_requests
Travel Requests List
این اندپوینت لیست درخواستهای سفر (مانند پرواز، هتل، قطار و ...) را با قابلیتهای پیشرفته فیلترینگ و غنیسازی (Data Enrichment) بازمیگرداند.
نکته متمایز این کنترلر، منطق Scoping (تعیین سطح دسترسی بر اساس گروه کاربری) و همچنین اتصال به چندین جدول (Operators, Customers, Colleagues, Cities, Factors) و دیتابیس Redis برای ساخت یک آبجکت پاسخ کامل است.
List Requests (Index)
/v2/travel_requestsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| branch | integer | (الزامی) شناسه شعبه. |
| group | string | تعیین کننده سطح دسترسی (`requester_id`):
|
| requester_id | integer | در صورتی که `group` برابر با `base` باشد، برای فیلتر کردن درخواستهای یک کاربر خاص استفاده میشود. |
| operator_id | integer | فیلتر بر اساس اپراتور هندلکننده درخواست. |
| method | string | فیلتر نوع اصلی خدمت (مثلاً `flight`, `hotel`). |
| submethod | string | فیلتر زیرمجموعه خدمت. |
| status | string | فیلتر وضعیت درخواست. |
| payment_status | string | فیلتر وضعیت پرداخت. |
| paginate[length] | integer | تعداد آیتم در صفحه (پیشفرض: ۳۰). |
| paginate[start] | integer | آفست شروع (پیشفرض: ۰). شماره صفحه به صورت دینامیک محاسبه میشود. |
Logic Details
- منطق صفحهبندی (Pagination Calculation):
سیستم از متد استاندارد `page` لاراول استفاده نمیکند، بلکه شماره صفحه را بر اساس `start` و `length` محاسبه میکند:Current Page = (start + length) / length
اگر `start=0` باشد، صفحه ۱ در نظر گرفته میشود. - منطق Scoping (تعیین Requester):
- اگر
groupبرابر base/b2e باشد: کوئری روی `requester_id` ورودی فیلتر میشود. - اگر colleague/b2b یا agent/b2c باشد: کوئری روی `operator->id` (کاربر لاگین شده) قفل میشود.
- در غیر این صورت: همه درخواستها نمایش داده میشوند.
- اگر
- غنیسازی دادهها (Data Enrichment Loop): پس از دریافت دادههای خام از جدول `travel_requests`، روی هر آیتم عملیات زیر انجام میشود:
- Polymorphic Requester: بسته به `operator_group`، اطلاعات درخواستدهنده از جدول `customers` (برای B2C) یا `colleagues` (برای B2B از طریق جدول واسط `colleague_auth`) واکشی میشود.
- Cities: نام فارسی و انگلیسی `origin` و `destination` از جدول `cities` جوین میشود.
- Operator: جزئیات اپراتور هندلکننده (شامل آواتار) اضافه میشود.
- Reference (Factor):
- سریال فاکتور از جدول `factors` خوانده شده و با 10000 جمع میشود.
- عنوان فاکتور از Redis با کلید
reference:{id}:information:title:faاستعلام میشود.
- Accommodation: اگر هتل باشد، اطلاعات آن از طریق متد استاتیک
StaticController::getAccommodationدریافت میشود. - Dynamic Title Construction: یک عنوان توصیفی شامل "مبدا + مقصد + هتل + تاریخها" ساخته میشود. (تاریخها برای نمایش صحیح در متون فارسی با کاراکتر کنترل یونیکد
\u{200E}محصور میشوند). - Details: فیلد `details` که JSON است، دیکد میشود.
Flowchart (Request Processing)
Page = (Start + Length) / Length
Requester_ID
Operator ID
(Admin View)
Apply branch, method, status...
Fetch Cities, Operator, Factor (Redis), Hotel info
Origin + Dest + Hotel + Dates
Response Structure
{
"items": [
{
"id": 123,
"branch": 1,
"requester_id": { "id": 50, "first_name": "...", "last_name": "..." }, // Object (Customer or Colleague)
"origin": { "id": 1, "fa_name": "تهران", "en_name": "Tehran" },
"destination": { "id": 2, "fa_name": "مشهد", "en_name": "Mashhad" },
"reference": {
"id": 99,
"serial": 10055, // Original + 10000
"title": "تور مشهد" // From Redis
},
"accommodation": {
"title": { "fa": "هتل درویشی" },
"category": { "title": "hotel", "subtitle": false }
},
"title": "تهران به مشهد هتل درویشی از 1403/01/01 تا 1403/01/04", // Generated String
"details": { ... } // Decoded JSON
// ... other fields
}
],
"meta": {
"timestamp": 1715000000,
"table": {
"total": 100,
"per_page": 30,
"current_page": 1,
"last_page": 4,
"from": 1,
"to": 30,
"next_page": 2
}
}
}
POST /v2/travel_requests/status/{id}
Change Request Status
این اندپوینت برای تغییر وضعیت یک درخواست سفر و همچنین تخصیص اپراتور مسئول استفاده میشود.
نکته مهم در این متد، منطق شرطی برای ثبت شماره فاکتور (`reference_id`) است که تنها در وضعیت خاصی (وضعیت ۴) اعمال میشود.
Update Status & Assign Operator
/v2/travel_requests/status/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| status | integer | (الزامی) کد وضعیت جدید درخواست. |
| operator_id | integer | (اختیاری) شناسه اپراتور مسئول رسیدگی. اگر مقدار خالی یا `0` ارسال شود، در دیتابیس `NULL` ثبت میشود. |
| reference_id | integer | (شرطی) شناسه فاکتور صادر شده. این فیلد تنها زمانی در دیتابیس آپدیت میشود که status برابر با 4 باشد. |
Logic Details
- آمادهسازی دادهها:
- مقدار
operator_idبررسی میشود؛ در صورت وجود مقدار معتبر ثبت میشود، در غیر این صورت (استفاده از Elvis Operator `?:`) مقدار `NULL` لحاظ میشود. - مقدار
statusمستقیماً از ورودی خوانده میشود.
- مقدار
- شرط ثبت فاکتور (Reference Logic):
سیستم تنها در صورتی فیلدreference_idرا آپدیت میکند که دو شرط زیر همزمان برقرار باشند:- وضعیت جدید (`status`) برابر با 4 باشد.
- مقدار `reference_id` در ورودی ارسال شده و دارای مقدار باشد.
- بهروزرسانی دیتابیس: عملیات با استفاده از `Query Builder` روی جدول `travel_requests` انجام میشود.
Flowchart (Status Change Logic)
(Val or NULL)
to update list
Update travel_requests WHERE id = $id
Response Example
{
"payload": {
"status": true
},
"meta": {
"timestamp": 1715000000
}
}
RESOURCE /v2/accommodation/facilities
Accommodation Facilities CRUD
این کنترلر وظیفه مدیریت امکانات اقامتگاهها (Facilities) را بر عهده دارد.
ویژگی بارز این پیادهسازی، استفاده مستقیم از Query Builder لاراول (`DB::table`) به جای Eloquent Models برای تمامی عملیاتها (خواندن، نوشتن، ویرایش و حذف) است که باعث افزایش پرفورمنس در کوئریهای ساده میشود.
List All Facilities
/v2/accommodation/facilitiesدریافت لیست کامل امکانات بدون هیچگونه فیلترینگ یا صفحهبندی.
Response Example
{
"status": true,
"time": 1715000000,
"data": [
{
"id": 1,
"category_id": 5,
"title_en": "Free WiFi",
"title_fa": "اینترنت رایگان",
"icon": "wifi-icon.svg",
"description": "High speed internet",
"branch": 1,
"status": 1
}
]
}
Create Facility
/v2/accommodation/facilitiesBody Parameters
| Parameter | Type | Description |
|---|---|---|
| category_id | integer | شناسه دستهبندی امکانات. |
| branch | integer | شناسه شعبه. |
| title_en | string | عنوان انگلیسی. |
| title_fa | string | عنوان فارسی. |
| icon | string | آدرس یا نام فایل آیکون. |
| description | string | توضیحات تکمیلی. |
| status | integer | وضعیت فعال/غیرفعال (معمولاً 1 یا 0). |
Implementation Note
سیستم از DB::table('facilities')->insertGetId() برای درج رکورد و دریافت همزمان ID استفاده میکند. سپس بلافاصله یک کوئری `SELECT` اجرا میشود تا آبجکت کامل ساخته شده را در پاسخ بازگرداند.
Show Facility Details
/v2/accommodation/facilities/{id}نمایش جزئیات یک رکورد خاص بر اساس ID.
Update Facility
/v2/accommodation/facilities/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| title_en | string | عنوان انگلیسی. |
| title_fa | string | عنوان فارسی. |
| icon | string | آدرس یا نام فایل آیکون. |
| description | string | توضیحات. |
| status | integer | وضعیت. |
category_id و branch در آرایه بهروزرسانی وجود ندارند. این بدان معناست که پس از ایجاد، امکان تغییر دستهبندی یا شعبه یک Facility از طریق این اندپوینت وجود ندارد (Immutable Fields).Delete Facility
/v2/accommodation/facilities/{id}حذف فیزیکی رکورد از دیتابیس (Hard Delete) با استفاده از دستور delete() کوئری بیلدر.
RESOURCE /v2/accommodation/facilities_category
Accommodation Facility Categories
این کنترلر وظیفه مدیریت دستهبندیهای امکانات (Facility Categories) را بر عهده دارد (مانند: تفریحی، بهداشتی، عمومی و ...).
مشابه کنترلر Facilities، این بخش نیز برای افزایش سرعت و کارایی از Query Builder لاراول (`DB::table`) استفاده میکند.
List Categories
/v2/accommodation/facilities_categoryدریافت لیست کامل دستهبندیها بدون فیلترینگ و صفحهبندی.
Response Example
{
"status": true,
"time": 1715000000,
"data": [
{
"id": 1,
"title_en": "General",
"title_fa": "عمومی",
"icon": "general.svg",
"description": "General amenities",
"branch": 1,
"status": 1
}
]
}
Create Category
/v2/accommodation/facilities_categoryBody Parameters
| Parameter | Type | Description |
|---|---|---|
| title_en | string | عنوان انگلیسی دستهبندی. |
| title_fa | string | عنوان فارسی دستهبندی. |
| branch | integer | شناسه شعبه. |
| icon | string | نام یا مسیر فایل آیکون. |
| description | string | توضیحات. |
| status | integer | وضعیت (1: فعال، 0: غیرفعال). |
Show Category
/v2/accommodation/facilities_category/{id}نمایش تکی یک دستهبندی بر اساس ID.
Update Category
/v2/accommodation/facilities_category/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| title_en | string | عنوان انگلیسی. |
| title_fa | string | عنوان فارسی. |
| branch | integer | شناسه شعبه. (قابل ویرایش) |
| icon | string | آیکون. |
| description | string | توضیحات. |
| status | integer | وضعیت. |
branch نیز در آرایه آپدیت وجود دارد و قابل ویرایش است.Delete Category
/v2/accommodation/facilities_category/{id}حذف فیزیکی رکورد از جدول facilities_category.
RESOURCE /v2/accommodation/rules
Accommodation Rules CRUD
این کنترلر وظیفه مدیریت قوانین اقامتگاه (Accommodation Rules) را بر عهده دارد (مانند: قوانین کنسلی، ساعت ورود/خروج، ممنوعیت حیوانات خانگی و ...).
دادهها مستقیماً در جدول accommodation_rules ذخیره میشوند و برای تمام عملیات دیتابیس از Query Builder استفاده شده است.
List All Rules
/v2/accommodation/rulesدریافت لیست کامل قوانین ثبت شده بدون فیلترینگ یا صفحهبندی.
Response Example
{
"status": true,
"time": 1715000000,
"data": [
{
"id": 1,
"accommodation": 105,
"title_en": "No Smoking",
"title_fa": "استعمال دخانیات ممنوع",
"description": "Smoking is strictly prohibited inside the rooms.",
"status": 1
}
]
}
Create Rule
/v2/accommodation/rulesBody Parameters
| Parameter | Type | Description |
|---|---|---|
| accommodation | integer | شناسه اقامتگاه مرتبط (Relation Key). |
| title_en | string | عنوان انگلیسی قانون. |
| title_fa | string | عنوان فارسی قانون. |
| description | string | توضیحات کامل قانون. |
| status | integer | وضعیت (1: فعال، 0: غیرفعال). |
Show Rule Details
/v2/accommodation/rules/{id}نمایش جزئیات یک قانون خاص بر اساس شناسه.
Update Rule
/v2/accommodation/rules/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| accommodation | integer | شناسه اقامتگاه. (قابل ویرایش) |
| title_en | string | عنوان انگلیسی. |
| title_fa | string | عنوان فارسی. |
| description | string | توضیحات. |
| status | integer | وضعیت. |
accommodation) نیز در متد Update وجود دارد و قابل تغییر است.Delete Rule
/v2/accommodation/rules/{id}حذف فیزیکی رکورد از جدول accommodation_rules.
GET /v2/comments
Comments: List & Filter
این اندپوینت برای دریافت لیستی از نظرات (Comments) با قابلیتهای فیلترینگ و مرتبسازی پیشرفته طراحی شده است.
این پیادهسازی از Eloquent ORM و API Resources لاراول بهره میبرد و نتایج را به صورت صفحهبندی شده (15 آیتم در هر صفحه) بازمیگرداند.
List All Comments
/v2/commentsQuery Parameters
این اندپوینت از پارامترهای زیر برای فیلتر و مرتبسازی پشتیبانی میکند:
| Parameter | Type | Description |
|---|---|---|
| operator_id | integer | فیلتر بر اساس شناسه اپراتور ثبتکننده نظر. |
| content_id | integer | فیلتر بر اساس شناسه محتوایی که نظر برای آن ثبت شده (مثلا شناسه مقاله). |
| category | string | فیلتر بر اساس دستهبندی محتوا (مثلا 'article', 'accommodation'). |
| branch | integer | فیلتر بر اساس شناسه شعبه. |
| status | integer | فیلتر بر اساس وضعیت نظر (مثلا تایید شده، در انتظار، رد شده). |
| flagged | integer | فیلتر نظراتی که علامتگذاری (Flag) شدهاند. (مثلا 1 برای فیلتر موارد علامتدار). |
| reply_to | integer | فیلتر برای نمایش پاسخهای یک نظر خاص (با ارسال ID نظر اصلی). |
| sortById | any | منطق مرتبسازی: - اگر این پارامتر در URL وجود داشته باشد (مثلا ?sortById=true)، نتایج بر اساس id به صورت نزولی (Desc) مرتب میشوند (جدیدترینها اول).- در غیر این صورت (پیشفرض)، نتایج به صورت صعودی (Asc) مرتب میشوند (قدیمیترینها اول). |
| page | integer | شماره صفحه برای صفحهبندی (مثلا ?page=2). |
Response Structure
خروجی این اندپوینت شامل آبجکتهای data (آرایهای از نظرات)، links (لینکهای صفحهبندی) و meta (اطلاعات متا صفحهبندی) است.
Response Example
{
"status": true,
"time": 1715000000,
"data": [
{
"id": 10,
"operatorId": 5,
"contentId": 152,
"category": "article",
"branch": 1,
"commentText": "مطلب بسیار مفیدی بود، ممنون.",
"status": "approved",
"isFlagged": false,
"replyTo": null,
"createdAt": "2024-10-22T14:30:00.000000Z"
}
],
"links": {
"first": "http://your.api/v2/comments?page=1",
"last": "http://your.api/v2/comments?page=5",
"prev": null,
"next": "http://your.api/v2/comments?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 5,
"path": "http://your.api/v2/comments",
"per_page": 15,
"to": 15,
"total": 75
}
}
GET /v2/comments/{id}
Show Comment Details
این اندپوینت جزئیات کامل یک نظر خاص را برمیگرداند.
در این متد از قابلیت Route Model Binding لاراول استفاده شده است؛ به این معنی که اگر شناسهی ارسال شده در URL در دیتابیس وجود نداشته باشد، فریمورک به صورت خودکار خطای 404 برمیگرداند.
Get Single Comment
/v2/comments/{id}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | integer | شناسه منحصر به فرد نظر (Comment ID). |
Response Logic
خروجی از طریق CommentResource فرمتدهی شده و در قالب استاندارد شامل وضعیت و زمان سرور بازگردانده میشود.
Response Example
{
"status": true,
"time": 1715000000,
"data": {
"id": 10,
"operatorId": 5,
"contentId": 152,
"category": "article",
"branch": 1,
"commentText": "مطلب بسیار مفیدی بود، ممنون.",
"status": "approved",
"isFlagged": false,
"replyTo": null,
"createdAt": "2024-10-22T14:30:00.000000Z",
"updatedAt": "2024-10-22T14:30:00.000000Z"
}
}
DELETE /v2/comments/{id}
Delete Comment
این اندپوینت برای حذف یک نظر مشخص از دیتابیس استفاده میشود.
این متد نیز از قابلیت Route Model Binding لاراول بهره میبرد؛ به این معنی که ابتدا مدل Comment متناظر با شناسه ارسالی را پیدا کرده و سپس آن را حذف میکند. اگر شناسه نامعتبر باشد، به طور خودکار پاسخ 404 (Not Found) بازگردانده میشود.
Delete a Single Comment
/v2/comments/{id}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | integer | شناسه منحصر به فرد نظری که باید حذف شود (Comment ID). |
Response on Success
در صورت موفقیتآمیز بودن عملیات حذف، یک پاسخ ساده با status: true بازگردانده میشود و هیچ دادهای در بدنه پاسخ وجود ندارد.
Response Example
{
"status": true,
"time": 1715000000
}
POST /v2/comments
Create Comment
این اندپوینت برای ثبت یک نظر جدید در سیستم طراحی شده است. فرآیند ارسال داده به این اندپوینت به دلیل ملاحظات امنیتی، پیچیدگی خاصی دارد.
دادههای اصلی نظر ابتدا باید به صورت یک آبجکت JSON ساخته شده، سپس با استفاده از کلید عمومی (Public Key) مربوط به هر شعبه رمزنگاری (Encrypt) شوند و در نهایت به صورت Base64 در یک فیلد به نام data به سرور ارسال گردند.
Request Flow and Encryption
- ساخت آبجکت JSON: ابتدا اطلاعات نظر را در قالب یک آبجکت JSON استاندارد آماده کنید (ساختار آن در جدول زیر آمده است).
- رمزنگاری (Encryption): رشته JSON حاصل را با استفاده از کلید عمومی RSA که مختص
branchمورد نظر است، رمزنگاری کنید. - کدگذاری Base64: خروجی باینری حاصل از مرحله رمزنگاری را به فرمت رشته Base64 تبدیل کنید.
- ارسال درخواست: یک درخواست
POSTبه این اندپوینت ارسال کنید که بدنه آن فقط شامل یک فیلدdataحاوی رشته Base64 نهایی است. - پردازش در سرور: میدلور
decryptDataدر سمت سرور با استفاده از کلید خصوصی (Private Key) متناظر، دادهها را رمزگشایی کرده و در اختیار کنترلر قرار میدهد.
Create a New Comment
/v2/commentsRequest Body Structure (Before Encryption)
آبجکت JSON که باید رمزنگاری و ارسال شود، باید شامل فیلدهای زیر باشد:
| Parameter | Type | Description | Validation |
|---|---|---|---|
| content_id | integer | شناسه محتوایی که نظر برای آن است (مثلاً شناسه مقاله). | Required |
| category | string | نوع محتوا (مثلاً 'article', 'accommodation'). | Required |
| content | string | متن اصلی نظر. | Required |
| name | string | نام نویسنده نظر. | Required |
| string | ایمیل نویسنده نظر. | Required, Email Format | |
| phone | string | شماره تلفن نویسنده نظر. | Optional |
| status | integer | وضعیت اولیه نظر (مثلاً 0 برای "در انتظار تایید"). | Required |
| reply_to | integer | اگر این نظر پاسخی به نظر دیگری است، شناسه نظر والد در اینجا قرار میگیرد. در غیر این صورت null یا حذف شود. | Optional, Nullable |
StoreCommentRequest اعتبارسنجی میشوند. در صورت ارسال داده نامعتبر، خطای 422 بازگردانده خواهد شد.Request Examples
مرحله ۱: ساختار JSON خام (قبل از رمزنگاری)
{
"content_id": 152,
"category": "article",
"content": "این یک تست برای ثبت کامنت است.",
"name": "کاربر تست",
"email": "test@example.com",
"phone": "09120000000",
"status": 0,
"reply_to": null
}
مرحله ۲: بدنه نهایی درخواست (بعد از رمزنگاری و Base64)
{
"data": "eyB...[Encrypted & Base64 Encoded String]...SIn0="
}
Server-Side Logic
پس از رمزگشایی موفق، کنترلر به صورت خودکار اطلاعات زیر را به دادههای دریافتی اضافه کرده و در دیتابیس ذخیره میکند:
operator_id: شناسه اپراتور لاگین کرده (در صورت وجود).branch: شناسه شعبه اپراتور.ip_address: آدرس IP درخواستدهنده.
Success Response
در صورت موفقیت، سرور یک پاسخ با کد 200 و حاوی آبجکت نظر جدید که از طریق CommentResource فرمتدهی شده، برمیگرداند.
{
"status": true,
"time": 1715000000,
"data": {
"id": 11,
"operatorId": 5,
"contentId": 152,
"category": "article",
"branch": 1,
"commentText": "این یک تست برای ثبت کامنت است.",
"status": "pending",
"isFlagged": false,
"replyTo": null,
"createdAt": "2024-10-23T10:00:00.000000Z",
"updatedAt": "2024-10-23T10:00:00.000000Z"
}
}
PUT /v2/comments/{id}
Update Comment
این اندپوینت برای بهروزرسانی اطلاعات یک نظر موجود استفاده میشود. همانند اندپوینت ایجاد نظر، دادههای ارسالی برای بهروزرسانی نیز باید ابتدا رمزنگاری شده و سپس ارسال شوند.
این متد از قابلیت Route Model Binding لاراول استفاده میکند. به این معنی که لاراول ابتدا به صورت خودکار مدل Comment مربوط به {id} ارسالی در URL را پیدا میکند و سپس عملیات بهروزرسانی را روی آن انجام میدهد. اگر شناسه نامعتبر باشد، پاسخ 404 بازگردانده میشود.
Encryption Flow
فرآیند رمزنگاری و ارسال دادهها برای این اندپوینت دقیقاً مشابه اندپوینت ایجاد نظر (Create Comment) است. خلاصهی مراحل به شرح زیر است:
- ساخت آبجکت JSON: دادههای مورد نظر برای آپدیت را در قالب یک آبجکت JSON آماده کنید.
- رمزنگاری و Base64: آبجکت JSON را با کلید عمومی (Public Key) شعبه رمزنگاری کرده و به Base64 تبدیل کنید.
- ارسال درخواست: درخواست
PUTرا به همراه بدنه حاوی فیلدdata(شامل رشته نهایی) ارسال نمایید.
decryptData در سمت سرور، با استفاده از کلید خصوصی (Private Key)، دادهها را رمزگشایی کرده و برای پردازش به کنترلر تحویل میدهد.Update an Existing Comment
/v2/comments/{id}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | integer | شناسه منحصر به فرد نظری که باید بهروزرسانی شود (Comment ID). |
Request Body Structure (Before Encryption)
آبجکت JSON که باید رمزنگاری و در فیلد data ارسال شود، میتواند شامل فیلدهای زیر باشد. تمام فیلدها اختیاری هستند و فقط فیلدهایی که قصد تغییر آنها را دارید، ارسال کنید.
| Parameter | Type | Description | Validation |
|---|---|---|---|
| content | string | متن جدید نظر. | Required |
| status | integer | وضعیت جدید نظر (مثلاً 1 برای "تایید شده"). | Required, Integer |
| flagged | boolean | علامتگذاری نظر به عنوان "پرچمدار". مقدار true یا false. |
Required, Boolean |
UpdateCommentRequest اعتبارسنجی میشوند. هر سه فیلد باید در آبجکت JSON وجود داشته باشند.Request Examples
مرحله ۱: ساختار JSON خام (قبل از رمزنگاری)
{
"content": "متن ویرایش شده نظر.",
"status": 1,
"flagged": false
}
مرحله ۲: بدنه نهایی درخواست (بعد از رمزنگاری و Base64)
{
"data": "AbC...[Encrypted & Base64 Encoded String]...dEfG=="
}
Success Response
در صورت موفقیتآمیز بودن عملیات، یک پاسخ با کد 200 و وضعیت true بازگردانده میشود. برخلاف متد store، در پاسخ این متد، آبجکت داده وجود ندارد.
{
"status": true,
"time": 1715000000
}
Error Responses
- کد 400 (Bad Request): در صورتی که میدلور
decryptDataنتواند دادهها را رمزگشایی کند، این خطا به همراه پیامDecryption failed.بازگردانده میشود. - کد 404 (Not Found): اگر شناسهی
{id}ارسال شده در URL در دیتابیس موجود نباشد. - کد 422 (Unprocessable Entity): اگر دادههای رمزگشایی شده از اعتبارسنجی
UpdateCommentRequestعبور نکنند (مثلاً یکی از فیلدها ارسال نشود).
POST /v2/comments/{commentId}/{action}
Like or Dislike a Comment
این اندپوینت برای ثبت لایک یا دیسلایک برای یک نظر مشخص استفاده میشود. عملیات مورد نظر (لایک یا دیسلایک) از طریق پارامتر action در URL تعیین میگردد.
این متد به صورت اتمیک عمل نمیکند، اما شمارندههای likes و dislikes را در مدل Comment یک واحد افزایش میدهد.
Increment Like/Dislike Counter
/v2/comments/{commentId}/{action}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| commentId | integer | شناسه منحصر به فرد نظری که لایک یا دیسلایک میشود. |
| action | string | عملیات مورد نظر. این پارامتر باید یکی از دو مقدار زیر باشد:
|
Request Body
این اندپوینت به هیچ دادهای در بدنه درخواست (Request Body) نیاز ندارد.
Server-Side Logic
- ابتدا سیستم با استفاده از متد
findOrFail، نظر مربوط بهcommentIdرا پیدا میکند. اگر شناسه نامعتبر باشد، به طور خودکار خطای 404 بازگردانده میشود. - اگر پارامتر
actionبرابر با'like'باشد، فیلدlikesیک واحد افزایش مییابد. - اگر پارامتر
actionبرابر با'dislike'باشد، فیلدdislikesیک واحد افزایش مییابد. - در نهایت، تغییرات در دیتابیس ذخیره شده و شمارندههای جدید بازگردانده میشوند.
action نامعتبر: اگر مقداری غیر از 'like' یا 'dislike' برای پارامتر action ارسال شود، هیچ خطایی رخ نمیدهد و هیچ شمارندهای تغییر نمیکند. درخواست با موفقیت پردازش شده و مقادیر فعلی لایک و دیسلایک بازگردانده میشود.Success Response
در صورت موفقیتآمیز بودن عملیات، سرور یک پاسخ با کد 200 به همراه مقادیر بهروز شدهی شمارندههای likes و dislikes برمیگرداند.
{
"status": true,
"time": 1715000000,
"data": {
"likes": 25,
"dislikes": 3
}
}
Error Response
- کد 404 (Not Found): در صورتی که
commentIdارسال شده در URL در دیتابیس موجود نباشد.
DELETE /v2/comments/{commentId}/{action}
Remove a Like or Dislike from a Comment
این اندپوینت برای کاهش (حذف) یک لایک یا دیسلایک از یک نظر مشخص استفاده میشود. این عملیات، معکوس اندپوینت POST برای افزودن لایک/دیسلایک است و به کاربر اجازه میدهد تا رأی خود را پس بگیرد.
Decrement Like/Dislike Counter
/v2/comments/{commentId}/{action}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| commentId | integer | شناسه منحصر به فرد نظری که رأی آن باید حذف شود. |
| action | string | نوع رأیی که باید حذف شود. این پارامتر باید یکی از دو مقدار زیر باشد:
|
Request Body
این اندپوینت به هیچ دادهای در بدنه درخواست (Request Body) نیاز ندارد.
فلوچارت منطق سرور (Server Logic Flow)
روند پردازش درخواست در سمت سرور به شرح زیر است:
- دریافت نظر (Find Comment): سیستم با استفاده از
commentIdارسالی در URL، نظر مربوطه را با متدfindOrFailاز دیتابیس جستجو میکند.- اگر نظر پیدا نشود، به صورت خودکار پاسخ 404 Not Found بازگردانده میشود.
- بررسی نوع عملیات (Check Action): مقدار پارامتر
actionبررسی میشود. - منطق کاهش شمارنده (Conditional Decrement):
- اگر `action` برابر با `like` باشد: سیستم ابتدا بررسی میکند که آیا شمارنده
likesبزرگتر از صفر است ($comment->likes > 0). اگر شرط برقرار باشد، یک واحد از آن کم میشود. در غیر این صورت (اگر شمارنده صفر باشد)، هیچ تغییری اعمال نمیشود. - اگر `action` برابر با `dislike` باشد: سیستم بررسی میکند که آیا شمارنده
dislikesبزرگتر از صفر است ($comment->dislikes > 0). اگر شرط برقرار باشد، یک واحد از آن کم میشود. در غیر این صورت، تغییری اعمال نمیشود.
- اگر `action` برابر با `like` باشد: سیستم ابتدا بررسی میکند که آیا شمارنده
- ذخیره تغییرات (Save Changes): تغییرات روی مدل
Commentدر دیتابیس ذخیره میشود ($comment->save()). - ارسال پاسخ نهایی (Return Response): یک پاسخ موفقیتآمیز با کد 200 حاوی شمارندههای بهروز شده بازگردانده میشود.
Success Response
در صورت موفقیتآمیز بودن عملیات، سرور یک پاسخ با کد 200 به همراه مقادیر بهروز شدهی شمارندههای likes و dislikes برمیگرداند.
{
"status": true,
"time": 1715000000,
"data": {
"likes": 24,
"dislikes": 2
}
}
Error Response
- کد 404 (Not Found): در صورتی که
commentIdارسال شده در URL در دیتابیس موجود نباشد.
POST /v2/invoice/process
Hub: Create Payment Invoice
این اندپوینت برای ایجاد یک صورتحساب قابل پرداخت طراحی شده است.
فرآیند شامل ثبت یک "قبض" (Bill)، یافتن یک درگاه پرداخت فعال، تولید یک فاکتور منحصر به فرد با اسلاگ (Slug) یکتا، و در نهایت بازگرداندن یک لینک پرداخت برای کاربر است.
Request Overview
/v2/invoice/processAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
- اپراتور باید به شعبه (`branch`) مربوطه دسترسی داشته باشد (این اطلاعات توسط میدلور به درخواست تزریق میشود).
Body Parameters
| Field | Type | Description |
|---|---|---|
| price | integer | (الزامی) مبلغ کل صورتحساب به ریال. حداقل مبلغ مجاز 10,000 ریال است. |
| type | string | (الزامی) نوع پرداخت. در حال حاضر فقط مقدار 'credit' پشتیبانی میشود. |
| id | integer | (الزامی) شناسه آبجکتی که این پرداخت به آن مرتبط است (مثلاً شناسه یک رزرو یا سفارش). |
| driver | string | (اختیاری) نام درایور درگاه پرداخت خاص (مانند 'zarinpal' یا 'behpardakht'). اگر ارسال نشود، درگاه پیشفرض شعبه انتخاب میشود. |
| return_url | string | (اختیاری) آدرسی که کاربر پس از اتمام عملیات پرداخت به آن هدایت میشود. |
| operator | object | (تزریق سیستمی) آبجکت اپراتور لاگین شده که توسط میدلور به درخواست اضافه میشود. |
| branch | integer | (تزریق سیستمی) شناسه شعبهای که اپراتور به آن تعلق دارد. |
Logic Details
فرآیند ایجاد لینک پرداخت دارای چندین مرحله کلیدی است:
- اعتبارسنجی اولیه:
- بررسی میشود که فیلدهای الزامی
price,type, وidدر درخواست وجود داشته باشند. - مبلغ
priceباید بزرگتر یا مساوی 10,000 ریال باشد. - نوع پرداخت (
type) باید دقیقاً برابر با'credit'باشد. در غیر این صورت خطا بازگردانده میشود.
- بررسی میشود که فیلدهای الزامی
- ثبت رکورد قبض (Bill):
- یک رکورد جدید در جدول
airplus_billsایجاد میشود. - این رکورد حاوی اطلاعات پایه پرداخت مانند مبلغ، شناسه اپراتور، شعبه و شناسه آبجکت مرتبط است. وضعیت اولیه آن
1(فعال) ثبت میشود.
- یک رکورد جدید در جدول
- انتخاب درگاه پرداخت (Gateway Selection):
- تابع کمکی
Functions::getGatewayConfigفراخوانی میشود. - اولویت اول: اگر فیلد
driverدر درخواست ارسال شده باشد، سیستم به دنبال یک درگاه فعال با همان نام درایور برای آن شعبه میگردد. - اولویت دوم (Fallback): اگر
driverارسال نشده باشد، سیستم از جدولoffice_configبه دنبال درگاه پیشفرض (`DEFAULT_BANKING_GATEWAY`) برای آن شعبه میگردد. - اگر هیچ درگاه فعالی (نه با درایور مشخص و نه به عنوان پیشفرض) یافت نشود، خطا بازگردانده میشود.
- تابع کمکی
- ایجاد فاکتور نهایی (Invoice):
- با استفاده از تابع
Functions::generateSlugUnique، یک رشته تصادفی و منحصر به فرد به طول ۸ کاراکتر به عنوان `slug` فاکتور تولید میشود. این تابع تضمین میکند که اسلاگ در جدولairplus_invoicesتکراری نباشد. - یک رکورد جدید در جدول
airplus_invoicesثبت میشود که حاوی اسلاگ، اطلاعات درگاه، مبلغ، و شناسه قبض (Bill) ایجاد شده در مرحله ۲ است.
- با استفاده از تابع
- تولید لینک و ارسال پاسخ:
- یک URL پرداخت با استفاده از دامنه ثابت
https://ipg.airplus.app/invoice/payment/و اسلاگ منحصر به فرد تولید میشود. - این URL به همراه مبلغ در قالب یک پاسخ موفقیتآمیز به کلاینت بازگردانده میشود.
- یک URL پرداخت با استفاده از دامنه ثابت
Response Structure
پاسخ موفق
- Status Code:
201 Created - Body: یک آبجکت حاوی لینک پرداخت نهایی.
{
"payload": {
"status": "payment_link",
"amount": 50000,
"url": "https://ipg.airplus.app/invoice/payment/aB3xZ7cR"
},
"meta": {
"timestamp": 1715001200
}
}
پاسخهای خطا (نمونه)
422 Unprocessable Entity: خطای اعتبارسنجی ورودیها (مانند عدم ارسال فیلدها یا مبلغ کم).400 Bad Request: نوع پرداخت نامعتبر است یا هیچ درگاه پرداخت فعالی برای شعبه یافت نشده است.
{
"error": {
"code": 1000,
"message": "درگاه پرداخت فعال یافت نشد"
}
}
Flowchart
1. Check for `driver` input
2. Fallback to branch default
(Loop until unique in `airplus_invoices`)
1. Insert into `airplus_invoices`
2. Link to Bill & Gateway
POST /v2/invoice/payment/details
Hub: Get Invoice Payment Details
این اندپوینت برای استعلام وضعیت و دریافت جزئیات تراکنش یک فاکتور خاص استفاده میشود.
معمولاً پس از بازگشت کاربر از درگاه بانک، کلاینت با ارسال slug فاکتور به این اندپوینت، وضعیت نهایی پرداخت (موفق یا ناموفق)، شماره پیگیری، شماره کارت و علت خطا (در صورت شکست) را دریافت میکند.
Request Overview
/v2/invoice/payment/detailsAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
Body Parameters
| Field | Type | Description |
|---|---|---|
| slug | string | (الزامی) شناسه منحصر به فرد (۸ کاراکتری) فاکتور که در مرحله ایجاد فاکتور تولید شده است. |
Logic Details
فرآیند استعلام شامل مراحل دقیق زیر است:
- جستجوی فاکتور:
- سیستم در جدول
airplus_invoicesبه دنبال رکوردی باslugارسال شده میگردد. - اگر رکوردی یافت نشود، پاسخ خطای
409با پیام "سند ارسالی به بانک یافت نشد" بازگردانده میشود.
- سیستم در جدول
- پردازش دادههای درگاه (Gateway Parsing):
- اگر فاکتور پیدا شد، ستون
result(که حاوی پاسخ JSON از بانک است) پردازش میشود. - سیستم بر اساس نوع درگاه (
drive)، اطلاعات شماره پیگیری و شماره کارت را استخراج میکند:- Behpardakht: از فیلدهای
SaleReferenceIdوCardHolderPanاستفاده میشود. - Sep (Saman): از فیلدهای
TraceNoوSecurePanاستفاده میشود.
- Behpardakht: از فیلدهای
- همچنین رکورد قبض (Bill) مرتبط از جدول
airplus_billsبازیابی میشود تا نوع سرویس (`object_type`) مشخص گردد.
- اگر فاکتور پیدا شد، ستون
- بررسی وضعیت نهایی (Status Check):
- حالت موفق (Status == 3): اگر وضعیت فاکتور برابر با 3 باشد، پرداخت موفقیتآمیز بوده است. جزئیات کامل تراکنش با کد
200ارسال میشود. - حالت ناموفق (Status != 3): اگر وضعیت هر چیزی غیر از 3 باشد:
- پیام خطای دقیق از پاسخ بانک استخراج میشود (مثلاً
errorDescبرای سامان یا پیام عمومی برای بهپرداخت). - پاسخ با کد وضعیت
409ارسال میشود که حاوی جزئیات تراکنش + آبجکتerrorاست.
- پیام خطای دقیق از پاسخ بانک استخراج میشود (مثلاً
- حالت موفق (Status == 3): اگر وضعیت فاکتور برابر با 3 باشد، پرداخت موفقیتآمیز بوده است. جزئیات کامل تراکنش با کد
Response Structure
پاسخ موفق (پرداخت تایید شده)
- Status Code:
200 OK
{
"payload": {
"type": "hotel",
"amount": 1500000,
"tracking_code": "17459821",
"drive": "sep",
"datetime": "2024-05-18 14:30:00",
"card": "610433******1234",
"return_url": "https://client-app.com/callback"
},
"meta": {
"timestamp": 1716025200
}
}
پاسخ ناموفق (خطای پرداخت یا یافت نشدن)
- Status Code:
409 Conflict - در این حالت، بادی پاسخ همچنان شامل اطلاعات تراکنش (در صورت وجود) است تا به کاربر نمایش داده شود، اما یک آبجکت خطا نیز دارد.
{
"payload": {
"type": "flight",
"amount": 5000000,
"tracking_code": null,
"drive": "behpardakht",
"datetime": "2024-05-18 14:35:00",
"card": null,
"return_url": null
},
"meta": {
"timestamp": 1716025500
},
"error": {
"code": 1000,
"message": "خطای مربوط به پرداخت"
}
}
Flowchart
Extract Tracking & Card No based on driver (Sep/Behpardakht)
(Payment Details)
Get specific error from gateway response
(Details + Error Obj)
POST /v2/invoice/payment/wallet
Hub: Pay using Wallet
این اندپوینت یک فرآیند پرداخت ترکیبی را مدیریت میکند که به کاربر اجازه میدهد از کیف پول داخلی شعبه برای تسویه حساب استفاده کند.
این مسیر دو سناریوی اصلی دارد:
۱. موجودی کافی: مبلغ کامل از کیف پول کسر شده و تراکنش با موفقیت ثبت میشود.
۲. موجودی ناکافی: موجودی کیف پول به طور کامل مصرف شده و برای باقیمانده مبلغ، یک لینک پرداخت آنلاین (از طریق اندپوینت createInvoice) ایجاد میشود.
Request Overview
/v2/invoice/payment/walletAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
- اطلاعات اپراتور و شعبه (`operator`, `branch`) توسط میدلور به درخواست تزریق میشود.
Body Parameters
| Field | Type | Description |
|---|---|---|
| type | string | (الزامی) نوع موجودیتی که پرداخت برای آن انجام میشود. اگر مقدار آن 'bill' باشد، مبلغ از روی رکورد قبض محاسبه میشود. |
| id | integer | (الزامی) شناسه موجودیت (مثلاً شناسه قبض یا سفارش). |
| amount | integer | (شرطی) مبلغ کل به ریال. ارسال این فیلد تنها زمانی الزامی است که type مقداری غیر از 'bill' داشته باشد. |
| operator | object | (تزریق سیستمی) آبجکت اپراتور لاگین شده. |
| branch | integer | (تزریق سیستمی) شناسه شعبهای که اپراتور به آن تعلق دارد. |
Logic Details
این اندپوینت فرآیند پیچیدهای را دنبال میکند که به دو شاخه اصلی تقسیم میشود:
- اعتبارسنجی و محاسبه مبلغ:
- بررسی میشود که فیلدهای
typeوidارسال شده باشند. - اگر
typeبرابر با'bill'باشد:- رکورد مربوطه از جدول
airplus_billsبا وضعیت1(فعال) جستجو میشود. - مبلغ نهایی از فرمول
($bill->amount + $bill->tax) - $bill->discountمحاسبه میشود. - اگر صورتحساب یافت نشود، خطای
422بازگردانده میشود.
- رکورد مربوطه از جدول
- در غیر این صورت: مبلغ از فیلد
amountدر درخواست خوانده میشود. - مبلغ محاسبه شده باید حداقل 10,000 ریال باشد.
- بررسی میشود که فیلدهای
- بررسی موجودی کیف پول:
- تابع
getBalanceWalletفراخوانی شده تا موجودی فعلی کیف پول شعبه (`branch`) را دریافت کند. - شرایط بررسی میشود: اگر مبلغ مورد نیاز از موجودی کیف پول بیشتر باشد، به سناریوی "موجودی ناکافی" میرویم.
- تابع
- سناریو ۱: موجودی ناکافی
- مبلغ باقیمانده (
$amount - $balance) محاسبه میشود. - اگر مبلغ باقیمانده کمتر از 10,000 ریال باشد، برای ایجاد لینک پرداخت، حداقل مبلغ 10,000 ریال در نظر گرفته میشود.
- یک درخواست جدید به صورت داخلی ساخته شده و به متد
createInvoiceفوروارد میشود تا برای مبلغ باقیمانده، یک لینک پرداخت آنلاین تولید کند. - خروجی این اندپوینت در این حالت، دقیقاً مشابه خروجی اندپوینت
createInvoiceخواهد بود (یک لینک پرداخت).
- مبلغ باقیمانده (
- سناریو ۲: موجودی کافی
- وضعیت صورتحساب (در جدول
airplus_bills) به5(پرداخت شده از کیف پول) تغییر میکند. - یک رکورد بدهی (Debit) جدید در جدول
walletبرای شعبه ثبت میشود. این رکورد شامل شناسه اپراتور، مبلغ کسر شده و توضیحات تراکنش است. - پاسخ موفقیتآمیز با کد
201بازگردانده میشود که حاوی شناسه تراکنش کیف پول است.
- وضعیت صورتحساب (در جدول
Response Structure
پاسخ موفق (پرداخت کامل از کیف پول)
- Status Code:
201 Created
{
"payload": {
"status": "succeed",
"id": 542,
"datetime": "2024-05-18 15:00:00"
},
"meta": {
"timestamp": 1716027000
}
}
پاسخ موفق (پرداخت ترکیبی - موجودی ناکافی)
- Status Code:
201 Created - در این حالت، پاسخ مشابه اندپوینت
createInvoiceاست و شامل لینک پرداخت برای مبلغ باقیمانده میباشد.
{
"payload": {
"status": "payment_link",
"amount": 450000,
"url": "https://ipg.airplus.app/invoice/payment/kL9sW1aP"
},
"meta": {
"timestamp": 1716027100
}
}
پاسخهای خطا
- Status Code:
422 Unprocessable Entity - برای خطاهای اعتبارسنجی مانند عدم ارسال فیلد، یافت نشدن صورتحساب یا مبلغ کمتر از حد مجاز.
{
"error": {
"code": 1000,
"message": "صورتحساب یافت نشد."
},
"meta": {
"timestamp": 1716027200
}
}
Flowchart
GET /v2/core/bill/{type}
Hub: Get Branch Bills
این اندپوینت برای دریافت لیست صورتحسابهای پرداخت نشده (`status = 1`) یک شعبه خاص طراحی شده است.
کلاینت میتواند با ارسال نوع سرویس (مانند `hotel`, `flight` و...)، فقط صورتحسابهای مربوط به آن سرویس را فیلتر و دریافت کند. خروجی شامل جزئیات کامل هر صورتحساب به همراه مبلغ نهایی قابل پرداخت است.
Request Overview
/v2/core/bill/{type}Access Control
- نیاز به توکن احراز هویت (JWT) دارد.
- شناسه شعبه (`branch`) به صورت خودکار از توکن کاربر استخراج و در کوئری استفاده میشود.
URL Parameters
| Field | Type | Description |
|---|---|---|
| type | string | (الزامی) نوع سرویسی که صورتحساب برای آن صادر شده است. مقادیر ممکن: 'hotel', 'flight', 'tour' و غیره. |
Logic Details
فرآیند دریافت صورتحسابها به شرح زیر است:
- استخراج اطلاعات: شناسه شعبه (`branch`) از توکن JWT و نوع سرویس (`type`) از URL خوانده میشود.
- کوئری از دیتابیس:
- یک کوئری به جدول
airplus_billsارسال میشود. - این کوئری نتایج را بر اساس سه شرط اصلی فیلتر میکند:
branch: باید با شناسه شعبه کاربر لاگین کرده مطابقت داشته باشد.status: باید دقیقاً برابر با 1 باشد (فقط صورتحسابهای در انتظار پرداخت).object_type: باید با پارامترtypeارسال شده در URL مطابقت داشته باشد.
- یک کوئری به جدول
- پردازش و تبدیل دادهها (`map`):
- اگر یک یا چند صورتحساب پیدا شود، سیستم روی هرکدام از آنها یک فرآیند تبدیل اجرا میکند:
- یک فیلد جدید به نام
totalبا استفاده از فرمول `(((amount + tax) - discount) + markup)` محاسبه و به خروجی اضافه میشود. - یک آبجکت
detailsساخته میشود که حاویmonth_titleاست. این فیلد با استفاده از تابعCalendarUtils::strftimeنام فارسی ماه (مانند "آذر ماه") را از روی تاریخ ایجاد صورتحساب استخراج میکند. - ساختار نهایی هر آیتم برای خوانایی بیشتر در پاسخ API بازآرایی میشود.
- ساختار پاسخ نهایی:
- اگر هیچ صورتحسابی یافت نشود، پاسخ با کد
200 OKبازگردانده شده و مقدار فیلدitemsبرابر باfalseخواهد بود. - نکته مهم: بلوک کد مربوط به خطای
404در کنترلر به دلیل نحوه عملکرد تابعget()، عملاً اجرا نمیشود و پاسخ همیشه200است. - اگر صورتحسابها پیدا شوند، آرایهای از آبجکتهای تبدیل شده در فیلد
itemsقرار میگیرد.
- اگر هیچ صورتحسابی یافت نشود، پاسخ با کد
Response Structure
پاسخ موفق (در صورت یافتن صورتحساب)
- Status Code:
200 OK
{
"items": [
{
"id": 101,
"details": {
"month_title": "آذر ماه"
},
"description": "بابت رزرو هتل اسپیناس پالاس",
"amount": 5000000,
"tax": 450000,
"discount": 0,
"markup": 0,
"total": 5450000,
"created_at": "2025-12-09 10:00:00",
"updated_at": "2025-12-09 10:00:00",
"expired_at": "2025-12-11 10:00:00"
}
],
"meta": {
"timestamp": 1733735400
}
}
پاسخ موفق (در صورت عدم وجود صورتحساب)
- Status Code:
200 OK - توجه کنید که در این حالت، فیلد
itemsبه جای آرایه خالی، مقدارfalseدارد.
{
"items": false,
"meta": {
"timestamp": 1733735400
}
}
Flowchart
- `branch` = user_branch
- `status` = 1
- `object_type` = {type}
- Calculate `total`
- Generate `month_title`
- Format structure
GET /v2/core/hub/information
Hub: Get Basic Information
این اندپوینت به عنوان یک مسیر عمومی برای دریافت اطلاعات پایهای و مرکزی (Hub Information) عمل میکند. رفتار این اندپوینت بر اساس پارامتر action که در کوئری استرینگ ارسال میشود، تغییر میکند.
در حال حاضر، این مسیر تنها برای دریافت لیست تمام دفاتر (Offices) پیادهسازی شده است.
Request Overview
/v2/core/hub/informationAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
Query Parameters
| Field | Type | Description |
|---|---|---|
| action | string | (اختیاری) عملکرد مورد نظر را مشخص میکند. اگر مقدار آن برابر با 'offices' باشد، لیست دفاتر برگردانده میشود. در غیر این صورت، خروجی متفاوت خواهد بود. |
Logic Details
منطق این اندپوینت کاملاً به پارامتر action وابسته است:
- بررسی شرط: سیستم ابتدا بررسی میکند که آیا پارامتر
actionدر درخواست وجود دارد و مقدار آن برابر با'offices'است یا خیر. - سناریو ۱: اگر
action = 'offices'باشد:- یک کوئری به جدول
officesدر دیتابیس زده میشود و فیلدهایid,brand_faوbrand_enبرای تمام رکوردها استخراج میشود. - نتایج با استفاده از متد
mapپردازش میشوند تا ساختار خروجی تغییر کند:- فیلد
idبدون تغییر باقی میماند. - یک آبجکت جدید به نام
titleایجاد میشود که دارای دو کلیدfaوenاست. - مقدار هر عنوان با فرمول زیر ساخته میشود:
(شناسه دفتر + 10,000) - نام برند
برای مثال، اگرid=25وbrand_fa='آژانس تابان'باشد، خروجی'10025 - آژانس تابان'خواهد بود.
- فیلد
- آرایه تبدیل شده در فیلد
itemsپاسخ نهایی قرار میگیرد.
- یک کوئری به جدول
- سناریو ۲: اگر
actionارسال نشود یا مقداری غیر از'offices'داشته باشد:- شرط اصلی برقرار نمیشود و بلوک کد مربوط به کوئری دفاتر اجرا نمیگردد.
- در این حالت، متغیر
$itemsتعریف نمیشود. - نکته کلیدی: به دلیل تعریف نشدن متغیر
$items، در پاسخ JSON نهایی، مقدار فیلدitemsبرابر باnullخواهد بود.
Response Structure
پاسخ موفق (برای action=offices)
- Status Code:
200 OK
{
"items": [
{
"id": 25,
"title": {
"fa": "10025 - آژانس تابان",
"en": "10025 - Taban Agency"
}
},
{
"id": 26,
"title": {
"fa": "10026 - آژانس سپهر",
"en": "10026 - Sepehr Agency"
}
}
],
"meta": {
"timestamp": 1733736000
}
}
پاسخ برای سایر مقادیر action (یا عدم ارسال آن)
- Status Code:
200 OK - در این حالت، مقدار
itemsبرابر باnullاست.
{
"items": null,
"meta": {
"timestamp": 1733736100
}
}
Flowchart
`title.fa = (id + 10k) + ' - ' + brand_fa`
`title.en = (id + 10k) + ' - ' + brand_en`
GET /v2/core/hub/analyze
Hub: Flight Reservation Analysis
این اندپوینت یک گزارش تحلیلی جامع از تمام رزروهای پرواز آنلاین (`product='online'`, `byproduct='aircraft'`) ارائه میدهد. دادهها بر اساس سال شمسی، تأمینکننده (Supplier)، و تأمینکننده سیستمی (System Supplier) دستهبندی و agregare میشوند. خروجی شامل تعداد کل رزروها و مجموع مبالغ خرید در هر دستهبندی است.
Request Overview
/v2/core/hub/analyzeAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
- این گزارش به صورت سراسری (Global) است و به شعبه کاربر لاگین کرده محدود نمیشود.
Logic Details
فرآیند تحلیل دادهها در این اندپوینت طی چندین مرحله پیچیده انجام میشود:
- استخراج دادههای اولیه:
- یک کوئری به جدول
factor_itemsارسال میشود تا تمام رکوردهایی که دارای شرایط زیر هستند، استخراج شوند:productبرابر با'online'byproductبرابر با'aircraft'
- نتایج بر اساس
idبه صورت نزولی (DESC) مرتب میشوند تا ابتدا جدیدترین رزروها پردازش شوند.
- یک کوئری به جدول
- پردازش تکتک رزروها:
- سیستم در یک حلقه (loop) تمام رزروهای استخراج شده را پیمایش میکند.
- برای هر رزرو، فیلد
detailsکه یک رشته JSON است، به آرایه PHP تبدیل میشود. - اعتبارسنجی حیاتی: سیستم بررسی میکند که آیا مسیر
$details['Book']['DepartureSegment']در آبجکت JSON وجود دارد یا نه. اگر وجود نداشته باشد، آن رزرو از فرآیند تحلیل حذف شده و حلقه به تکرار بعدی میرود.
- گروهبندی و تجمیع دادهها (Aggregation):
- استخراج تاریخ: سال و ماه شمسی از فیلد
created_atرزرو با استفاده ازCalendarUtils::strftimeاستخراج میشود (مثلاً `1404-09`). - استخراج تأمینکنندگان: شناسههای
SupplierوSystemSupplierاز آبجکتDepartureSegmentخوانده میشوند. - کش کردن نام تأمینکننده:
- برای جلوگیری از کوئریهای تکراری به دیتابیس، یک آرایه به نام
$colleaguesبه عنوان کش داخلی عمل میکند. - هر بار که یک شناسه تأمینکننده جدید یافت میشود، سیستم یک بار به جدول
colleaguesکوئری میزند تا نام دفتر (office) را پیدا کند. - اگر نام دفتر پیدا شود، در کش ذخیره میشود. در غیر این صورت، خود شناسه تأمینکننده به عنوان نام پیشفرض استفاده میشود.
- برای جلوگیری از کوئریهای تکراری به دیتابیس، یک آرایه به نام
- افزایش شمارندهها: برای هر رزرو معتبر، شمارندههای
count(تعداد) وpaid(مجموع مبلغ خرید از فیلدbuy) در سطوح مختلف ساختار داده افزایش مییابند:- مجموع کل (Grand Total)
- مجموع کل سال مربوطه
- مجموع کل تأمینکننده (Supplier) در آن سال
- مجموع ماهانه تأمینکننده (Supplier) در آن سال
- همین فرآیند به طور موازی برای تأمینکننده سیستمی (System Supplier) نیز تکرار میشود.
- استخراج تاریخ: سال و ماه شمسی از فیلد
- ساختار نهایی خروجی:
- نتیجه نهایی یک آبجکت بسیار تودرتو است که دادهها را به تفکیک سال، تأمینکننده، و ماه نمایش میدهد.
- این آبجکت در فیلد
payloadپاسخ نهایی قرار میگیرد.
Response Structure
پاسخ موفق
- Status Code:
200 OK - ساختار خروجی بسیار تودرتو است. کلیدهای آبجکتهای
years،suppliers،system_suppliersوmonthsبه ترتیب شناسههای سال، تأمینکننده، و ماه هستند.
{
"payload": {
"years": {
"1404": { // سال شمسی
"suppliers": {
"55": { // شناسه Supplier
"id": 55,
"title": "Ghasreshirin", // نام استخراج شده از جدول colleagues
"total": {
"count": 10, // کل رزروها از این Supplier در سال 1404
"paid": 95000000 // مجموع خرید از این Supplier در سال 1404
},
"months": {
"08": { // ماه شمسی
"total": {
"count": 4,
"paid": 38000000
}
},
"09": {
"total": {
"count": 6,
"paid": 57000000
}
}
}
}
},
"system_suppliers": {
"10": { // شناسه SystemSupplier
"id": 10,
"title": "Parsian System",
"total": {
"count": 10, // کل رزروها از این SystemSupplier در سال 1404
"paid": 95000000
},
"months": {
"08": { /* ... */ },
"09": { /* ... */ }
}
}
},
"total": { // مجموع کل برای سال 1404
"count": 10,
"paid": 95000000
}
}
},
"total": { // مجموع کل در تمام سالها
"count": 10,
"paid": 95000000
}
},
"meta": {
"timestamp": 1733736600
}
}
Flowchart
Increment `count` & `paid` at all levels (Grand, Year, Supplier, Month) for both Supplier & SystemSupplier
GET /v2/core/hub/reservation
Hub: Get Master Reservation List
این اندپوینت یک لیست جامع و صفحهبندی شده از تمام رزروهای ثبت شده در سیستم مرکزی (Hub) را برمیگرداند. برای هر رزرو، اطلاعات از جداول مختلفی مانند offices، factor_items، colleagues، و wallet استخراج و با دادههای اصلی ادغام میشود تا یک خروجی کامل و غنیشده ارائه دهد.
Request Overview
/v2/core/hub/reservationAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
Query Parameters (Custom Pagination)
این اندپوینت از یک ساختار صفحهبندی سفارشی استفاده میکند که معمولاً توسط کتابخانههای جدولی مانند (jQuery DataTables) ارسال میشود. پارامترها باید در یک آبجکت به نام paginate ارسال شوند.
| Field | Type | Description |
|---|---|---|
| paginate[length] | integer | (الزامی) تعداد آیتمهای مورد نظر در هر صفحه. |
| paginate[start] | integer | (الزامی) آفست (offset) شروع رکوردها. برای صفحه اول 0، برای صفحه دوم (با length=10)، 10 و به همین ترتیب. |
Example URL: /v2/core/hub/reservation?paginate[length]=15&paginate[start]=0
Logic Details
فرآیند دریافت و پردازش رزروها بسیار گسترده است و شامل مراحل زیر میشود:
- کوئری اولیه و صفحهبندی:
- یک کوئری اصلی به جدول
hub_reservationزده میشود. - این کوئری با جدول
officesبر اساسhub_reservation.branchجوین میشود تا نام فارسی شعبه (brand_fa) استخراج شود. - نتایج بر اساس
idبه صورت نزولی مرتب میشوند. - با استفاده از پارامترهای
lengthوstart، صفحهبندی سفارشی روی نتایج اعمال میشود.
- یک کوئری اصلی به جدول
- حلقه غنیسازی دادهها (Data Enrichment Loop):
- سیستم روی هر یک از رزروهای برگشتی از کوئری اصلی، یک حلقه اجرا کرده و برای هرکدام، مجموعهای از عملیات و کوئریهای اضافی را انجام میدهد:
- نکته عملکردی (Performance Consideration): این رویکرد (کوئری در حلقه) به مشکل N+1 Query منجر میشود و ممکن است با افزایش تعداد آیتمها در هر صفحه، عملکرد را تحت تأثیر قرار دهد.
- مراحل داخل حلقه برای هر رزرو:
- واکشی آیتم فاکتور: از جدول
factor_items، آیتم مربوط به رزرو (item_id) واکشی میشود. اگر آیتم یافت نشود، کل آن رزرو از خروجی نهایی حذف میشود. - واکشی نام سرویسها: شناسههای عددی
serviceوsub_serviceبا اجرای کوئری روی جدولcolleaguesبه نام متنی (فیلدoffice) تبدیل میشوند. - تولید عنوان آیتم (
title_item): با فراخوانی متد پیچیدهTradeController::getReferenceItemTitle، یک عنوان توصیفی و قابل فهم برای آیتم (مانند "هواپیما تهران به مشهد | ۱۴۰۴/۰۹/۱۸ ۱۲:۳۰") تولید میشود. این متد به شدت از کش Redis برای بهینهسازی عملکرد استفاده میکند. - واکشی اطلاعات اپراتور: با فراخوانی متد
StaticController::getOperators، شناسه اپراتور به یک آبجکت کامل شامل نام، نام خانوادگی و سایر مشخصات تبدیل میشود. در صورت عدم وجود اپراتور، مقدارfalseبازگردانده میشود. - پردازش فیلدهای JSON: رشتههای JSON موجود در فیلدهای
supplier_detailsوdetailsبه آبجکت/آرایه تبدیل میشوند. - ضمیمه کردن دادههای جدید: اطلاعات پردازش شده (نام سرویس، عنوان، اطلاعات اپراتور و...) به آبجکت اصلی رزرو اضافه میشوند.
- بررسی استرداد (Refund): سیستم در جدول
walletجستجو میکند تا ببیند آیا تراکنش استردادی (target_type='refund') برای این رزرو ثبت شده است یا خیر. در صورت وجود، جزئیات آن به فیلدrefundاضافه میشود؛ در غیر این صورت، این فیلد مقدارfalseمیگیرد.
- واکشی آیتم فاکتور: از جدول
Response Structure
پاسخ موفق
- Status Code:
200 OK - فیلد
itemsحاوی آرایهای از آبجکتهای رزرو غنیشده است. - فیلد
meta.tableحاوی جزئیات کامل صفحهبندی است.
{
"items": [
{
"id": 12345,
"branch": "آژانس تابان", // از جدول offices
"item_id": 54321,
"operator": { // از متد getOperators
"id": 101,
"text": "1100 - علی اکبری",
"query": { /* ... details ... */ }
},
"service": "Ghasreshirin", // نام تبدیل شده
"sub_service": "Parsian System", // نام تبدیل شده
"supplier_details": { /* آبجکت JSON */ },
"details": { /* آبجکت JSON */ },
"status": "issued",
"created_at": "2025-12-09T12:00:00.000000Z",
// --- Fields added in the loop ---
"title_item": "هواپیما تهران به مشهد | ۱۴۰۴/۰۹/۱۸ ۱۲:۳۰ | W5065 | ماهان", // از getReferenceItemTitle
"item": { // آبجکت کامل از factor_items
"id": 54321,
"product": "online",
"byproduct": "aircraft",
"details": { /* ... */ },
"buy": 5400000,
"sell": 5500000
/* ... */
},
"refund": { // یا false اگر وجود نداشته باشد
"id": 789,
"credit": 5000000,
"description": "استرداد بابت کنسلی پرواز"
}
}
],
"meta": {
"timestamp": 1733737200,
"table": {
"total": 500,
"per_page": 15,
"current_page": 1,
"last_page": 34,
"from": 1,
"to": 15
}
}
}
Flowchart
(Using custom `start`/`length` params)
POST /v2/core/hub/reservation
Hub: Create Manual Wallet Transaction
این اندپوینت برای ثبت دستی یک تراکنش مالی (سند) در کیف پول (`wallet`) استفاده میشود. اگرچه URL به "رزرو" اشاره دارد، اما عملکرد اصلی آن ایجاد یک رکورد بدهکار (`debit`) یا بستانکار (`credit`) برای یک شعبه خاص است. این عملیات معمولاً توسط مدیران هاب برای اصلاح حساب یا ثبت سند دستی انجام میشود.
Request Overview
/v2/core/hub/reservationAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
- شناسه اپراتور (`operator`) به صورت خودکار از توکن یا میدلور استخراج میشود (`$request->get('operator')`).
Request Body Parameters
| Field | Type | Description |
|---|---|---|
| branch | integer/string | (الزامی) شناسه شعبهای که تراکنش برای آن ثبت میشود. این مقدار هم در فیلد branch و هم در فیلد object ذخیره میگردد. |
| target_type | string | نوع مقصد تراکنش (مثلاً user, office, provider). |
| target | integer/string | شناسه مقصد تراکنش (ID مربوط به target_type). |
| diagnosis | string | (الزامی) نوع ماهیت تراکنش. مقادیر مجاز:
|
| value | numeric | (الزامی) مبلغ تراکنش. بسته به diagnosis در ستون credit یا debit قرار میگیرد. |
| description | string | توضیحات تراکنش (بابت چه چیزی). |
Logic Details
فرآیند ثبت در کنترلر به صورت زیر است:
- آمادهسازی دادهها:
- فیلد
object_typeبه صورت سختکد شده برابر با'branch'قرار میگیرد. - فیلد
objectبرابر با مقدار ارسالیbranchتنظیم میشود. - شناسه اپراتور انجامدهنده عملیات از
$request->get('operator')->idخوانده میشود.
- فیلد
- تعیین ماهیت مالی (Diagnosis Logic):
- اگر
diagnosis == 'credit'باشد: مقدارvalueدر ستون credit و ستون debit برابر 0 میشود. - اگر
diagnosis == 'debit'باشد: مقدارvalueدر ستون debit و ستون credit برابر 0 میشود.
- اگر
- درج در دیتابیس: آرایه ساخته شده مستقیماً با استفاده از
DB::table('wallet')->insert()در جدول ذخیره میشود.
Response Structure
پاسخ موفق
- Status Code:
201 Created - Body: Empty (بدون محتوا).
پاسخ خطا
- Status Code:
400 Bad Request - در صورت بروز هرگونه Exception در فرآیند درج.
{
"error": {
"code": 1000,
"message": "SQLSTATE[23000]: Integrity constraint violation..." // پیام خطا
},
"meta": {
"timestamp": 1733737200
}
}
Flowchart
Set Debit = 0
Set Credit = 0
(Set object_type='branch')
PATCH /v2/core/hub/reservation/refund
Hub: Process Reservation Refund
این اندپوینت برای انجام عملیات استرداد (Refund) یک رزرو ثبتشده در سیستم هاب استفاده میشود. فرآیند شامل محاسبه مبلغ جریمه (به صورت درصدی یا مبلغ ثابت)، ثبت مبلغ قابل بازگشت به عنوان یک تراکنش بستانکار در کیف پول شعبه، و در نهایت بهروزرسانی وضعیت رزرو به "استرداد شده" است.
Request Overview
/v2/core/hub/reservation/refundAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
Request Body Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه (ID) رزروی که باید استرداد شود. |
| value_type | string | (الزامی) نوع محاسبه جریمه. مقادیر مجاز:
|
| value | numeric | (الزامی) مقدار جریمه. اگر value_type برابر 'percent' باشد، این مقدار درصد است (مثلاً 10 برای ۱۰٪). در غیر این صورت، این مقدار مبلغ ریالی جریمه است. |
| description | string | (اختیاری) توضیحات اضافی که به انتهای توضیحات پیشفرض تراکنش کیف پول اضافه میشود. |
Logic Details
فرآیند استرداد در کنترلر به صورت گامبهگام زیر انجام میشود:
- یافتن رزرو:
- سیستم با استفاده از
idارسال شده، در جدولhub_reservationجستجو میکند تا رکورد رزرو مورد نظر را پیدا کند.
- سیستم با استفاده از
- محاسبه جریمه (Penalty):
- اگر
value_typeدر درخواست برابر با'percent'باشد، مبلغ جریمه از فرمول زیر محاسبه میشود:$penalty = ($reservation->buy * $request->value) / 100 - در غیر این صورت، مقدار
valueارسالی مستقیماً به عنوان مبلغ ثابت جریمه در نظر گرفته میشود.
- اگر
- ایجاد تراکنش کیف پول (Wallet Transaction):
- یک رکورد جدید برای درج در جدول
walletآماده میشود: - Mبلغ بستانکاری (credit): برابر است با مبلغ خرید اولیه رزرو منهای جریمه محاسبهشده (
$reservation->buy - $penalty). - توضیحات (description): یک رشته توصیفی به صورت خودکار ساخته میشود که شامل شناسه رزرو، مبلغ جریمه، و توضیحات اختیاری ارسال شده در درخواست است.
- این رکورد در جدول
walletدرج میشود و اعتبار به حساب شعبه بازمیگردد.
- یک رکورد جدید برای درج در جدول
- بهروزرسانی وضعیت رزرو:
- رکورد اصلی رزرو در جدول
hub_reservationبهروزرسانی میشود: - فیلد
refund_penaltyبا مبلغ جریمه محاسبهشده پر میشود. - فیلد
statusبه مقدار ثابت ۴ (به معنای استرداد شده) تغییر میکند. - فیلد
updated_atبا زمان فعلی بهروز میشود.
- رکورد اصلی رزرو در جدول
Response Structure
پاسخ موفق
- Status Code:
201 Created - Body: Empty (بدون محتوا).
پاسخ خطا
- Status Code:
400 Bad Request - این خطا زمانی رخ میدهد که رزروی با
idارسال شده پیدا نشود.
{
"error": {
"code": 1000,
"message": "رزرو یافت نشد."
}
}
Flowchart
`buy_price * value / 100`
PATCH /v2/core/hub/reservation/refund/undo
Hub: Undo Reservation Refund
این اندپوینت برای لغو یک عملیات استرداد (Refund) که قبلاً انجام شده، استفاده میشود. فرآیند شامل پیدا کردن تراکنش بستانکاری مربوط به استرداد، بررسی کافی بودن موجودی کیف پول شعبه برای بازگرداندن آن مبلغ، ایجاد یک تراکنش بدهکاری جدید برای خنثی کردن تراکنش قبلی، و در نهایت بازگرداندن وضعیت رزرو به حالت اولیه (فعال) است.
Request Overview
/v2/core/hub/reservation/refund/undoAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
- شناسه شعبه (`branch`) به صورت خودکار از درخواست (احتمالاً توسط میدلور) استخراج شده و برای بررسی موجودی استفاده میشود.
Request Body Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه (ID) رزروی که عملیات استرداد آن باید لغو شود. سیستم از این شناسه برای یافتن تراکنش استرداد مرتبط در جدول wallet استفاده میکند. |
Logic Details
فرآیند لغو استرداد به صورت گامبهگام زیر است:
- یافتن تراکنش استرداد:
- سیستم در جدول
walletبه دنبال تراکنشی میگردد که شرایط زیر را داشته باشد:target_type == 'refund'target == request('id')(شناسه رزرو)credit > 0(تراکنش بستانکاری که پول را به شعبه بازگردانده است)
- اگر چنین تراکنشی یافت نشود، خطای 400 با پیام "استرداد یافت نشد" بازگردانده میشود.
- سیستم در جدول
- بررسی موجودی کیف پول (Critical Step):
- قبل از برداشت وجه، سیستم با فراخوانی متد
AccountingController::getCheckWalletموجودی شعبه را بررسی میکند. - این متد چک میکند که آیا موجودی فعلی شعبه (با در نظر گرفتن حد اعتبار یا
credit_limit) برای پوشش مبلغ استرداد شده ($transaction->credit) کافی است یا خیر. - اگر موجودی کافی نباشد، خطای 400 با پیام "موجودی کیف پول جهت برگشت این آیتم کافی نیست" بازگردانده میشود.
- قبل از برداشت وجه، سیستم با فراخوانی متد
- ایجاد تراکنش بدهکاری (Debit Transaction):
- یک رکورد جدید در جدول
walletبرای برداشت مبلغ استرداد شده از حساب شعبه ایجاد میشود: - Mبلغ بدهکاری (debit): برابر است با مبلغ
creditتراکنش استرداد اولیه. - توضیحات (description): متنی مانند "برگشت استرداد [شناسه تراکنش]" ثبت میشود.
- این رکورد بدهکاری، تراکنش بستانکاری قبلی را خنثی میکند.
- یک رکورد جدید در جدول
- بازگردانی وضعیت رزرو:
- رکورد اصلی رزرو در جدول
hub_reservationبهروزرسانی میشود: - فیلد
refund_penaltyبهnullتغییر میکند. - فیلد
statusبه مقدار ۱ (به معنای فعال/صادر شده) بازمیگردد. - فیلد
updated_atبهروز میشود.
- رکورد اصلی رزرو در جدول
Response Structure
پاسخ موفق
- Status Code:
201 Created - Body: Empty (بدون محتوا).
پاسخهای خطا
- Status Code:
400 Bad Request - این خطا در سه حالت ممکن است رخ دهد:
۱. تراکنش استرداد یافت نشد:
{
"error": {
"code": 1000,
"message": "استرداد یافت نشد"
}
}
۲. موجودی کیف پول ناکافی است:
{
"error": {
"code": 1000,
"message": "موجودی کیف پول جهت برگشت این آیتم کافی نیست."
},
"meta": {
"timestamp": 1733737200
}
}
۳. بروز خطای عمومی (Exception):
{
"error": {
"code": 1000,
"message": "General exception message..."
},
"meta": {
"timestamp": 1733737200
}
}
Flowchart
POST /v2/flights/ticket/information/{type}
L. Flight Ticket Information (Nira)
این اندپوینت برای استعلام اطلاعات دقیق بلیطهای صادر شده از طریق سیستم تامینکننده نیرا (Nira) استفاده میشود. این سرویس قابلیت جستجو بر اساس "شماره بلیط" یا "PNR" را دارد و به صورت هوشمند وضعیت استرداد (Refund) بلیط را بررسی کرده و در صورت نیاز، جزئیات مالی استرداد (جریمه و مبلغ پرداختی) را از سیستم نیرا استخراج میکند.
Request Overview
/v2/flights/ticket/information/{type}Access Control
- نیاز به توکن احراز هویت (JWT) دارد.
- دسترسی بر اساس تنظیمات شعبه و سطح دسترسی کاربر کنترل میشود.
Path Parameters
| Field | Type | Description |
|---|---|---|
| type | string | (الزامی) نوع وبسرویس. برای این مستند باید مقدار nira باشد. |
Request Body Parameters
| Field | Type | Description |
|---|---|---|
| service | string | (الزامی) کد یاتا (IATA) ایرلاین (مثلاً W5). جهت تعیین کانکشن استفاده میشود. |
| ticket_number | string | (اختیاری) شماره بلیط کامل. در صورت وجود، اولویت با این فیلد است. |
| pnr | string | (اختیاری) شماره رزرو. در صورت عدم ارسال شماره بلیط، سیستم بر اساس PNR جستجو میکند. |
Logic Details
منطق پردازش در کنترلر به شرح زیر است:
- اعتبارسنجی اولیه:
- اگر پارامتر
typeبرابر باniraنباشد، خطای409بازگردانده میشود. - تنظیمات اتصال (Api Url, Username, Password) بر اساس شناسه شعبه کاربر یا شعبه پیشفرض (1) بارگذاری میشود.
- اگر پارامتر
- سناریوی ۱: جستجو با شماره بلیط (Ticket Number):
- متد
ticket_informationنیرا فراخوانی میشود. - اگر
StatusCode == 'R'(استرداد شده) باشد:- یک درخواست کامند (
command) با مقدارDMB + ticket_numberارسال میشود. - پاسخ متنی با استفاده از Regex (متد
getRefundedTicketData) پارس شده و فیلدهایRefundedAmountوPenaltyاستخراج میشوند.
- یک درخواست کامند (
- متد
- سناریوی ۲: جستجو با PNR:
- متد
reserve_informationنیرا فراخوانی میشود. - روی تمام بلیطهای موجود در رزرو (آرایه
Tickets) حلقه زده میشود. - برای هر بلیط، منطق "بررسی استرداد" (مشابه سناریوی ۱) اجرا میشود.
- باگ شناسایی شده: در کد فعلی، هنگام استعلام استرداد در حلقه PNR، به اشتباه از
$request->ticket_number(که خالی است) استفاده میشود، در حالی که باید از شماره بلیط جاری حلقه استفاده شود.
- متد
Response Structure
پاسخ موفق (200 OK)
{
"items": [
{
"TicketNo": "096-23651478",
"PassengerName": "TEST USER",
"StatusCode": "R", // وضعیت استرداد
"RefundData": { // دادههای استخراج شده استرداد
"RefundDate": "14DEC2025",
"Penalty": 500000,
"RefundedAmount": 4500000
},
// ... سایر اطلاعات بلیط
}
],
"meta": {
"timestamp": 1733738000
}
}
پاسخ خطا
- 409 Conflict: اگر سرویس (type) اشتباه باشد.
- 500 Server Error: خطای اتصال به وبسرویس.
Flowchart
Parse Regex for Penalty
POST /v2/flights/routes/update
M. Update Airline Active Routes (Manual Trigger)
این اندپوینت برای بروزرسانی دستی جدول مسیرهای فعال (Airline Active Routes) استفاده میشود. سیستم با فراخوانی این سرویس، موجودی پروازها را برای ۷ روز آینده (از فردا) به تفکیک روزهای هفته بررسی کرده و در دیتابیس ذخیره میکند. این فرآیند با ارسال درخواستهای متوالی به وبسرویس تامینکننده (Nira) انجام میشود.
Request Overview
/v2/flights/routes/updateAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
- این سرویس عملیات سنگینی است و مستقیماً با وبسرویسهای خارجی درگیر میشود.
Request Body Parameters
| Field | Type | Description |
|---|---|---|
| airline | array/string | (اختیاری) کد IATA ایرلاینها. در صورت ارسال، بروزرسانی فقط برای این ایرلاین(ها) انجام میشود (مثلاً ['W5', 'NV']). |
| route_id | integer | (اختیاری) شناسه مسیر (ID) در جدول approved_flight_rate. اگر ارسال شود، پردازش فقط برای این مسیر خاص انجام میگیرد. |
Logic Details
منطق پردازش در CronController بسیار پیچیده است و شامل مراحل زیر میباشد:
- پاکسازی وضعیت قبلی: کلید ردیس
airline_active_route:cron:route_idدر ابتدای کار حذف میشود تا پردازش از نو آغاز شود. - واکشی مسیرها (Routes):
- از جدول
approved_flight_rateمسیرهایی که مبدا و مقصد غیرفارسی دارند انتخاب میشوند. - فیلتر بر اساس
route_id(در صورت وجود در ورودی) اعمال میشود.
- از جدول
- واکشی ایرلاینها:
- از جدول
application_interfaceرکوردهایی با تایپapiو سرویسniraکه فعال هستند (status=1) دریافت میشوند. - اگر پارامتر
airlineارسال شده باشد، لیست ایرلاینها محدود میشود.
- از جدول
- حلقه پردازش (هفتگی):
- برای ۷ روز آینده (شروع از فردا) یک بازه زمانی ایجاد میشود.
- به ازای هر مسیر و هر روز، متد
NiraApi->sendRequestFlightفراخوانی میشود. - اگر
flight['Data']['Information']حاوی داده باشد، وضعیت آن روز (مثلاًmonday) برابر با 1 و در غیر این صورت 0 در نظر گرفته میشود.
- بروزرسانی دیتابیس (Upsert):
- دادهها در جدول
airline_active_routeذخیره میشوند. - سیستم بررسی میکند که آیا رکوردی با ترکیب (Airline + Origin + Destination) وجود دارد یا خیر (بدون توجه به جهت مسیر).
- اگر موجود باشد Update و اگر نباشد Insert انجام میشود.
- دادهها در جدول
- محدودیت پردازش: یک شرط
breakوجود دارد: اگر تعداد ایرلاینهای یافت شده بیش از ۱ مورد باشد، حلقه مسیرها (Routes) پس از اولین اجرا متوقف میشود (جهت جلوگیری از Timeout در پردازشهای انبوه).
Response Structure
پاسخ موفق (200 OK)
{
"payload": {
"Status": true,
"Time": 1733738500
},
"meta": {
"timestamp": 1733738500
}
}
Flowchart
1. Call NiraApi->sendRequestFlight
2. Check Availability (1 or 0)
3. Store in Memory ($checkedRoutes)
POST /v2/flights/routes/min_price
N. Flight Route Min Price (Cache)
این اندپوینت برای دریافت حداقل قیمت پرواز در یک مسیر خاص (مبدا و مقصد) استفاده میشود. دادههای این سرویس مستقیماً از Redis Cache خوانده میشوند و دو حالت عملکرد دارد: دریافت قیمت برای یک تاریخ خاص، یا دریافت لیست تمام قیمتهای موجود (Calendar View) برای آن مسیر.
Request Overview
/v2/flights/routes/min_priceAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
- این سرویس بسیار سریع است زیرا هیچ درخواستی به تامینکنندگان خارجی ارسال نمیکند و فقط با ردیس در ارتباط است.
Request Body Parameters
| Field | Type | Description |
|---|---|---|
| origin | string | (الزامی) کد IATA فرودگاه مبدا (مثلاً MHD). |
| destination | string | (الزامی) کد IATA فرودگاه مقصد (مثلاً THR). |
| date | string | (اختیاری) تاریخ پرواز (فرمت YYYY-MM-DD). اگر ارسال شود، فقط قیمت همان روز برگردانده میشود. اگر ارسال نشود، لیست تمام تاریخهای موجود بازگردانده میشود. |
Logic Details
منطق کنترلر بر اساس وجود یا عدم وجود پارامتر date به دو شاخه تقسیم میشود:
- حالت تک تاریخ (Specific Date):
- کلید ردیس به صورت مستقیم ساخته میشود:
min_price:flights:{origin}:{destination}:{date}. - اگر کلید موجود باشد، مقدار آن (قیمت) در قالب آبجکت
payloadبازگردانده میشود. - اگر موجود نباشد، پاسخ JSON با خطای داخلی
code: 404بازگردانده میشود.
- کلید ردیس به صورت مستقیم ساخته میشود:
- حالت کلی (Calendar View):
- اگر پارامتر
dateارسال نشود، سیستم تمام کلیدهای منطبق با الگویmin_price:flights:{origin}:{destination}:*را جستجو میکند. - مدیریت پیشوند ردیس: کد به طور هوشمند Prefix اتصال ردیس را دریافت کرده و هنگام پردازش کلیدها، آن را حذف میکند (
str_replace) تا به نام کلید خالص برسد. - تاریخ از بخش پنجم کلید (اینکس 4 در explode) استخراج شده و به همراه قیمت در آرایه
itemsقرار میگیرد.
- اگر پارامتر
Response Structure
حالت ۱: پاسخ موفق (تاریخ مشخص)
{
"payload": {
"origin": "MHD",
"destination": "THR",
"date": "2025-12-10",
"min_price": 1500000
},
"meta": {
"timestamp": 1733739000
}
}
حالت ۲: پاسخ موفق (لیست کلی)
{
"items": [
{
"origin": "MHD",
"destination": "THR",
"date": "2025-12-10",
"min_price": 1500000
},
{
"origin": "MHD",
"destination": "THR",
"date": "2025-12-11",
"min_price": 1450000
}
],
"meta": {
"timestamp": 1733739000
}
}
پاسخ خطا (یافت نشد)
فقط در حالت "تک تاریخ" رخ میدهد:
{
"error": {
"code": 404,
"message": "not found."
},
"meta": {
"timestamp": 1733739000
}
}
Flowchart
1. Remove Prefix
2. Extract Date
3. Get Price
GET /v2/core/application_interface
O. List Application Interfaces (Core)
این اندپوینت برای دریافت لیست رابطهای نرمافزاری (Application Interfaces) استفاده میشود. خروجی این سرویس لیستی از شعب (Offices) است که تنظیمات و دسترسیهای مربوطه (مانند APIهای ایرلاینها، دسترسی همکاران و...) به عنوان زیرمجموعه آنها گروهبندی شدهاند. همچنین امکان دریافت مانده حساب کیف پول هر شعبه نیز به صورت اختیاری وجود دارد.
Request Overview
/v2/core/application_interfaceAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
- دادهها بر اساس رکوردهای موجود در جداول
application_interfaceوofficesتجمیع میشوند.
Query Parameters (Filters)
| Field | Type | Description |
|---|---|---|
| branch | integer | (اختیاری) شناسه شعبه. در صورت ارسال، خروجی فقط شامل همین شعبه و تنظیمات مربوط به آن خواهد بود. |
| service | string | (اختیاری) نام سرویس (مثلاً nira). فقط رابطهای مربوط به این سرویس بازگردانده میشوند. |
| type | string | (اختیاری) نوع رابط (مثلاً api). |
| status | integer | (اختیاری) وضعیت فعال/غیرفعال بودن رابط (1 یا 0). |
| balance | boolean | (اختیاری) اگر مقدار true یا 1 ارسال شود، سیستم مانده حساب کیف پول (Wallet) هر شعبه را محاسبه و در خروجی قرار میدهد. |
Logic Details
فرآیند پردازش دادهها شامل مراحل زیر است:
- دریافت و فیلتر رابطها: ابتدا دادهها از جدول
application_interfaceبر اساس فیلترهای ورودی (branch, service, type, status) دریافت میشوند. - غنیسازی دادهها (Data Mapping):
- اگر
object_typeبرابر باcolleagueباشد، اطلاعات همکار (نام، سریال، آفیس) از مدلColleagueاستخراج و جایگزین فیلدobjectمیشود. - فیلدهای رشتهای ساده (service, type, object_type) به ساختار استاندارد آرایهای
{id, title}تبدیل میشوند.
- اگر
- گروهبندی: دادههای پردازش شده بر اساس شناسه شعبه (
branch) گروهبندی میشوند. - تجمیع با شعب (Offices):
- لیست شعب از جدول
officesدریافت میشود (اگر فیلترbranchباشد، فقط همان شعبه). - برای هر شعبه، رابطهای مربوطه از مرحله قبل در فیلد
itemsقرار میگیرند.
- لیست شعب از جدول
- محاسبه مانده (Balance):
- اگر پارامتر
balance=trueباشد، متدAccountingController::getBalanceWalletفراخوانی میشود. - این متد مجموع
creditوdebitرا از جدولwalletبرای آن شعبه (با شرطoperator_type='erp') محاسبه میکند. - وضعیت حساب (diagnosis) به صورت
creditor(بستانکار)،debtor(بدهکار) یاneutralتعیین میشود.
- اگر پارامتر
Response Structure
نمونه پاسخ موفق (JSON)
{
"items": [
{
"id": 1,
"title_fa": "دفتر مرکزی",
"title_en": "Headquarters",
"brand_fa": "برند نمونه",
"items": [
{
"id": 105,
"branch": 1,
"service": { "id": "nira", "title": "nira" },
"type": { "id": "api", "title": "api" },
"object_type": { "id": "colleague", "title": "colleague" },
"object": {
"id": 50,
"first_name": "Ali",
"last_name": "Rezaei",
"serial": "12345"
},
"status": 1
}
],
"balance": {
"credit": 1000000,
"debit": 200000,
"balance": 800000,
"diagnosis": "creditor"
}
}
],
"meta": {
"timestamp": 1733745000
}
}
Flowchart
1. Expand 'colleague' object
2. Format fields to {id, title}
GET /v2/core/application_interface/{id}
P. Show Application Interface (Single Item)
این اندپوینت برای دریافت جزئیات کامل یک رابط نرمافزاری خاص استفاده میشود. با ارسال شناسه (ID) رکورد، سیستم اطلاعات آن را از دیتابیس استخراج کرده و پس از استانداردسازی فیلدها و غنیسازی اطلاعات (مانند اطلاعات همکار)، خروجی را بازمیگرداند.
Request Overview
/v2/core/application_interface/{id}Access Control
- نیاز به توکن احراز هویت (JWT) دارد.
- شناسه ارسالی باید در جدول
application_interfaceموجود باشد.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه منحصر به فرد (Primary Key) رکورد در جدول رابطهای نرمافزاری. |
Logic Details
منطق پردازش این متد به شرح زیر است:
- جستجو در دیتابیس: ابتدا با استفاده از
DB::table('application_interface')->find($id)رکورد مورد نظر بازیابی میشود. - بررسی نوع آبجکت (Object Expansion):
- اگر
object_typeبرابر باcolleagueباشد، سیستم شناسه موجود در فیلدobjectرا برداشته و اطلاعات همکار (نام، نام خانوادگی، سریال، دفتر و وضعیت) را از مدلColleagueجستجو میکند. - نتیجه جایگزین مقدار عددی فیلد
objectمیشود.
- اگر
- استانداردسازی فرمت (Data Formatting):
- فیلدهای
object_type،serviceوtypeکه به صورت رشته ساده هستند، به آرایهای شاملidوtitleتبدیل میشوند تا ساختار پاسخ یکپارچه باشد (Front-end friendly).
- فیلدهای
- خروجی: داده نهایی درون آبجکت
payloadقرار گرفته و ارسال میشود.
Response Structure
نمونه پاسخ موفق (200 OK)
{
"payload": {
"id": 105,
"branch": 1,
"service": {
"id": "nira",
"title": "nira"
},
"type": {
"id": "api",
"title": "api"
},
"object_type": {
"id": "colleague",
"title": "colleague"
},
"object": {
"id": 50,
"first_name": "Ali",
"last_name": "Rezaei",
"office": 1,
"status": 1,
"serial": "12345"
},
"status": 1,
"created_at": "2024-01-01 12:00:00"
},
"meta": {
"timestamp": 1733748000
}
}
Flowchart
& Replace Object
Convert Strings to {id, title} Structure
DELETE /v2/core/application_interface/{id}
Q. Delete Application Interface (Core)
این اندپوینت برای حذف یک رابط نرمافزاری از سیستم استفاده میشود. عملیات حذف به صورت مستقیم بر روی دیتابیس انجام شده و غیرقابل بازگشت است (Hard Delete).
Request Overview
/v2/core/application_interface/{id}Access Control
- نیاز به توکن احراز هویت (JWT) دارد.
- دسترسی مدیریتی برای حذف رکوردها الزامی است.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه منحصر به فرد (Primary Key) رکورد که باید حذف شود. |
Logic Details
منطق پردازش این متد بسیار ساده و مستقیم است:
- اجرای دستور حذف: متد به صورت مستقیم کوئری
DELETEرا بر روی جدولapplication_interfaceبا شرطidاجرا میکند. - نتیجه عملیات: خروجی تابع دیتابیس، تعداد ردیفهای حذف شده است (معمولاً
1در صورت وجود رکورد و0در صورت عدم وجود). - پاسخدهی: تعداد ردیفهای حذف شده به عنوان
payloadبازگردانده میشود.
Response Structure
نمونه پاسخ موفق (JSON)
در این نمونه، عدد 1 نشاندهنده حذف موفقیتآمیز یک رکورد است.
{
"payload": 1,
"meta": {
"timestamp": 1733751000
}
}
Flowchart
POST /v2/core/application_interface
Store Application Interface (Create New)
این اندپوینت برای ایجاد یک رابط نرمافزاری جدید در سیستم استفاده میشود. نکته حائز اهمیت در این متد، نحوه ارسال پارامترهای دستهبندی است؛ فیلدهایی مانند نوع و سرویس باید به صورت آبجکت ارسال شوند تا سیستم بتواند عنوان (Title) آنها را استخراج و ذخیره کند.
Request Overview
/v2/core/application_interfaceAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
Body Parameters
پارامترهای زیر باید در بدنه درخواست (Body) به صورت JSON ارسال شوند:
| Field | Type | Description |
|---|---|---|
| branch | integer | شناسه شعبه مربوطه. |
| type | object | (ساختار خاص) باید شامل کلید title باشد. مثال: {"title": "api"} |
| service | object | (ساختار خاص) باید شامل کلید title باشد. مثال: {"title": "nira"} |
| object_type | object | (ساختار خاص) نوع موجودیت هدف. باید شامل کلید title باشد. مثال: {"title": "colleague"} |
| object | object | null | موجودیت هدف. باید شامل کلید id باشد. اگر ارسال نشود، null ذخیره میشود. مثال: {"id": 50} |
| url | string | آدرس URL رابط (در صورت وجود). |
| username | string | نام کاربری اتصال. |
| password | string | کلمه عبور اتصال. |
| data | string/text | سایر دادههای پیکربندی (معمولاً JSON String یا کلید API). |
| priority | integer | اولویت رابط. |
| status | integer | وضعیت فعال (1) یا غیرفعال (0). |
Logic Details
فرآیند ذخیرهسازی به شرح زیر است:
- استخراج دادهها: مقادیر از آبجکتهای ورودی استخراج میشوند:
type['title']→ ذخیره در ستونtypeservice['title']→ ذخیره در ستونserviceobject_type['title']→ ذخیره در ستونobject_typeobject['id']→ ذخیره در ستونobject(اگر موجود نباشد Null).
- درج در دیتابیس: رکورد جدید با دستور
insertGetIdایجاد شده و شناسه (ID) تولید شده دریافت میشود. - بازیابی: بلافاصله پس از درج، رکورد کامل با استفاده از ID جدید از دیتابیس خوانده میشود (
find($id)). - خروجی: رکورد ذخیره شده به عنوان پاسخ بازگردانده میشود.
Response Structure
نمونه Body ارسالی (Request)
{
"branch": 1,
"type": { "id": "api", "title": "api" },
"service": { "id": "nira", "title": "nira" },
"object_type": { "id": "colleague", "title": "colleague" },
"object": { "id": 50, "name": "Ali..." },
"url": "https://api.example.com",
"username": "user1",
"password": "pass123",
"data": "API_KEY_STRING",
"priority": 0,
"status": 1
}
نمونه پاسخ موفق (200 OK)
{
"payload": {
"id": 106,
"branch": 1,
"type": "api",
"service": "nira",
"object_type": "colleague",
"object": 50,
"url": "https://api.example.com",
"username": "user1",
"password": "pass123",
"data": "API_KEY_STRING",
"priority": 0,
"status": 1,
"created_at": null,
"updated_at": null
},
"meta": {
"timestamp": 1733752000
}
}
Flowchart
type -> title
service -> title
object -> id
PUT /v2/core/application_interface/{id}
Update Application Interface
این اندپوینت برای ویرایش اطلاعات یک رابط نرمافزاری موجود استفاده میشود. با ارسال شناسه رکورد و دادههای جدید، سیستم رکورد را بروزرسانی میکند. همانند متد ثبت، پارامترهای دستهبندی باید به صورت آبجکت ارسال شوند تا سیستم بتواند مقادیر مورد نیاز (Title یا ID) را از آنها استخراج کند.
Request Overview
/v2/core/application_interface/{id}Access Control
- نیاز به توکن احراز هویت (JWT) دارد.
- شناسه ارسالی در URL باید معتبر باشد.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | integer | (الزامی) شناسه رکورد مورد نظر برای ویرایش. |
Body Parameters
ساختار بادی درخواست مشابه متد ثبت (Store) است:
| Field | Type | Description |
|---|---|---|
| branch | integer | شناسه شعبه. |
| type | object | باید شامل کلید title باشد. مقدار این کلید در دیتابیس ذخیره میشود. |
| service | object | باید شامل کلید title باشد. |
| object_type | object | باید شامل کلید title باشد (مثلاً colleague). |
| object | object | null | باید شامل کلید id باشد. اگر نال باشد یا ارسال نشود، مقدار دیتابیس NULL میشود. |
| url, username, password, data | string | اطلاعات اتصال و تنظیمات (رشته ساده). |
| priority, status | integer | اولویت و وضعیت فعال بودن. |
Logic Details
فرآیند بروزرسانی به شرح زیر است:
- دریافت و استخراج دادهها: مقادیر ورودی پردازش میشوند:
- برای فیلدهای
type،serviceوobject_typeمقدار['title']برداشته میشود. - برای فیلد
objectمقدار['id']برداشته میشود (با استفاده از عملگر Null Coalescing).
- برای فیلدهای
- بروزرسانی دیتابیس: دستور
UPDATEبر روی جدولapplication_interfaceبرای رکوردی کهidآن برابر با پارامتر مسیر است اجرا میشود. - بازیابی مجدد: پس از اعمال تغییرات، رکورد بروزرسانی شده با
find($id)فراخوانی میشود تا تغییرات منعکس شده و دادهها بازگردانده شوند.
Response Structure
نمونه Body ارسالی (JSON)
{
"branch": 1,
"type": { "id": "api", "title": "api" },
"service": { "id": "parto", "title": "parto" },
"object_type": { "id": "branch", "title": "branch" },
"object": { "id": 2 },
"url": "https://new-api.com",
"username": "admin",
"password": "newpass",
"data": "{}",
"priority": 5,
"status": 1
}
نمونه پاسخ موفق (200 OK)
{
"payload": {
"id": 105,
"branch": 1,
"type": "api",
"service": "parto",
"object_type": "branch",
"object": 2,
"url": "https://new-api.com",
"username": "admin",
"password": "newpass",
"data": "{}",
"priority": 5,
"status": 1,
"created_at": "2024-01-01...",
"updated_at": null
},
"meta": {
"timestamp": 1733753000
}
}
Flowchart
type['title'], service['title']
object['id'] ?? null
GET /v2/core/application_interface_types
List Application Interface Types
این اندپوینت لیست ثابت و از پیش تعریفشدهای از انواع (Types) قابل قبول برای رابطهای نرمافزاری را بازمیگرداند. این لیست معمولاً برای پر کردن Dropdownها در فرمهای ایجاد یا ویرایش (اندپوینتهای R و S) استفاده میشود تا کاربر یکی از مقادیر مجاز را انتخاب کند.
Request Overview
/v2/core/application_interface_typesAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
Logic Details
این متد هیچ پردازش دیتابیسی انجام نمیدهد و یک آرایه استاتیک از مقادیر زیر را برمیگرداند:
| ID / Title | Description |
|---|---|
| recaptcha | سرویس کپچا گوگل یا مشابه. |
| sms | پنلهای ارسال پیامک. |
| ami | رابطهای Asterisk Manager Interface (VoIP). |
| ftp | پروتکل انتقال فایل. |
| support | سیستمهای پشتیبانی/تیکتینگ. |
| api | سایر APIهای عمومی وبسرویس. |
| smtp | پروتکل ارسال ایمیل. |
Response Structure
نمونه پاسخ موفق (200 OK)
{
"items": [
{ "id": "recaptcha", "title": "recaptcha" },
{ "id": "sms", "title": "sms" },
{ "id": "ami", "title": "ami" },
{ "id": "ftp", "title": "ftp" },
{ "id": "support", "title": "support" },
{ "id": "api", "title": "api" },
{ "id": "smtp", "title": "smtp" }
],
"meta": {
"timestamp": 1733754000
}
}
Flowchart
(recaptcha, sms, api, ...)
GET /v2/core/application_interface_services
List Application Interface Services
این اندپوینت لیست ثابت و از پیش تعریفشدهای از سرویسها (Services) قابل انتخاب برای رابطهای نرمافزاری را بازمیگرداند. این مقادیر مشخص میکنند که یک رابط خاص (مثلاً یک API Key) مربوط به کدام سرویسدهنده یا پلتفرم خارجی است.
Request Overview
/v2/core/application_interface_servicesAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
Logic Details
این متد هیچ پردازش دیتابیسی انجام نمیدهد و یک آرایه استاتیک شامل لیست سرویسدهندگان پشتیبانی شده را برمیگرداند:
| ID / Title | Description |
|---|---|
| airplus | وبسرویس خدمات پرواز ایرپلاس. |
| سرویسهای گوگل (مانند Recaptcha). | |
| ami | رابط مدیریت استریسک (Asterisk Manager Interface). |
| gmini | سرویس Gmini. |
| irnoti | سامانه پیامکی Irnoti. |
| issabel | سیستم تلفنی ایزابل (VoIP). |
| goftino | پلتفرم چت آنلاین گفتینو. |
| navasan | سرویس دریافت نرخ ارز. |
| ravis | وبسرویس گردشگری راویس. |
| sepehr | سیستم رزرواسیون سپهر. |
| nira | سیستم رزرواسیون نیرا سافت. |
| liara | سرویس ابری لیارا (Object Storage و ...). |
| tport | پلتفرم T-Port (گردشگری). |
| sepehr_hotel | سرویس هتل سیستم سپهر. |
| jibit | خدمات بانکی و پرداخت جیبیت. |
| snapptrip_hotel | سرویس هتل اسنپتریپ. |
Response Structure
نمونه پاسخ موفق (200 OK)
{
"items": [
{ "id": "airplus", "title": "airplus" },
{ "id": "google", "title": "google" },
{ "id": "ami", "title": "ami" },
{ "id": "gmini", "title": "gmini" },
{ "id": "irnoti", "title": "irnoti" },
{ "id": "issabel", "title": "issabel" },
{ "id": "goftino", "title": "goftino" },
{ "id": "navasan", "title": "navasan" },
{ "id": "ravis", "title": "ravis" },
{ "id": "sepehr", "title": "sepehr" },
{ "id": "nira", "title": "nira" },
{ "id": "liara", "title": "liara" },
{ "id": "tport", "title": "tport" },
{ "id": "sepehr_hotel", "title": "sepehr_hotel" },
{ "id": "jibit", "title": "jibit" },
{ "id": "snapptrip_hotel", "title": "snapptrip_hotel" }
],
"meta": {
"timestamp": 1733754000
}
}
Flowchart
(airplus, google, nira, ...)
GET /v2/core/application_interface_object_types
List Application Interface Object Types
این اندپوینت لیست ثابت انواع موجودیتهای (Object Types) قابل اتصال به رابطهای نرمافزاری را بازمیگرداند. این فیلد تعیین میکند که شناسه موجود در فیلد object به کدام جدول دیتابیس (مثلاً جدول همکاران) اشاره دارد.
Request Overview
/v2/core/application_interface_object_typesAccess Control
- نیاز به توکن احراز هویت (JWT) دارد.
Logic Details
این متد در حال حاضر تنها یک مقدار ثابت را بازمیگرداند که نشاندهنده اتصال رابط به موجودیت «همکار» است:
| ID / Title | Description |
|---|---|
| colleague | نشان میدهد که این رابط نرمافزاری متعلق به یک همکار (Colleague) خاص است و فیلد object باید حاوی شناسه آن همکار باشد. |
Response Structure
نمونه پاسخ موفق (200 OK)
{
"items": [
{
"id": "colleague",
"title": "colleague"
}
],
"meta": {
"timestamp": 1733754000
}
}
Flowchart
(Currently only 'colleague')
PUT /v2/core/application_interface/status/{id}
Update Application Interface Status
این اندپوینت به منظور تغییر سریع وضعیت (Status) یک رابط نرمافزاری استفاده میشود. برخلاف متد ویرایش کلی، این متد تنها فیلد status را در دیتابیس بروزرسانی میکند.
Request Overview
/v2/core/application_interface/status/{id}Access Control
- نیاز به توکن احراز هویت (JWT) دارد.
Parameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| id | Integer | Path | شناسه یکتای رکورد در جدول application_interface. |
| status | String | Body | وضعیت جدید برای اعمال (مثلاً active یا inactive). |
Logic Details
عملیات به صورت مستقیم روی دیتابیس انجام میشود:
- ابتدا با استفاده از
idدریافتی، کوئری آپدیت روی جدولapplication_interfaceاجرا شده و فیلدstatusبا مقدار ورودی جایگزین میشود. - بلافاصله پس از آپدیت، رکورد مورد نظر مجدداً از دیتابیس (با
find($id)) بازیابی میشود تا آخرین وضعیت تایید شده برگردانده شود.
Response Structure
نمونه پاسخ موفق (200 OK)
{
"payload": {
"id": 15,
"branch_id": 1,
"type": "sms",
"service": "irnoti",
"object_type": "colleague",
"object": 102,
"settings": "{\"apiKey\":\"...\"}",
"status": "inactive",
"created_at": "2023-01-01 12:00:00",
"updated_at": "2023-12-09 14:30:00"
},
"meta": {
"timestamp": 1733754000
}
}
Flowchart
(Where ID = $id)
(DB::find($id))
RESOURCE /v2/scrumboard/boards
List Scrum Boards
این اندپوینت لیست بوردهای اسکرام را بازیابی میکند. نتایج شامل بوردهایی است که کاربر جاری یا سازنده (Owner) آنهاست و یا به عنوان عضو (Member) به آنها دعوت شده است.
/v2/scrumboard/boardsParameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| branch | Integer | Query | شناسه شعبه (الزامی برای فیلتر اولیه). |
| status | Integer | Query | (اختیاری) فیلتر بر اساس وضعیت فعال/غیرفعال. |
Logic Details
سیستم پردازشهای زیر را هنگام دریافت لیست انجام میدهد:
- فیلتر دسترسی: تنها بوردهایی بازگردانده میشوند که
operator_idبرابر با کاربر جاری باشد یا شناسه کاربر در آرایه JSON ستونmembersوجود داشته باشد. - Hydration (تزریق دادهها):
- فیلد
members(آرایهای از IDها) با آبجکت کامل اطلاعات اپراتورها (نام، آواتار و...) جایگزین میشود. - لیستهای بورد (Lists) و لیبلها (Labels) از جداول مربوطه دریافت و به پاسخ اضافه میشوند.
- فیلد
Success Response
{
"items": [
{
"id": 10,
"title": "پروژه توسعه",
"operator_id": 55,
"members": [
{ "id": 60, "first_name": "Ali", "last_name": "Rezaei", "avatar": "..." }
],
"lists": [
{ "id": 1, "title": "درحال انجام", "priority": 2 }
],
"labels": [
{ "id": 5, "title": "فوری" }
]
}
],
"meta": { "timestamp": 1733754000 }
}
Create Scrum Board
این اندپوینت یک بورد اسکرام جدید ایجاد میکند. نکته مهم در این متد، ایجاد خودکار لیستها و لیبلهای پیشفرض همزمان با ساخت بورد است تا بورد بلافاصله قابل استفاده باشد.
/v2/scrumboard/boardsParameters
| Parameter | Type | Description |
|---|---|---|
| title | String | عنوان بورد (الزامی). |
| icon | String | آیکون یا ایموجی بورد (الزامی). |
| branch | Integer | شناسه شعبه (الزامی). |
| sprint_unit | String | (اختیاری) واحد زمان اسپرینت. |
| sprint_duration | Integer | (اختیاری) مدت زمان اسپرینت. |
Logic Details (Default Data)
پس از درج بورد، سیستم موارد زیر را به صورت خودکار ایجاد میکند:
- نیازمند بررسی (رنگ: #ffdb6d، اولویت: 3)
- درحال انجام (رنگ: #4287f4، اولویت: 2)
- انجام شده (رنگ: #4caf50، اولویت: 1)
Success Response
{
"payload": {
"id": 12,
"title": "New Board",
"lists": [ ... ],
"labels": [ ... ]
},
"meta": { "timestamp": 1733754000 }
}
Show Scrum Board
دریافت اطلاعات کامل یک بورد خاص شامل اعضا، لیستها و لیبلها. دسترسی به دادههای خروجی این متد محدود به سازنده یا اعضای آن بورد است.
/v2/scrumboard/boards/{id}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | Integer | شناسه بورد. |
Logic Details
این متد چک میکند که آیا کاربر درخواست کننده (`operator`) یا سازنده بورد است و یا شناسه او در فیلد JSON `members` وجود دارد. در غیر این صورت (یا اگر بورد وجود نداشته باشد)، نتیجه خالی برمیگرداند.
Success Response
{
"payload": {
"id": 12,
"title": "Board Title",
"members": [...],
"lists": [...],
"labels": [...]
},
"meta": { "timestamp": 1733754000 }
}
Update Scrum Board
بروزرسانی اطلاعات بورد. نکته امنیتی مهم این است که برخلاف نمایش، فقط سازنده بورد (Operator ID منطبق) اجازه ویرایش را دارد.
/v2/scrumboard/boards/{id}Parameters
| Parameter | Type | Description |
|---|---|---|
| title | String | عنوان جدید. |
| description | String | توضیحات. |
| members | Array (IDs) | لیست ID اعضای جدید (به JSON تبدیل میشود). |
| status | Integer | وضعیت (پیشفرض 1). |
Error Response (Forbidden)
اگر کاربر جاری سازنده بورد نباشد:
{
"error": {
"code": 1001,
"message": "You do not have permission to edit this scrum board."
},
"meta": { "timestamp": 1733754000 }
}
Success Response
{
"payload": { "id": 12, "title": "Updated Title", ... },
"meta": { "timestamp": 1733754000 }
}
Delete Scrum Board
حذف کامل بورد از دیتابیس. این عملیات نیز مانند ویرایش، تنها توسط سازنده بورد قابل انجام است.
/v2/scrumboard/boards/{id}Logic Details
ابتدا مالکیت بورد بررسی میشود. اگر کاربر جاری سازنده نباشد، خطای کد 1001 بازگردانده میشود. در صورت موفقیت، رکورد از جدول scrumboard_boards حذف میشود.
Success Response
{
"payload": 1, // تعداد رکوردهای حذف شده
"meta": {
"timestamp": 1733754000
}
}
RESOURCE /v2/scrumboard/lists
Deep Hydration Logic Flow
منطق غنیسازی عمیق (Deep Hydration) که در اکثر متدها (Index, Store, Show, Update) اجرا میشود تا ساختار درختی کامل بورد را بسازد:
Fetch `scrumboard_cards`
Return Empty Cards []
Fetch Operators (Avatar, Name)
Fetch Label Details
Filter Sprints by JSON
Fetch & Hydrate Operator
Decode Items & Hydrate Operator
Fetch Attachments
Get Board Lists
دریافت تمام لیستهای (ستونهای) مربوط به یک بورد خاص به همراه تمام کارتها و جزئیات آنها.
/v2/scrumboard/listsParameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| board_id | Integer | Query | (الزامی) شناسه بوردی که لیستها متعلق به آن هستند. |
| status | Integer | Query | (اختیاری) فیلتر بر اساس وضعیت لیست. |
Response Structure
{
"items": [
{
"id": 1,
"board_id": 10,
"title": "To Do",
"priority": 2,
"cards": [
{
"id": 101,
"title": "Task 1",
"members": [ { "id": 1, "first_name": "Ali", ... } ],
"labels": [ { "id": 5, "title": "Bug", "color": "red" } ],
"comments": [ ... ],
"checklists": [ ... ],
"attachments": [ ... ],
"sprints": [ ... ]
}
]
}
],
"meta": { "timestamp": 1733754000 }
}
Create List
ایجاد یک لیست (ستون) جدید در بورد. بلافاصله پس از ایجاد، رکورد بازیابی شده و فرآیند Hydration روی آن اجرا میشود.
/v2/scrumboard/listsParameters
| Parameter | Type | Description |
|---|---|---|
| board_id | Integer | شناسه بورد والد. |
| title | String | عنوان لیست. |
| color | String | رنگ لیست (کد HEX). |
Success Response
{
"payload": {
"id": 5,
"title": "New Column",
"cards": [],
"created_at": "..."
},
"meta": { "timestamp": 1733754000 }
}
Show List
دریافت اطلاعات یک لیست خاص با شناسه ID. این متد نیز تمامی کارتها و وابستگیهای آنها را بارگذاری میکند.
/v2/scrumboard/lists/{id}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | Integer | شناسه لیست (List ID). |
Update List
ویرایش اطلاعات یک لیست.
/v2/scrumboard/lists/{id}Parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
| board_id | Integer | شناسه بورد. | - |
| title | String | عنوان لیست. | - |
| color | String | رنگ لیست. | - |
| priority | Integer | اولویت نمایش (Sort Order). | 0 |
| status | Integer | وضعیت لیست. | 1 |
Success Response
{
"payload": {
"id": 5,
"title": "Edited Title",
"priority": 10,
"cards": [ ... ]
},
"meta": { "timestamp": 1733754000 }
}
Delete List
حذف کامل یک لیست از دیتابیس.
/v2/scrumboard/lists/{id}Success Response
{
"payload": 1, // تعداد ردیفهای حذف شده
"meta": {
"timestamp": 1733754000
}
}
RESOURCE /v2/scrumboard/labels
Label Logic Flow
منطق پردازش ساده برای مدیریت لیبلها (برچسبها) که مستقیماً با دیتابیس در تعامل است:
Set Timestamps
Get Board Labels
دریافت لیست تمامی لیبلهای تعریف شده برای یک بورد خاص.
/v2/scrumboard/labelsParameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| board_id | Integer | Query | (الزامی) شناسه بوردی که لیبلها متعلق به آن هستند. |
| status | Integer | Query | (اختیاری) فیلتر بر اساس وضعیت فعال/غیرفعال بودن. |
Response Structure
نکته: اگر دادهای یافت نشود، مقدار items برابر با false خواهد بود.
{
"items": [
{
"id": 1,
"board_id": 10,
"title": "Bug Fix",
"status": 1,
"created_at": "2023-12-01 10:00:00",
"updated_at": "2023-12-01 10:00:00"
}
],
"meta": { "timestamp": 1733754000 }
}
Create Label
ایجاد یک لیبل جدید برای بورد.
/v2/scrumboard/labelsParameters
| Parameter | Type | Description |
|---|---|---|
| board_id | Integer | شناسه بورد والد. |
| title | String | عنوان لیبل. |
Success Response
{
"payload": {
"id": 5,
"board_id": 10,
"title": "Urgent",
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733754000 }
}
Show Label
دریافت جزئیات یک لیبل خاص.
/v2/scrumboard/labels/{id}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | Integer | شناسه لیبل. |
Response
اگر لیبل یافت نشود، مقدار payload برابر با false خواهد بود.
{
"payload": {
"id": 5,
"board_id": 10,
"title": "Urgent",
"status": 1,
...
},
"meta": { "timestamp": 1733754000 }
}
Update Label
ویرایش نام، وضعیت یا انتقال لیبل به بورد دیگر.
/v2/scrumboard/labels/{id}Parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
| board_id | Integer | شناسه بورد. | - |
| title | String | عنوان جدید لیبل. | - |
| status | Integer | وضعیت (1 فعال، 0 غیرفعال). | 1 |
Success Response
{
"payload": {
"id": 5,
"board_id": 10,
"title": "New Title",
"status": 1,
"updated_at": "..."
},
"meta": { "timestamp": 1733754000 }
}
Delete Label
حذف لیبل از دیتابیس.
/v2/scrumboard/labels/{id}Success Response
{
"payload": 1, // تعداد ردیفهای حذف شده
"meta": {
"timestamp": 1733754000
}
}
RESOURCE /v2/scrumboard/checklists
Checklist Hydration Logic
در تمامی متدها (لیست، ایجاد، نمایش، ویرایش)، دادههای خام دیتابیس قبل از ارسال به کلاینت پردازش میشوند. فیلد `checkitems` از رشته JSON به آرایه تبدیل شده و شناسه `operator` به آبجکت کامل اپراتور تبدیل میگردد.
Input: JSON String (e.g., "[{...}]")
Output: Array (or [] if null)
Input: Operator ID (Int)
Action: Fetch from `operators` table
Output: Object {id, name, avatar...}
Get Card Checklists
دریافت لیست تمامی چکلیستهای متصل به یک کارت (Card) مشخص. آیتمهای داخلی چکلیست و اطلاعات اپراتور مسئول به صورت خودکار بارگذاری میشوند.
/v2/scrumboard/checklistsParameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| card_id | Integer | Query | (الزامی) شناسه کارتی که چکلیستها متعلق به آن هستند. |
| status | Integer | Query | (اختیاری) فیلتر بر اساس وضعیت (مثلاً 1 فعال). |
Response Structure
{
"items": [
{
"id": 1,
"card_id": 101,
"title": "QA Testing",
"checkitems": [
{ "text": "Test Login", "done": true },
{ "text": "Test Logout", "done": false }
],
"operator": {
"id": 5,
"first_name": "Ali",
"last_name": "Rezaei",
"avatar": "path/to/img.jpg"
...
},
"duration": "2h",
"status": 1,
"created_at": "..."
}
],
"meta": { "timestamp": 1733754000 }
}
Create Checklist
ایجاد یک چکلیست جدید. کلاینت میتواند آرایهای از آیتمها (`checkitems`) را ارسال کند که در سمت سرور به JSON تبدیل و ذخیره میشود.
/v2/scrumboard/checklistsParameters
| Parameter | Type | Description |
|---|---|---|
| card_id | Integer | شناسه کارت والد. |
| title | String | عنوان کلی چکلیست. |
| checkitems | Array | آرایهای از آبجکتها (آیتمهای تیکدار). |
| operator | Integer | شناسه اپراتور مسئول (اختیاری). |
| duration | String | مدت زمان تخمینی/صرف شده (اختیاری). |
Success Response
{
"payload": {
"id": 15,
"title": "Deployment Steps",
"checkitems": [ ... ], // آرایه دیکد شده
"operator": { ... }, // آبجکت اپراتور (اگر ارسال شده باشد)
"created_at": "..."
},
"meta": { "timestamp": 1733754000 }
}
Show Checklist
دریافت جزئیات یک چکلیست خاص با شناسه ID.
/v2/scrumboard/checklists/{id}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | Integer | شناسه چکلیست. |
Response
اگر یافت نشود، payload: false برمیگرداند.
{
"payload": {
"id": 15,
"title": "Deployment Steps",
"checkitems": [],
"operator": null,
...
},
"meta": { "timestamp": 1733754000 }
}
Update Checklist
ویرایش اطلاعات چکلیست. این متد معمولاً برای تغییر عنوان، وضعیت، تغییر مسئول (`operator`) یا بهروزرسانی محتوای آیتمهای داخلی (`checkitems`) استفاده میشود.
/v2/scrumboard/checklists/{id}Parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
| card_id | Integer | شناسه کارت. | - |
| title | String | عنوان چکلیست. | - |
| checkitems | Array | آرایه کامل آیتمها (جایگزین قبلی میشود). | null |
| operator | Integer | شناسه اپراتور جدید. | null |
| duration | String | مدت زمان. | null |
| status | Integer | وضعیت. | 1 |
Success Response
{
"payload": {
"id": 15,
"title": "Updated Title",
"checkitems": [ ... ],
"operator": { ... },
"updated_at": "..."
},
"meta": { "timestamp": 1733754000 }
}
Delete Checklist
حذف چکلیست از دیتابیس.
/v2/scrumboard/checklists/{id}Success Response
{
"payload": 1, // تعداد ردیفهای حذف شده
"meta": {
"timestamp": 1733754000
}
}
RESOURCE /v2/scrumboard/comments
Comment Operator Hydration
در تمامی متدها (لیست، ثبت، نمایش و ویرایش)، سیستم پس از دریافت اطلاعات کامنت از جدول `scrumboard_comments`، به صورت دستی اطلاعات اپراتور (نویسنده کامنت) را از جدول `operators` استخراج کرده و به پاسخ اضافه میکند.
Input: operator_id
Query: SELECT id, first_name, last_name, avatar FROM operators WHERE id = ?
Result: Attach object to `operator` field
Get Card Comments
دریافت لیست نظرات ثبت شده برای یک کارت (Card). این متد امکان فیلتر کردن بر اساس اپراتور و وضعیت را نیز دارد.
/v2/scrumboard/commentsParameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| card_id | Integer | Query | (الزامی) شناسه کارتی که نظرات مربوط به آن است. |
| operator_id | Integer | Query | (اختیاری) فیلتر نظرات یک اپراتور خاص. |
| status | Integer | Query | (اختیاری) فیلتر بر اساس وضعیت نظر. |
Response Structure
{
"items": [
{
"id": 10,
"card_id": 55,
"operator_id": 3,
"message": "This needs to be fixed ASAP.",
"ip_address": "192.168.1.5",
"status": 1,
"created_at": "2024-01-01 12:00:00",
"updated_at": "2024-01-01 12:00:00",
"operator": {
"id": 3,
"first_name": "John",
"last_name": "Doe",
"first_name_en": "John",
"last_name_en": "Doe",
"avatar": "path/to/avatar.jpg"
}
}
],
"meta": { "timestamp": 1733754000 }
}
Create Comment
ثبت یک نظر جدید برای یک کارت. آدرس IP کاربر به صورت خودکار توسط سیستم (`getIP()`) ذخیره میشود.
/v2/scrumboard/commentsParameters
| Parameter | Type | Description |
|---|---|---|
| card_id | Integer | شناسه کارت مربوطه. |
| operator_id | Integer | شناسه اپراتوری که نظر را ثبت میکند. |
| message | String | متن نظر. |
Success Response
{
"payload": {
"id": 12,
"card_id": 55,
"operator_id": 3,
"ip_address": "127.0.0.1",
"message": "Task completed.",
"created_at": "...",
"updated_at": "...",
"operator": {
"id": 3,
"first_name": "John",
"last_name": "Doe",
...
}
},
"meta": { "timestamp": 1733754000 }
}
Show Comment
مشاهده جزئیات یک نظر خاص با استفاده از شناسه آن.
/v2/scrumboard/comments/{id}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | Integer | شناسه نظر. |
Response
در صورت عدم یافتن رکورد، مقدار payload: false بازگردانده میشود.
{
"payload": {
"id": 10,
"message": "Specific comment details...",
"operator": { ... },
...
},
"meta": { "timestamp": 1733754000 }
}
Update Comment
ویرایش متن، وضعیت و یا حتی انتقال نظر به کارت/اپراتور دیگر.
/v2/scrumboard/comments/{id}Parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
| card_id | Integer | شناسه کارت (جهت جابجایی نظر). | - |
| operator_id | Integer | شناسه اپراتور (جهت تغییر نویسنده). | - |
| message | String | متن اصلاح شده نظر. | - |
| status | Integer | وضعیت نمایش نظر. | 1 |
Success Response
{
"payload": {
"id": 10,
"message": "Updated message text",
"status": 1,
"updated_at": "...",
"operator": { ... }
},
"meta": { "timestamp": 1733754000 }
}
Delete Comment
حذف دائمی یک نظر از دیتابیس.
/v2/scrumboard/comments/{id}Success Response
{
"payload": 1, // تعداد ردیفهای حذف شده
"meta": {
"timestamp": 1733754000
}
}
RESOURCE /v2/scrumboard/cards
Card Deep Hydration Logic
کارتها قلب سیستم اسکرام هستند. در تمامی متدها (Index, Store, Show, Update)، پس از دریافت اطلاعات خام کارت، یک فرآیند سنگین برای بارگذاری تمام وابستگیها اجرا میشود. این فرآیند شامل دیکد کردن JSONها و کوئریهای متعدد به جداول مختلف است.
Decode `members` & `labels`
Fetch Operators & Label Objects via IDs
1. Get Comments (+ Hydrate Operators)
2. Get Checklists (+ Hydrate Operators + Decode Items)
3. Get Attachments (Media table)
4. Get Sprints (JSON Search)
Get Cards List
دریافت لیست کارتهای موجود در یک لیست (ستون) خاص. تمامی اطلاعات وابسته (اعضا، لیبلها، پیوستها و...) به همراه کارت برگردانده میشوند.
/v2/scrumboard/cardsParameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| list_id | Integer | Query | (الزامی) شناسه لیست (ستون) والد. |
| status | Integer | Query | (اختیاری) فیلتر وضعیت کارت. |
Response Structure
{
"items": [
{
"id": 101,
"list_id": 5,
"title": "Fix Login Bug",
"description": "User cannot login with email...",
"members": [
{ "id": 1, "first_name": "Ali", "avatar": "..." }
],
"labels": [
{ "id": 2, "title": "Bug", "color": "#ff0000" }
],
"comments": [ ... ], // لیست کامل نظرات با اطلاعات نویسنده
"checklists": [ ... ], // لیست چکلیستها با آیتمها
"attachments": [ ... ], // مدیاهای متصل
"sprints": [ ... ], // اسپرینتهای مرتبط
"created_at": "..."
}
],
"meta": { "timestamp": 1733754000 }
}
Create Card
ایجاد یک کارت جدید ساده. پس از ایجاد، کارت بلافاصله بازیابی شده و ساختار کامل (با آرایههای خالی برای وابستگیها) برگردانده میشود.
/v2/scrumboard/cardsParameters
| Parameter | Type | Description |
|---|---|---|
| list_id | Integer | شناسه لیست مقصد. |
| title | String | عنوان کارت. |
Success Response
{
"payload": {
"id": 102,
"title": "New Feature",
"members": [],
"labels": [],
"comments": [],
"checklists": [],
"attachments": [],
"sprints": [],
...
},
"meta": { "timestamp": 1733754000 }
}
Show Card Details
دریافت جزئیات کامل یک کارت با شناسه مشخص.
/v2/scrumboard/cards/{id}Response
اگر کارت پیدا نشود، payload: false برمیگرداند.
{
"payload": {
"id": 101,
"title": "Fix Login Bug",
"members": [ ... ],
"checklists": [
{
"id": 5,
"title": "QA",
"checkitems": [{ "text": "Test A", "done": true }],
"operator": { ... }
}
],
...
},
"meta": { "timestamp": 1733754000 }
}
Update Card & Sub-Resources
این متد علاوه بر آپدیت فیلدهای اصلی کارت، قابلیت مدیریت تودرتوی (Nested Management) برای comments و checklists را نیز دارد. شما میتوانید همزمان با ویرایش کارت، یک کامنت یا چکلیست را ایجاد، ویرایش یا حذف کنید.
/v2/scrumboard/cards/{id}Basic Parameters
| Parameter | Type | Description |
|---|---|---|
| list_id | Integer | جابجایی کارت به لیست دیگر. |
| title | String | تغییر عنوان. |
| description | String | توضیحات تکمیلی. |
| due_date | Date/String | تاریخ سررسید. |
| members | Array[Int] | لیست جدید شناسههای اعضا (جایگزین کل لیست قبلی میشود). |
| labels | Array[Int] | لیست جدید شناسههای لیبلها. |
| done_at | DateTime | زمان انجام شدن. |
| status | Integer | وضعیت (پیشفرض 1). |
Advanced Parameters (Nested Actions)
ارسال این پارامترها اختیاری است و برای عملیات سریع روی زیرمجموعهها استفاده میشود.
1. مدیریت کامنت (پارامتر comment)
| Action | Payload Structure |
|---|---|
| Create | { "card_id": 1, "operator_id": 2, "message": "text" }(بدون ارسال id) |
| Delete | { "id": 10, "delete": true } |
2. مدیریت چکلیست (پارامتر checklist)
| Action | Payload Structure |
|---|---|
| Create | { "card_id": 1, "title": "Checklist 1", "checkitems": [...] }(بدون ارسال id) |
| Update | { "id": 5, "card_id": 1, "title": "New Title", "checkitems": [...] } |
| Delete | { "id": 5, "delete": true } |
Success Response
{
"payload": {
"id": 101,
// کارت آپدیت شده به همراه تمام تغییرات اعمال شده در کامنتها/چکلیستها
"comments": [ ... ],
"checklists": [ ... ],
...
},
"meta": { "timestamp": 1733754000 }
}
Delete Card
حذف کارت از دیتابیس. توجه: طبق کد فعلی، وابستگیها (مثل کامنتها) به صورت Cascade در کد PHP حذف نمیشوند مگر اینکه در سطح دیتابیس تنظیم شده باشد.
/v2/scrumboard/cards/{id}Success Response
{
"payload": 1, // 1: موفق، 0: ناموفق
"meta": { "timestamp": 1733754000 }
}
RESOURCE /v2/scrumboard/sprints
Sprint Hydration Logic
اسپرینتها ظروف نگهداری کارتها در بازههای زمانی مشخص هستند. منطق Hydration در اینجا بسیار سنگین است زیرا با دریافت اسپرینت، سیستم به صورت خودکار تمام کارتهای داخل آن را واکشی کرده و فرآیند "Card Deep Hydration" را برای تک تک آنها اجرا میکند.
Fetch `scrumboard_sprint_reports`
+ Hydrate Operator for each report
Decode `cards` JSON (Array of IDs)
Loop through Cards & Apply Card Deep Hydration:
(Members, Labels, Comments, Checklists, Attachments)
Get Sprints List
دریافت لیست اسپرینتهای یک بورد. هر آیتم در لیست شامل آرایهای کامل از کارتهای داخل آن اسپرینت است.
/v2/scrumboard/sprintsParameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| board_id | Integer | Query | (الزامی) شناسه بورد مربوطه. |
| status | Integer | Query | (اختیاری) فیلتر بر اساس وضعیت اسپرینت. |
Response Structure
{
"items": [
{
"id": 10,
"board_id": 1,
"title": "Sprint #4 - Login Refactor",
"goal": "Improve security",
"duration": "2 weeks",
"status": 1,
"created_at": "...",
// گزارشهای اسپرینت
"reports": [
{ "id": 1, "note": "Daily meeting...", "operator": { ... } }
],
// کارتهای داخل اسپرینت (کاملاً Hydrate شده)
"cards": [
{
"id": 101,
"title": "Login API",
"members": [...],
"labels": [...],
"comments": [...],
"checklists": [...],
"attachments": [...]
}
]
}
],
"meta": { "timestamp": 1733754000 }
}
Create Sprint
ایجاد یک اسپرینت جدید. میتوان در لحظه ایجاد، لیست کارتهای مرتبط را نیز تعیین کرد.
/v2/scrumboard/sprintsBody Parameters
| Parameter | Type | Description |
|---|---|---|
| board_id | Integer | (الزامی) شناسه بورد والد. |
| title | String | (الزامی) عنوان اسپرینت. |
| duration | String | (الزامی) مدت زمان (مثلاً "2 weeks"). |
| goal | String | (اختیاری) هدف اسپرینت. |
| description | String | (اختیاری) توضیحات تکمیلی. |
| color | String | (اختیاری) کد رنگ برای نمایش در UI. |
| cards | Array[Int] | (اختیاری) آرایهای از شناسه کارتها جهت اتصال به این اسپرینت. مثال: [101, 102] |
Success Response
{
"payload": {
"id": 11,
"title": "New Sprint",
"cards": [], // یا شامل آبجکتهای کارت اگر در ورودی ارسال شده باشد
"reports": [],
...
},
"meta": { "timestamp": 1733754000 }
}
Show Sprint Details
مشاهده جزئیات یک اسپرینت خاص به همراه تمامی وابستگیها (کارتها و گزارشها).
/v2/scrumboard/sprints/{id}Update Sprint
بهروزرسانی اطلاعات اسپرینت. مهم: اگر پارامتر cards ارسال شود، لیست کارتهای اسپرینت با لیست جدید جایگزین میشود (Sync).
/v2/scrumboard/sprints/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| title | String | عنوان اسپرینت. |
| status | Integer | وضعیت (مثلاً 1: فعال، 0: آرشیو). |
| cards | Array[Int] | لیست جدید شناسه کارتها. مثال: [101, 105, 109] |
| ... | ... | سایر فیلدهای مشابه متد Create (goal, color, duration, etc). |
Success Response
{
"payload": {
"id": 11,
"title": "Updated Title",
"cards": [ ... ], // لیست جدید کارتها با جزئیات کامل
...
},
"meta": { "timestamp": 1733754000 }
}
Delete Sprint
حذف اسپرینت از دیتابیس. این عملیات کارتها را حذف نمیکند، بلکه فقط ارتباط اسپرینت را از بین میبرد (مگر اینکه در دیتابیس تنظیمات دیگری اعمال شده باشد).
/v2/scrumboard/sprints/{id}Response
{
"payload": 1, // تعداد رکوردهای حذف شده
"meta": { "timestamp": 1733754000 }
}
RESOURCE /v2/scrumboard/sprint_reports
Sprint Report Hydration Logic
گزارشهای اسپرینت، زیرمجموعهای از اسپرینتها هستند و منطق Hydration سادهای دارند. در تمام متدها، پس از دریافت دادههای گزارش، سیستم فقط اطلاعات اپراتور (نویسنده) را از جدول `operators` بارگذاری میکند.
If `operator` field exists, fetch the full operator object from the `operators` table.
Get Sprint Reports List
دریافت لیست گزارشهای ثبت شده برای یک اسپرینت مشخص.
/v2/scrumboard/sprint_reportsParameters
| Parameter | Type | Location | Description |
|---|---|---|---|
| sprint_id | Integer | Query | (الزامی) شناسه اسپرینت والد. |
| status | Integer | Query | (اختیاری) فیلتر بر اساس وضعیت گزارش. |
Response Structure
در صورت عدم وجود گزارش، مقدار items: false برگردانده میشود.
{
"items": [
{
"id": 1,
"sprint_id": 10,
"title": "Daily Stand-up - Day 3",
"description": "Discussed blocker on API endpoint...",
"issued_at": "2025-12-08 09:00:00",
"status": 1,
"operator": {
"id": 5,
"first_name": "Reza",
"last_name": "Ahmadi",
"avatar": "..."
},
"created_at": "..."
}
],
"meta": { "timestamp": 1733754000 }
}
Create Sprint Report
ایجاد یک گزارش جدید برای یک اسپرینت. شناسه اپراتور (نویسنده) به صورت خودکار از توکن کاربر احراز هویت شده (`authWithJwt`) استخراج و ثبت میشود.
/v2/scrumboard/sprint_reportsBody Parameters
| Parameter | Type | Description |
|---|---|---|
| sprint_id | Integer | (الزامی) شناسه اسپرینت والد. |
| title | String | (الزامی) عنوان گزارش. |
| description | String | (الزامی) متن گزارش. |
| issued_at | DateTime | (الزامی) تاریخ و زمان صدور گزارش (مثلاً زمان جلسه). |
Success Response
{
"payload": {
"id": 2,
"sprint_id": 10,
"title": "Sprint Review Notes",
...
"operator": {
"id": 1,
"first_name": "Admin",
...
}
},
"meta": { "timestamp": 1733754000 }
}
Show Sprint Report Details
مشاهده جزئیات یک گزارش خاص با شناسه آن.
/v2/scrumboard/sprint_reports/{id}Response
در صورت پیدا نشدن گزارش، payload: false برمیگردد.
{
"payload": {
"id": 1,
"title": "Daily Stand-up - Day 3",
"operator": { ... },
...
},
"meta": { "timestamp": 1733754000 }
}
Update Sprint Report
بهروزرسانی اطلاعات یک گزارش. توجه داشته باشید که نویسنده (operator) قابل تغییر نیست.
/v2/scrumboard/sprint_reports/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| sprint_id | Integer | انتقال گزارش به اسپرینت دیگر. |
| title | String | تغییر عنوان. |
| description | String | تغییر متن گزارش. |
| issued_at | DateTime | تغییر زمان صدور. |
| status | Integer | تغییر وضعیت (پیشفرض 1). |
Success Response
{
"payload": {
"id": 1,
"title": "Updated report title",
...
},
"meta": { "timestamp": 1733754000 }
}
Delete Sprint Report
حذف یک گزارش از سیستم.
/v2/scrumboard/sprint_reports/{id}Response
{
"payload": 1, // 1 برای موفقیت، 0 برای عدم موفقیت
"meta": { "timestamp": 1733754000 }
}
RESOURCE /v2/cartable/letters
Letter & Workflow Logic
ماژول کارتابل دارای یک منطق گردش کار (Workflow) است. زمانی که یک نامه ایجاد میشود، سیستم به صورت خودکار اولین مرحله (Step) آن فرآیند را ایجاد میکند. همچنین زمانی که وضعیت یک مرحله به "انجام شده" تغییر میکند، سیستم به صورت هوشمند مرحله بعدی را تشخیص داده و ایجاد میکند.
Generate Serial ID
Insert Letter Record
Fetch `cartable_subjects` (Process Config)
Decode `steps` JSON
Insert First Step into `cartable_letter_steps`
Get Letters List
دریافت لیست نامههای موجود در کارتابل. این متد از یک سیستم صفحهبندی (Pagination) خاص استفاده میکند که پارامترهای `length` و `start` را به شماره صفحه لاراول تبدیل میکند.
/v2/cartable/lettersQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| paginate[length] | Integer | (اختیاری) تعداد آیتم در هر صفحه (پیشفرض: 30). |
| paginate[start] | Integer | (اختیاری) آفست شروع دیتا (برای محاسبه صفحه فعلی). |
| requester_id | Integer | (اختیاری) فیلتر بر اساس شناسه درخواستکننده. |
| subject | Integer | (اختیاری) فیلتر بر اساس شناسه موضوع نامه. |
| status | Integer | (اختیاری) فیلتر بر اساس وضعیت کلی نامه. |
Response Structure
خروجی توسط LetterResource فرمتدهی میشود.
{
"items": [
{
"id": 50,
"serial": "LET-1402-001",
"subject": { "id": 1, "title": "Leave Request" },
"requester": { "id": 10, "name": "Ali..." },
"current_step": { "id": 205, "status": "pending" },
"created_at": "..."
}
],
"meta": { "timestamp": 1733754000 }
}
Create Letter
ایجاد یک نامه جدید.
نکته مهم: پس از ایجاد نامه، سیستم به جدول `cartable_subjects` مراجعه کرده و لیست مراحل تعریف شده برای آن موضوع (`subject`) را میخواند و به صورت خودکار اولین مرحله را در جدول `cartable_letter_steps` برای این نامه ثبت میکند.
/v2/cartable/lettersBody Parameters
| Parameter | Type | Description |
|---|---|---|
| branch | Integer | (الزامی) شناسه شعبه (برای تولید شماره سریال). |
| subject | Integer | (الزامی) شناسه موضوع نامه (تعیین کننده مراحل گردش کار). |
| recipient | String | (اختیاری) گیرنده نامه. |
| title | String | (اختیاری) عنوان نامه. |
| description | String | (اختیاری) توضیحات نامه. |
| operators | Array[Int] | (اختیاری) لیست اپراتورهای دخیل (به صورت JSON ذخیره میشود). |
Success Response
{
"payload": {
"id": 51,
"serial": "100-2569",
"subject": 5,
"steps": [ ... ] // شامل اولین مرحله ایجاد شده
},
"meta": { "timestamp": 1733754000 }
}
Show Letter Details
مشاهده جزئیات کامل یک نامه. اگر نامه حذف شده باشد (`deleted_at` داشته باشد) یا وجود نداشته باشد، خطای 404 برمیگرداند.
/v2/cartable/letters/{id}Update Letter
ویرایش اطلاعات عمومی نامه (عنوان، گیرنده، توضیحات و...). این متد مراحل گردش کار را تغییر نمیدهد.
/v2/cartable/letters/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| title | String | عنوان نامه. |
| subject | Integer | تغییر موضوع نامه. |
| operators | Array[Int] | لیست اپراتورها. |
| description | String | توضیحات. |
| recipient | String | گیرنده. |
Delete Letter
حذف نرم (Soft Delete) نامه. فیلد deleted_at با زمان فعلی مقداردهی میشود.
/v2/cartable/letters/{id}Update Step & Advance Workflow
توجه: این متد در کنترلر وجود دارد اما روت آن در اسنیپت ارائه شده نیست (احتمالاً روت سفارشی دارد).
این متد برای تغییر وضعیت یک مرحله (Step) استفاده میشود.
منطق هوشمند: اگر وضعیت ارسالی برابر با 3 (انجام شده) باشد، سیستم به صورت خودکار مرحله بعدی فرآیند را از تنظیمات موضوع (`subject steps`) پیدا کرده و آن را ایجاد میکند.
(Custom Route) /v2/cartable/steps/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| status | Integer | (الزامی) وضعیت جدید مرحله (مثلاً 3 برای اتمام). |
| description | String | (اختیاری) توضیحات یا دستور انجام کار روی مرحله. |
Check for Next Step in Config
Insert Next Step
RESOURCE /v2/cartable/subjects
Subject & Workflow Configuration Logic
این کنترلر وظیفه مدیریت "موضوعات نامه" را بر عهده دارد. اهمیت اصلی این بخش در فیلد steps است.
فیلد steps یک آرایه JSON است که "نقشه راه" یا "گردش کار" (Workflow) یک نامه را تعریف میکند. وقتی نامهای با موضوع خاصی ایجاد میشود، سیستم از این تنظیمات برای تولید مراحل (`cartable_letter_steps`) استفاده میکند.
Example: [Step1: Secretary, Step2: Manager, Step3: Archive]
List Subjects
دریافت لیست تمام موضوعات تعریف شده. برخلاف نامهها، در اینجا فعلاً صفحهبندی (Pagination) وجود ندارد و همه رکوردها بر اساس فیلترها بازگردانده میشوند.
/v2/cartable/subjectsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| department | Integer | (اختیاری) فیلتر بر اساس دپارتمان. |
| status | Integer | (اختیاری) فیلتر بر اساس وضعیت فعال/غیرفعال بودن موضوع. |
Response Structure
{
"items": [
{
"id": 1,
"branch": 101,
"department": 5,
"title": "درخواست مرخصی",
"steps": "[{\"order\":1, \"role\":\"manager\"}, ...]", // JSON String
"created_at": "...",
"updated_at": "..."
}
],
"meta": { "timestamp": 1733758000 }
}
Create Subject & Workflow
تعریف یک موضوع جدید به همراه مراحل گردش کار آن.
نکته فنی: آرایه `steps` به صورت `json_encode` شده در دیتابیس ذخیره میشود.
/v2/cartable/subjectsBody Parameters
| Parameter | Type | Description |
|---|---|---|
| branch | Integer | (الزامی) شناسه شعبه. |
| department | Integer | شناسه دپارتمان مربوطه. |
| title | String | عنوان موضوع (مثلاً: "درخواست وام"). |
| steps | Array | آرایه مراحل گردش کار (JSON). این ساختار برای تولید خودکار مراحل حیاتی است. |
Success Response
{
"payload": {
"id": 12,
"title": "New Workflow",
"steps": "[\"step1\", \"step2\"]",
"created_at": "..."
},
"meta": { "timestamp": 1733758000 }
}
Show Subject Details
مشاهده جزئیات یک موضوع.
/v2/cartable/subjects/{id}Update Subject
ویرایش تنظیمات موضوع.
نکته: طبق کد، فیلد `branch` در متد Update قابل ویرایش نیست (فقط department, title, steps).
/v2/cartable/subjects/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| department | Integer | دپارتمان. |
| title | String | عنوان موضوع. |
| steps | Array | بازتعریف مراحل گردش کار (این تغییر روی نامههای قدیمی تاثیر نمیگذارد، فقط نامههای جدید). |
Delete Subject
حذف فیزیکی (Hard Delete) یک موضوع از دیتابیس.
هشدار: این عملیات Soft Delete نیست و رکورد کاملاً پاک میشود.
/v2/cartable/subjects/{id}RESOURCE /v2/cartable/subject_steps
Subject Steps Management Logic
این کنترلر وظیفه مدیریت مراحل پیشفرض (Step Templates) را بر عهده دارد.
این جدول (`cartable_subject_steps`) به عنوان یک بانک اطلاعاتی از مراحل عمل میکند که شامل یک عنوان (`title`) و یک اپراتور مسئول (`operator`) است. این مراحل مستقل هستند اما میتوانند هنگام تعریف گردش کار (Workflow) در بخش Subjectها مورد استفاده قرار گیرند.
(e.g. Operator: 105, Title: "Financial Review")
List Step Definitions
دریافت لیست مراحل تعریف شده. این لیست بر اساس تاریخ ایجاد (نزولی) مرتب شده است.
/v2/cartable/subject_stepsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| operator | Integer | (اختیاری) فیلتر بر اساس شناسه اپراتور مسئول. |
| status | Integer | (اختیاری) فیلتر بر اساس وضعیت مرحله (اگرچه در متد Store فیلد status ست نمیشود، اما در Index قابل فیلتر است). |
Response Structure
{
"items": [
{
"id": 10,
"operator": 105,
"title": "بررسی مالی",
"created_at": "...",
"updated_at": "..."
}
],
"meta": { "timestamp": 1733760000 }
}
Create Step Definition
ایجاد یک تعریف مرحله جدید (Template).
/v2/cartable/subject_stepsBody Parameters
| Parameter | Type | Description |
|---|---|---|
| operator | Integer | (الزامی) شناسه اپراتوری که مسئول انجام این مرحله است. |
| title | String | (الزامی) عنوان مرحله (مثلاً: "تایید نهایی"). |
Success Response
{
"payload": {
"id": 15,
"operator": 105,
"title": "تایید نهایی",
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733760000 }
}
Show Step Details
مشاهده جزئیات یک مرحله خاص.
/v2/cartable/subject_steps/{id}Response Structure
{
"payload": {
"id": 15,
"operator": 105,
"title": "تایید نهایی",
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733760000 }
}
Update Step Definition
ویرایش اطلاعات اپراتور یا عنوان مرحله.
/v2/cartable/subject_steps/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| operator | Integer | شناسه اپراتور جدید. |
| title | String | عنوان جدید مرحله. |
Success Response
{
"payload": {
"id": 15,
"operator": 200, // Updated
"title": "تایید مدیریت ارشد", // Updated
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733760000 }
}
Delete Step Definition
حذف فیزیکی (Hard Delete) یک مرحله از دیتابیس.
هشدار: اگر این مرحله در Subjectها استفاده شده باشد، ممکن است باعث ناهماهنگی در تنظیمات شود (بسته به منطق فرانتاند). در اینجا حذف کامل انجام میشود.
/v2/cartable/subject_steps/{id}Success Response
{
"payload": 1, // تعداد رکوردهای حذف شده
"meta": { "timestamp": 1733760000 }
}
RESOURCE /v2/cartable/recipients
Department Recipients Management Logic
این کنترلر وظیفه مدیریت لیست "گیرندگان" (`recipients`) را در سطح دپارتمانها بر عهده دارد.
این جدول (`cartable_department_recipients`) عناوین شغلی یا واحدهایی را نگهداری میکند که در یک شعبه و دپارتمان خاص مجاز به دریافت نامه هستند (مانند: "دبیرخانه"، "مدیریت مالی" و ...).
(e.g. Branch: 100, Dept: 5, Title: "Secretariat")
List Recipients
دریافت لیست تمام گیرندگان تعریف شده. این لیست بر اساس تاریخ ایجاد (نزولی) مرتب شده است.
/v2/cartable/recipientsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| department | Integer | (اختیاری) فیلتر بر اساس شناسه دپارتمان. |
| status | Integer | (اختیاری) فیلتر بر اساس وضعیت فعال/غیرفعال بودن (در صورتی که در دیتابیس این فیلد پر شده باشد). |
Response Structure
{
"items": [
{
"id": 10,
"department": 5,
"branch": 101,
"title": "دبیرخانه مرکزی",
"created_at": "...",
"updated_at": "..."
}
],
"meta": { "timestamp": 1733762000 }
}
Create Recipient
تعریف یک گیرنده جدید برای یک شعبه و دپارتمان خاص.
/v2/cartable/recipientsBody Parameters
| Parameter | Type | Description |
|---|---|---|
| department | Integer | (الزامی) شناسه دپارتمان مربوطه. |
| branch | Integer | (الزامی) شناسه شعبه. |
| title | String | (الزامی) عنوان گیرنده (مثلاً: "حسابداری"). |
Success Response
{
"payload": {
"id": 12,
"department": 5,
"branch": 101,
"title": "حسابداری",
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733762000 }
}
Show Recipient Details
مشاهده جزئیات یک گیرنده خاص.
/v2/cartable/recipients/{id}Response Structure
{
"payload": {
"id": 12,
"department": 5,
"branch": 101,
"title": "حسابداری",
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733762000 }
}
Update Recipient
ویرایش اطلاعات گیرنده.
نکته مهم: طبق منطق کد، فیلد `branch` در متد Update قابل تغییر نیست و فقط `department` و `title` بروزرسانی میشوند.
/v2/cartable/recipients/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| department | Integer | شناسه دپارتمان جدید. |
| title | String | عنوان جدید. |
Success Response
{
"payload": {
"id": 12,
"department": 6, // Changed
"branch": 101,
"title": "حسابداری ارشد", // Changed
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733762000 }
}
Delete Recipient
حذف فیزیکی (Hard Delete) یک گیرنده از سیستم.
/v2/cartable/recipients/{id}Success Response
{
"payload": 1, // تعداد رکوردهای حذف شده
"meta": { "timestamp": 1733762000 }
}
RESOURCE /v2/cartable/operator_role
Operator Role & Signature Logic
این کنترلر برای انتساب یک "عنوان" (`title`) به یک "اپراتور" (`operator`) و مهمتر از آن، ذخیره **تصویر امضای دیجیتال** (`signature_file`) استفاده میشود.
فایلهای آپلود شده در فضای ذخیرهسازی ابری (دیسک `liara`) با ساختار پوشهبندی بر اساس شناسه شعبه (`branch`) و تاریخ ذخیره میشوند.
(Image validation: max 2MB, jpeg/png...)
Record Saved in DB
List Operator Roles
دریافت لیست نقشها و امضاهای تعریف شده.
/v2/cartable/operator_roleQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| operator | Integer | (اختیاری) فیلتر بر اساس شناسه اپراتور. |
| status | Integer | (اختیاری) فیلتر بر اساس وضعیت. |
Response Structure
{
"items": [
{
"id": 5,
"operator": 105,
"title": "مدیر فنی",
"signature_file": "uploads/100/cartable/signatures/2024/12/example.png",
"created_at": "...",
"updated_at": "..."
}
],
"meta": { "timestamp": 1733764000 }
}
Create Role & Upload Signature
تعریف نقش جدید و آپلود تصویر امضا.
نکته مهم: این درخواست باید به صورت multipart/form-data ارسال شود.
/v2/cartable/operator_roleBody Parameters (Multipart)
| Parameter | Type | Description |
|---|---|---|
| operator | Integer | (الزامی) شناسه اپراتور صاحب امضا. |
| title | String | (الزامی) عنوان شغلی (مثلاً "رئیس هیئت مدیره"). |
| branch | Integer | (الزامی) شناسه شعبه (جهت ساخت مسیر ذخیرهسازی فایل). |
| signature_file | File | (اختیاری) فایل تصویر امضا. فرمتهای مجاز: jpeg, png, jpg, gif, svg. حداکثر حجم: 2MB. |
Success Response
{
"payload": {
"id": 8,
"operator": 105,
"title": "رئیس هیئت مدیره",
"signature_file": "uploads/101/cartable/signatures/2025/12/sig-123.png",
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733764000 }
}
Show Role Details
مشاهده جزئیات یک نقش خاص.
/v2/cartable/operator_role/{id}Response Structure
{
"payload": {
"id": 8,
"operator": 105,
"title": "رئیس هیئت مدیره",
"signature_file": "uploads/101/cartable/signatures/2025/12/sig-123.png",
"created_at": "...",
"updated_at": "..."
},
"meta": { "timestamp": 1733764000 }
}
Update Role & Signature
ویرایش عنوان یا جایگزینی فایل امضا.
اگر فایل جدیدی ارسال شود، آپلود شده و مسیر آن در دیتابیس جایگزین میشود.
توجه: در لاراول برای ارسال فایل در متد PUT معمولاً باید از `_method: PUT` در یک درخواست POST استفاده کنید.
/v2/cartable/operator_role/{id}Body Parameters
| Parameter | Type | Description |
|---|---|---|
| operator | Integer | شناسه اپراتور. |
| title | String | عنوان شغلی جدید. |
| branch | Integer | شناسه شعبه (الزامی اگر فایل ارسال شود). |
| signature_file | File | (اختیاری) تصویر امضای جدید برای جایگزینی. |
Success Response
{
"payload": {
"id": 8,
"operator": 105,
"title": "عنوان ویرایش شده",
"signature_file": "uploads/101/cartable/signatures/2025/12/new-sig.png",
"updated_at": "..."
},
"meta": { "timestamp": 1733764000 }
}
Delete Role
حذف نقش و امضا.
پاکسازی فایل: این متد علاوه بر حذف رکورد از دیتابیس، فایل امضای مربوطه را نیز از فضای ابری (Liara) پاک میکند.
/v2/cartable/operator_role/{id}Success Response
{
"payload": 1, // 1 for success
"meta": { "timestamp": 1733764000 }
}
PUT /v2/cartable/letters/step/{id}
Workflow Progression Logic
این متد یک مرحله خاص (`cartable_letter_steps`) از یک نامه را بروزرسانی میکند.
جادوی گردش کار: اگر وضعیت ارسالی برابر با 3 (انجام شده) باشد، سیستم به صورت خودکار پیکربندی "موضوع نامه" (`subject->steps`) را بررسی کرده و مرحله بعدی را در دیتابیس ایجاد میکند.
Update Letter Step
تغییر وضعیت یک مرحله، ثبت توضیحات و (در صورت اتمام) ارجاع خودکار به مرحله بعد.
/v2/cartable/letters/step/{id}URL Parameters
| Parameter | Type | Description |
|---|---|---|
| id | Integer | (الزامی) شناسه رکورد مرحله در جدول cartable_letter_steps. |
Body Parameters
| Parameter | Type | Description |
|---|---|---|
| status | Integer | (الزامی) وضعیت جدید. 3 = انجام شده (Done) -> محرک مرحله بعدی.سایر اعداد = صرفاً بروزرسانی وضعیت. |
| description | String | (اختیاری) توضیحات یا هامش روی این مرحله. |
Logic Details (Backend)
- ثبت زمان اتمام: اگر
status == 3باشد، فیلدdone_atبا زمان فعلی پر میشود. - منطق ایجاد مرحله بعد:
- سیستم `letter_id` را پیدا کرده و از روی آن `subject_id` را مییابد.
- آرایه JSON مراحل (`steps`) را از جدول `cartable_subjects` میخواند.
- موقعیت فعلی را در آرایه پیدا کرده و اگر ایندکس بعدی (`index + 1`) وجود داشته باشد، یک رکورد جدید با وضعیت پیشفرض برای مرحله بعد ایجاد میکند.
Success Response
{
"payload": 1, // نشان دهنده موفقیت آمیز بودن عملیات (تعداد ردیف تغییر کرده)
"meta": {
"timestamp": 1733765000
}
}
Visual Logic Flow
(Find current step index in Subject's JSON)
Insert new record into `cartable_letter_steps`
POST /v2/ai/chat/completions
AI Financial Analysis Logic
این اندپوینت دادههای خام مالی (شامل خرید، فروش، سود و زیان) را دریافت کرده و آن را به یک گزارش متنی/بصری تبدیل میکند.
نقش سیستم: سیستم به عنوان یک مدیر مالی هوشمند برای نرمافزار AirPlus عمل میکند.
فرمت خروجی: پرامپت سیستم به گونهای تنظیم شده که خروجی نهایی صرفاً کد HTML Body باشد تا در فرانتاند مستقیماً داخل یک تگ div نمایش داده شود.
Generate AI Report
ارسال دادههای JSON مالی و دریافت تحلیل متنی به همراه جداول HTML.
در این فرآیند نام آژانس (`branch`) از دیتابیس خوانده شده و در متن گزارش استفاده میشود تا تحلیل شخصیسازی شده باشد.
/v2/ai/chat/completionsBody Parameters
| Parameter | Type | Description |
|---|---|---|
| message | JSON/Array | (الزامی) دادههای خام گزارش عملکرد (لیست خرید، فروش، سود، زیان و نوع خدمات). |
| branch | Integer | (الزامی) شناسه شعبه/آژانس (جهت واکشی نام آژانس و درج در گزارش). |
| model | String | (اختیاری) مدل هوش مصنوعی. پیشفرض: google/gemini-2.0-flash-001. |
| prompt | String | (اختیاری) دستورالعمل سفارشی برای تحلیلگر. در صورت عدم ارسال، از پرامپت پیشفرض (تحلیل 5 مرحلهای AirPlus) استفاده میشود. |
Default Prompt Structure
اگر پارامتر prompt ارسال نشود، سیستم دستورالعمل زیر را به AI میدهد:
- خلاصه وضعیت: کل فروش، هزینه و سود/زیان.
- تحلیل مقایسهای: مقایسه بین محصولات (پرواز، هتل، قطار و...).
- روندها: شناسایی رشد یا افت ناگهانی.
- جدول آمار: ارائه جدول HTML خلاصه.
- توصیه مدیریتی: پیشنهاد کاربردی برای بهبود سود (واحد پول: ریال).
Success Response Example
{
// خروجی بسته به کلاینت AI متفاوت است اما محتوا HTML است
"content": "
گزارش عملکرد آژانس تعطیلات رویایی
بر اساس دادههای دریافتی...
..."
}
Visual Logic Flow
(Get 'title_fa' from DB)
(Inject Branch Name + JSON Data + HTML formatting rules)
POST /v2/trade/reference/type
Global Reference Search
این متد به عنوان یک جستجوگر چندمنظوره (Poly-morphic Search) عمل میکند. فرانتاند با ارسال پارامتر goal مشخص میکند که به دنبال چه نوع دادهای است (مثلاً مسافر، حساب بانکی، یا ایرلاین) و سیستم با جستجو در جداول مربوطه، لیست استاندارد شدهای شامل id و text برمیگرداند.
Search Entities (Autocomplete)
جستجو و واکشی اطلاعات برای ورودیهای Select و Autocomplete در سراسر سیستم.
/v2/trade/reference/typeRequest Parameters
| Parameter | Type | Description |
|---|---|---|
| goal | String/Array | (الزامی) شناسه نوع موجودیت مورد جستجو. میتواند یک عدد (مثل 1000) یا یک رشته (مثل pay_type) یا ترکیبی جدا شده با کاما باشد. |
| q | String | (اختیاری) عبارت جستجو (متن یا شناسه عددی). |
| branch | Integer | (الزامی - معمولاً در هدر یا بادی) شناسه شعبه فعال کاربر. |
| separator | Boolean | (اختیاری) اگر ارسال شود، به ابتدای id در خروجی، نوع موجودیت اضافه میشود (مثلاً account-123 به جای 123). مناسب برای جستجوهای ترکیبی. |
| extra | JSON/Array | (اختیاری) فیلترهای تکمیلی. مثلاً برای حسابها (goal=1000) نوع پرداخت (type_pay: cash, pos, check) را مشخص میکند. |
Goal Codes (Magic Numbers) & Logic
| Goal Code | Target Entity | Search Logic / Filters |
|---|---|---|
| 1000 | حسابها (Accounts) | جستجو در accounting_accounts. پشتیبانی از فیلتر extra['type_pay'] برای نمایش حسابهای خاص (نقد، پوز، آنلاین، چک). |
| 1100 | ایرلاینها (Airlines) | جستجو بر اساس نام فارسی، انگلیسی یا کد IATA. |
| 1200 | هتلها (Hotels) | جستجو در نام هتل و نام استان (State). |
| 1300 | پرسنل (Personnel) | جستجو در کاربران (`users`). اعمال محدودیت دسترسی بر اساس سطح دسترسی اپراتور. |
| 1500 | تنخواهگردانها | کاربرانی که فیلد imprest آنها مقدار دارد. |
| 1600 | اعلامیهها | جدول announcements. |
| 2000 | بانکها | جدول accounting_banks (به جز نوع داخلی). |
| 3000 | حوالجات | حسابهای بانکی متصل به بانک با شناسه 32. |
| 4000 | اقساط | حسابهای بانکی متصل به بانک با شناسه 33. |
| 5000, 5001 | رفرنس/فاکتور | جستجو در شماره سریال فاکتور (`factors`) یا نام مشتری مرتبط. |
| 8000, 8100 | همکاران (Suppliers) | جدول colleagues با وضعیت فعال. |
| 8200 | متعهدین (Commitments) | جدول colleagues (معمولاً بدهکاران/بستانکاران خاص). |
| 9000 | مسافرین (Customers) | جستجو در نام، نام خانوادگی (فارسی/انگلیسی) جدول customers. |
| pay_type | روشهای پرداخت | لیست استاتیک بر اساس extra['type'] (دریافت یا پرداخت). شامل: نقد، پوز، چک، پایا، ساتنا و... |
| type_functor | طرف حساب | لیست انواع موجودیتهای مجاز برای تراکنش (مسافر، همکار، پرسنل و...). |
| status | وضعیتها | لیست استاتیک وضعیت (پایان یافته، در حال بررسی و...). |
| gateways | درگاهها | لیست درگاههای پرداخت فعال شعبه. |
Success Response Example
{
"total_count": 10,
"incomplete_results": false,
"results": [
{
"id": 105,
"text": "1205 - هتل داریوش (کیش)"
},
{
"id": "account-50", // در صورت فعال بودن separator
"text": "1050 - صندوق ریالی (Main Branch)"
}
],
"pagination": {
"more": false
}
}
POST /v2/base/reference
Base System References
این اندپوینت دقیقاً همان موتور جستجوی مرکزی (Global Search) است که در ماژول Trade استفاده میشد، اما با آدرس base برای استفادههای عمومی در کل سیستم (مانند فرمهای تنظیمات، داشبورد و غیره) در دسترس قرار گرفته است.
وظیفه آن تبدیل کدهای موجودیت (Goals) به لیستهای استاندارد برای Dropdownها است.
General Reference Lookup
جستجوی تمامی موجودیتهای پایه سیستم (کاربران، شیفتها، دپارتمانها، حسابها و...) برای استفاده در کامپوننتهای Select2.
/v2/base/referenceRequest Parameters
| Parameter | Type | Description |
|---|---|---|
| goal | String/Array | (الزامی) شناسه نوع داده مورد نیاز. (لیست کامل در جدول پایین). |
| q | String | (اختیاری) عبارت متنی یا عددی برای جستجو. |
| branch | Integer | (الزامی) شناسه شعبه فعال. |
| separator | Boolean | (اختیاری) افزودن پیشوند متنی به ID خروجی (مثلاً office-1). |
| details | Boolean | (اختیاری) برای برخی موجودیتها مثل shift_works جزئیات کامل رکورد را در خروجی برمیگرداند. |
Reference Codes (Goal Dictionary)
| Code | Entity | Notes |
|---|---|---|
| 1000 | حسابهای حسابداری | فیلتر بر اساس extra['type_pay'] (نقد، بانک، چک). |
| 1300 | پرسنل | کاربران سیستم. دسترسی محدود به سطح دسترسی اپراتور. |
| 1500 | تنخواهگردان | کاربران دارای فیلد imprest. |
| 1600 | اعلامیهها | جستجو در عنوان و شرکت صادرکننده. |
| 2000 | بانکها | لیست بانکهای تعریف شده (بجز نوع داخلی). |
| 5000 | فاکتور/رفرنس | جستجو در سریال فاکتور یا نام مشتری. |
| 8000/8100 | همکاران | تامینکنندگان (Suppliers). |
| 9000 | مشتریان/مسافرین | جستجو در نام فارسی و انگلیسی. |
| office | دفاتر/شعب | لیست دفاتر فعال (`offices`). |
| gateways | درگاههای پرداخت | درگاههای متصل به شعبه (`gateways`). |
| shift_works | شیفتهای کاری | لیست شیفتها. اگر details=true باشد، کل آبجکت برمیگردد. |
| office_departments | دپارتمانها | لیست دپارتمانهای شعبه (فروش، مالی و...). |
| status | وضعیتها | لیست استاتیک (پایان یافته، در حال بررسی، ...). |
Example Request (Shift Works)
{
"goal": "shift_works",
"branch": 1,
"details": true
}
Success Response
{
"total_count": 5,
"incomplete_results": false,
"results": [
{
"id": 1,
"text": "شیفت صبح",
"query": {
"id": 1,
"title": "شیفت صبح",
"start_time": "08:00",
"end_time": "16:00"
}
}
],
"pagination": {
"more": false
}
}
Technical Logic Flow
Select Table (User, Account, Office...)
(Branch check, Search 'q', Extra params)
{ id: ..., text: ... }
POST /v2/config
System Configuration & Tenant Init
این متد حیاتیترین اندپوینت برای راهاندازی اولیه اپلیکیشن (Bootstrapping) است.
سیستم بر اساس هدر Domain تشخیص میدهد که کاربر مربوط به کدام شعبه (Office) است و تنظیمات ظاهری، مالی، حقوقی و ماژولهای فعال آن شعبه را برمیگرداند.
Get Office Configuration
/v2/configHeaders (Required)
| Header Name | Description |
|---|---|
| Domain | آدرس دامنهای که اپلیکیشن روی آن اجرا میشود (مثلاً crm.example.com). سیستم www. و پورت (:8080) را به صورت خودکار حذف میکند تا دامنه پایه را پیدا کند. |
Response Structure (JSON)
خروجی شامل چندین بخش اصلی است:
| Key Block | Description |
|---|---|
| office_id | شناسه شعبه + 1000 (جهت Obfuscation). |
| title / brand | عنوانها و نام برند به فارسی و انگلیسی. |
| communicational | اطلاعات تماس (تلفن، موبایل، ایمیل، آدرس، کد پستی و لوکیشن). |
| design | تنظیمات ظاهری شامل:
|
| support_online | کدهای مربوط به ابزارهای چت آنلاین (مثل Raychat یا Crisp). |
| details.financial_debt | Boolean آیا شعبه بدهی پرداخت نشده بابت پشتیبانی دارد؟ (بررسی جدول airplus_bills). |
| details.official | قوانین هاردکد شده سیستم برای مرخصیها (Calendar Limit):
|
| details.config | تنظیمات داینامیک خوانده شده از جدول office_config (بصورت Key-Value). |
Example Response
{
"office_id": 1001,
"title": {
"fa": "آژانس مسافرتی نمونه",
"en": "Sample Travel Agency"
},
"brand": {
"fa": "برند نمونه",
"en": "Sample Brand"
},
"short_domain": "smpl",
"expiration": "2026-01-01",
"communicational": {
"phone": "02188888888",
"mobile": "09120000000",
"email": "info@example.com",
"site": "example.com",
"address": {
"fa": "تهران، خیابان ولیعصر...",
"en": "Tehran, Valiasr St..."
}
},
"design": {
"login_text": "Welcome to Automation",
"logo": "https://liara.../logo.png",
"base_color": "#3b82f6",
"theme": "light"
},
"services": ["flight", "hotel", "train"],
"details": {
"financial_debt": false,
"financial": {
"value_added": 9
},
"official": {
"calendar_limit": {
"leave": {
"1": { "overtime": -60, "substitute": true },
"2": { "overtime": -60, "substitute": false }
}
}
},
"config": {
"allow_negative_stock": "0",
"print_footer_text": "Thanks for your purchase"
}
}
}
Technical Logic Flow
Remove 'www.' and ':port'
WHERE json_contains(domain, $baseDomain)
"The office is not defined"
1. VAT Info
2. Office Configs
3. Check Expired Bills
GET /v2/config
Initial System Configuration
این متد نقطه شروع (Entry Point) اپلیکیشن است. فرانتاند قبل از هر کاری باید این اندپوینت را صدا بزند.
سیستم با بررسی هدر Domain، شعبه (Office) مورد نظر را پیدا کرده و تمام تنظیمات حیاتی شامل رنگبندی، لوگو، اطلاعات تماس، قوانین مالی و وضعیت بدهی را برمیگرداند.
Get Tenant Config
/v2/configHeaders (الزامی)
| Header Name | Description |
|---|---|
| Domain | آدرس دامنهای که کاربر با آن سایت را باز کرده است. مثال: crm.travel-agency.com یا localhost:3000 سیستم به صورت خودکار www. و پورت را حذف میکند. |
Response Structure
| Field Block | Description |
|---|---|
| office_id | شناسه واقعی شعبه + 1000 (جهت امنیت و عدم نمایش ID اصلی). |
| design | تنظیمات UI/UX:
|
| details.financial_debt | مهم: (Boolean) اگر true باشد، یعنی شعبه فاکتور پرداخت نشده "پشتیبانی" (Support Bill) دارد و دسترسی باید محدود شود. |
| details.official | قوانین هاردکد شده (Hardcoded) مربوط به سیستم حضور و غیاب:
|
| details.config | تنظیمات داینامیک تعریف شده در جدول office_config. خروجی به صورت Key-Value است (مثلاً allow_negative_stock: "1"). |
Example Response
{
"office_id": 1005,
"title": {
"fa": "آژانس مسافرتی آسمان",
"en": "Aseman Travel Agency"
},
"brand": {
"fa": "آسمان",
"en": "Aseman"
},
"short_domain": "aseman",
"expiration": "2025-12-29",
"communicational": {
"phone": "021-44444444",
"mobile": "09121234567",
"address": {
"fa": "تهران، میدان آزادی...",
"en": "Tehran, Azadi Sq..."
},
"location": "35.6892,51.3890"
},
"design": {
"login_text": "به سیستم اتوماسیون خوش آمدید",
"logo": "https://storage.../logo.png",
"base_color": "#ff5722",
"theme": "dark"
},
"support_online": {
"service": "raychat",
"code": "e45-fgh-789"
},
"details": {
"financial_debt": false,
"financial": {
"value_added": 9
},
"official": {
"calendar_limit": {
"leave": {
"1": { "overtime": -60, "substitute": true },
"2": { "overtime": -60, "substitute": false }
}
}
},
"config": {
"print_header": "1",
"currency_default": "IRR"
}
}
}
Logic Flow
Remove 'www.', Remove Port
Extract Base Domain
(table: offices)
"The office is not defined"
1. Configs (office_config)
2. VAT (accounting_titles)
3. Debt Check (airplus_bills)
4. Redis Cache Update
GET /b2c/v1/config
B2C Frontend Configuration
این اندپوینت مخصوص وبسایتهای فروش آنلاین (White-label B2C) است.
سیستم با بررسی هدر Domain، آدرس سایت مشتری را در ستون b2c_domains جستجو میکند و تنظیمات ظاهری، بنرها، مجوزهای نماد اعتماد و پیکربندی ماژولهای فروش را برمیگرداند.
Get B2C Config
/b2c/v1/configHeaders (الزامی)
| Header Name | Description |
|---|---|
| Domain | دامنه سایت فروش B2C (مثلاً booking.agency.com). سیستم www. و پورت را حذف کرده و در offices.b2c_domains جستجو میکند. |
Response Structure
| Key Block | Description |
|---|---|
| erp_domain | دامنه اصلی پنل مدیریت (ERP) که کارمندان از آن استفاده میکنند (جهت ارجاع لینکهای ادمین). |
| communicational.certificates | لیست مجوزها (نماد اعتماد، ساماندهی و ...). سیستم به صورت خودکار تگ <a><img .../></a> را برای نمایش تصویر مجوز تولید میکند. |
| design.advertisement | لیست بنرهای تبلیغاتی فعال (Status 1 or 3) برای نمایش در اسلایدر یا بخشهای تبلیغاتی سایت. |
| hub | تنظیمات اختصاصی فروشگاه (Hub):
|
Example Response
{
"office_id": 1025,
"title": { "fa": "سفر آنلاین", "en": "Safar Online" },
"brand": { "fa": "سفر24", "en": "Safar24" },
"short_domain": "s24",
"erp_domain": "admin.safar24.ir",
"communicational": {
"phone": "02199999999",
"whatsapp": "989120000000",
"social_media": { "instagram": "safar24_inst" },
"certificates": [
{
"title": "Enamad",
"content": "
"
}
]
},
"design": {
"logo": "https://storage.../logo_b2c.png",
"theme": "minimal-light-1",
"advertisement": [
{
"id": 55,
"title": "تور نوروزی استانبول",
"image": "https://storage.../banner1.jpg",
"link": "/tours/istanbul"
}
]
},
"hub": {
"services": ["flight", "hotel"],
"theme": "minimal",
"palette": "1",
"slogan": "سفری خاطرهانگیز با ما",
"modules": {
"flight_domestic": true,
"train": false
},
"authentication": "otp_mobile"
},
"cache": {
"update": "Tue, 09 Dec 2025 10:30:00 +0330"
}
}
Technical Logic
Normalize URL (Remove www)
WHERE json_contains(b2c_domains, $domain)
1.
advertisement (Active banners)2.
certificates (Trust logos)3.
hub_config (Site settings)POST /b2c/v1/auth/otp
B2C Customer Authentication (OTP)
این اندپوینت وظیفه مدیریت ورود و ثبتنام کاربران در وبسایتهای فروش (B2C) را بر عهده دارد.
سیستم از مکانیزم رمز یکبار مصرف (OTP) استفاده میکند. نکته کلیدی این است که اگر شماره موبایل در سیستم وجود نداشته باشد، کاربر به صورت خودکار (Auto-Register) ثبتنام شده و شناسه مسافر (`passenger_id`) ایجاد میگردد.
Request OTP / Login
/b2c/v1/auth/otpBody Parameters (الزامی)
| Parameter | Type | Description |
|---|---|---|
| mobile | String | شماره موبایل کاربر. سیستم فرمت را بررسی کرده و در صورت نیاز عدد '0' را به ابتدای آن اضافه میکند. |
| branch | Integer | شناسه شعبه فعال (Office ID). در صورت ثبتنام جدید، کاربر به این شعبه منتسب میشود. |
Response Structure
| Key | Description |
|---|---|
| status | بولین (true/false) که موفقیت عملیات ارسال پیامک را نشان میدهد. |
| data.passenger_id | شناسه منحصر به فرد کاربر (Customer ID) در دیتابیس. چه کاربر جدید باشد چه قدیمی، این شناسه برگردانده میشود. |
| message | پیام متنی وضعیت عملیات جهت نمایش به کاربر. |
Example Responses
۱. موفقیت آمیز (201 Created):
{
"status": true,
"time": 1702134567,
"data": {
"passenger_id": 5421
},
"message": "OTP با موفقیت ارسال گردید."
}
۲. خطای محدودیت ارسال (422 Unprocessable Entity - Code 1204):
{
"status": false,
"code": 1204,
"message": "تعداد درخواست های کاربر بیش از حد مجاز بوده است. لطفا 3 ساعت دیگر اقدام نمائید."
}
۳. خطای پنل پیامک (422 Unprocessable Entity - Code 1202):
{
"status": false,
"code": 1202,
"message": "پیامک OTP ارسال نشد. مشکلی رخ داده است. لطفا دوباره تلاش کنید",
"trace": { ... }
}
Technical Logic
Ensure starts with '0'
Convert Persian nums to English
INSERT INTO customers
Branch: [$branch]
StaticController::generateOtpStaticController::sendNotificationLog to SnailJob Queue
POST /b2c/v1/auth/submit
B2C Verify OTP & Login
این اندپوینت مرحله نهایی احراز هویت است.
کلاینت کد دریافتی (OTP) را به همراه شناسه مسافر ارسال میکند. سیستم کد را در جدول otp_requests اعتبارسنجی کرده (بررسی انقضا و عدم استفاده قبلی) و در صورت صحت، وضعیت کد را به "استفاده شده" تغییر میدهد و یک JWT Token با اعتبار ۷ روز برای کاربر صادر میکند.
Submit OTP
/b2c/v1/auth/submitHeaders (الزامی)
| Header Name | Description |
|---|---|
| Domain | دامنه سایت فروش (جهت ثبت در Payload توکن به عنوان iss و aud). |
Body Parameters (الزامی)
| Parameter | Type | Description |
|---|---|---|
| passenger_id | Integer | شناسه مسافر (دریافت شده در پاسخ مرحله قبل). |
| otp | String/Int | کد یکبار مصرف دریافتی در پیامک. |
| branch | Integer | شناسه شعبه (جهت ثبت در کلیم brn توکن). |
Response Structure
| Key | Description |
|---|---|
| status | وضعیت عملیات (true). |
| access_token | توکن احراز هویت (JWT) که باید در هدر Authorization: Bearer ... درخواستهای بعدی ارسال شود. مدت اعتبار: ۶۰۴۸۰۰ ثانیه (یک هفته). |
| user | آبجکت حاوی اطلاعات پروفایل کاربر (نام، نام خانوادگی، موبایل و کدملی). |
Example Responses
۱. موفقیت آمیز (Login Success):
{
"status": true,
"user": {
"uuid": 5421,
"from": "user",
"role": "passenger",
"type": "passenger",
"level": 1,
"data": {
"title": {
"fa": "محمد محمدی",
"en": "Mohammad Mohammadi"
},
"sex": "male",
"first_name": "Mohammad",
"last_name": "Mohammadi",
"first_name_fa": "محمد",
"last_name_fa": "محمدی",
"mobile": "09121234567",
"national_code": "0012345678"
}
},
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
۲. خطای نامعتبر بودن کد (422 Unprocessable Entity - Code 1209):
{
"status": false,
"code": 1209,
"message": "رمز یکبار مصرف وارد شده معتبر نمی باشد."
}
Technical Logic
Check table `otp_requests`
Match ID & Code AND
Not Expired AND Not Used
Return 422 JSON
Set
is_used = 1SELECT from `customers`
Detect User Agent (Device)
Claims: uuid, branch, ip, domain
Exp: +7 Days
POST /b2c/v1/auth/basic
B2C Colleague Authentication (Basic)
این اندپوینت جهت ورود همکاران و آژانسهای طرف قرارداد (Colleagues) طراحی شده است.
سیستم با دریافت نام کاربری و رمز عبور، اعتبار حساب را در جدول colleague_auth بررسی کرده و در صورت فعال بودن حساب و عدم انقضای قرارداد، یک توکن JWT صادر میکند. همچنین جزئیات سقف اعتبار و اطلاعات تماس همکار نیز بازگردانده میشود.
Colleague Login
/b2c/v1/auth/basicHeaders (الزامی)
| Header Name | Description |
|---|---|
| Domain | دامنه سایت جهت ثبت در توکن (iss/aud). |
Body Parameters (الزامی)
| Parameter | Type | Description |
|---|---|---|
| username | String | نام کاربری همکار. |
| password | String | رمز عبور. |
| branch | Integer | شناسه شعبه فعال (Office ID). |
Response Structure (Success)
| Key | Description |
|---|---|
| access_token | توکن دسترسی JWT با طول عمر ۷ روز. |
| user.uuid | شناسه رکورد همکار در جدول colleagues. |
| user.role | نقش همکار:
|
| user.ceiling | اطلاعات سقف اعتباری:
|
| user.data | آبجکت جزئیات شامل نام آژانس، لوگو، تلفن و کدملی صاحب حساب. |
Example Responses
۱. موفقیت آمیز (Login Success):
{
"user": {
"uuid": 105,
"from": "colleague",
"type": "agency",
"role": "partner",
"data": {
"id": 88,
"title": {
"fa": "آژانس مسافرتی آسمان",
"en": "Aseman Travel Agency"
},
"subtitle": {
"fa": "user123 - علی رضایی",
"en": "Aseman Travel Agency"
},
"additional": false,
"avatar": "logo_105.jpg",
"sex": "male",
"first_name": "Ali",
"last_name": "Rezaei",
"first_name_fa": "علی",
"last_name_fa": "رضایی",
"mobile": "09121111111",
"phone": "02188888888",
"national_code": "0055555555"
},
"level": 2,
"ceiling": {
"amount": 50000000,
"deadline": 1
},
"expired_at": null
},
"access_token": "eyJ0eXAiOiJjb2xsZWFndWUiLCJhbGciOiJIUzI1NiJ9..."
}
۲. خطا - اطلاعات نادرست (Invalid Credentials):
{
"error": {
"type": "username",
"message": "اطلاعات وارد شده همخوانی ندارد."
}
}
۳. خطا - حساب غیرفعال (Inactive Account):
{
"error": {
"type": "username",
"message": "وضعیت شما بصورت غیرفعال می باشد."
}
}
Technical Logic
Match Username & Branch
Check
expired_at > Now (or Null)Hash::check(input, db_hash)Dispatch `SystemLog` to
snailJob queue (10min delay)Type:
colleagueClaims: uuid, brn, uip, brw
POST /b2c/v1/get_country
Get Nationalities List
این اندپوینت لیست کشورهای فعال که دارای عنوان «ملیت» (Nationality) تعریف شده هستند را باز میگرداند.
از این دادهها معمولاً در فرمهای ورود اطلاعات مسافر جهت انتخاب ملیت/تابعیت (مثلاً: ایرانی، آلمانی و...) استفاده میشود. لیست خروجی تنها شامل رکوردهایی است که وضعیت آنها فعال (`status = 1`) باشد.
Fetch Country/Nationality Data
/b2c/v1/get_countryParameters
این درخواست نیاز به پارامتر ورودی (Body Parameters) ندارد.
Response Structure
| Key | Type | Description |
|---|---|---|
| status | Boolean | وضعیت درخواست (true). |
| data.countries | Array | آرایهای از آبجکتهای کشور/ملیت. |
| ↳ id | Integer | شناسه عددی کشور در دیتابیس. |
| ↳ iso | String | کد دو حرفی ایزو کشور (مثلاً IR, US). |
| ↳ fa_nationality | String | عنوان فارسی ملیت (مثلاً: ایرانی). |
| ↳ en_nationality | String | عنوان انگلیسی ملیت (مثلاً: Iranian). |
Example Responses
۱. موفقیت آمیز (200 OK):
{
"status": true,
"time": 1702138899,
"data": {
"countries": [
{
"id": 105,
"iso": "IR",
"fa_nationality": "ایرانی",
"en_nationality": "Iranian"
},
{
"id": 220,
"iso": "TR",
"fa_nationality": "ترک",
"en_nationality": "Turkish"
},
{
"id": 68,
"iso": "DE",
"fa_nationality": "آلمانی",
"en_nationality": "German"
}
]
}
}
۲. خطای سرور (400 Bad Request):
{
"status": false,
"error": "Database connection error..."
}
Technical Logic
WHERE status = 1AND fa_nationality IS NOT NULLid, iso, fa_nationality, en_nationality
GET /b2c/v1/user/passengers
Get User Passengers List
این اندپوینت لیست تمامی مسافران مرتبط با حساب کاربری فعلی را باز میگرداند.
سیستم لیست مسافران را از دو منبع استخراج میکند:
- سابقه خرید (History): افرادی که کاربر لاگین شده قبلاً در فاکتورهای خود (`factors`) برای آنها بلیط (`factor_items`) تهیه کرده است (به جز خود کاربر).
- زیرمجموعهها (Relationship): افرادی که در پروفایل کاربر به عنوان همراه یا خانواده تعریف شدهاند (`relationship = user_id`).
Fetch Passengers
/b2c/v1/user/passengersHeaders (الزامی)
| Header Name | Description |
|---|---|
| Authorization | توکن احراز هویت (Bearer Token). |
| Domain | دامنه سایت. |
Response Structure
| Key | Type | Description |
|---|---|---|
| status | Boolean | وضعیت درخواست. |
| time | Timestamp | زمان سرور. |
| data | Array[Object] | لیست کامل آبجکتهای مسافران (از جدول customers). |
| ↳ id | Integer | شناسه مسافر. |
| ↳ first_name / last_name | String | نام و نام خانوادگی. |
| ↳ national_code | String | کد ملی. |
| ↳ passport_number | String | شماره پاسپورت (در صورت وجود). |
| ↳ gender | Boolean/Int | جنسیت (معمولاً 1: مرد، 0: زن). |
| ↳ birthday | String | تاریخ تولد. |
Example Responses
۱. موفقیت آمیز (200 OK):
{
"status": true,
"time": 1702145000,
"data": [
{
"id": 501,
"first_name": "Saeed",
"last_name": "Mohammadi",
"first_name_fa": "سعید",
"last_name_fa": "محمدی",
"national_code": "0077777777",
"gender": 1,
"birthday": "1990-05-20",
"passport_number": "A12345678",
"relationship": 105,
"created_at": "2023-01-01 12:00:00"
},
{
"id": 890,
"first_name": "Sara",
"last_name": "Tehrani",
"first_name_fa": "سارا",
"last_name_fa": "تهرانی",
"national_code": "0088888888",
"gender": 0,
"birthday": "1995-10-10",
"passport_number": null,
"relationship": null,
"note": "Fetched from history"
}
]
}
Technical Logic
Get
operator->id from JWTFind `customer_id` in `factor_items` where:
1. Invoice owner is User (`factors.customer == id`)
2. Passenger is NOT User (`customer_id != id`)
SELECT * FROM customers WHERE:Condition A:
relationship == user_idOR
Condition B:
id IN [History IDs]GET /b2c/v1/office/users
Get Office/Company Users
این اندپوینت جهت دریافت لیست تمامی کاربران (Users) تعریف شده در زیرمجموعه یک شرکت یا آژانس (Colleague) استفاده میشود.
نکته مهم: دسترسی به این متد محدود است و تنها کاربرانی که سطح دسترسی آنها مدیریت (Management) باشد مجاز به فراخوانی آن هستند. در غیر این صورت خطای 406 بازگردانده میشود.
Fetch Sub-Users
/b2c/v1/office/usersHeaders (الزامی)
| Header Name | Description |
|---|---|
| Authorization | توکن احراز هویت (Bearer Token) کاربر مدیر. |
Response Structure (Success)
| Key | Type | Description |
|---|---|---|
| items | Array | لیست کاربران زیرمجموعه. |
| ↳ id | Integer | شناسه کاربر. |
| ↳ username | String | نام کاربری جهت ورود. |
| ↳ category | String | دستهبندی نقش کاربر (مثلاً management, sales). |
| ↳ level | Integer | سطح دسترسی عددی. |
| ↳ ceiling | Object | تنظیمات سقف اعتبار (amount: مبلغ, deadline: مهلت). |
| ↳ status | Integer | وضعیت کاربر (1: فعال، 0: غیرفعال). |
| meta.timestamp | Timestamp | زمان تولید پاسخ. |
Example Responses
۱. موفقیت آمیز (مدیر):
{
"items": [
{
"id": 10,
"colleague": 55,
"username": "admin_agency",
"category": "management",
"level": 1,
"ceiling": {
"amount": false,
"deadline": false
},
"expired_at": false,
"created_at": "2023-01-01 10:00:00",
"updated_at": "2023-05-20 14:30:00",
"status": 1
},
{
"id": 12,
"colleague": 55,
"username": "sales_counter",
"category": "sales",
"level": 2,
"ceiling": {
"amount": 5000000,
"deadline": 1
},
"expired_at": "2024-12-29",
"created_at": "2023-02-15 09:00:00",
"updated_at": "2023-02-15 09:00:00",
"status": 1
}
],
"meta": {
"timestamp": 1702150000
}
}
۲. خطا - عدم دسترسی (کاربر عادی):
{
"error": {
"code": 1000,
"message": "دسترسی فقط برای سطح مدیریت"
},
"meta": {
"timestamp": 1702150005
}
}
Technical Logic
Query `colleague_auth`
Where `colleague` == Operator's Colleague ID
"Access Denied"
Format Dates, Check Ceilings, Enrich Colleague Info (if applicable)
GET /b2c/v1/cost-center/list/form
Get Cost Centers List (Form)
این اندپوینت جهت دریافت لیست مراکز هزینه (Cost Centers) برای فرمها استفاده میشود.
محدودیت دسترسی: این سرویس صرفاً مخصوص کاربران B2B (همکاران) است که شرکت آنها در سطح هلدینگ (Category 7) تعریف شده باشد. هلدینگها از این طریق لیست زیرمجموعههای خود (که فیلد relationship آنها برابر با شناسه هلدینگ است) را دریافت میکنند.
Fetch Cost Centers
/b2c/v1/cost-center/list/formHeaders (الزامی)
| Header Name | Description |
|---|---|
| Authorization | توکن احراز هویت (Bearer Token). |
Response Structure (Success)
| Key | Type | Description |
|---|---|---|
| items | Array | لیست مراکز هزینه (زیرمجموعههای هلدینگ). |
| ↳ id | Integer | شناسه مرکز هزینه (Colleague ID). |
| ↳ title | Object | عنوان نمایشی مرکز هزینه. |
| ↳ title.fa | String | عنوان فارسی (ترکیب نام آفیس + نام و نام خانوادگی مسئول). |
| ↳ title.en | String | عنوان انگلیسی آفیس. |
| meta.timestamp | Timestamp | زمان تولید پاسخ. |
Example Responses
۱. موفقیت آمیز (کاربر هلدینگ):
{
"items": [
{
"id": 205,
"title": {
"fa": "دپارتمان مالی - علی رضایی",
"en": "Finance Department"
}
},
{
"id": 208,
"title": {
"fa": "شعبه شمال تهران",
"en": "North Branch"
}
}
],
"meta": {
"timestamp": 1702155000
}
}
۲. خطا - عدم دسترسی (کاربر معمولی یا شرکت غیر هلدینگ):
{
"error": {
"code": 1000,
"message": "دسترسی فقط برای همکاران سطح هلدینگ امکانپذیر است."
},
"meta": {
"timestamp": 1702155005
}
}
Technical Logic
is 'colleague' OR 'b2b'?
Get operator's colleague info.
Condition:
category == 7 (Holding)"Access Denied"
SELECT * FROM colleaguesWHERE
relationship == holding_id"Not a Holding Company"
Generate FA Title: Office + (First Name + Last Name)
GET /b2c/v1/credit-card
Get Credit Card Detail
این اندپوینت برای دریافت اطلاعات یک کارت اعتباری خاص استفاده میشود.
ویژگی امنیتی (Security Masking): سیستم به طور خودکار بررسی میکند که آیا کاربر درخواستدهنده (`operator`) همان مالک کارت (`passenger_id`) است یا خیر:
- اگر مالک باشد: اطلاعات کامل شامل `cvv2`، تاریخ انقضا و شماره کامل کارت نمایش داده میشود.
- اگر مالک نباشد: شماره کارت به صورت ماسک شده (مثلاً `6037****1234`) برمیگردد و فیلدهای حساس (`cvv2`, `expire_date`) حذف میشوند.
Fetch Credit Card
/b2c/v1/credit-cardHeaders (الزامی)
| Header Name | Description |
|---|---|
| Authorization | توکن احراز هویت (Bearer Token). |
Query Parameters (فیلترها)
| Parameter | Type | Description |
|---|---|---|
| id | Integer | (اختیاری) جستجو بر اساس شناسه کارت در سیستم. |
| passenger_id | Integer | (اختیاری) جستجو بر اساس شناسه مسافر (مالک کارت). |
Response Structure
| Key | Type | Description |
|---|---|---|
| payload.id | Integer | شناسه رکورد کارت. |
| payload.status | Integer | وضعیت کارت (معمولاً 1 فعال). |
| payload.card.number | String | شماره کارت (کامل یا ماسک شده بسته به مالکیت). |
| payload.card.withdrawal_limit | Integer/Bool | سقف برداشت (در صورت وجود). |
| payload.card.blocked_amount | Integer/Bool | مبلغ بلوکه شده. |
| payload.card.cvv2 | String | (شرطی) فقط اگر کاربر لاگین شده مالک کارت باشد ارسال میشود. |
| payload.card.expire_date | String | (شرطی) فقط اگر کاربر لاگین شده مالک کارت باشد ارسال میشود. |
| payload.passenger | Object | اطلاعات صاحب کارت. |
Example Responses
۱. موفقیت آمیز - مالک کارت (نمایش کامل):
{
"payload": {
"id": 101,
"status": 1,
"card": {
"number": "6037991122334455",
"withdrawal_limit": 50000000,
"blocked_amount": 0,
"cvv2": "1234", // موجود است
"expire_date": "04/05" // موجود است
},
"passenger": {
"id": 55,
"first_name": "علی",
"last_name": "محمدی"
}
},
"meta": {
"timestamp": 1702160000
}
}
۲. موفقیت آمیز - غیر مالک (اطلاعات امنیتی مخفی):
{
"payload": {
"id": 101,
"status": 1,
"card": {
"number": "6037********4455", // ماسک شده
"withdrawal_limit": false,
"blocked_amount": false
// فیلدهای cvv2 و expire_date وجود ندارند
},
"passenger": {
"id": 55,
"first_name": "علی",
"last_name": "محمدی"
}
},
"meta": {
"timestamp": 1702160010
}
}
۳. خطا - یافت نشد:
{
"error": {
"code": 1000,
"message": "کارت اعتباری پیدا نشد."
},
"meta": {
"timestamp": 1702160020
}
}
Technical Logic
Join `credit_cards` with `customers`
Filter by `id` OR `passenger_id` from Request
request->operator->id == card->passenger_id"Card Not Found"
Include `cvv2`, `expire_date`
Real Card Number
Exclude Sensitive Fields
Mask Number:
1234********5678POST /b2c/v1/media/upload/s3
Upload Media to S3
این اندپوینت جهت آپلود فایلها (تصاویر، اسناد و ...) بر روی فضای ذخیرهسازی ابری (S3 Compatible / Liara) استفاده میشود.
انعطافپذیری: کلاینت میتواند محدودیتهای حجم، فرمت فایل و مسیر ذخیرهسازی را در درخواست تعیین کند. در غیر این صورت، مقادیر پیشفرض سیستم اعمال میشوند.
Upload File
/b2c/v1/media/upload/s3Body Parameters (Multipart/Form-Data)
| Parameter | Type | Required | Description | Default / Note |
|---|---|---|---|---|
| file | File | Yes | فایل مورد نظر برای آپلود. | - |
| branch | String | No* | شناسه برنچ (برای ساختار پوشهبندی پیشفرض). | در مسیر پیشفرض استفاده میشود. |
| type | String | No* | نوع فایل (مثلاً avatar, ticket) برای پوشهبندی. | در مسیر پیشفرض استفاده میشود. |
| name | String | No | نام دلخواه برای ذخیره فایل (بدون پسوند). | Timestamp + Random ID |
| size | Integer | No | حداکثر حجم مجاز به کیلوبایت. | 5120 (5MB) |
| mimes | String | No | فرمتهای مجاز (با کاما جدا شوند). | jpeg,png,jpg,gif,pdf,webp |
| path | String | No | مسیر کامل ذخیرهسازی (Folder Path). | b2c/uploads/{branch}/{type}/{Y}/{m}/ |
Response Structure
| Key | Type | Description |
|---|---|---|
| status | Boolean | وضعیت عملیات (true موفق، false ناموفق). |
| file | String | مسیر کامل (Key) فایل آپلود شده در باکت S3. |
Example Responses
۱. آپلود موفق (Success - 201):
{
"status": true,
"file": "b2c/uploads/1001/avatar/2023/12/custom-name.jpg"
}
۲. خطای اعتبارسنجی (Validation Error - 422):
{
"message": "The file must be a file of type: jpeg, png, jpg, gif, pdf, webp.",
"errors": {
"file": [
"The file must be a file of type: jpeg, png, jpg, gif, pdf, webp."
]
}
}
۳. خطای سرور/آپلود (Error - 400):
{
"status": false,
"time": 1702165000
}
Technical Logic
Set Size: input OR 5120KB
Set Mimes: input OR defaults
Set Path: input OR
b2c/uploads/...If `name` exists → Use it
Else →
Y-m-d-His-microtimeValidation Exception
Disk: 'liara'
put(path + filename, contents){"status": true, "file": "path..."}
{"status": false}
GET /b2c/v1/base/data
Get Base Data (Cities, Trains, Configs)
این اندپوینت مرکزی برای دریافت دادههای پایه (Base Data) و لیستهای انتخابی (Select Options) در سیستم استفاده میشود.
عملکرد داینامیک: خروجی این سرویس کاملاً وابسته به پارامتر ورودی subject است. این متد برای پر کردن لیست شهرها، ایستگاههای قطار، فرودگاهها و تنظیمات هتل کاربرد دارد.
Fetch Base Data
/b2c/v1/base/dataQuery Parameters (پارامترهای ورودی)
| Parameter | Type | Required | Description |
|---|---|---|---|
| subject | String | Yes* | نوع داده درخواستی. مقادیر مجاز:
|
| search | String | No | عبارت جستجو (برای شهرها، ایستگاهها و شرکتها). |
| state | Integer | No | شناسه استان (جهت فیلتر کردن شهرها یا ایستگاهها بر اساس استان). |
| type | String | No | فقط برای subject=cities کاربرد دارد.اگر مقدار train ارسال شود، فقط شهرهایی که دارای ایستگاه قطار هستند بازگردانده میشوند. |
| action | String | No | حالت خاص: اگر مقدار airports باشد، فرمت خروجی جیسون تغییر میکند. |
| route | Integer | No | فقط برای فرودگاهها (1 یا 2) - جهت سورت یا فیلتر خاص (در کد فعلی منطق مشابه است). |
Response Structure
ساختار پاسخ بسته به subject متفاوت است، اما به طور کلی در فرمت زیر است:
{
"items": [ ...Array of Data... ],
"meta": {
"timestamp": 1702123456
}
}
Examples per Subject (مثالها بر اساس موضوع)
۱. شهرها (subject=cities):
{
"items": [
{
"id": 10,
"title": { "fa": "تهران", "en": "Tehran" },
"category": {
"id": 5,
"title": { "fa": "تهران", "en": "Tehran" } // نام استان
}
}
],
"meta": { ... }
}
۲. انواع قطار (subject=train_types):
{
"items": [
{
"id": 1,
"title": { "fa": "4 ستاره اتوبوسی", "en": false },
"details": {
"type": "bus",
"star": 4
}
}
],
"meta": { ... }
}
۳. نرخ/دید اتاق (subject=room_rate / room_view):
{
"items": [
{
"title": { "fa": "برد", "en": "Board" },
"status": "1",
"selected": "0"
}
],
"meta": { ... }
}
۴. حالت خاص فرودگاه (action=airports):
اگر پارامتر action=airports ارسال شود، ساختار پاسخ متفاوت است:
{
"status": true,
"time": 1702123456,
"data": {
"titles": [
{
"id": 1,
"iata": "MHD",
"title": "Mashhad",
"title_fa": "مشهد",
"country": 1,
"group_by": "ایران",
"state": "خراسان رضوی"
...
}
]
}
}
Technical Logic
If type='train' → Filter IDs from `train_stations`
Search/State Filter
Query `train_companies`, `types`, or `stations`
Decode JSON from `hotel_titles` table
Join Countries & States
{"status": true, "data": ...}
{"items": [...], "meta": ...}
GET /b2c/v1/base/accommodations/list
Search Accommodations & Cities
این اندپوینت برای "Autocomplete" یا جستجوی اولیه در بخش هتل استفاده میشود.
ورودی کاربر را گرفته و به صورت همزمان در لیست شهرها و اقامتگاهها (هتل، آپارتمان و...) جستجو میکند.
Get Accommodation List
/b2c/v1/base/accommodations/listQuery Parameters (پارامترهای ورودی)
| Parameter | Type | Required | Description |
|---|---|---|---|
| search | String | No | عبارت مورد نظر برای جستجو. در نام شهر، نام هتل، آدرس، نام استان و نام کشور (فارسی و انگلیسی) جستجو میشود. |
Business Logic (منطق تجاری)
- جستجوی شهرها (Cities Search):
- جدول
citiesبا جداولstatesوcountriesجوین میشود. - اگر
searchارسال شده باشد، در نامهای فارسی و انگلیسی (شهر، استان، کشور) جستجو میکند. - نتایج بر اساس
priorityمرتب شده و حداکثر ۳۰ مورد بازگردانده میشود.
- جدول
- جستجوی اقامتگاهها (Accommodations Search):
- شرط اجرا: تنها در صورتی اجرا میشود که پارامتر
searchخالی نباشد. - فیلتر: فقط اقامتگاههای فعال (
status=1). - دامنه جستجو: عنوان (فارسی/انگلیسی)، آدرس، و نام مکانهای جغرافیایی مرتبط.
- بهینهسازی (Redis): ابتدا شناسه (ID) هتلها پیدا میشود. سپس سیستم چک میکند آیا اطلاعات کامل هتل در Redis (کلید:
accommodations:{id}) وجود دارد یا خیر.- اگر در کش نباشد: تابع
StaticController::getAccommodationفراخوانی شده و نتیجه در Redis ذخیره میشود. - اگر در کش باشد: اطلاعات از Redis خوانده میشود.
- اگر در کش نباشد: تابع
- ترجمه نوع: نوع اقامتگاه (مثلا hotel) توسط تابع
getTitleTypeبه فارسی (هتل) تبدیل میشود.
- شرط اجرا: تنها در صورتی اجرا میشود که پارامتر
Response Structure
{
"status": true,
"time": 1702123456,
"data": {
"cities": [ ...Array of Cities OR false... ],
"accommodation": [ ...Array of Hotels OR false... ]
}
}
1. ساختار آبجکت شهر (Inside `cities` array)
{
"id": 10,
"title": {
"fa": "تهران",
"en": "Tehran"
},
"country": {
"fa": "ایران",
"en": "Iran"
},
"state": {
"fa": "تهران",
"en": "Tehran"
},
"category": {
"title": "شهر",
"subtitle": false
}
}
2. ساختار آبجکت اقامتگاه (Inside `accommodation` array)
{
"id": 505,
"en_title": "Espinas Palace",
"fa_title": "هتل اسپیناس پالاس",
"type": "هتل", // Translated value
"category": {
"title": "هتل",
"subtitle": false
},
"order": 1,
// ... سایر فیلدهای بازگردانده شده از Redis/StaticController ...
}
Accommodation Types Mapping
مقادیر فیلد type در خروجی اقامتگاهها به صورت خودکار به فارسی ترجمه میشوند:
| Database Value (key) | Output Value (fa) |
|---|---|
| hotel | هتل |
| apartment | آپارتمان |
| suite | سویت |
| house | خانه |
| villa | ویلا |
| traditional | بومگردی |
| motel | موتل |
| guesthouse | میهمانسرا |
GET /b2c/v1/base/suggestions/items/{type}
Get Suggestions & Featured Items
این اندپوینت لیستی از آیتمهای پیشنهادی (Featured) را برای نمایش در صفحه اصلی یا لندینگ پیجها بازمیگرداند.
نکته مهم: طبق کد فعلی، دادههای این سرویس به صورت Hardcoded (ثابت) در کنترلر تعریف شدهاند و برای شعب خاص (Branch IDs: 2, 4) شخصیسازی شدهاند.
Fetch Suggestion Items
/b2c/v1/base/suggestions/items/{type}Parameters (پارامترها)
| Type | Parameter | Required | Description |
|---|---|---|---|
| Path | {type} | Yes | نوع پیشنهاد مورد نظر:
|
| Query | branch | Yes* | شناسه شعبه/سایت. (در کد فعلی فقط برای مقادیر 2 و 4 خروجی تعریف شده است). |
Response Structure
{
"items": [
// ... Array of Suggestion Objects ...
],
"meta": {
"timestamp": 1702123456
}
}
Item Object Examples (نمونه خروجی آیتمها)
ساختار آبجکتهای داخل آرایه items بسته به type درخواستی متفاوت است:
۱. نوع تور (type = tour)
{
"type": "tour",
"vehicle": "train", // یا aircraft
"cover": "https://storage.../image.jpg",
"link": "/search/route-accommodation?origin=37&destination=396...", // لینک فرانتاند
"title": {
"fa": "تور چهارشب فول برد",
"en": "4 nights full boards"
},
"accommodation": {
"title": {
"fa": "هتل آوازه مشهد",
"en": "Avazeh Hotel"
},
"star": 2
},
"origin": "اصفهان",
"destination": "مشهد",
"price": 96000000,
"nights": 4
}
۲. نوع بلیط (type = train / aircraft)
{
"type": "ticket",
"vehicle": "aircraft", // یا train
"link": "/search/flights?origin=IFN&destination=MHD",
"title": { // ممکن است در برخی موارد ارسال نشود
"fa": "بلیت مشهد قطار",
"en": "Mashhad ticket"
},
"origin": "اصفهان",
"destination": "مشهد",
"price": 33990000
}
۳. نوع اقامتگاه (type = accommodation)
{
"type": "accommodation",
"cover": "https://storage.../hotel.jpg",
"link": "/search/accommodations?destination=335&action=accommodation",
"title": {
"fa": "اتاق دوتخته فول برد",
"en": ""
},
"accommodation": {
"title": {
"fa": "هتل آوازه مشهد",
"en": "Avazeh Hotel"
},
"star": 2
},
"destination": "مشهد",
"price": 12000000
}
Logic & Constraints
- وابستگی به Branch: سیستم ابتدا پارامتر
branchرا چک میکند. اگر شعبه تعریف نشده باشد (چیزی غیر از 2 یا 4)، آرایهitemsخالی برمیگردد. - لینکدهی (Links): فیلد
linkحاوی مسیر نسبی (Relative Path) برای هدایت کاربر در سمت فرانتاند است (مثلاً به صفحه جستجو با فیلترهای از پیش تعیین شده). - دادههای استاتیک: قیمتها، تصاویر و عناوین در حال حاضر مستقیماً در کد PHP نوشته شدهاند و از دیتابیس خوانده نمیشوند.
GET /b2c/v1/trade/list
User Trade History (List Orders)
این اندپوینت لیست تمامی سفارشات و فاکتورهای کاربر (B2C) یا همکار (B2B) را بازمیگرداند.
اطلاعات مالی، وضعیت پرداخت و جزئیات محصول (پرواز، هتل و...) برای هر سفارش محاسبه و ارسال میشود.
List User Trades
/b2c/v1/trade/listauthWithJwt (Required)Business Logic (منطق تجاری)
- شناسایی کاربر (User Identification):
- بر اساس توکن JWT، نوع کاربر (
group) و اطلاعات کاربر (operator) استخراج میشود. - اگر گروه کاربر
b2bیاcolleagueباشد، فیلتر بر اساسcolleague_authانجام میشود. - در غیر این صورت (مسافر عادی)، فیلتر بر اساس ستون
customerانجام میشود.
- بر اساس توکن JWT، نوع کاربر (
- فیلتر سفارشات (Query Factors):
- سفارشاتی که وضعیت (status) آنها 2 یا 5 باشد، از لیست حذف میشوند (
whereNotIn). - لیست بر اساس شناسه (ID) به صورت نزولی (جدیدترین به قدیمیترین) مرتب میشود.
- سفارشاتی که وضعیت (status) آنها 2 یا 5 باشد، از لیست حذف میشوند (
- غنیسازی دادهها (Data Enrichment Loop):
برای هر فاکتور یافت شده عملیات زیر انجام میشود:- عنوان (Redis): تلاش برای دریافت عنوان فارسی سفارش از کلید Redis:
reference:{id}:information:title:fa. - نوع محصول: استخراج
productوbyproductاز جدولfactor_items. - محاسبات مالی: اگر عنوان در Redis موجود باشد، تابع
ApiTradeController::financialفراخوانی شده تا وضعیت مالی دقیق (فروش، جریمه، پرداختی و مانده) محاسبه شود.
- عنوان (Redis): تلاش برای دریافت عنوان فارسی سفارش از کلید Redis:
Response Structure
{
"status": true,
"time": 1702123456,
"data": [
{
"method": "flight", // نوع محصول (flight, train, hotel, ...)
"submethod": false, // زیر مجموعه (در صورت وجود)
"title": "پرواز تهران به مشهد - ماهان", // عنوان فارسی (از کش)
"details": {
// تمام فیلدهای سفارش (id, created_at, status, ...)
"id": 10250,
"status": 1,
"created_at": "2024-01-01 12:00:00",
"financial": {
"sum_sale": 15000000, // مبلغ کل فروش
"sum_return_sale": 0, // مبلغ استرداد شده به مشتری
"sum_penalty_sale": 0, // جریمه کنسلی کسر شده
"sum_receive": 15000000, // مبلغ دریافت شده از مشتری
"balance_received": 0 // مانده حساب (بدهی/بستانکاری)
}
}
},
{
"method": "hotel",
"submethod": false,
"title": 10249, // حالت Fallback: اگر عنوان در کش نباشد، ID برمیگردد
"details": false // در صورت نبودن در کش، جزئیات مالی محاسبه نمیشود
}
]
}
Financial Fields Description
| Field | Description |
|---|---|
| sum_sale | مبلغ نهایی فاکتور (قیمت محصول). |
| sum_receive | مجموع مبالغی که کاربر تاکنون برای این سفارش پرداخت کرده است. |
| sum_return_sale | مبلغی که بابت استرداد (کنسلی) باید به کاربر برگشت داده شود (یا شده است). |
| sum_penalty_sale | مبلغ جریمه کنسلی. |
| balance_received | تراز مالی سفارش (اگر 0 باشد یعنی تسویه شده، مثبت یا منفی نشاندهنده بدهی یا بستانکاری است). |
POST /b2c/v1/trade/store
Store Trade / Payment Initiation
این اندپوینت برای ثبت نهایی درخواست و انجام عملیات مالی استفاده میشود.
سیستم ابتدا وضعیت و قیمت محصول را چک میکند. اگر تغییری نباشد، بسته به انتخاب کاربر (کیف پول یا درگاه)، یا تراکنش را آنی انجام میدهد و یا لینک پرداخت تولید میکند.
Submit Order & Pay
/b2c/v1/trade/storeauthWithJwt (Required)Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| branch | Integer | Yes | شناسه شعبه (Branch ID). |
| group | String | Yes | نوع گروه کاربری: b2c (مسافر) یا colleague (همکار/B2B). |
| requests[pay] | Object | Yes | آبجکت تنظیمات پرداخت (جزئیات در جدول زیر). |
| ...product_data | Mixed | Yes | سایر اطلاعات مربوط به محصول (لیست مسافران، شناسه پرواز/هتل و...) که برای رزرو ضروری است. (این اطلاعات مستقیماً به سرویس رزرو ارسال میشود). |
ساختار `requests[pay]`
| Key | Type | Description |
|---|---|---|
| amount | Integer | مبلغ قابل پرداخت (ریال). |
| drive | String | روش پرداخت:
|
| return | String | لینک بازگشت (Callback URL) پس از پرداخت موفق در درگاه بانکی. |
Business Logic Scenarios
۱. سناریوی تغییر قیمت/وضعیت (Price/Status Changed)
قبل از هر اقدامی، متد statusItemProgress اجرا میشود. اگر قیمت یا ظرفیت محصول تغییر کرده باشد:
- عملیات متوقف میشود.
- کد وضعیت 201 برمیگردد.
- در پاسخ،
status_data['changed'] = trueخواهد بود. کلاینت باید قیمت جدید را به کاربر نمایش دهد.
۲. پرداخت با کیف پول (Wallet Payment)
اگر drive = 'wallet' باشد:
- بررسی موجودی: سیستم فرمول زیر را چک میکند:
(Wallet Credit - Wallet Debit) + Ceiling Amount >= Pay Amount - خطای موجودی: اگر موجودی کافی نباشد، خطای 402 بازگردانده میشود.
- موفقیت: مبلغ از حساب کسر شده، رکورد در جدول
walletثبت میشود و خرید نهایی میشود (Reference ID تولید میشود).
۳. پرداخت آنلاین (Online Gateway)
اگر drive != 'wallet' باشد:
- متد
paymentInvoiceOperationاجرا میشود. - یک رکورد در جدول
payment_gatewayبا یک Slug یکتا ایجاد میشود. - لینک پرداخت به فرمت
https://{short_domain}/p/{slug}تولید و برگردانده میشود. - یک رزرو موقت (Temporary Reservation) ایجاد میشود.
Response Examples
حالت اول: تولید موفق لینک پرداخت (Online)
{
"payload": {
"payment_link": "https://airplus.app/p/a1b2c3d4",
"temporary_id": 12345, // شناسه رزرو موقت
"status_data": {
"changed": false // یعنی قیمت تغییر نکرده است
}
},
"meta": {
"timestamp": 1702123456
}
}
حالت دوم: خرید موفق با کیف پول (Wallet)
{
"payload": {
"payment_id": 9876,
"reference_id": 500120 // شماره فاکتور نهایی (رزرو قطعی)
},
"meta": {
"timestamp": 1702123456
}
}
حالت سوم: تغییر قیمت (نیاز به تایید کاربر)
{
"payload": {
"payment_link": null,
"temporary_id": false,
"status_data": {
"changed": true,
"message": "قیمت پرواز تغییر کرده است...",
"new_price": 15500000
}
},
"meta": {
"timestamp": 1702123456
}
}
حالت چهارم: خطای موجودی ناکافی
{
"error": {
"code": 1001,
"message": "اعتبار شما برای این خرید کافی نمی باشد. لطفا قبل از انجام این عملیات موجودی حساب خود را افزایش دهید."
},
"meta": {
"timestamp": 1702123456
}
}
POST /b2c/v1/trade/completion
Trade Completion & Issuance
این اندپوینت برای تکمیل فرآیند خرید استفاده میشود.
زمانی که پرداخت (آنلاین یا کیف پول) تایید شد، این متد برای صدور فاکتور (Factor)، بروزرسانی اطلاعات مسافران، ثبت تراکنشهای مالی (Pledgers/Pays) و ارسال SMS نهایی به کاربر فراخوانی میگردد.
Finalize Order
/b2c/v1/trade/completionauthWithJwt (Required)Request Body Parameters
این متد یک آبجکت JSON پیچیده شامل اطلاعات پرداخت و جزئیات درخواست را دریافت میکند.
| Parameter | Type | Required | Description |
|---|---|---|---|
| branch | Integer | Yes | شناسه شعبه (Branch ID) برای تعیین تنظیمات مالی و اعتباری. |
| payment | Object | Yes | اطلاعات مربوط به پرداخت انجام شده (مبلغ، درگاه و...). |
| request | Object | Yes | شامل جزئیات اصلی سفارش:
|
Step-by-Step Logic Breakdown
۱. ایجاد فاکتور (Factor Creation)
یک رکورد در جدول factors ایجاد میشود:
- Serial: تولید سریال یکتا بر اساس شعبه.
- Slug: شناسه یکتای 8 کاراکتری برای پیگیری (Reference ID).
- Status: وضعیت پیشفرض
3(صادر شده/موفق) تنظیم میشود. - Colleague Auth: اگر کاربر B2B باشد، شناسه همکار ثبت میشود.
۲. مدیریت مسافران (Passengers Upsert)
سیستم روی آرایه request['passengers'] حلقه میزند:
- اطلاعات هویتی (نام، نام خانوادگی، کد ملی، پاسپورت، تاریخ انقضا و...) در جدول
customersبروزرسانی (Update) میشوند. - تاریخ تولد و انقضای پاسپورت استانداردسازی میشود (حذف کاراکترهای اضافی).
- رابطه (Relationship) بین مسافر اصلی و همراهان تنظیم میشود.
۳. مدیریت مالی و تعهدات (Pledgers & Pays)
این بخش مشخص میکند "چه کسی پول را میدهد":
- حالت B2B (همکار): اگر
pledgersارسال شده باشد، رکوردهایی در جدولpledgersوpays(نوع contract) ثبت میشود تا بدهی همکار مشخص شود. - حالت B2C (عادی): اگر متعهدی نباشد، خودِ اپراتور/کاربر جاری به عنوان متعهد (type='operator') در نظر گرفته میشود.
۴. پردازش آیتمها (Items Processing)
بر اساس نوع سرویس (aircraft, train, accommodation):
- Aircraft: وضعیت بلیط بررسی شده، سن مسافر (برای تشخیص Infant) محاسبه میشود و آیتم در
factor_itemsدرج میشود. - Hub Reservation: اگر سرویس از نوع
airplusHubباشد، درخواست به جدولhub_reservationاضافه شده و مبلغ خرید به عنوان Debit (بدهی) در جدولwalletشعبه ثبت میشود. - Train/Hotel: مشابه پرواز، اطلاعات رزرو پردازش و ذخیره میشوند.
- Temporary Update: وضعیت رزرو موقت (در جدول
temporary_reservations) به تکمیل شده تغییر میکند.
۵. ارسال اعلان (Notification)
اگر notices: true باشد:
- یک متن پیامک (Magic Text) به صورت تصادفی انتخاب میشود (مثلاً: "سفری هیجان انگیز").
- لینک فاکتور (
/f/{slug}) تولید میشود. - جاب
SendNotificationبه صفfastJobارسال میشود. - لاگ سیستمی در
snailJobثبت میگردد.
Response Examples
پاسخ موفق (Success - 200 OK)
{
"status": true,
"time": 1702123456,
"payment": {
"amount": 15000000,
"method": "wallet"
},
"reference_id": "8XKA9L2P", // شناسه فاکتور (Factor Slug)
"details": false // یا آرایهای از خطاها در صورت وجود مشکل جزئی
}
پاسخ موفق همراه با هشدار (Success with Warnings)
اگر خرید کلی انجام شود اما برخی آیتمها (مثلاً یکی از پروازها) رزرو نشوند:
{
"status": true,
"time": 1702123456,
"reference_id": "8XKA9L2P",
"details": [
{
"local_id": 101,
"message": "ظرفیت این کلاس پروازی تکمیل شده است."
}
]
}
پاسخ خطا (Failure)
{
"status": false,
"code": "1004-500",
"message": "1004-500 : خطای داخلی سرور در هنگام ثبت رکورد",
"trace": "..."
}
POST /b2c/v1/online/search/{type}
Search Flights & Trains
این اندپوینت برای جستجوی بلیطهای سفر استفاده میشود.
سیستم به صورت هوشمند بر اساس نوع درخواست (قطار یا پرواز)، ورودیها را اعتبارسنجی کرده، اطلاعات تکمیلی مبدا/مقصد را از دیتابیس یا کش Redis استخراج میکند و نتایج جستجو را از سرویسهای تجمیعکننده (Aggregators) بازمیگرداند.
Route Search
/b2c/v1/online/search/{type}Path Parameters
| Parameter | Type | Description |
|---|---|---|
| {type} | String | نوع وسیله نقلیه. مقادیر مجاز: train یا aircraft (یا هر مقدار غیر train برای پرواز). |
Request Body Parameters
پارامترهای مشترک (Common)
| Parameter | Type | Required | Description |
|---|---|---|---|
| branch | Integer | Yes | شناسه شعبه. |
| group | String | No | گروه کاربری (b2c, b2b, b2e, colleague). پیشفرض: b2c. |
| level | Integer | No | سطح دسترسی کاربر (پیشفرض: 1). |
اگر type = 'train' باشد:
| Parameter | Type | Required | Description |
|---|---|---|---|
| origin | Integer | Yes | شناسه شهر مبدا (City ID) از جدول cities. |
| destination | Integer | Yes | شناسه شهر مقصد (City ID) از جدول cities. |
| departure | Datetime | Yes | تاریخ رفت (فرمت استاندارد). |
| returning | Datetime | No | تاریخ برگشت. باید بعد از تاریخ رفت باشد. |
اگر type != 'train' (پرواز) باشد:
| Parameter | Type | Required | Description |
|---|---|---|---|
| origin | String | Yes | کد IATA فرودگاه مبدا (مانند MHD, THR). |
| destination | String | Yes | کد IATA فرودگاه مقصد. |
| departure_date | Date | Yes | تاریخ رفت. (توجه: نام پارامتر با قطار متفاوت است). |
| returning_date | Date | No | تاریخ برگشت. |
| only_charters | Boolean | No | فیلتر فقط پروازهای چارتر. |
Business Logic Details
۱. احراز هویت اختیاری (Optional Pipeline Auth)
حتی اگر روت عمومی باشد، کد چک میکند اگر هدر Authorization وجود داشته باشد، درخواست را از پایپلاین AuthWithJWT عبور میدهد. این کار برای اعمال تخفیفهای خاص یا دسترسیهای سطح کاربر (Level) انجام میشود.
۲. اعتبارسنجی قطار (Train Validation)
برای جستجوی قطار اعتبارسنجی سختگیرانهتری روی تاریخها انجام میشود:
- اگر فرمت
departureاشتباه باشد -> خطای 409 (کد 1000). - اگر تاریخ برگشت قبل از رفت باشد -> خطای 409.
۳. غنیسازی اطلاعات مبدا/مقصد (Metadata Enrichment)
سیستم قبل از پاسخدهی، اطلاعات کامل شهر یا فرودگاه را به پاسخ اضافه میکند:
- برای پرواز (String input): اطلاعات فرودگاه (IATA, Title, Country, State, City) را از جدول
airportsمیگیرد. برای سرعت بیشتر، این اطلاعات در Redis با کلیدairports:{IATA}کش میشوند. - برای قطار (Numeric input): اطلاعات شهر (نام فارسی/انگلیسی، استان) را از جدول
citiesجوین شده باstatesدریافت میکند.
۴. فراخوانی سرویس پایه (Base Service)
- برای قطار متد
BaseService::combineRouteفراخوانی میشود. - برای پرواز متد
LibBaseService::combineFlightفراخوانی میشود.
Response Examples
پاسخ جستجوی قطار (Train Response)
ساختار پاسخ قطار شامل payload و meta است.
{
"payload": [
// لیست قطارهای یافت شده از سرویس
{
"id": 105,
"price": 2500000,
"provider": "raja",
"capacity": 4
}
],
"meta": {
"search": {
"origin": {
"id": 37,
"title": {"fa": "اصفهان", "en": "Isfahan"},
"category": {"id": 5, "title": {"fa": "اصفهان", "en": "Isfahan"}}
},
"destination": {
"id": 396,
"title": {"fa": "مشهد", "en": "Mashhad"},
"category": {"id": 10, "title": {"fa": "خراسان رضوی", "en": "Razavi Khorasan"}}
},
"departure": "2025-12-10",
"returning": false
},
"timestamp": 1702123456
}
}
پاسخ جستجوی پرواز (Flight Response)
ساختار پاسخ پرواز کمی متفاوت است و مستقیماً آرایه برمیگرداند.
{
"search": {
"origin": {
"id": 1,
"iata": "MHD",
"title": "فرودگاه هاشمی نژاد",
"country": { "id": 100, "title_fa": "ایران" },
"city": { "id": 396, "title_fa": "مشهد" }
},
"destination": {
"id": 2,
"iata": "THR",
"title": "فرودگاه مهرآباد",
"city": { "id": 1, "title_fa": "تهران" }
},
"departure_date": "2025-12-10",
"returning_date": null
},
"data": [
// لیست پروازهای یافت شده
{
"flight_id": "WS-123",
"airline": "Mahan",
"price": 15000000
}
]
}
خطای اعتبارسنجی تاریخ (Train Error)
{
"error": {
"code": 1000,
"message": "تاریخ شروع و پایان جستجو معتبر نمی باشد"
}
}
POST /b2c/v1/online/{type}/lock
Lock Item (Pre-Booking)
این متد برای "قفل کردن" یا "اعتبارسنجی نهایی" یک آیتم انتخابی (مانند یک پرواز یا بلیط قطار) قبل از ورود اطلاعات مسافران استفاده میشود.
در این مرحله، سیستم با تامینکننده ارتباط برقرار کرده، تغییرات احتمالی قیمت را بررسی میکند و یک رکورد در جدول رزروهای موقت ایجاد میکند.
Lock Selection
/b2c/v1/online/{type}/lockauthWithJwtPath Parameters
| Parameter | Type | Description |
|---|---|---|
| {type} | String | نوع سرویس (مثلاً train, aircraft, hotel). اگرچه در بدنه کنترلر مستقیماً استفاده نشده، اما برای تفکیک منطقی روتها کاربرد دارد. |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| data | Object/Array | Yes | آبجکت کامل آیتم انتخاب شده که از خروجی جستجو (Search) دریافت شده است. این داده عیناً برای اعتبارسنجی به سرویس ارسال میشود. |
| branch | Integer | No* | شناسه شعبه. (معمولاً توسط میدلور authWithJwt به درخواست تزریق میشود، اما اگر کلاینت ارسال کند اولویت دارد). |
Business Logic Details
۱. احراز هویت و زمینه (Auth Context)
این متد توسط میدلور authWithJwt محافظت میشود. این میدلور اطلاعات کاربر (`operator`) و شعبه (`branch`) را به آبجکت $request تزریق میکند.
۲. فراخوانی سرویس پایه (BaseService Call)
متد BaseService::lockItemProgress با پارامترهای زیر فراخوانی میشود:
$request->data: اطلاعات آیتم انتخابی.false: پارامتر دوم (احتمالاً مربوط به force lock یا تنظیمات خاص که اینجا غیرفعال است).$request->get('branch'): زمینه شعبه برای قیمتگذاری.1: شناسه سیستمی یا نوع عامل (Hardcoded).
۳. ثبت در رزروهای موقت (Temporary Reservation)
نتیجهی عملیات به همراه اطلاعات ورودی در جدول temporary_reservations ذخیره میشود:
- operator: شناسه کاربر جاری (
$operator->id). - data: JSON ورودی (آنچه کاربر انتخاب کرده).
- result: JSON خروجی از سرویس (تاییدیه قفل و قیمت نهایی).
شناسه این رکورد (`LockId`) تولید شده و در پاسخ بازگردانده میشود. این شناسه کلید اصلی برای مرحله بعد (ثبت مسافر و پرداخت) است.
۴. مدیریت خطا (Exception Handling)
اگر سرویس خارجی خطا دهد یا آیتم موجود نباشد (Exception)، سیستم:
- کد وضعیت 400 (Bad Request) برمیگرداند.
- پیام خطا و Trace سیستم را در پاسخ قرار میدهد.
- اطلاعات تماس پشتیبانی (تلفن 021-91016838 و پنل Helpdesk) را به صورت خودکار به پاسخ اضافه میکند تا کلاینت بتواند به کاربر نمایش دهد.
Response Examples
مثال درخواست (Request)
{
"data": {
"flight_id": "WS-5050",
"provider_key": "some_encrypted_key_from_search_result",
"price": 1500000,
"class": "Economy"
}
}
پاسخ موفق (Success - 200 OK)
{
"status": true,
"time": 1702123456,
"lock_id": 9852,
"data": {
"status": "confirmed",
"new_price": 1500000,
"token": "reservation_token_12345",
"expiration": "20 mins"
}
}
نکته: مقدار lock_id (در این مثال 9852) باید توسط کلاینت ذخیره شده و در اندپوینت خرید (`trade/store`) ارسال شود.
پاسخ خطا (Error - 400 Bad Request)
{
"status": false,
"time": 1702123499,
"error": {
"code": 1002,
"message": "ظرفیت پرواز تکمیل شده است یا قیمت تغییر کرده است.",
"trace": [ ... ]
},
"support": {
"phone": "021-91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
POST /b2c/v1/online/{type}/penalty
Refund Penalty Calculation
این اندپوینت برای محاسبه جریمه استرداد رزرو یا بلیط آنلاین طراحی شده است.
سیستم با توجه به نوع سرویس ({type}) و شعبه ارسالکننده، میزان جریمه، مبلغ قابل استرداد و قوانین مربوط به لغو رزرو را محاسبه میکند.
Calculate Refund Penalty
/b2c/v1/online/{type}/penaltyPath Parameters
| Parameter | Type | Description |
|---|---|---|
| {type} | String | نوع سرویس برای محاسبه جریمه؛ یکی از مقادیر: aircraft, train, accommodation. |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| data | Object / Array | ✅ | دادههای مربوط به آیتم رزرو یا بلیط؛ شامل اطلاعات تاریخی، شماره بلیط، ارائهدهنده و اطلاعات سفر. |
| branch | Integer | ✅ | شناسه شعبه ارسالکننده درخواست برای محاسبه قوانین جریمه محلی. |
Response Structure
| Key | Description |
|---|---|
| items | خروجی اصلی محاسبه جریمه از کلاس BaseService::getRefundPenalty. شامل جزئیات هر آیتم است. |
| meta.timestamp | زمان پاسخ (Unix Timestamp). |
Example Response - Success
{
"items": [
{
"provider": "Mahan Air",
"ticket_number": "WS5050",
"original_price": 15000000,
"penalty_amount": 3000000,
"refundable_amount": 12000000,
"currency": "IRR",
"rule": "لغو تا ۳ ساعت مانده به پرواز با جریمه ۲۰٪"
}
],
"meta": {
"timestamp": 1733739501
}
}
Error Response (400 Bad Request)
{
"status": false,
"time": 1733739501,
"error": {
"code": 1002,
"message": "Invalid booking data format or provider error.",
"trace": [ ... ]
},
"support": {
"phone": "021-91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
Technical Logic
data + branchساخت نمونه:
$BaseService = new \App\Lib\BaseService()$BaseService->getRefundPenalty($request->data, $request->get('branch'))شامل اطلاعات جریمه و مبلغ قابل بازگشت برای هر آیتم
POST /b2c/v1/online/{type}/refund
Refund Request Operation
این اندپوینت جهت ثبت و پردازش عملیات درخواست استرداد (Refund) برای بلیط یا رزرو آنلاین ایجاد شده است. پس از ارسال اطلاعات رزرو، توضیحات کاربر و مشخصات شعبه، فرآیند استرداد از طریق سرویس اصلی BaseService::refundItemProgress اجرا میشود. پاسخ شامل اطلاعات تراکنش استرداد و وضعیت نهایی عملیات خواهد بود.
Refund Item Progress
/b2c/v1/online/{type}/refundV1OnlineController@refundItemProgressPath Parameters
| Parameter | Type | Description |
|---|---|---|
| {type} | String | نوع سرویس آنلاین که قرار است فرایند استرداد برای آن انجام شود — یکی از aircraft، train یا accommodation. |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| data | Object / Array | ✅ | اطلاعات رزرو، شامل شماره بلیط، شناسه رزرو، مشخصات سرویس و دادههای ارائهدهنده. |
| description | String | ☑️ | توضیحات متنی برای علت درخواست استرداد؛ توسط کاربر یا شعبه ارائه میشود. |
| branch | Integer | ✅ | شناسه شعبهی درخواستدهنده که از قوانین داخلی برای محاسبه استرداد استفاده میکند. |
Response Structure
| Key | Description |
|---|---|
| items | نتایج بازگشتی از تابع BaseService::refundItemProgress شامل جزئیات استرداد، کد رهگیری و وضعیت مالی. |
| meta.timestamp | زمان لحظه ثبت پاسخ (Unix Timestamp). |
Example Response — Success
{
"items": {
"reference": "RFD-83234",
"status": "accepted",
"refund_amount": 1200000,
"currency": "IRR",
"provider": "Mahan Airlines",
"description": "لغو پرواز توسط مسافر",
"processed_at": "2025-12-09T17:40:22Z"
},
"meta": {
"timestamp": 1733739622
}
}
Example Response — Error (400)
{
"status": false,
"time": 1733739622,
"error": {
"code": 1002,
"message": "Refund process failed: invalid ticket reference",
"trace": [ ... ]
},
"support": {
"phone": "021-91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
Technical Logic
data, description, and branch$BaseService = new \App\Lib\BaseService()$BaseService->refundItemProgress($request->data, $request->description, $request->get('branch'))شامل دادههای استرداد، مبلغ بازگشتی و وضعیت نهایی.
1002 و اطلاعات تماس پشتیبانی.GET /b2c/v1/online/accommodation/list
List Accommodation Items
این اندپوینت فهرستی از اقامتگاهها (هتلها) را بر اساس شهر، IATA، یا شناسهی مستقیم هتل بازیابی میکند. امکان اعمال فیلترهای متنوع شامل ستاره، نوع، نام، سرویسدهندهها و محدودهی تاریخی رزرو وجود دارد. در صورت انتخاب only_charters=true، نتایج محدود به هتلهای دارای چارتر فعال در بازهی دادهشده خواهند بود.
Endpoint Info
/b2c/v1/online/accommodation/listV1OnlineController@listAccommodationLibBaseService::listAccommodation()Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| type | string | ✅ | نوع جستجو: یکی از مقادیر city، iata یا accommodation. |
| value | string|integer | ✅ | مقدار مرتبط با نوع؛ مثلاً نام شهر، کد IATA، یا شناسه هتل. |
| checkin_date | date | ✅ | تاریخ ورود. |
| checkout_date | date | ✅ | تاریخ خروج. |
| branch | integer | ✅ | شناسهی شعبه برای چک چارترهای فعال. |
| filters[only_charters] | boolean | ❌ | اگر true باشد فقط هتلهایی که چارتر فعال دارند بازگردانده میشوند. |
| filters[stars][] | array | ❌ | لیست تعداد ستارههای مد نظر (مثلاً [3,4,5]). |
| filters[types][] | array | ❌ | انواع اقامتگاه شامل hotel، apartment، guesthouse و ... |
| filters[name] | string | ❌ | جستجو بر اساس بخشی از نام فارسی هتل. |
| filters[services][] | array | ❌ | لیست سرویسدهندهها (مثلاً snapptrip، sepehr، tport، airplus). |
| paginate[length] | integer | ❌ | تعداد رکورد در هر صفحه (پیشفرض 30). |
| paginate[start] | integer | ❌ | شماره شروع ایندکس صفحه (پیشفرض 0). |
Response Structure
| Key | Description |
|---|---|
| Status | وضعیت پاسخ (true در صورت موفقیت). |
| Time | Timestamp زمان پاسخ. |
| Search | اطلاعات شناساییشده برای نوع جستجو (شهر، کشور، یا هتل). |
| Data | لیست اقامتگاهها در قالب AccommodationListResource. |
| Table | اطلاعات مربوط به صفحهبندی (total, per_page, current_page, last_page, از، تا، next_page). |
Example Response (Success)
{
"Status": true,
"Time": 1733742000,
"Search": {
"city": {
"id": 396,
"title": {"fa": "مشهد", "en": "Mashhad"},
"country": {"fa": "ایران", "en": "Iran"},
"category": {"title": "شهر"}
}
},
"Data": [
{
"id": 2652,
"fa_title": "اکسلسیور کیش",
"en_title": "Excelsior Kish Hotel",
"rate": 5,
"type": "hotel",
"city": "مشهد",
"priority": 0,
"mapping": {"airplus": true, "snapptrip": true}
}
],
"Table": {
"total": 250,
"per_page": 30,
"current_page": 1,
"last_page": 9,
"from": 1,
"to": 30,
"next_page": 2
}
}
Example Response (Error)
{
"status": false,
"time": 1733742053,
"error": {
"code": 1002,
"message": "Invalid city or date format.",
"trace": [ ... ]
},
"support": {
"phone": "021-91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
Technical Flow (Logic Overview)
اگر پارامتر paginate وجود نداشته یا کامل نیست، مقدار پیشفرض (length=30, start=0) ست میشود.
بسته به
type، داده از جدولهای cities، states، countries یا hotels واکشی میشود.روی جدول
hotels با join بر روی mapping_accommodations انجام میشود.- ستارهها (rate)
- نوع اقامتگاه (type)
- نام جزئی (fa_title LIKE '%name%')
- وجود چارتر فعال بین تاریخهای واردشده.
پاسخ شامل Search (محل جغرافیایی) + Data (Resource List) + Table meta.
GET /b2c/v1/online/accommodation/get_min_prices
Get Accommodation Minimum Prices
این اندپوینت حداقل قیمت (Minimum Price) اقامتگاهها را در بازهای از تاریخ ارائه میدهد. دادهها از چند سرویس خارجی شامل AirPlus، Sepehr، Tport و SnappTrip واکشی میشوند. نتایج هر اقامتگاه بهصورت cache شده در Redis با TTL دینامیک نگهداری میشود تا بار API کاهش پیدا کند.
Endpoint Info
/b2c/v1/online/accommodation/get_min_pricesV1OnlineController@getAccommodationMinPricesLibBaseService::getAccommodationMinPricesQuery Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| checkin_date | string (YYYY-MM-DD) | ✅ | تاریخ ورود به اقامتگاه. |
| checkout_date | string (YYYY-MM-DD) | ✅ | تاریخ خروج از اقامتگاه. |
| branch | integer | ✅ | شناسهی شعبه برای context فراخوانی AirPlus API. |
| accommodations[] | array of integers | ✅ | لیست شناسههای اقامتگاههایی که باید نرخ حداقلشان محاسبه شود. |
Response Structure
| Key | Description |
|---|---|
| Status | وضعیت درخواست، true در صورت موفقیت |
| Time | زمان پاسخ UNIX timestamp |
| Data | آبجکتی از اقامتگاهها که کلید آن ID اقامتگاه است. برای هر مورد، `min_price` و `min_original_price` بر اساس مجموع روزهای اقامت محاسبه شده است. |
Example Response (Success)
{
"Status": true,
"Time": 1733743200,
"Data": {
"2137": {
"id": 2137,
"min_price": 4800000,
"min_original_price": 5500000
},
"2652": {
"id": 2652,
"min_price": 3600000,
"min_original_price": 4100000
}
}
}
Example Response (Error)
{
"status": false,
"time": 1733743300,
"error": {
"code": 1002,
"message": "Invalid accommodations parameter.",
"trace": [ ... ]
},
"support": {
"phone": "021-91016838 in 121",
"email": "ict@airplus.app",
"panel": "helpdesk.airplus.app"
}
}
Logic Flow
محاسبه تعداد روز اقامت (diff)، همچنین تولید تاریخهای
next Tuesday → next Wednesday برای تست قیمتها.بررسی وضعیت APIهای فعال در جدول
application_interface برای سرویسهای tport، sepehr_hotel، snapptrip_hotel.برای هر شناسه اقامتگاه → بررسی cache Redis. در نبود cache، شروع واکشی از همه providerها.
- TportApi::get_hotel_room_price: بر اساس mapped id و بازه تاریخی.
- SepehrApi::search_by_city_and_date: بر اساس IATA شهر یا استان.
- AirPlusApi::search: منبع داخلی hub.
- SnappTripApi::get_hotel_availability_calendar.
محاسبه کمترین مقدار نهایی و اصل بین همه نتایج موجود
minNetPrice = min(Tport, Sepehr, AirPlus, SnappTrip)کلید
accommodations:min_price:{id} با TTL وابسته به درجه هتل (۵ستاره تا ۷روزه)لیست اقامتگاهها با فیلدهای
min_price و min_original_price ضربدر تعداد روزها.Expiration (Redis TTL Rules)
| Hotel Rating | Expiration (seconds) | Comment |
|---|---|---|
| 5 stars | 21600 (6 hours) | High-traffic, frequent updates |
| 4 stars | 43200 (12 hours) | Medium update cycle |
| 3 stars | 172800 (2 days) | Stable pricing |
| Others | 604800 (7 days) | Low-traffic accommodation |
| No price (fallback) | 86400 (1 day) | در صورت عدم دریافت قیمت فعال از سرویس |
GET /b2c/v1/online/accommodation/get_room_type_prices
Get Room Type Prices
این اندپوینت کلیهٔ قیمتهای مرتبط با نوع اتاق (RoomType) را از سرویسدهندگان مختلف واکشی و تجمیع میکند. پس از محاسبهٔ قیمت خالص (Net)، قیمت اصلی (Board)، و ماکاپها برای هر تأمینکننده، نتیجهی مرتبشده و کمترین قیمت محاسبهشده در Redis ذخیره میشود. این ورودی، موتور قیمتگذاری اصلی صفحه هتل در B2C است.
Endpoint Info
/b2c/v1/online/accommodation/get_room_type_pricesV1OnlineController@getRoomTypePricesLibBaseService::getRoomTypePricesQuery Parameters
| پارامتر | نوع | ضروری | توضیح |
|---|---|---|---|
| accommodation_id | integer | ✅ | شناسهٔ اقامتگاه در جدول hotels. |
| checkin_date | string (YYYY-MM-DD) | ✅ | تاریخ ورود. |
| checkout_date | string (YYYY-MM-DD) | ✅ | تاریخ خروج. |
| branch | integer | ✅ | شناسه شعبه برای پردازش قیمت نهایی. |
| group | integer | ❌ | شناسه گروه کاربری. |
| level | integer | ❌ | سطح کاربری (برای تفکیک نرخگذاری مارکاپ). |
| adults_count | integer | ❌ | تعداد بزرگسال؛ فیلتر ظرفیت اتاقها. |
| children_count | integer | ❌ | تعداد کودک؛ فیلتر ظرفیت. |
Logical Flow
accommodation_id از mapping_accommodations خوانده میشود.واکشی تمام رکوردهای
accommodation_roomtypes با join روی mapping_roomtypes برای شناسههای تپورت، سپهر، ایرپلاس و اسنپتریپ.بررسی فعال بودن اینترفیسهای سرویسدهنده در جدول
application_interface.فعالهای معمول:
tport، sepehr_hotel، snapptrip_hotel، و AirPlus (همیشه فعال).🔹 فراخوانی
TportApi::get_hotel_room_price واکشی مالیات و قیمت هر روز → محاسبهی تجمعی و markups از BaseService::getRoomTypeMarkups.ذخیره در
$room->board_type_list[].🔹 فراخوانی
SepehrApi::search_by_city_and_date بر اساس IATA مقصد. واکشی RoomTypeList و RateDetailList. فیلتر + مارکاپ → ایجاد Financial array.🔹 فراخوانی
AirPlusApi::search با نوع accommodation. محاسبه قیمت روزانه بزرگسال/کودک/تخت اضافه → ذخیره در فیلد financial_total.بررسی ظرفیت در جدولهای رزرو موقت از
ReservationController::getAccommodationRooms().🔹 فراخوانی
SnappTripApi::get_hotel_availability. قیمتهای نهایی بر اساس pricing.original_sell_price و تعداد روز اقامت محاسبه میشوند. ضرب در 10 برای تبدیل به ریال.مرتبسازی بر اساس کمترین
net_price از فیلد financial_total. ذخیرهٔ کمترین قیمت با کلید Redis accommodations:min_price:{id} و TTL تطبیقی.Redis Cache Logic
| درجهٔ هتل | TTL (seconds) | توضیح |
|---|---|---|
| rate = 5 | 21600 | نرخ گذاری زیاد تغییر میکند. |
| rate = 4 | 43200 | دورهٔ بهروزرسانی متوسط. |
| rate = 3 | 172800 | پایدار دو روزه؛ قیمت ثابتتر. |
| سایر | 604800 | هتلهای کمتردد؛ TTL یک هفته. |
| قیمت صفر | 43200 | در صورت عدم دریافت نرخ معتبر. |
Response Structure
| کلید | توضیح |
|---|---|
| Status | true در صورت واکشی موفق. |
| Time | تایماستمپ unix. |
| Data | آرایهای از RoomTypeها؛ هر آیتم شامل جزئیات board_type_list، قیمتها، و ظرفیت خرید. |
نمونه خروجی موفق
{
"Status": true,
"Time": 1733744200,
"Data": [
{
"id": 10502,
"fa_title": "اتاق دوتخته فول برد",
"purchase_capacity": 3,
"board_type_list": [
{
"service": "airplus",
"financial_total": {
"board_price": 4860000,
"net_price": 4500000,
"markup": 0.07
},
"supplier": { "id": 28, "title_fa": "AirPlus Hub" }
}
]
}
]
}
GET /b2c/v1/online/accommodation/get_details
Get Accommodation Details
این اندپوینت تمام جزئیات مربوط به یک اقامتگاه (هتل) را بر اساس شناسه ورودی برمیگرداند. جزئیات شامل اطلاعات عمومی هتل، رسانهها (تصاویر، ویدیو، لوگو)، امکانات گروهبندیشده، سیاستها، قوانین عمومی و قوانین استرداد میباشد. در صورت وجود چارتر فعال برای آن هتل، اطلاعات انتقال (Transfer) نیز به سیاستها افزوده میشود.
Endpoint Info
/b2c/v1/online/accommodation/get_detailsV1OnlineController@getAccommodationDetailsLibBaseService::getAccommodationDetailsQuery Parameters
| پارامتر | نوع | الزامی | توضیح |
|---|---|---|---|
| accommodation_id | integer | ✅ | شناسه اقامتگاه مورد نظر در جدول hotels. |
Logic Flow
hotels توسط accommodation_id.media:
- اگر
type = cover→ در اولویت نمایش. - مدیاها بر اساس نوع گروهبندی میشوند: تصاویر (image)، ویدیوها (video).
- اگر لوگوی خاص موجود نباشد، لوگوی پیشفرض (default) جایگزین میشود.
- اتصال جدول
facilitiesباaccommodation_facilities_mapping. - سپس گروهبندی امکانات بر اساس
facilities_categories. - اگر هیچ موردی موجود نباشد → مقدار false برگردانده میشود.
- واکشی
accommodation_policies،accommodation_cancellation_rules، وaccommodation_rules. - در صورت وجود چارتر فعال (جدول
charters)، قوانین Transfer (Welcome/Return) ازcharter_items.detailsافزوده میشود.
دادهها در قالب
AccommodationResource بازگردانده میشوند.Response Structure
| کلید | توضیح |
|---|---|
| Status | true در صورت موفقیت در یافتن هتل. |
| Time | timestamp یونیکس. |
| Data | شیء بازگشتی از نوع AccommodationResource. |
AccommodationResource شامل فیلدهای زیر است:
| فیلد | توضیح |
|---|---|
| id | شناسه اقامتگاه |
| fa_title / en_title | عنوان فارسی و انگلیسی |
| rate | ستاره (رتبه) |
| city, state, country | محل جغرافیایی |
| address, location | آدرس و موقعیت مکانی |
| description | توضیحات |
| media | شامل images و videos (یا false در صورت عدم وجود) |
| logo | مسیر لوگوی اختصاصی یا پیشفرض. |
| facility_categories | آرایهای از دستهبندی امکانات (هر مورد شامل id، title، facilities). |
| rules | آبجکت شامل کلیدهای cancellation، public، و policies. |
Example Response (Success)
{
"Status": true,
"Time": 1733744800,
"Data": {
"id": 2137,
"fa_title": "هتل بزرگ شیراز",
"en_title": "Grand Hotel Shiraz",
"rate": 5,
"city": "شیراز",
"address": "بلوار قرآن، نرسیده به دروازه قرآن",
"logo": "https://storage.service01.ir/media/accommodations/2025/logo/grand-shiraz.png",
"media": {
"images": [{ "path": "https://storage.../1.jpg" }],
"videos": false
},
"facility_categories": [
{
"id": 3,
"title": "امکانات رفاهی",
"facilities": [
{ "id": 12, "title": "استخر سرپوشیده" },
{ "id": 24, "title": "سونا و جکوزی" }
]
}
],
"rules": {
"cancellation": [
{ "id": 1, "hours_before": 48, "percent": 10 }
],
"public": [
{ "id": 2, "rule": "ورود حیوانات خانگی ممنوع است" }
],
"policies": {
"checkin": "14:00",
"checkout": "12:00",
"welcome_transfer": 1,
"return_transfer": 0
}
}
}
}
Example Response (Not Found)
{
"Status": false,
"Time": 1733744801,
"Message": "Accommodation not found."
}
GET /b2c/v1/online/flight/class/{calculation_id}
Get Flight Class Data
این اندپوینت برای واکشی اطلاعات کامل یک کلاس پروازی از روی شناسه محاسبه (`calculation_id`) طراحی شده است. داده بازگشتی شامل جزئیات فنی، مالی، مارکاپها و تخفیفها برای سنین مختلف (Adult/Child/Infant) است. درصورت معتبر بودن شناسه، داده از سرویس AirPlusApi با متد search واکشی میشود و پس از بررسی فیلترهای application_filter و پردازش مارکاپها توسط LibBaseService::checkSearchFlightItem() به صورت JSON بازگردانده میشود.
Endpoint Info
/b2c/v1/online/flight/class/{calculation_id}V1OnlineController@getFlightClassDataAirPlusApi & LibBaseService::checkSearchFlightItemPath Parameters
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
| calculation_id | integer | ✅ | شناسه محاسبهٔ نرخ که از جدول charter_calculations_route استخراج میشود. مقدار واقعی فیلد در DB برابر calculation_id - 10000 است. |
Query Parameters
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
| branch | string | ✅ | شناسهٔ شعبه فعلی برای تنظیم مسیر فروش (مثل b2c-shiraz). |
| group | string | ❌ | گروه کاربری فعلی. مقادیر مجاز: b2c, b2b, b2e, agency. |
| level | integer | ❌ | سطح کاربر در گروه (پیشفرض ۱). |
Logic Flow
calculation_id و پارامترهای branch, group, levelجستجو در جدول
charter_calculations_route با شرطهای:id = calculation_id - 10000 و status = 1 → واکشی main_id.متد
sendRequest('search') با پارامترهای زیر:- charter_id = main_id
- calculation_id = calculation_id - 10000
- all_routes = true
Data.Information[0] شامل کلاسهای پروازی است.انتقال داده به
LibBaseService::checkSearchFlightItem() برای:- مرتبسازی کلاسها بر اساس قیمت، ظرفیت، و ویژگیهای Remarks.
- اعمال مارکاپها و تخفیفها بر اساس گروه کاربر و نوع پرواز.
- بررسی
application_filterبرای اعتبار مبدا/مقصد، ایرلاین و حالتهای allowed/unallowed.
payload بازگردانده میشود.Response Samples
✅ موفق (پردازش کلاسهای پروازی)
{
"payload": {
"Airline": { "iata": "W5", "title": "Mahan Air" },
"Origin": { "iata": "THR" },
"Destination": { "iata": "MHD" },
"FlightNumber": "W51234",
"FlightType": "Charter",
"Classes": [
{
"CabinType": "Economy",
"Financial": {
"Adult": {
"BaseFare": 950000,
"Tax": 50000,
"Payable": 1000000,
"Markup": {
"percent": 10,
"price": 100000,
"final": 1100000,
"discount": { "percent": 0, "price": 0 }
}
}
},
"AvailableSeat": 4,
"Remarks": {
"Inbound": { "Special": 0, "TourRequirement": false }
}
}
]
},
"meta": { "timestamp": 1733750500 }
}
❌ خطا – آیتم یافت نشد
{
"error": {
"code": 1001,
"message": ".آیتم مورد نظر یافت نشد"
},
"meta": { "timestamp": 1733750510 }
}
Technical Notes
- مارکاپها از
HubController::markups($branch)خوانده میشوند و طبق گروه و سطح کاربر اعمال میشوند. - محاسبات تخفیف شامل فیلدهای
discount.percentوdiscount.valueبرای هر سن مسافر است. - فیلترهای سیستم (جدول
application_filter): بسته به نوع پرواز (system/charter/any) و وضعیت (allowed/unallowed) ترتیب اجرا دارند. - در صورتی که دادهای با شرایط فیلتر تطابق نداشته باشد، تابع
checkSearchFlightItemمقدارfalseبازمیگرداند. - در خروجی API، مقادیر مالی هر رده سنی شامل
BaseFare,Tax,TotalFare,Payable,MarkupوCommissionاست.
GET /b2c/v1/online/payment/flight/tracking
Tracking Payment Flight
اندپوینت زیر برای بررسی وضعیت پرداخت و رزرو پرواز از روی کد ملی و شماره رفرنس طراحی شده است. این متد با اتصال داخلی به V2TradeController::operationTrade() وضعیت فاکتور، اطلاعات پرواز و مسافران را بازیابی کرده و دادههای حساس مالی را از خروجی حذف میکند.
Endpoint Info
/b2c/v1/online/payment/flight/trackingV1OnlineController@trackingPaymentFlightQuery Parameters
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
| special_code | string | ✅ | کد ملی مسافر یا شناسه ویژه ثبت شده در سیستم مشتریان (customers.national_code) |
| reference | integer | ✅ | شماره فاکتور یا رفرنس پرداخت (با offset داخلی -10000 هنگام مقایسه در جدول فاکتورها) |
| branch | integer | ✅ | شناسهٔ شعبهای که پرداخت در آن انجام شده است |
Logic Flow
special_code و referenceجستجو در جدول
customers برای یافتن رکوردی با national_code = special_code.اگر یافت نشود، پاسخ خطا با کد 404 و پیام "مسافر مورد نظر یافت نشد" برگردانده میشود.
در جدول
factors فیلدهای زیر بررسی میشوند:serial = reference - 10000branch = branch_idcustomer = customer.id
افزودن پارامتر
id = reference به درخواست.تنظیم اپراتور فرضی:
{ id: 12, access: ... } برای اجرای تابع operationTrade.ساخت شیء
V2TradeController و اجرای متد operationTrade($request).دادهٔ بازگشتی (JSON) شامل فاکتور و اقلام تراکنش واکشی میشود.
در آرایهٔ خروجی
data، فیلدهای محرمانه حذف میشوند:[buy, value_added, serial, provider, currency, deadline, failure_bill]و فیلدها به شکل زیر اصلاح میشوند:
serial_id = serial_id + 10000payload برمیگردد.Response Samples
✅ موفق - فاکتور یافت شد
{
"payload": {
"serial_id": 26789,
"data": [
{
"serial_id": 26790,
"title": "تهران → مشهد",
"status": 3,
"departure_datetime": "2025-12-09 10:15",
"arrival_datetime": "2025-12-09 11:45",
"airline_code": "W5",
"aircraft": "Airbus A321",
"cabin": "Economy"
}
],
"passengers": [
{ "name_fa": "علیرضا رضایی", "sex": "male", "national_code": "1234567890" }
],
"leader": { "name_fa": "علیرضا رضایی", "mobile": "09121234567" }
},
"meta": { "timestamp": 1733750909 }
}
❌ خطا – مشتری یافت نشد
{
"error": {
"code": 1001,
"message": ".مسافر مورد نظر یافت نشد"
},
"meta": { "timestamp": 1733750915 }
}
❌ خطا – فاکتور یافت نشد
{
"error": {
"code": 1001,
"message": ".رفرنس مورد نظر یافت نشد"
},
"meta": { "timestamp": 1733750921 }
}
Technical Notes
- تابع
operationTrade()از مسیر داخلی/v2/trade/operationفراخوانی شده و مسئول واکشی جزئیات پرداخت است. - در فرآیند اعتبارسنجی، شناسهٔ فاکتور با آفست 10000− بررسی میشود تا با ساختار فاکتورهای داخلی سازگار گردد.
- عامل اجرای عملیات با شناسهٔ ثابت
operator.id = 12تنظیم شده تا از سطح دسترسی سیستم مرکزی استفاده کند. - تمام زمانهای بازگشتی در خروجی با فرمت میلادی و فیلد
meta.timestamp(ثانیههای یونیکس) اضافه میشوند. - کلید
leaderفقط در صورتی وجود دارد که فاکتور دارای سرپرست یا مشتری اصلی باشد.
GET /b2c/v1/financial/list
List Financial History
این اندپوینت برای نمایش لیست تراکنشهای مالی کاربران طراحی شده است و بسته به نوع گروه کاربری (b2c یا b2b/ colleague) دادهها از جداول متفاوتی واکشی میگردند. برای کاربران B2C، از جداول factor_items، factors و pays اطلاعات استخراج میشود و برای کاربران B2B/Colleague از جدول wallet.
Endpoint Info
/b2c/v1/financial/listCreditDebitController@listFinancialmiddleware: authWithJwt)Query Parameters
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
| group | string | ✅ | نوع گروه کاربری فعال. مقادیر مجاز: b2c, b2b, colleague. |
| branch | integer | ❌ | شناسه شعبه کاربر (مورد استفاده فقط برای B2B/Colleague). |
| operator.id | integer | ✅ | شناسهٔ کاربر یا اپراتور فعلی که از JWT تزریق میشود. |
Logic Flow
operator.id و group- جستجوی فاکتورها در
factor_itemsکهcustomer_id = operator.idاست یا درfactors.customer = operator.id. - فاکتورها با وضعیت غیر از (۲،۵) انتخاب میشوند.
- شناسههای فاکتور استخراج و با جدول
paysترکیب میشوند. - کوئری شامل تراکنشهایی با شرایط زیر است:
type = 'receive'object_type = 'reference'object ∈ references- یا:
functor_type = 'customer'وfunctor_account = operator.id
برای هر رکورد
pays، مقدار توضیحات حسابداری از کلید accounting:pays:{id} در Redis واکشی میشود و در فیلد description درج میگردد.واکشی تراکنشهای کیف پول (
wallet) با شرطهای زیر:
branch = request.branchstatus = 1operator_type = 'b2b'operator = operator.id
id+10 000 ⇒ serial / tracking_code و نوع تراکنش بر اساس credit ≠ 0 ? 'receive' : 'payment'.data شامل لیست کامل تراکنشها (دریافت، پرداخت، کیفپول، فاکتور) همراه با فیلدهای استاندارد مالی بازگردانده میشود.Response Sample
{
"status": true,
"time": 1733751800,
"data": [
{
"serial": 19087,
"type": "receive",
"type_pay": "reference",
"deadline": "20251209",
"currency": "IRR",
"fee": 0,
"amount": 2500000,
"tracking_code": "TRX-19572",
"description": {
"reason": "پرداخت بابت فاکتور 17894",
"gateway": "Parsian",
"invoice": "Ref#X20991"
}
},
{
"serial": 26789,
"type": "payment",
"type_pay": "wallet",
"deadline": "20251209",
"currency": "IRR",
"fee": 0,
"amount": 1200000,
"tracking_code": 26789,
"description": "بابت هزینه خدمات هاب با عطف 2851"
}
]
}
Technical Notes
- دادهها از Redis کلید
accounting:pays:{id}فقط اگر مقدار JSON معتبر باشد استخراج میگردند. - در بخش B2C، فقط فاکتورهایی با وضعیت فعال (غیر از ۲ و ۵) در نظر گرفته میشوند.
- در صورت عدم وجود تراکنش، خروجی مقدار
data: []بازمیگرداند وstatusهموارهtrueاست. - تمام تاریخها در خروجی با فرمت
YYYYMMDDمحاسبه میشوند. - تفاوت مقدار
feeبا مبلغ کل در تراکنشها، کارمزد داخلی نیست و صفر برمیگردد مگر از Redis قابل استخراج باشد. - برای B2B، شناسهٔ تراکنش کیفپول همیشه با offset ثابت
+10 000در serial نمایش داده میشود تا از شناسههای فاکتور تفکیک گردد.
GET /b2c/v1/passengers/previous
List Previous Passengers
این اندپوینت جهت واکشی لیست مسافرانی که پیشتر در رزروهای کاربر (یا همکار) شرکت داشتهاند طراحی شده است. اطلاعات از جدول customers واکشی شده و شامل جزئیات هویتی، ملیتی، گذرنامه و تماس فرد میباشد.
Endpoint Info
/b2c/v1/passengers/previousPassengersController@indexPassengersPreviousmiddleware: authWithJwt)Query Parameters (from JWT + Request)
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
| operator.id | integer | ✅ | شناسه اپراتور / مسافر اصلی (از JWT) |
| group | string | ✅ | نوع گروه کاربری فعال. مقادیر مجاز: b2c، b2b، colleague. |
Logic Flow
$passengerId = $request->get('operator')->idpassengerId. اگر نباشد، پاسخ خطای 400 با پیام Passenger ID is required و code = passenger_id_required.- اگر گروه کاربری
b2bیاcolleagueباشد:- از جدول
factorsرکوردهایی کهcolleague_auth = operator.idدارند و وضعیتشان خارج از [2,5] است استخراج میشود. - مقادیر فیلد
customerاز آنها گرفته و از جدولcustomersواکشی میشود.
- از جدول
- اگر گروه کاربری
b2cباشد: در جدولcustomersرکوردهایی کهrelationship = passengerIdهستند واکشی میشوند.
orWhere('id', passengerId) به نتایج افزوده میشود.برای هر مسافر:
- در صورت نبود ملیت،
citizenship = 118(ایران) تنظیم میشود. - دادهٔ کشور از جدول
countriesفیلتر میشود باstatus=1وfa_nationality IS NOT NULL. - تارگت خروجی شامل ساختارهای استاندارد برای fullname، identity، passport، mobile، birth و ... است.
{"items": [...], "meta": {"timestamp": time()}}Response Samples
✅ موفق - لیست مسافران قبلی
{
"items": [
{
"id": 1547,
"gender": 1,
"fullname": {
"first_name": { "fa": "علیرضا", "en": "Alireza" },
"last_name": { "fa": "رضایی", "en": "Rezaei" }
},
"email": "alireza@example.com",
"mobile": "09123456789",
"birth": "13721021",
"identity": {
"id": "1234567890",
"nationality": {
"id": 118,
"iso": "IR",
"title": { "fa": "ایران", "en": "Iran" },
"nationality": { "fa": "ایرانی", "en": "Iranian" }
}
},
"passport": {
"id": "P98651234",
"expire_at": "20270930"
}
}
],
"meta": { "timestamp": 1733752711 }
}
❌ خطا – Passenger ID خالی
{
"error": {
"message": "Passenger ID is required",
"code": "passenger_id_required"
},
"meta": { "timestamp": 1733752715 }
}
Technical Notes
- منبع اصلی داده: جدول
customers، با ارتباط ضمنی از فیلدrelationshipبه شناسهٔ اپراتور. - در حالت B2B/Colleague، ارتباط با فیلد
colleague_authاز جدولfactorsبرقرار میشود. - ملیتها از جدول
countriesبر اساس وضعیت فعال (status=1) و فیلدهایfa_nationality/en_nationalityاستخراج میشوند. - فرایند map در انتها دادهها را از آبجکت DB به قالب JSON با فرمت Front‑Friendly تبدیل میکند.
- در همه پاسخها کلید
meta.timestampجهت تطبیق زمانی اضافه میشود.
GET /b2c/v1/gateway/details
Payment Gateway Details
این اندپوینت جزئیات تراکنش از درگاه بانکی (Payment Gateway) را بر اساس serial_id برمیگرداند. در صورتیکه وضعیت تراکنش موفق (status=3) باشد، اطلاعات کارت و کد رهگیری نمایش داده میشود؛ در غیر این صورت، پیام خطا و جزئیات تراکنش ناموفق بازگردانده میشود.
Endpoint Info
/b2c/v1/gateway/detailsCreditDebitController@paymentGatewayDetailspayment_gateway, gatewaysQuery Parameters
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
| serial_id | string | integer | ✅ | شناسهٔ تراکنش در جدول payment_gateway |
Logic Flow
serial_id از QueryStringSELECT payment_gateway.*, gateways.drive as drive_title
FROM payment_gateway
JOIN gateways ON payment_gateway.drive = gateways.id
WHERE payment_gateway.serial_id = :serial_id
- ✅ بله → ادامه پردازش
- ❌ خیر → بازگشت پیام 404
status:
3→ پرداخت موفق ✅- غیر از 3 → پرداخت ناموفق ❌
- اگر
drive_title = 'behpardakht'یا'sep'→ مقدارresultدیکد (JSON decode) میشود. - مقادیر زیر استخراج میشوند:
amount: مبلغ پرداختcard: شماره کارت (ازCardHolderPan)tracking_code: کد رهگیری (SaleReferenceId)datetime: زمان تراکنش
- درگاههای دیگر در خروجی خام (`$tempPaymentGateway`) بازگردانده میشوند.
اگر behpardakht باشد → پیام خطا از
$result->message گرفته میشود و SaleOrderId به عنوان tracking_code بازگردانده میشود.200 (در موفقیت) یا 400/404 در خطا بازگردانده میشود.Response Samples
🎯 پرداخت موفق (درگاه Behpardakht)
{
"payload": {
"drive": "behpardakht",
"amount": 1250000,
"card": "610433******8124",
"datetime": "2025-12-09T10:12:35",
"tracking_code": "74632159"
},
"meta": { "timestamp": 1733749500 }
}
⚠️ پرداخت ناموفق (درگاه Behpardakht)
{
"error": {
"code": 1000,
"message": "پرداخت ناموفق | تراکنش توسط دارنده کارت لغو شد"
},
"meta": {
"timestamp": 1733749600,
"data": {
"message": "تراکنش توسط دارنده کارت لغو شد",
"datetime": "2025-12-09T10:12:35",
"tracking_code": "69854172"
}
}
}
❌ پرداختی یافت نشد
{
"error": {
"code": 1000,
"message": "پرداختی با این مشخصات یافت نشد."
},
"meta": { "timestamp": 1733749700 }
}
Technical Notes
- منبع اصلی داده: جدول
payment_gateway(اطلاعات تراکنش) + جدولgateways(متادیتای درگاه). - پارامتر
serial_idاز Query دریافت میشود و باید منحصربهفرد باشد. - فیلد
status=3معرف پرداخت نهایی و تایید شده است. - درگاههای اختصاصی (Behpardakht و Sep) دارای فیلد JSON
resultهستند که حاوی دادههای تراکنش مانند شماره کارت و کد مرجع است. - در صورت عدم match درگاه خاص، پاسخ به صورت خام (Object کامل DB row) بازگردانده میشود.
GET /b2c/v1/discount/submit
Submit Discount Code
این اندپوینت برای بررسی و اعمال کد تخفیفهای فعال هر branch (دفتر عامل) طراحی شده است. کد تخفیف فقط در صورتی قابل استفاده است که هنوز منقضی نشده باشد، محدودیت استفاده آن تمام نشده باشد و در صورت تعریف کاربر خاص، توسط همان کاربر ارسال شود.
Endpoint Info
/b2c/v1/discount/submitCreditDebitController@discountSubmitoperator)payment_discountQuery Parameters
| پارامتر | نوع | الزامی | توضیح |
|---|---|---|---|
| branch | integer | ✅ | شناسه دفتر (Branch ID) |
| type | string | ✅ | نوع استفاده از تخفیف (مثلاً flight، hotel، train) |
| code | string | ✅ | کد تخفیف واردشده توسط کاربر |
Logic Flow
payment_discountSELECT * FROM payment_discount
WHERE branch = :branch
AND status = 1
AND type = :type
AND code = LOWER(:code)
LIMIT 1;
- ✅ بله → ادامه
- ❌ خیر → پیام خطای «کد تخفیف معتبر نمیباشد»
- اگر null → بدون محدودیت
- اگر >0 → قابل استفاده
- اگر 0 → پیام خطای «کد تخفیف تمام شده است»
- اگر user=null → برای همه مجاز
- اگر user=id اپراتور → مجاز
- در غیر این صورت → «کد تخفیف برای کاربر دیگری میباشد»
expiration)
- اگر null → همیشه معتبر
- اگر زمان فعلی < expiration → معتبر
- در غیر این صورت → «کد تخفیف منقضی شده است»
status = 3, limit = 0
idtitletype(discount_type در DB)value(discount_value در DB)
Response Samples
✅ تخفیف معتبر
{
"status": true,
"time": 1733756000,
"data": {
"id": 47,
"title": "تخفیف ویژه پرواز نوروزی",
"type": "percent",
"value": 10
}
}
❌ کد تخفیف منقضی شده
{
"status": false,
"time": 1733756005,
"message": "کد تخفیف وارد شده منقضی شده است."
}
❌ کد تخفیف برای کاربر دیگری است
{
"status": false,
"time": 1733756010,
"message": "کد تخفیف وارد شده برای کاربر دیگری می باشد."
}
❌ کد تخفیف پیدا نشد
{
"status": false,
"time": 1733756020,
"message": "کد تخفیف وارد شده معتبر نمی باشد."
}
Technical Notes
- جدول مورد استفاده:
payment_discountفیلدهای کلیدی:id,title,type,discount_type,discount_value,branch,user,limit,status,expiration. - در صورت
status = 3تخفیف عملاً غیرفعال محسوب میشود. - کیس‑اِنسنسیتیو بودن بررسی کد با
strtolower($request->code). - مدیریت TTL (انقضا) و محدودیت استفاده در همان جدول انجام میشود؛ نیازی به Redis نیست.
- پاسخها ساختاری ساده و سازگار با سایر اندپوینتهای B2C دارند: کلیدهای مشترک
status،timeوmessageدر اختیاریترین حالت.
GET /b2c/v1/discount/unsubmit
Unsubmit Discount Code
این اندپوینت برای بازگرداندن ظرفیت یا اعتبار مصرفشدهی یک کد تخفیف در سیستم B2C طراحی شده است. در واقع، اگر کد تخفیف اشتباهی استفاده یا تراکنش مربوط به آن لغو شود، با این درخواست میتوان مقدار limit آن را افزایش داد و status را در حالت فعال (1) قرار داد.
Endpoint Info
/b2c/v1/discount/unsubmitCreditDebitController@discountUnSubmitpayment_discountQuery Parameters
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
| id | integer | ✅ | شناسه رکورد تخفیف در جدول payment_discount |
Logic Flow
id از Query string.payment_discount با شرط id.- ✅ بله → ادامه پردازش
- ❌ خیر → بازگرداندن پیام «کد تخفیف معتبر نمیباشد»
limit:
- اگر
limit == 0→ یعنی تخفیف کاملاً مصرف شده بوده؛ حالا برگردان به حالت اولیه:limit = 1status = 1(فعال)
- اگر
limit > 0→ فقط یک واحد افزایش پیدا میکند.
payment_discount با مقادیر جدید.status = truetime = timestamp()
Response Samples
✅ بازگردانی موفق اعتبار
{
"status": true,
"time": 1733758800
}
❌ کد تخفیف یافت نشد
{
"status": false,
"time": 1733758810,
"message": "کد تخفیف معتبر نمی باشد."
}
Technical Notes
- عملیات بر روی جدول اصلی
payment_discountانجام میشود. - مقادیر قابل تغییر:
limit: شمارش باقیمانده استفاده از تخفیف.status: وضعیت فعلی تخفیف (۱ = فعال، ۳ = مصرف شده).
- اگر
limit == 0یعنی تخفیف قبلاً کامل مصرف شده، پس این متد آن را مجدداً فعال میکند. - در صورت موفقیت هیچ دیتا از رکورد برگردانده نمیشود؛ فقط تایید عملیات با timestamp عرضه میشود.
- در آینده بهتر است امنیت اضافه شود تا فقط کاربران مجاز یا مدیران سیستم قادر به revert باشند.
GET /b2c/v1/wallet/ballance
Wallet Balance
این اندپوینت برای واکشی موجودی کیفپول کاربر در سیستم B2C طراحی شده است. بر اساس group (مقدار JWT احراز هویت)، نوع کاربر بین B2C و B2B (colleague) تشخیص داده میشود و سپس موجودی کیف پول از جدول wallet با استفاده از کنترلر مالی اصلی (AccountingController) فراخوانی میگردد.
Endpoint Info
/b2c/v1/wallet/ballanceCreditDebitController@walletBalanceauthWithJwtAccountingController::getBalanceWallet()walletQuery Parameters (JWT‑required)
| پارامتر | نوع | الزامی | توضیح |
|---|---|---|---|
| group | string | ✅ | گروه کاربر: b2c یا colleague. هر مقدار دیگر منجر به خطای 1000 میشود. |
| operator | object (JWT) | ✅ | شیء اپراتور احراز هویتشده شامل فیلدهای id، ceiling و deadline_month |
Logic Flow
$request->group)$userType='b2c'اگر group مقدار colleague داشت →
$userType='b2b'در غیر این صورت → بازگشت خطا:
{
"error": {
"code": 1000,
"message": "گروه کاربری یافت نشد"
},
"meta": { "timestamp": 1733763100 }
}
ceiling→ سقف اعتبار (Credit Ceiling)deadline_month→ مهلت تسویه حساب ماهیانه
AccountingController::getBalanceWallet(type, operator.id, 'website')Response Samples
✅ پاسخ موفق (کاربر B2C)
{
"payload": {
"ceiling": 2000000,
"deadline": "2025-12",
"wallet": {
"credit": 5000000,
"debit": 2500000,
"balance": 2500000,
"diagnosis": "creditor"
}
},
"meta": { "timestamp": 1733763100 }
}
✅ پاسخ موفق (کاربر colleague/B2B)
{
"payload": {
"ceiling": 10000000,
"deadline": false,
"wallet": {
"credit": 3200000,
"debit": 3500000,
"balance": -300000,
"diagnosis": "debtor"
}
},
"meta": { "timestamp": 1733763120 }
}
❌ گروه اشتباه
{
"error": {
"code": 1000,
"message": "گروه کاربری یافت نشد"
},
"meta": { "timestamp": 1733763130 }
}
Internal Logic – AccountingController::getBalanceWallet()
static function getBalanceWallet($type, $id, $method='branch') {
// تعیین نوع اپراتور
if ($type == 'colleague') { $type = 'b2b'; } else { $type = 'erp'; }
// جمع مجموع بدهکار و بستانکار
$wallet = DB::table('wallet')
->selectRaw('SUM(credit) as credit, SUM(debit) as debit')
->where(function ($q) use ($type, $id, $method) {
if ($method == 'branch')
$q->where('operator_type', 'erp')->where('branch', $id);
else
$q->where('operator_type', $type)->where('operator', $id);
})
->where('status', '!=', 2)
->first();
return [
"credit" => (int)$wallet->credit,
"debit" => (int)$wallet->debit,
"balance" => ($wallet->credit - $wallet->debit),
"diagnosis" => ($wallet->credit - $wallet->debit) >= 0
? (($wallet->credit - $wallet->debit) == 0 ? 'neutral' : 'creditor')
: 'debtor'
];
}
Technical Notes
- منبع داده: جدول
walletبا فیلدهایcreditوdebit. - تمام رکوردهایی با وضعیت
status != 2در محاسبه لحاظ میشوند. - تشخیص نوع حساب:
- creditor: بستانکار (balance > 0)
- debtor: بدهکار (balance < 0)
- neutral: خنثی (balance = 0)
- در پاسخ نهایی، تمام مقادیر به ریال هستند و integer برگردانده میشوند.
- نکته مهم: در پیادهسازی فعلی خطای تایپی وجود دارد. خط زیر در متد AccountingController باید با `==` تصحیح گردد:
✅ باید باشد:if ($type = 'colleague')
در غیر این صورت همیشه مقدار `'colleague'` ست شده و مسیر شرطی اشتباه عمل میکند.if ($type == 'colleague')
POST /b2c/v1/wallet/credit
POST /b2c/v1/wallet/credit
این اندپوینت به کاربران B2C و همکاران (B2B/Colleague) اجازه میدهد تا از طریق درگاه پرداخت فعال دفتر خود، مبلغی را به کیفپولشان واریز کنند. نتیجه نهایی تولید لینک پرداخت منحصربهفرد با ساختار /p/{slug} است.
Endpoint Information
/b2c/v1/wallet/creditCreditDebitController@walletCreditauthWithJwtpays, payment_gateway, gateways, office_config, officesRequest Body Parameters
| پارامتر | نوع | الزامی | توضیح |
|---|---|---|---|
| price | integer | ✅ | مبلغ واریز (حداقل 10000 ریال) |
| driver | string | ❌ | اختیاری؛ نوع درگاه بانکی (مثل sep یا behpardakht) |
| return_link | string (URL) | ❌ | آدرس بازگشت پس از پرداخت موفق |
| group | enum('b2c','colleague','b2b') | ✅ | گروه کاربر برای تنظیم نوع حساب مالی |
| branch | integer | ✅ | شناسه دفتر کاربر در سیستم |
| operator | object (JWT) | ✅ | شامل اطلاعات کاربر جاری (id, colleague_id, ...) |
Validation & Logic Flow
- ❌ در صورت خالی بودن → پاسخ با HTTP 422 و پیام «لطفا تمامی فیلدها را پر کنید»
- ❌ اگر مبلغ < 10000 → پاسخ با HTTP 422 و پیام «حداقل مبلغ قابل پرداخت 10000 ریال است»
- ✅ در غیر اینصورت → ادامه
Functions::getGatewayConfig(branch, driver) برای یافتن درگاه فعال:
- اگر درگاه بهصورت مستقیم انتخاب شود (driver)، از جدول
gatewaysبررسی میگردد. - در غیر اینصورت مقدار پیشفرض از جدول
office_configبا کلیدDEFAULT_BANKING_GATEWAYاستخراج میشود. - ❌ در صورت نبودن درگاه فعال → پاسخ 400 با پیام «درگاه پرداخت فعال یافت نشد»
- b2c → userType='b2c', objectType='customer', functorType='customer'
- colleague یا b2b → userType='b2b', objectType='colleague', functorType='colleague_user'
- ❌ سایر موارد → پاسخ 400 (پیام «گروه کاربری یافت نشد»)
pays با مشخصات:
- type='receive'
- moeen=14 (حساب معین برای شارژ کیف پول)
- type_pay='online'
- currency_amount = مبلغ درخواست شده
- status=1 (در انتظار پرداخت)
- year =
StaticController::getYearFinancial()
payment_gateway:
object_type='pay',object={id_pay}- مقدار
slugمنحصر به فرد باFunctions::generateSlugUnique(...) - تعیین لینک بازگشت (return_link) و شناسه درایو درگاه پرداخت
🎯 Response Samples
✅ موفق
{
"payload": {
"status": "payment_link",
"amount": 250000,
"url": "https://agency-domain.com/p/ABX21SD2",
"slug": "ABX21SD2",
"pay_id": 35876,
"gateway_id": 44
},
"meta": { "timestamp": 1733765953 }
}
❌ فیلد ناقص
{
"error": {
"code": 1000,
"message": "لطفا تمامی فیلد ها را پر کنید."
},
"meta": { "timestamp": 1733765958 }
}
❌ درگاه پیدا نشد
{
"error": {
"code": 1000,
"message": "درگاه پرداخت فعال یافت نشد"
}
}
ساختار جدولهای مرتبط
| جدول | فیلدهای کلیدی | نقش |
|---|---|---|
pays |
type, serial, object_type, object, currency_amount, functor_type, functor_account, status | ثبت پرداخت در انتظار برای کیف پول |
payment_gateway |
slug, object_type, object, drive, amount, return | رهگیری پرداخت و ایجاد لینک پرداخت |
gateways |
id, bank, drive, branch, data(JSON) | مشخصات اتصال به درگاه بانکی |
office_config |
office, key='DEFAULT_BANKING_GATEWAY', value | تعیین درگاه پیشفرض شعبه |
توضیح توابع وابسته
- Functions::getGatewayConfig($branch, $drive): در صورتی که درگاه مشخص نشده باشد، مقدار پیشفرض (DEFAULT_BANKING_GATEWAY) را از پیکربندی دفتر میخواند.
- StaticController::getYearFinancial(): سال مالی را بر اساس تاریخ شمسی برمیگرداند (مثلاً 1404).
- Functions::generateSlugUnique('payment_gateway', 8): رشته یکتا برای URL پرداخت ایجاد میکند.
نکات فنی
- حداقل مبلغ مجاز پرداخت 10 000 ریال است.
- خطای عمومی برای group ناشناخته با پیام «گروه کاربری یافت نشد» بازگردانده میشود.
- status پرداخت در جدول
paysمقدار ۱ (در انتظار تأیید) دارد تا پس از بازگشت درگاه تغییر یابد. - لینک نهایی پرداخت از
offices.short_domainساخته میشود.
GET /b2c/v1/ledger-account
ledger-account
این اندپوینت برای واکشی حساب کل دفتر همکار (colleague) در سیستم مالی B2C بهکار میرود و از دادههای تجمیعشده در Redis و خروجی متد ColleaguesController::colleagueLedgerAccounts استفاده میکند تا هم جزئیات صورتحسابها (ledger items) و هم ماندهی کل حساب را برگرداند.
Endpoint Information
/b2c/v1/ledger-accountCreditDebitController@ledgerAccountauthWithJwtColleaguesController@colleagueLedgerAccounts, Redis, Morilog\Jalali\Jalalianپارامترهای ورودی
| نام | نوع | الزامی | توضیح |
|---|---|---|---|
| operator (JWT) | object | ✅ | حاوی اطلاعات کاربر احراز شده، خصوصاً colleague_id |
| branch | integer | ✅ | شناسه دفتر یا شعبه کاربر |
منطق پردازشی (Flowchart)
operator->colleague_id)from= یک ماه قبل به تاریخ شمسی (باJalalian::now()->subMonths(1))to= تاریخ امروزstatus= "0"lbalance= false
$request['json'] و $request['id'] سپس ایجاد نمونه از ColleaguesController و فراخوانی متد colleagueLedgerAccounts() برای واکشی جزئیات تراکنشها.colleagues:general_billing:all{colleague_id} در صورت موجود بودن داده، پارس آن با json_decode().- ✅ اگر موجود بود → ساخت آرایه شامل debit, credit, balance, diagnosis, documents از داده Redis
- ❌ اگر نبود → مقادیر صفر پیشفرض تنظیم میشود
{ items, payload, meta.timestamp }ساختار خروجی JSON
پاسخ موفق
{
"items": [
{
"serial_id": "1404-22",
"datetime": "1404/09/01 00:00:00",
"credit": 500000,
"debit": 0,
"description": { "html": "واریز بابت فاکتور #22", "text": "واریز بابت فاکتور #22" },
"details": { "type": { "title": "دریافت نقدی" } }
}
],
"payload": {
"debit": 1250000,
"credit": 500000,
"balance": -750000,
"diagnosis": "بدهکار",
"documents": 17
},
"meta": { "timestamp": 1733768901 }
}
پاسخ بدون داده
{
"items": [],
"payload": {
"debit": 0,
"credit": 0,
"balance": 0,
"diagnosis": 0,
"documents": 0
},
"meta": { "timestamp": 1733768903 }
}
نکات فنی و باگهای شناساییشده
- اگر Redis کلید
colleagues:general_billing:all{id}را نداشته باشد، پاسخ همواره با موجودی صفر ست میشود؛ بهتر است در این حالت سرویسcolleagueLedgerAccountsبرای جمعآوری دادهی جدید اجرا شود. - در نسخه فعلی، فیلد زمانها و فرمت تاریخی بر اساس تاریخ شمسی با Morilog\Jalali تنظیم شده اما در سطح پایگاه داده (Carbon) تاریخ میلادی ذخیره میشود — نیاز به تطبیق هنگام sort در کلاینت دارد.
- پاسخ دارای آرایهای از آیتمهای Ledger است که بهصورت مستقیم از کنترلر همکار (`colleagueLedgerAccounts`) گرفته میشوند؛ در صورت تغییر ساختار آن متد، این خروجی نیز باید بهروزرسانی شود.
- بازگشت داده درون `meta.timestamp` با
time()انجام میشود (یونیکس تایم).
GET /b2c/v1/articles
GET /b2c/v1/articles
این اندپوینت برای دریافت فهرست مقالات در سیستم B2C استفاده میشود. امکان فیلتر بر اساس دستهبندیها، تگها و موقعیتها (places) و همچنین مرتبسازی بر اساس امتیاز (score) یا تعداد بازدید (views) فراهم شده است. در حالت عادی خروجی صفحهبندیشده برمیگردد، اما در حالت مرتبسازی خاص، دادهها به صورت محدود و غیر صفحهبندی بازگردانده میشوند.
Endpoint Information
/b2c/v1/articlesV1ArticleController@indexweb (بدون JWT)ArticleArticleResourceپارامترهای Query قابل استفاده
| نام پارامتر | نوع | الزامی | توضیح |
|---|---|---|---|
branch |
integer | ✅ | شناسه دفتر یا شعبهای که مقاله در آن تعریف شده است |
categories |
array (JSON) | ❌ | لیست شناسههای دستهبندی برای فیلتر (مثلاً [1,5,7]) |
tags |
array (JSON) | ❌ | لیست شناسههای تگها برای فیلتر |
places |
array (JSON) | ❌ | لیست شناسه نواحی مکانی مرتبط با مقاله |
sortByScore |
boolean | ❌ | مرتبسازی بر اساس بیشترین امتیاز (برترین ۱۰ مقاله) |
sortByViews |
boolean | ❌ | مرتبسازی بر اساس بیشترین بازدید (۶ مقاله برتر) |
منطق پردازشی و جریان داده (Flowchart)
Article با شرط اولیه branch = $request->branch- اگر
categoriesارسال شده →orWhereJsonContains('categories', $value)در حلقه - اگر
tagsارسال شده →orWhereJsonContains('tags', $value) - اگر
placesارسال شده →orWhereJsonContains('places', $value)
- در صورت وجود
sortByScore: مرتبسازی بر اساس امتیاز نزولی و محدودیت به ۱۰ نتیجه - در صورت وجود
sortByViews: مرتبسازی بر اساس بازدید نزولی و محدودیت به ۶ نتیجه
- اگر
sortByScoreیاsortByViewsفعال باشد → خروجیget()بدون pagination - در غیر این صورت → خروجی
paginate(15)
📦 ساختار پاسخ JSON
پاسخ موفق
{
"status": true,
"time": 1733799551,
"data": [
{
"id": 245,
"title": "راهنمای سفر مشهد",
"excerpt": "در این مقاله به جاذبههای گردشگری...",
"cover": "https://cdn.site.com/uploads/article245.jpg",
"views": 932,
"score": 4.8,
"categories": [5, 7],
"tags": [12, 98],
"created_at": "2025-11-22T10:00:00Z"
},
...
],
"links": {
"first": "https://api.domain.com/b2c/v1/articles?page=1",
"last": "https://api.domain.com/b2c/v1/articles?page=15",
"prev": null,
"next": "https://api.domain.com/b2c/v1/articles?page=2"
}
}
🔹 پاسخ در حالت مرتبسازی (بدون pagination)
{
"status": true,
"time": 1733799553,
"data": [ { ... }, { ... }, ... ],
"links": false
}
نکات فنی
- فیلترها با استفاده از
orWhereJsonContainsاعمال میشوند، بنابراین اگر مقاله شامل هرکدام از مقادیر دادهشده باشد در نتایج بازمیگردد. - رتبهبندی بر اساس
scoreیاviewsباعث بایپس شدن pagination میشود تا فقط n نتیجه برتر برگردد. - در خروجی از
ArticleResourceبرای ساختاردهی دادهها استفاده شده است. - پارامتر
branchالزامی است و باید در Query لحاظ شود؛ در غیر این صورت هیچ رکوردی پیدا نخواهد شد.
🚀 پیشنهادات بهبود برای توسعه آینده
- افزودن پارامتر
limitدلخواه برای sortByScore و sortByViews جهت کنترل خروجی. - بهینهسازی Query با استفاده از
->whereIn()در صورتی که فیلترها زیاد تکرار شوند تا از تکرارorWhereJsonContainsجلوگیری شود. - افزودن cache با کلید
articles:list:{branch}:{hash_of_params}جهت افزایش سرعت واکشی مقالات محبوب.
GET /b2c/v1/articles/{article}
GET /b2c/v1/articles/{article}
این اندپوینت برای مشاهده جزئیات یک مقاله خاص در سامانهی B2C طراحی شده است. ورودی شامل شناسه یا slug مقاله است که بهصورت Model Binding لاراول به Article تبدیل میشود. نتیجه در قالب ArticleResource بازگردانده میشود تا دادهها به شکل استاندارد JSON ارائه شود.
Endpoint Information
/b2c/v1/articles/{article}V1ArticleController@showweb (بدون JWT)ArticleArticleResourceپارامترهای مسیر (Path Parameters)
| نام | نوع | الزامی | توضیح |
|---|---|---|---|
article |
integer|string | ✅ | شناسه عددی یا slug مقاله؛ بهصورت خودکار در لاراول از طریق Route Model Binding واکشی میشود. |
منطق اجرای متد (Flow Logic)
{article} و Resolve به مدل Article توسط لاراول.ArticleResource برای تبدیل فیلدها به JSON ساختیافته.پاسخ موفق (Success Response)
{
"status": true,
"time": 1733802321,
"data": {
"id": 245,
"title": "راهنمای سفر به تبریز",
"excerpt": "بررسی بهترین جاذبههای گردشگری …",
"body": "
در این مقاله به بررسی مکانهای دیدنی ...
",
"cover": "https://cdn.site.com/uploads/articles/245.jpg",
"views": 537,
"score": 4.6,
"categories": [1, 5],
"tags": [12, 33, 70],
"places": [18],
"created_at": "2025-11-29T08:00:00Z",
"updated_at": "2025-12-05T14:10:00Z"
}
}
خطاهای احتمالی
| کد وضعیت | شرط وقوع | توضیح |
|---|---|---|
| 404 | هنگامی که شناسهی مقاله در پایگاه داده یافت نشود. | پاسخ پیشفرض خطای ModelNotFound |
📘 توضیحات اجرایی برای توسعهدهنده (Dev Notes)
- Binding اتوماتیک لاراول ارجاع
{article}را بر اساس route parameter انجام میدهد—درصورت داشتن ستون slug میتوان آن را در مدلArticle::getRouteKeyName()تعریف کرد. - Resource در اینجا میتواند شامل روابط
author،commentsیا متادیتا باشد، بسته به پیادهسازیArticleResource. - پارامتر
timeبرای نمایش زمان پاسخ API (بهصورت Unix timestamp) افزوده شده است.
GET /b2c/v1/categories
GET /b2c/v1/categories
این اندپوینت برای دریافت فهرست دستهبندیها (Categories) طراحی شده است. دادهها از جدول categories گرفته میشوند و امکان فیلتر بر اساس نوع (type) و شعبه (branch) وجود دارد. فقط دستهبندیهای سطح اول (یعنی مواردی که main آنها NULL است) بازگردانده میشوند.
Endpoint Information
/b2c/v1/categoriesV1CategoryController@indexweb (بدون JWT)CategoryCategoryResourceپارامترهای ورودی (Query Parameters)
| نام | نوع | الزامی | توضیح |
|---|---|---|---|
branch |
integer | ✅ | شناسه دفتر یا شعبهای که دستهبندیها به آن تعلق دارند |
type |
string | ❌ | نوع دستهبندی (مثلاً article، product، destination) برای فیلتر اختیاری |
page |
integer | ❌ | شماره صفحه برای صفحهبندی (بهصورت پیشفرض ۱) |
منطق پردازشی (Process Flow)
branch، type و page از QueryCategory:- اعمال شرط
where('branch', branch) - در صورت وجود
type→where('type', type) - نمایش فقط دستههای سطح اول (
whereNull('main'))
paginate(15).📦 ساختار پاسخ JSON
پاسخ موفق
{
"status": true,
"time": 1733806124,
"data": [
{
"id": 12,
"title": {
"fa": "مقاصد محبوب",
"en": "Popular Destinations"
},
"slug": "popular-destinations",
"type": "article",
"main": null,
"cover": "https://cdn.site.com/uploads/categories/12.jpg",
"created_at": "2025-11-25T07:10:00Z"
},
{
"id": 13,
"title": {
"fa": "تورهای داخلی",
"en": "Domestic Tours"
},
"slug": "domestic-tours",
"type": "tour",
"main": null,
"cover": "https://cdn.site.com/uploads/categories/13.jpg"
}
],
"links": {
"first": "https://api.domain.com/b2c/v1/categories?page=1",
"last": "https://api.domain.com/b2c/v1/categories?page=10",
"prev": null,
"next": "https://api.domain.com/b2c/v1/categories?page=2"
}
}
پاسخ خطا (نمونه)
{
"status": false,
"time": 1733806125,
"message": "پارامتر branch ارسال نشده است."
}
نکات فنی و اجرایی
- تمام دستهها از جدول
categoriesفیلتر میشوند و تنها سطح ریشه (main = NULL) نمایش داده میشود. - در خروجی دادههای هر رکورد توسط
CategoryResourceقالببندی شدهاند. - صفحهبندی بر اساس ۱۵ آیتم در هر صفحه انجام میشود و لینکها توسط Laravel Paginator تولید میشوند.
- پارامتر
branchاجباری است؛ در صورت نبود، خروجی خالی یا خطای منطقی بازگردانده میشود. - پاسخ شامل فیلد کمکی
timeجهت هماهنگی با دیگر APIهای B2C است.
🚀 پیشنهاد بهبود آینده
- افزودن قابلیت
includeChildren=trueبرای لود درختی زیردستهها همراه سطح اول. - امکان تعیین
limitدلخواه در Query برای انعطاف بیشتری در بخش Front. - افزودن پارامتر
sort=popular|alphabeticalجهت کنترل ترتیب خروجی.
POST /b2c/v1/articles/{id}/views
POST /b2c/v1/articles/{id}/views
این اندپوینت برای افزایش شمارش بازدید مقاله استفاده میشود. هنگام نمایش جزئیات مقاله در سمت کاربر (مانند صفحهی article detail)، فرانتاند میتواند پس از بارگذاری موفق مقاله، این متد را برای ثبت بازدید فراخوانی کند. افزایش بازدید مستقیماً در دیتابیس انجام میشود و مقدار جدید بازگردانده میشود.
Endpoint Information
/b2c/v1/articles/{id}/viewsV1ArticleController@incrementViewsweb (بدون JWT)Articleviews در ستون پایگاه دادهپارامترهای مسیر (Path Parameter)
| نام | نوع | الزامی | توضیح |
|---|---|---|---|
id |
integer | ✅ | شناسه عددی مقاله که میخواهیم بازدید آن را افزایش دهیم |
بدنه درخواست (Request Body)
{}
// بدون نیاز به بدنه خاص؛ صرفاً فراخوانی ساده POST کافی است.
خروجی موفق (Success Response)
{
"status": true,
"time": 1733810253,
"views": 121
}
در صورت بروز خطا (Error Response)
{
"message": "No query results for model [App\\Models\\Article] 9999",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException",
"status_code": 404
}
فلوچارت منطق عملکرد (Flowchart)
{id} از مسیر درخواستArticle::findOrFail($id)در صورت نبود مقاله ⇒ خطای ۴۰۴ با پیام ModelNotFoundException برگردانده میشود.
$article->increment('views')مقدار ستون
views در پایگاه داده یک واحد افزایش مییابد.views برگردانده میشود.نکات فنی برای توسعهدهنده (Developer Notes)
- از تابع Eloquent
increment()برای جلوگیری از race condition در محیطهای همزمان (concurrent) استفاده شده است. - در صورت نیاز به جلوگیری از افزایش تکراری در همان session، توصیه میشود کنترل فرانتاند (یا cookie flag) اضافه شود.
- فیلد
viewsباید از نوعunsignedBigIntegerباشد تا در بازدید بالا overflow نکند. - بهتر است عملیات آماری (views امروز، بازدید یکتا و …) در آینده با Redis counter یا جدول جداگانه مدیریت شود.
RESOURCE /b2c/v1/travel_requests
Travel Requests API
مجموعه اندپوینتهای /b2c/v1/travel_requests برای ثبت، مشاهده، و مدیریت درخواستهای سفر طراحی شده است. این مجموعه از ساختار Route::resource لاراول استفاده میکند و زیر مجموعهای از کنترلر TravelRequestsController میباشد. تمام مسیرها از طریق middleware: authWithJwt محافظت میشوند.
Endpoints Overview
| Method | Endpoint | Action | Description |
|---|---|---|---|
| GET | /b2c/v1/travel_requests | index | لیست درخواستهای سفر با فیلتر و صفحهبندی |
| POST | /b2c/v1/travel_requests | store | ثبت درخواست جدید سفر |
| GET | /b2c/v1/travel_requests/{id} | show | دریافت جزئیات یک درخواست مشخص |
| PUT/PATCH | /b2c/v1/travel_requests/{id} | update | ویرایش اطلاعات درخواست |
| DELETE | /b2c/v1/travel_requests/{id} | destroy | حذف درخواست |
| POST | /b2c/v1/travel_requests/{id}/change-status | changeStatus | تغییر وضعیت (status) با امکان ثبت reference |
۱. لیست درخواستها — GET /b2c/v1/travel_requests
- صفحهبندی: در فیلد
paginate.length(پیشفرض 30)،paginate.start(پیشفرض 0) - فیلترهای پشتیبانیشده:
method(accommodation, flight, tour...)submethodstatuspayment_statusoperator_id
- دسترسی به دادهها محدود به گروه کاربر است:
- B2C/Agent: فقط درخواستهای خودش
- B2B/Colleague: درخواستهای اپراتور جاری
- Base/B2E: از فیلد
requester_idدر ورودی
پاسخ نمونه
{
"items": [
{
"id": 45,
"title": "اصفهان به مشهد هتل آوازه مشهد از 2025/12/14 تا 2025/12/18",
"method": "accommodation",
"budget": 5000000,
"status": 1,
"operator_group": "b2c",
"origin": { "id": 37, "fa_name": "اصفهان", "en_name": "Isfahan" },
"destination": { "id": 396, "fa_name": "مشهد", "en_name": "Mashhad" },
"accommodation": { "title": { "fa": "هتل آوازه مشهد" }, "category": { "title": "hotel" } },
"details": { "rooms": 2, "adults": 3 },
"requester_id": { "id": 12, "first_name": "علیرضا", "last_name": "احمدی" }
}
],
"meta": {
"timestamp": 1733821800,
"table": { "total": 25, "per_page": 30, "current_page": 1, "last_page": 1 }
}
}
۲. ایجاد درخواست — POST /b2c/v1/travel_requests
در این درخواست، اپراتور با استفاده از JWT خود به عنوان operator، رکوردی جدید در جدول travel_requests ایجاد میکند.
پارامترهای ورودی (JSON Body)
{
"method": "accommodation",
"origin": {"id": 37},
"destination": {"id": 396},
"accommodation": {"id": 52, "type": "accommodation"},
"from_date": "2025-12-14",
"to_date": "2025-12-18",
"budget": 5000000,
"cost_center": 8,
"trip_type": "business",
"description": "سفر کاری به مشهد",
"details": { "adults": 3, "children": 1 }
}
methodالزامی است (مانند accommodation, flight, tour).- اگر فیلد
accommodation.type = "destination"باشد، مقدار آن جایگزین مقصد میشود. - در تمام حالات، branch و group از JWT استخراج میشوند.
خروجی موفق (201 Created)
{
"payload": {
"id": 45,
"title": "اصفهان به مشهد هتل آوازه مشهد از 2025/12/14 تا 2025/12/18",
"budget": 5000000,
"method": "accommodation"
},
"meta": { "timestamp": 1733821800 }
}
۳. مشاهده جزئیات — GET /b2c/v1/travel_requests/{id}
کلیه روابط مرتبط (origin، destination، accommodation، requester و غیره) از جداول مجزا خوانده میشوند.
- درخواستکننده (B2C → customers، B2B → colleagues).
- رفرنس فاکتور در صورت اتصال به
factorsبا offest +10000 بازگردانده میشود. - عنوان اصلی مبتنی بر مسیر + تاریخ + اقامتگاه تولید میشود.
۴. ویرایش — PUT /b2c/v1/travel_requests/{id}
- تمامی فیلدها مشابه Store هستند؛ فقط نیاز به شناسه مسیر دارد.
- در بروزرسانی،
statusوpayment_statusنیز میتوانند تغییر کنند.
پاسخ نمونه
{
"payload": {
"id": 45,
"status": 3,
"payment_status": 1,
"title": "اصفهان به مشهد هتل آوازه مشهد از 2025/12/14 تا 2025/12/18"
},
"meta": { "timestamp": 1733821880 }
}
۵. حذف — DELETE /b2c/v1/travel_requests/{id}
درخواست با شناسه مشخص حذف میشود و مقدار حذف (true/false) بازگردانده میشود.
۶. تغییر وضعیت — POST /b2c/v1/travel_requests/{id}/change-status
- برای بهروزرسانی سریع وضعیت (status) و تخصیص اپراتور یا فاکتور مرجع به کار میرود.
- اگر مقدار
status = 4وreference_idارسال شود → فاکتور مرتبط ثبت میشود.
{
"operator_id": 5,
"status": 4,
"reference_id": 999
}
خروجی موفق
{
"payload": { "status": true },
"meta": { "timestamp": 1733821930 }
}
نکات فنی و عملکردی
- عنوان ترکیبی از مبدأ، مقصد، اقامتگاه و بازه تاریخ بهشکل داینامیک ساخته میشود.
- روابط دادهای:
- Customers ↔ Requester (برای B2C)
- Colleagues ↔ Requester (برای B2B)
- Cities ↔ Origin/Destination
- Hotels ↔ Accommodation
- تمامی تاریخها در فرمت شمسی-لاتین با
LTR Markersجلوگیری از بهمریختگی نمایش دارند. - فیلد
detailsبهصورت JSON ذخیره و در خروجی decode مشود.