API.php

این مستند نمای کلی مسیرهای API پروژه را ارائه می‌دهد که در معماری بک‌اند Laravel توسعه یافته است. ساختار به‌صورت ماژولار طراحی شده و در قالب چند دامنه اصلی شامل Panel (v2)، B2C (v1)، Hub، AI و Core System پیاده‌سازی شده است.

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 خواهد بود.

منطق اعتبارسنجی

ورودی‌ها (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 می‌باشد.

پیوست: نگهداری و امنیت

POST /api/v2/colleagues/billing

Route Info

Method Endpoint Controller Middleware Purpose دسته‌بندی عملکردی
POST /api/auth/connect/branch UserController@connectBranch ندارد اتصال و معتبرسازی شعبه بر اساس کد ورودی برای پایان‌دامنه مرتبط Authentication / Branch Connection

(Analysis) تحلیل عملکرد

این Endpoint برای شناسایی و اعتبارسنجی شعبه است. هدف آن بارگزاری دامنه محیط (Unauthenticated) به عنوان نقطه ورود اولیه بدون نیاز به احراز هویت است. این درخواست با دریافت کد شعبه ورودی، هویت دامنه مرتبط با آن دفتر را بازگردانی می‌کند.

مکانیسم کدگذاری شعبه

سیستم از فرمول زیر برای استخراج شناسه واقعی شعبه از کد ارسالی استفاده می‌کند:
Actual Office ID = Input Branch Code - 1000
  
برای مثال اگر کد ورودی 1050 باشد، شناسه واقعی شعبه 50 خواهد بود.

منطق اعتبارسنجی

پس از استخراج شناسه واقعی، سیستم پارامتر زیر را بررسی می‌کند:
  • در جدول offices جستجو انجام می‌شود تا مطمئن شود شعبه فعال است.
  • در صورت یافت نشدن یا غیرفعال بودن، پاسخ خطا بازگردانده می‌شود.
  • اگر شعبه معتبر باشد، شناسه و اطلاعات دامنه مرتبط (domain) بازگردانده می‌شود.

(Inputs) ورودی‌ها

فیلد نوع داده الزامی توضیح
branch Integer بله کد شعبه (شناسه واقعی + 1000)
نمونه درخواست (Request Body):
{
  "branch": 1050
}
  

(Outputs) خروجی‌ها

در همه حالات پاسخ HTTP 200 برمی‌گردد.

خروجی موفق:

{
  "status": true,
  "office": {
    "id": 50,
    "domain": "example.domain.ir",
    "title": "دفتر مرکزی"
  }
}
  

خروجی ناموفق:

{
  "status": false,
  "message": "کد شعبه معتبر نیست یا یافت نشد."
}
  

(Dependencies) وابستگی‌ها

(Testing & Usage) تست و استفاده

نمونه تست با Postman:
POST /api/auth/connect/branch
Content-Type: application/json

{
  "branch": 1050
}
  

(Typical Error Cases) موارد خطا

(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) نتیجه‌گیری

این Endpoint یک نقطه ورود کاربردی برای لاگین دامنه‌ها بدون نیاز به احراز هویت اولیه است و فرایند تشخیص دامنه بر اساس کد شعبه را ساده می‌کند.

(Appendix) پیوست: نگهداری و امنیت

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)

این Endpoint وظیفه‌ی بررسی صحت کد ملی و در صورت معتبر بودن، شناسایی شهر متناظر با سه رقم ابتدایی آن را بر عهده دارد. در صورتی که کد ملی از نظر ساختاری صحیح تشخیص داده شود، شناسهٔ شهر از مدل City بر اساس مقدار national_prefix بازیابی می‌شود.

مکانیزم شناسایی شهر

سیستم از سه رقم ابتدایی کد ملی برای یافتن پیش‌شمارهٔ ملی (national_prefix) در جدول شهرها استفاده می‌کند.
SELECT * FROM cities WHERE national_prefix LIKE '%123%' LIMIT 1;
  
در این مثال، اگر کاربر کد ملی "1234567890" را ارسال کند، مقدار 123 به عنوان پیش‌شماره استخراج خواهد شد.

منطق اعتبارسنجی کد ملی

فرایند منطقی تابع nationalPrefix() به شرح زیر است:
  1. بررسی صحت کد ملی با Validator::nationalCode().
  2. در صورت معتبر بودن، سرچ در جدول City بر اساس سه رقم ابتدایی.
  3. بازگشت نام شهر در کلید data در صورت یافتن رکورد.
  4. در صورتی که رکوردی یافت نشود، پیام “شهر یافت نشد” بازگردانده می‌شود.
  5. در صورت نامعتبر بودن کد ملی، پیام “کد ملی نامعتبر” بازگردانده می‌شود.

ورودی‌ها (Inputs)

پارامتر نوع داده الزامی توضیح
code String بله کد ملی (ده‌رقمی) جهت بررسی صحت و تشخیص شهر.
نمونه درخواست:
{
  "code": "0451234567"
}
  

خروجی‌ها (Outputs)

خروجی موفق (شهر یافت شد)

{
  "status": true,
  "time": 1731926417,
  "data": "تبریز"
}
  

خروجی معتبر اما بدون شهر

{
  "status": true,
  "time": 1731926417,
  "data": "کد ملی صحیح می باشد. اما شهر یافت نشد"
}
  

خروجی نامعتبر (کد اشتباه)

{
  "status": false,
  "time": 1731926417,
  "data": "کد ملی نامعتبر"
}
  

وابستگی‌ها (Dependencies)

تست و استفاده (Testing & Usage)

نمونه درخواست با Postman:
POST /api/v2/trade/national
Content-Type: application/json

{
  "code": "4580021347"
}
  

موارد خطا (Typical Error Cases)

جزئیات پیاده‌سازی (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)

این Endpoint، روشی سریع، سبک و بدون نیاز به احراز هویت برای شناسایی شهر بر اساس کد ملی کاربر ارائه می‌دهد. در پروژه‌های تجاری داخلی می‌توان از آن برای ارائهٔ شناسهٔ مکانی خودکار در فرم‌های ثبت‌نام یا رزرو بهره برد.

پیوست: نگهداری و امنیت

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() مسئول احراز هویت اپراتور بر اساس شناسهٔ پرسنلی و رمز عبور است. این فرایند شامل مراحل زیر می‌باشد:
  1. اعتبارسنجی ورودی branch (شعبه) و دادهٔ data که شامل personnelId و password است.
  2. جستجوی اپراتور در جدول operators، بررسی انطباق شعبه (مقدار [0] یا شامل branch در JSON).
  3. بررسی وضعیت مسدودی اکانت (حوزه فیلد blocked_up).
  4. بررسی رمز عبور با Hash::check().
  5. در صورت معتبر بودن و فعال بودن حساب (status == 1): تولید JWT Token، ثبت لاگ با تأخیر ۱۰ دقیقه، ذخیره Shortcutها و ارسال نوتیفیکیشن تلگرام.
  6. در صورت وضعیت غیرفعال یا خطای رمز، بازگرداندن پیام خطا مطابق Swagger.
  7. در بخش 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

توکن JWT شامل فیلدهای کلیدی زیر است:
{

“typ”: “base”,

“iss”: “{DomainHeader}”,

“aud”: “{DomainHeader}”,

“iat”: 1731927000,

“exp”: 1732531800,

“uuid”: “{OperatorId}”,

“brn”: “{Branch}”,

“uip”: “{ClientIP}”,

“brw”: “{UserAgentClient}”

}

سیستم لاگ و اعلان‌ها

مدیریت اعلان (Push & Telegram)

در صورت وجود شناسهٔ تلگرام در درجۀ Operator->telegram، Dispatcher پیام مارک‌دان را ارسال می‌کند شامل:
  • نام و آیدی پرسنلی
  • شعبه و دامنه
  • IP و مرورگر دستگاه
  • دکمه مسدودی موقت

موارد خطا (Error Cases)

وابستگی‌ها (Dependencies)

نمونه تست واقعی (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)

در صورت موفقیت، JWT توکن برای ۷ روز معتبر ایجاد می‌شود و اپراتور احراز هویت‌شده به همراه تنظیمات رابط کاربری از Redis و دسترسی‌ها برگردانده می‌شود. مسیر در Swagger با Security نوع bearerAuth ثبت می‌گردد.

پیوست: نکات امنیتی

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 بازگردانده می‌شود. عملکرد تابع شامل مراحل زیر است:
  1. دریافت ورودی data از درخواست.
  2. تحلیل و اعتبارسنجی توکن access_token با استفاده از JWT::decode.
  3. مقایسه IP کاربر فعلی با IP ذخیره‌شده در توکن (uip).
  4. در صورت تطابق، یافتن اپراتور بر اساس uuid استخراج‌شده از JWT.
  5. اگر اپراتور فعال باشد (status == 1)، دادهٔ shortcuts جدید در Redis ذخیره می‌شود.
  6. در صورت غیرفعال بودن وضعیت اپراتور، پیام خطا بازگردانده می‌شود.
  7. اگر IP تغییر کرده باشد، جزئیات هر دو IP در خروجی خطا قرار می‌گیرد.
  8. در صورت بروز هر 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 مجاز

در هنگام بررسی صحت IP، سیستم IP توکن را با IP جاری تطبیق می‌دهد. سرویس آدرس‌های داخلی (Local IP Range) را مجاز می‌داند:
  • 192.168..
  • 10...*
  • 172.16..
در این شبکه‌ها حتی در صورت تغییر IP نیازی به invalid کردن توکن نیست.

ذخیره اطلاعات در Redis

Redis::set(
𝑟𝑒𝑞𝑢𝑒𝑠𝑡−>𝑔𝑒𝑡(′𝑏𝑟𝑎𝑛𝑐ℎ′).′𝑜𝑝𝑒𝑟𝑎𝑡𝑜𝑟:′.
request−>get( ′branch ′). ′operator:′.
Operator->id . ‘:shortcuts’,
json_encode($data[‘user’][‘data’][‘shortcuts’]));
اطلاعات ذخیره‌شده در Redis جهت شخصی‌سازی محیط کاربری اپراتور در ورودهای بعدی استفاده می‌شوند.

مدیریت استثناها (Exceptions)

وابستگی‌ها (Dependencies)

مثال تست (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”]

}

}

}

}

تحلیل امنیتی

نتیجه عملکرد (Summary Result)

این Endpoint به‌طور خاص برای همگام‌سازی داده‌های رابط کاربری طراحی شده تا تجربه کاربر در اتوماسیون حفظ شود. در صورت تغییر IP یا وضعیت کاربر، کلاینت باید فرآیند Sign-In را تکرار نماید.

پیوست: نگهداری و بهینه‌سازی

POST /api/auth/blocking

Route Info

Method Endpoint Controller Middleware Purpose تگ Swagger
POST /api/auth/blocking UserController@blocking domainAccess, ipTrust مسدودسازی موقت نشست‌های کاربر و خروج اجباری از تمامی نشست‌ها tags={“Auth”}

توضیح عملکرد (Function Logic)

این مسیر مسئول بستن تمامی نشست‌های فعال کاربر است و با دریافت توکن مربوط به تعامل (مثلاً از بات تلگرام)، دسترسی کاربر را به مدت مشخص (پیش‌فرض ۱۵ دقیقه) مسدود می‌کند. عملیات در سه مرحله انجام می‌شود:
  1. دریافت توکن از پارامتر token در بدنه درخواست یا آدرس مسیر.
  2. یافتن رکورد اپراتور با مقدار توکن «telegram:{token}» در ستون operators.token.
  3. به‌روزرسانی ستون‌ها:
    • blocked_up: تاریخ فعلی به‌علاوه مدت زمان مسدودسازی.
    • token: مقدار null برای قطع دسترسی فعلی.
در نهایت پاسخ خروجی شامل پیام موفقیت و زمان فعلی (epoch) است. در صورت بروز استثنا، جزئیات خطا همراه با trace بازگردانده می‌شود.

ورودی‌ها (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”: […]

}

منطق مسدودسازی نشست کاربر

با اجرای درخواست، تمامی نشست‌های فعال متصل به همان توکن تلگرام منقضی می‌شوند. کاربر تا پایان بازه زمانی blocked_up قادر به ورود مجدد نخواهد بود. پس از انقضا، سیستم به صورت خودکار دسترسی را بازمی‌گرداند.

تغییرات دیتابیس (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)

وابستگی‌ها (Dependencies)

تست Endpoint (Postman Example)

POST https://console.service01.ir/api/auth/blocking

Content-Type: application/json

{

“token”: “tg-ops-9902”,

“duration”: 20

}

تحلیل امنیتی

پیوست نگهداری و توسعه آینده

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)

این مسیر جهت ارسال رمز موقت (OTP) به کاربرانی است که قصد دارند کلمه عبور خود را بازیابی کنند. پس از بررسی شماره موبایل و کد پرسنلی کاربر، سیستم رمز یک‌بار مصرف ۶ رقمی تولید کرده و آن را از طریق پیامک به شماره کاربر ارسال می‌کند. همچنین محدودیت ارسال (Throttle) برای جلوگیری از اسپم به کمک لاگ داخلی Visa::showSystemLogs کنترل می‌شود.
  1. یافتن اپراتور فعال با مشخصات دریافتی در جدول operators.
  2. بررسی آخرین ۵ ساعت لاگ برای نوع SendOtp (حداکثر ۳ تلاش در ۵ ساعت).
  3. در صورت مجاز بودن، ساخت رمز یکبار مصرف با طول ۶ رقم.
  4. ذخیره OTP و زمان صدور (otp_issuing) در پایگاه داده.
  5. ارسال پیامک حاوی OTP با تابع StaticController::sendNotification.
  6. در صورت موفقیت، ثبت لاگ در صف snailJob با تاخیر ۱۰ دقیقه‌ای.
  7. بازگشت خروجی استاندارد شامل وضعیت، زمان، و پیام نتیجه.

ورودی‌ها (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)

برای جلوگیری از ارسال مکرر OTP، سیستم لاگ‌های نوع 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)

نمونه تست (Postman Example)

POST https://console.service01.ir/api/auth/forgot/otp
Content-Type: application/json

{
  "personnel_id": "P00999",
  "mobile": "09138889999",
  "branch": 3
}

تحلیل امنیتی

نتیجه عملکرد (Summary Result)

این Endpoint فرآیند صدور و ارسال OTP را برای بازنشانی رمز عبور مدیریت می‌کند. در کنار ارسال پیامک، با بررسی سوابق در SystemLog از ارسال‌های مکرر جلوگیری می‌شود.

پیوست نگهداری و توسعه آتی

`

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)

این Endpoint برای اپراتورهایی طراحی شده که قصد اتصال حساب کاربریشان به سرویس‌های جانبی مانند ربات تلگرام را دارند. سیستم پس از بررسی اصالت اپراتور و اطمینان از عدم اتصال قبلی، رمز ۸ رقمی موقتی (OTP) صادر کرده و از طریق پیامک برای او ارسال می‌کند. هر کاربر مجاز است حداکثر ۳ بار در بازه ۵ ساعته درخواست ارسال OTP ثبت کند.
  1. بررسی وضعیت و مشخصات اپراتور بر اساس personnel_id و وضعیت فعال.
  2. بررسی عدم اتصال قبلی به تلگرام (telegram IS NULL).
  3. کنترل سقف ارسال OTP در ۵ ساعت گذشته با Visa::showSystemLogs.
  4. صدور OTP عددی ۸ رقمی و ذخیره در operators.otp همراه زمان صدور.
  5. ارسال پیامک OTP از طریق StaticController::sendNotification.
  6. ثبت رویداد 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)

ارسال OTP به کمک بررسی لاگ‌های SystemLog انجام می‌گیرد. هر اپراتور مجاز به ارسال حداکثر ۳ بار در بازه‌ی زمانی ۵ ساعت است. در صورت تجاوز از این حد پاسخ با کد 1203 بازمی‌گردد.

قالب پیامک ارسالی (SMS Template)

code: 12345678
این رمز جهت ارتباط با سایر سرویس ها صادر شده است.
از دراختیار قراردادن آن به دیگران جدا خودداری فرمائید.
مدت اعتبار: 15 دقیقه

🌐 example.domain

ثبت رویداد SystemLog

پس از ارسال موفق پیامک، رویداد زیر در صف 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');

تغییرات دیتابیس (Database Update)

UPDATE operators
SET
  otp = {random 8-digit code},
  otp_issuing = NOW()
WHERE id = {operator.id};

وابستگی‌ها (Dependencies)

نمونه تست (Postman Example)

POST https://console.service01.ir/api/auth/connect/otp
Content-Type: application/json

{
  "personnel_id": "P00231",
  "branch": 1
}

تحلیل امنیتی

پیوست نگهداری و توسعه‌آتی

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)

این Endpoint برای فاز نهایی فرآیند اتصال کاربر به سرویس جانبی (نظیر ربات تلگرام) استفاده می‌شود. پس از ارسال رمز موقت (OTP) از طریق مسیر POST /api/auth/connect/otp، اپراتور باید طی ۱۵ دقیقه آن را در این مسیر ثبت کند. در صورت صحت OTP و اعتبار زمانی آن، ارتباط کاربر ثبت و موفق اعلام می‌شود.
  1. دریافت پارامترهای personnel_id، otp و telegram از درخواست.
  2. جست‌وجوی اپراتور فعال در جدول operators براساس کد پرسنلی و رمز فعلی OTP.
  3. بررسی انقضای OTP با درنظر گرفتن بازه زمانی ۱۵ دقیقه‌ای.
  4. در صورت اعتبار، به‌روزرسانی ستون‌های telegram و otp (پاک کردن OTP).
  5. ثبت رویداد در SystemLog با نوع SubmitTelegram و تاخیر ۱۰ دقیقه‌ای در صف snailJob.
  6. ارسال پیامک اطلاع‌رسانی موفقیت اتصال با جزئیات 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

Carbon::now()->addMinutes(-15) <= $operator->otp_issuing
این شرط بررسی می‌کند که OTP مورد استفاده حداکثر ۱۵ دقیقه قبل صادر شده باشد. در صورت گذشتن بیش از ۱۵ دقیقه، سیستم خطای Expired Code با کد 1204 برمی‌گرداند.

به‌روزرسانی جدول 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)

نمونه تست Postman

POST https://console.service01.ir/api/auth/connect/submit
Content-Type: application/json

{
  "personnel_id": "P00442",
  "otp": "55667788",
  "telegram": "@operator_test"
}

تحلیل امنیتی

پیوست نگهداری و توسعه بعدی

`

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)

این Endpoint فاز نهایی فرآیند بازیابی رمز عبور را مدیریت می‌کند. اپراتور باید پس از دریافت رمز یکبار مصرف (OTP) از طریق مسیر POST /api/auth/forgot/otp، آن را به همراه رمز عبور جدید به این مسیر ارسال کند. این مسیر اعتبار OTP را بررسی کرده، رمز جدید را با استفاده از الگوریتم هشینگ ایمن کرده و در پایگاه داده ثبت می‌کند.
  1. جست‌وجوی اپراتور فعال (status=1) با personnel_id و otp یکسان.
  2. بررسی زمان صدور OTP (otp_issuing): اگر بیش از ۱۵ دقیقه گذشته باشد، رمز منقضی شده است (کد ۱۲۰۴).
  3. اگر معتبر باشد: رمز عبور جدید ($request->password) با Hash::make() هش و ذخیره می‌شود.
  4. ستون otp اپراتور به NULL تغییر داده می‌شود تا از استفاده مجدد جلوگیری شود.
  5. ثبت رویداد UpdatePassword در SystemLog در صف snailJob.
  6. ارسال پیامک اطلاع‌رسانی موفقیت‌آمیز بودن تغییر رمز با جزئیات 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 ثبت می‌شود.
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)

تحلیل امنیتی

پیوست نگهداری و توسعه بعدی

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)

این Endpoint، اعتبار و اصالت توکن فعلی کاربر را پس از Decode بررسی کرده و در صورت تطبیق پارامترهای امنیتی، توکن جدید ۷ روزه صادر می‌کند. اگر هر کدام از فاکتورهای مرورگر، IP، دامنه یا شعبه تغییر کند، درخواست با پیام خطای دقیق رد می‌شود.
  1. دریافت access_token از $request->data و Decode با کلید env('JWT_SECRET_KEY').
  2. تحلیل User-Agent با DeviceDetector برای استخراج اطلاعات مرورگر فعلی (نام و نسخه).
  3. تابع داخلی checkBrowser() مقایسه‌ای بین مرورگر ثبت‌شده در توکن و مرورگر فعلی انجام می‌دهد.
  4. اگر مرورگر تطبیق داشت، سپس IP بررسی می‌شود:
    • اگر IP فعلی با uip در توکن یا با یکی از IPهای خصوصی (10.* / 192.168.* / 172.16.*) هم‌خوانی داشت، ادامه داده می‌شود.
  5. تطبیق دامنه iss با هدر Domain درخواست.
  6. تطبیق شناسه شعبه brn با ورودی branch درخواست.
  7. در صورت قبولی همه مراحل: یافتن اپراتور فعال (status=1، بدون بلوک).
  8. تولید توکن جدید با ساختار زیر:
{
  "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.
نمونه خطای تغییر IP:
{
  "error": {
    "type": "changeIp",
    "message": "وضعیت اینترنت شما تغییر پیدا کرده",
    "details": {
      "token_ip": "10.1.1.2",
      "current_ip": "45.66.88.100"
    }
  }
}

تولید JWT جدید

توکن جدید با کتابخانه firebase/php-jwt و الگوریتم HS256 تولید می‌شود؛ مدت اعتبار آن ۷ روز (۶۰۴٬۸۰۰ ثانیه) است. اطلاعات کلیدی آن شامل شناسه کاربر، شناسه شعبه، مرورگر و IP فعلی است.

تحلیل امنیتی

وابستگی‌ها (Dependencies)

پیوست نگهداری و توسعه بعدی

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)

این Endpoint آرایه‌ای از اعلان‌های عمومی (Public System Notifications) را برمی‌گرداند که معمولاً شامل تغییرات جدید در امکانات، هشدارهای داخلی یا پیام‌های اداری است. در حال حاضر ثابت (Static) است اما معمولاً در محیط Production داده‌ها از جدول notifications یا کش Redis تأمین می‌شوند.
  1. احراز IP و دامنه درخواست از طریق Middlewareهای domainAccess و ipTrust.
  2. تولید آرایه‌ای از اعلان‌ها با فیلدهای کلیدی:
    • id: شناسه یکتا
    • icon: آیکن متریال مرتبط با اعلان
    • title: تیتر اعلان
    • description: متن توضیحی اعلان
    • time: زمان ایجاد اعلان به فرمت شمسی
    • read: وضعیت مطالعه‌شده بودن توسط کاربر
    • link: مسیر واکنش‌پذیر (قابل کلیک در UI)
    • useRouter: نوع هدایت (true ⇒ استفاده از Router داخلی SPA)
  3. بازگشت خروجی به صورت 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
  }
]

نکات امنیتی

توضیح فیلدهای پاسخ

وابستگی‌ها (Dependencies)

پیوست نگهداری و توسعه بعدی

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() بر اساس نوع درخواست مشخص می‌کند که آزمون از کدام شاخه بارگذاری شود:

منطق کلی:

  1. بررسی مقدار $request->type.
  2. اجرای مسیر مرتبط متناسب با نوع آزمون.
  3. در صورت یافتن شرایط واجد، تولید ساختار آزمون شامل سؤالات، هدر، فوتر و فیلدهای جایگزین‌شده از داده‌های مالی یا اطلاعات همکار.
  4. بازگشت پاسخ 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»

  1. یافتن فاکتور از جدول factors بر اساس slug داده‌شده.
  2. بررسی وجود پاسخ قبلی در جدول exam_response با فیلدهای: object_type='reference' و object=factor.id. اگر وجود داشته باشد → پیغام «این سفر قبلاً نظرسنجی شده است.».
  3. در غیراین‌صورت:
    • دریافت داده مالی از Redis با کلید reference:{id}:information. در صورت عدم وجود، صدا زدن TradeController::financial() و ذخیره در Redis.
    • واکشی کالاهای موجود در فاکتور از جدول factor_items.
    • یافتن آزمون فعال با type=trip_survey برای همان شعبه.
    • دریافت سؤالات فعال از exam_questions مرتب‌شده بر اساس order و id.
    • اعمال فیلتر سؤالات: اگر موضوع hotel است، فقط هنگام وجود محصول مرتبط استفاده می‌شود.
    • جایگزینی متغیرهای دینامیک در title، header و footer با متد replaceExamItem('trip',...).
    • ساخت ساختار خروجی نهایی شامل مشخصات آزمون و آرایه سؤالات.

ب) جریان عملکرد نوع «360_degree_feedback»

  1. بررسی وجود operator.id؛ اگر کاربر لاگین نکرده → پیام خطا.
  2. عدم تطابق شناسه خواسته‌شده با personnel_id کاربر فعلی (جلوگیری از خودارزیابی).
  3. جست‌وجوی همکار هدف در جدول operators با شروط:
    • branch JSON contains current branch
    • status = 1، no_feedback = null.
  4. بررسی اینکه تاریخ استخدام کارمند حداقل ۱ ماه قبل باشد.
  5. در صورت گذشت شرط: بررسی عدم وجود پاسخ از کاربر فعلی در ۶ ماه اخیر (exam_response).
  6. اگر تاکنون پاسخ نداده → واکشی آزمون با type=360_degree_feedback و branch جاری.
  7. واکشی سؤالات و تولید ساختار خروجی مشابه نوع 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": "این سفر قبلا نظرسنجی شده است."
}

تحلیل امنیتی

وابستگی‌ها (Dependencies)

ساختار داخلی data در پاسخ موفق

فیلد نوع توضیح
id int شناسه آزمون.
object int شناسه مرجع (سفر یا کارمند).
title string عنوان اصلی آزمون.
header/footer string متن معرفی و پایان آزمون.
questions array لیست سؤالات با فیلدهای id, type, subject, title, options, description, mandatory, score.

پیغام‌های خطای محتمل

نکات کارایی (Performance)

پیوست نگهداری و توسعه بعدی

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 اختیاری شماره موبایل ثبت‌کننده
email 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": [ ... ]
}

تحلیل امنیتی

وابستگی‌ها (Dependencies)

نکات کارایی و ضعف منطقی

پیوست نگهداری و توسعه بعدی

POST /api/v2/trade/list

Route Info

Method Endpoint Controller Middleware Purpose
POST /api/v2/trade/list V2TradeController@getTradesList authWithJwt نمایش فهرست معاملات (فاکتورها) با فیلترهای پیشرفته

منطق عملکرد تابع

تابع getTradesList لیست فاکتورها (تراکنش‌ها / معاملات) را برای شعبهٔ جاری واکشی می‌کند. در ابتدا داده‌های ورودی از پارامتر 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":"پرواز تهران-کیش", ... } ]
}

امنیت و کنترل دسترسی

نکات کارایی و پیاده‌سازی

وابستگی‌ها

کدهای خطا و خروجی‌های 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

پیشنهادهای امنیتی

پیشنهادهای بهبود

ممیزی دسترسی و عدم قطعیت

جمع‌بندی

این Endpoint یکی از مرکزی‌ترین بخش‌های سیستم حسابداری تجاری است. امنیت پایه‌ای به لطف JWT حفظ شده اما حجم داده بزرگ، منطق فیلتر پیچیده و عدم validation ورودی می‌تواند منبع اشکالات عملکردی یا امنیتی شود. برای تبدیل این منطق به ساختار enterprise-grade، نیاز به جداسازی cache layer، اضافه‌کردن input schema validation و تعریف audit trail قطعی است.

POST /api/v2/trade/search

Route Info

Method Endpoint Controller Middleware Purpose
POST /api/v2/trade/search V2TradeController@searchTrades authWithJwt جستجوی معاملات/فاکتورها با فیلترهای چندلایه و ترجمه خودکار مسیرها

منطق عملکرد و مسیر داده

تابع searchTrades، درخواست را بر اساس فیلترهای 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
  }
}

تحلیل امنیتی و کنترل خطا

نکات کارایی و ضعف طراحی

وابستگی‌های کلیدی

کدهای خطا و خروجی‌های 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

پیشنهادهای بهبود

جمع‌بندی

این Endpoint جستجو، یکی از پرریسک‌ترین و پیچیده‌ترین نقاط جریان معاملاتی است. قدرت زیادی با منطق ترکیبی و ترجمه هوشمند دارد، اما ضعف‌های امنیتی و عملکردی باعث می‌شود اگر مهندسی نشده باشد، اثرش کشنده باشد. پیشنهاد اکید: جداسازی سرچ‌سرویس، اعتبارسنجی ورودی، و مهار cache.

POST /api/v2/trade/cost-benefit

اطلاعات مسیر (Route Info)

Method Endpoint Controller Middleware Purpose
POST /api/v2/trade/cost-benefit V2TradeController@costBenefit authWithJwt گزارش‌گیری سود و زیان معاملات در بازه زمانی دلخواه با تفکیک گروهی

منطق عملکرد

تابع costBenefit گزارش تحلیلی از عملکرد مالی را با معیارهای 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)

  1. ورود درخواست با JSON شامل بازه و نوع تحلیل.
  2. خواندن فاکتورها از DB (با branch و زمان) → select(id,operator,created_at).
  3. واکشی مالی از Redis → اگر خالی بود: ApiTradeController::financial() اجرا و ذخیره می‌شود.
  4. واکشی تعهدها (pledgers) از DB یا Redis.
  5. محاسبات تفکیکی مالی برای بخش‌های فعال.
  6. تجمیع اعداد کل در آرایه‌های Total*.
  7. تولید گزارش نهایی شامل دسته‌بندی‌ها و داده‌های نموداری.

عملکرد و کش

امنیت و کنترل ورودی

Dependencies

خطاها و حالت‌های خاص

پیچیدگی زمانی و منابع

مرحله O-Complexity
واکنش DB (Factor Select) O(N)
Redis Access O(2N)
تحلیل مالی nested O(N × M)
ترتیب‌دهی (usort) O(N log N)

پیشنهادهای بهینه‌سازی

جمع‌بندی

POST /api/v2/trade/cost-benefit قلب تپنده آنالیز مالی سیستم است. قدرتش در دقت آمار گروهی حک شده، اما ضعفش در عدم کنترل کش و تایپ‌ورودی‌هاست. در ساختار فعلی مناسب برای پردازش‌های تحلیلی آفلاین (batch job) است، نه اجرای زنده برای کاربران چندگانه.

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"
}

تحلیل امنیتی و کنترل خطا

نکات کارایی

وابستگی‌های کلیدی

کدهای خطا و حالت‌های Exception

کد خطا شرح منبع
1000 پارامتر type نامعتبر است referenceCreditDebit logic
1002‑1006 JWT نامعتبر یا منقضی / دسترسی غیرمجاز authWithJwt middleware
500 Database / Redis Exception referenceCreditDebit

پیشنهادهای بهبود

جمع‌بندی

این Endpoint برای واکشی سریع و ساده مراجع مالی طراحی شده است. با ساختار JSON استاندارد و پشتیبانی از فیلترگذاری ساده در عین حال با قابلیت افزودن cache – بهینه و سریع است. با افزودن لایه validation و TTL در cache می‌تواند کاملاً پایدار و قابل اطمینان در سیستم مالی شود.

POST /api/v2/trade/store

Route Info

Method Endpoint Controller Middleware Purpose
POST /api/v2/trade/store V2TradeController@storeTrade authWithJwt ثبت تراکنش خرید/رزرو با جزئیات مسافران، آیتم‌ها، پرداخت و نوتیفیکیشن

منطق عملکرد

ورودی‌ها

فیلد نوع داده ضروری توضیح
branch int بله شناسه شعبه ثبت‌کننده
operator object بله اطلاعات کاربر اپراتور (دارای id و نام)
passengers array بله لیست مسافران همراه با جزئیات کامل
data array بله لیست آیتم‌های خرید/رزرو
income_id int خیر شناسه مرجع درآمد
print 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
}

امنیت

کارایی

وابستگی‌ها

کدهای خطا

کد شرح منبع
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 ویرایش جزئیات فاکتور یا ادغام رفرنس‌های مالی

منطق عملکرد

  1. financial_description: ویرایش توضیحات مالی فاکتور (فیلد financial_desc) و ثبت لاگ.
  2. details: بروزرسانی فیلدهای عمومی شامل description، print، income، leader و status + پاکسازی Cache Redis مربوطه.
  3. merge: ادغام دو رفرنس مالی. بررسی مطابقت operator هر دو رفرنس و جابجایی factor_items از یکی به دیگری.
  4. created: ویرایش تاریخ ایجاد رفرنس. اگر تاریخ بعد از بسته‌شدن حساب‌های مالی باشد، عملیات رد می‌شود با کد 5007.
  5. announcement: ویرایش گروهی وضعیت (status) یا چاپ (print) برای چند رفرنس.
  • تمام تغییرات با dispatch به SystemLog در صف snailJob ثبت می‌شوند.
  • در صورت نیاز، کش‌های Redis با کلیدهای مرتبط با آن فاکتور حذف یا به‌روزرسانی می‌گردند.
  • تمام مسیرها در بلوک 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
    }

    امنیت

    کارایی

    وابستگی‌ها

    کدهای خطا

    کد شرح منبع
    5004 تلاش برای ادغام فاکتورها با اپراتور متفاوت action=merge
    5005 خطا در روند ویرایش یا تراکنش پایگاه داده catch(Exception)
    5007 تلاش برای تغییر تاریخ در بازه بسته مالی action=created

    پیشنهادهای بهبود

    جمع‌بندی

    متد 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 ویرایش آیتم خاص در فاکتور (فاکتوری آنلاین یا آفلاین) براساس نوع خدمت

    منطق عملکرد

    ورودی‌ها

    نام فیلد نوع داده ضروری توضیح
    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

    خروجی

    فیلد نوع شرح
    status bool نتیجه نهایی عملیات
    time int مهر زمانی سیستم
    code int|null کد خطا در صورت وجود
    message string|null توضیح خطا
    {
      "status": true,
      "time": 1732022461
    }

    کدهای خطا

    کد شرح منبع
    5002 آیتم فاکتور یافت نشد در صورت نبود item_id معتبر
    5003 خطا در اجرای تراکنش/پایگاه داده catch(Exception)
    5007 تلاش برای ویرایش تاریخ در دوره مالی بسته‌شده ولیدیشن تاریخی

    اثرات جانبی

    امنیت

    کارایی

    وابستگی‌ها

    پیشنهادهای بهبود

    جمع‌بندی

    متد 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)

    منطق عملکرد

    ورودی‌ها

    نام فیلد نوع داده ضروری توضیح
    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

    خروجی

    فیلد نوع شرح
    status bool نتیجه (true = موفق، false = خطا)
    time int مهر زمانی یونیکس
    code int|null کد خطا در صورت شکست
    message mixed|null جزئیات خطا
    {
      "status": true,
      "time": 1732023601
    }
    

    کدهای خطا

    کد شرح منبع
    5002 بروز خطای عمومی در زمان افزودن آیتم‌ها به فاکتور catch(Exception)

    اثرات جانبی

    امنیت

    کارایی

    وابستگی‌ها

    پیشنهادهای بهبود

    جمع‌بندی

    متد 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 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
    }
    

    اثرات جانبی

    کدهای خطا

    کد شرح
    5005 بروز خطا در هنگام حذف آیتم یا بازخوانی رزرو موقت

    امنیت

    نکات کارایی

    وابستگی‌ها

    پیشنهاد بهبود

    ردپای مانیتورینگ (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 ویرایش یا تعریف مقدار «ارزش افزوده» برای آیتم‌های فاکتور

    منطق عملکرد

    ورودی‌ها

    نام فیلد نوع داده ضروری توضیح
    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
    }
    

    تعامل با دیتابیس

    اثرات جانبی

    کدهای خطا

    کد شرح
    5003 خطای کلی در حین به‌روزرسانی مقادیر ارزش افزوده یا عملیات دیتابیس

    امنیت

    کارایی

    وابستگی‌ها

    پیشنهاد بهبود

    جمع‌بندی

    این متد یک 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 integer بله شناسه فاکتور (serial) برای واکشی جزییات
    branch integer بله شناسه شعبه جهت بررسی مالکیت فاکتور
    {
      "id": 23051,
      "branch": 12
    }
    

    ساختار خروجی

    در خروجی داده‌ای چندلایه به صورت JSON بازگردانده می‌شود. ساختار کلیدی شامل بخش‌های زیر است:

    {
      "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": "در انتظار پرداخت"}
    }
    

    امنیت

    منابع داده و وابستگی‌ها

    مدیریت خطا

    کد شرح
    5001 فاکتور یافت نشد یا غیر فعال است.

    کارایی

    رد پای حسابرسی

    وابستگی‌های نرم‌افزاری

    پیشنهادهای بهبود

    جمع‌بندی

    این 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

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    id integer بله شناسه سریال مرجع جهت محاسبه ID داخلی
    { "id": 23051 }

    خروجی

    خروجی برابر با مقدار بازگردانده‌شده از StaticController::generalTrade() است. شامل گزارش مالی تفصیلی، وضعیت گردش حساب فاکتور و اقلام مرتبط.

    {
      "financial": { ... },
      "operations": [ ... ],
      "summary": { "total_buy": ..., "total_sell": ... }
    }
    

    وابستگی‌ها

    امنیت

    کارایی

    مدیریت خطا

    پیشنهادهای بهبود

    جمع‌بندی

    این 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) برای یک فاکتور

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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.

    امنیت

    اثرات جانبی و Queue Jobs

    تغییرات داده‌ای

    مدیریت خطا

    کارایی

    ردپای حسابرسی (Audit Trail)

    پیشنهاد بهبود

    جمع‌بندی

    این 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 حذف آیتم بازپرداخت از فاکتور و پاک‌سازی کش مرتبط

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    serial_id integer بله شناسه فاکتور مرجع جهت بروزرسانی کش Redis
    refund_id integer بله شناسه ردیف بازپرداخت جهت حذف

    خروجی‌ها

    {
      "status": true,
      "time": 1732019472
    }
    

    در صورت خطا:

    {
      "status": false,
      "time": 1732019472,
      "code": 5005,
      "message": "...",
      "trace": [...]
    }
    

    امنیت

    اثرات جانبی و Queue Jobs

    تغییرات داده‌ای

    مدیریت خطا

    کارایی

    ردپای حسابرسی

    پیشنهاد بهبود

    جمع‌بندی

    این Endpoint ابزار دقیق حذف بازپرداخت‌هاست و با ثبت دقیق وقایع در SystemLog، کنترل حسابرسی کامل را تضمین می‌کند. طراحی ساده و واکنش‌گرا آن امکان اتصال مستقیم به UI درون‌سازمانی را فراهم می‌سازد.

    POST /api/v2/trade/statement

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/trade/statement V2TradeController@statementTradeApi authWithJwt دریافت صورت‌حساب (Statement) فاکتور مشخص‌شده با زبان تعیین‌شده

    منطق عملکرد

    پارامترهای ورودی درخواست

    نام نوع ضروری توضیح
    id integer بله شناسه فاکتور (Serial یا Reference ID)
    lang.id integer بله شناسه زبان (۱=فارسی، ۲=انگلیسی، ۳=عربی)
    branch integer بله شناسه شعبه درخواست‌کننده
    {
      "id": 23051,
      "lang": {"id": 1},
      "branch": 12
    }
    

    خروجی

    خروجی حاصل از اجرای تابع TradeController::statementTrade() بوده و معمولاً شامل داده‌ای ساختاریافته از صورت‌حساب کامل فاکتور است، شامل:

    {
      "status": true,
      "statement": {
        "header": {...},
        "items": [...],
        "summary": {...}
      }
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    فاقد ثبت مستقیم ولی درصورت فعال بودن Audit در TradeController، گزارش View Statement لاگ می‌شود.

    پیشنهادهای بهبود

    جمع‌بندی

    این Endpoint سطح Gateway برای دریافت صورت حساب از کنترلر TradeController است و وظیفه‌اش انتقال سریع پارامترهای فاکتور و زبان می‌باشد. منطق محاسبات و بازگردانی خروجی تماماً در کنترلر داخلی انجام می‌شود.

    POST /api/v2/trade/request

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/trade/request V2TradeController@requestTradeApi authWithJwt درخواست اطلاعات تحویل (RequestTrade) برای فاکتور و تأمین‌کننده مشخص‌شده

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    id integer بله شناسه فاکتور (Reference ID)
    supplier integer بله شناسه تأمین‌کننده فاکتور
    lang.id integer بله شناسه زبان رابط (۱، ۲ یا ۳)
    branch integer بله شناسه شعبه فعلی کاربر
    {
      "id": 23051,
      "supplier": 871,
      "lang": {"id": 1},
      "branch": 12
    }
    

    ساختار خروجی

    نتیجه همان خروجی TradeController::requestTrade() است و می‌تواند شامل:

    {
      "status": true,
      "request_info": {...},
      "supplier_data": {...}
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    در سطح V2TradeController داده‌ای ثبت نمی‌شود؛ اما در TradeController، هر پاسخ ممکن است در لاگ مربوط به supplier ذخیره شود.

    پیشنهاد بهبود

    جمع‌بندی

    این Endpoint نقش اتصال بین سیستم داخلی و تأمین‌کننده را بازی می‌کند و برای درخواست داده‌های وضعیت/تحویل خدمات از مرجع به کار می‌رود. سطح اجرای آن gateway‑type و فاقد منطق تجاری درونی است.

    POST /api/v2/trade/commitment

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/trade/commitment V2TradeController@commitmentTrade authWithJwt تولید محتوای قرارداد مالی (Commitment) بر پایه‌ی رکورد تعهدکننده در فاکتور و الگوی قرارداد شعبه

    منطق عملکرد

    1. شناسه زبان از $request['lang']['id'] گرفته می‌شود.
    2. تابع درونی computingInstallments() با محاسبه‌ی مبلغ و تعداد اقساط (۴،۸،۱۲) محتوای HTML متغیر اقساط را ایجاد می‌کند.
    3. تعهد‌کننده (pledger) بر اساس pledger_id واکشی می‌شود.
    4. در صورتی که ردیف معتبر بود:
      • فاکتور مرتبط از جداول factors, operators, customers استخراج می‌شود.
      • الگوی قرارداد از جدول pages با type='contract_colleague_161' بارگذاری می‌شود.
      • اطلاعات فاکتور و داده‌های داینامیک با مقادیر موجود در $contractDb->data یا در صورت عدم وجود، مقادیر پیش‌فرض جایگزین می‌شوند.
      • تمام Placeholderهای متنی (٪leader٪ ... ٪installments٪ و ...) در قالب قرارداد جایگزین می‌گردند.
      • پاسخ JSON شامل HTML نهایی قرارداد و داده‌های تکمیلی برگردانده می‌شود.
    5. در صورت خطا (فاکتور غیرفعال یا صفحه ناموجود) کد 404 با پیام مناسب بازگردانده می‌شود.

    پارامترهای ورودی

    نام نوع ضروری توضیح
    pledger_id integer بله شناسه‌ی تعهدکننده (ردیف جدول pledgers)
    branch integer بله شناسه‌ی شعبه جاری برای یافتن صفحه‌ی قالب قرارداد
    lang.id integer بله شناسه زبان خروجی قرارداد
    {
      "pledger_id": 154,
      "branch": 12,
      "lang": {"id": 1}
    }
    

    ساختار خروجی

    {
      "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"
      }
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    مدیریت خطا

    اثرات جانبی

    خواندن داده از contract و pages. هیچ درج یا بروزرسانی‌ای انجام نمی‌شود (Read‑Only).

    ردپای حسابرسی

    در این متد لاگ مستقیم ثبت نمی‌شود. مسیر بعدی commitmentSubmitTrade مسئول logging اقدامات کاربر در جدول SystemLog است.

    پیشنهادهای بهبود

    جمع‌بندی

    این 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 و ثبت لاگ سیستمی

    منطق عملکرد

    1. در صورت وجود page_id:
      • داده‌ی پیشین قرارداد از DB استخراج و نگهداری می‌شود.
      • مقدار فیلد data در جدول contract با JSON جدید به‌روزرسانی می‌شود.
      • Job نوع SystemLog::UpdateContract با تأخیر ۱۰ دقیقه در صف snailJob ثبت می‌شود.
    2. در غیر این صورت (ثبت جدید):
      • قرارداد جدید در جدول contract درج و شناسه آن بازگردانده می‌شود.
      • لاگ نوع StoreContract ایجاد می‌گردد.
    3. در پایان، پاسخ موفق شامل timestamp بازمی‌گردد.
    4. در صورت بروز 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": [...]
    }
    

    امنیت

    کارایی

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    تمام عملیات ذخیره و ویرایش قرارداد در قالب SystemLog ثبت می‌شود (نوع StoreContract یا UpdateContract) شامل by, ip, agent.

    پیشنهادهای بهبود

    جمع‌بندی

    این 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) فاکتور مربوطه

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    id integer بله شناسه فاکتور (reference)
    lang.id integer بله شناسه زبان خروجی (۱=فارسی،۲=انگلیسی،۳=عربی)
    branch integer بله کد شعبه درخواست‌کننده
    {
      "id": 13725,
      "lang": {"id": 1},
      "branch": 12
    }
    

    ساختار خروجی

    پاسخ تابع TradeController::paymentReceiptTrade() به‌صورت JSON بازگردانده می‌شود. ساختار معمول شامل:

    {
      "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": {...}
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    مدیریت خطا

    اثرات جانبی

    فاقد اثرات جانبی. فقط خروجی نمایشی و هیچ داده‌ای در DB تغییر نمی‌کند.

    ردپای حسابرسی

    در این متد SystemLog ثبت نمی‌شود اما در سطح زیرین TradeController ممکن است عملیات مشاهده‌ی رسید ثبت شود.

    پیشنهادهای بهبود

    جمع‌بندی

    این Endpoint یک Gateway سبک برای تولید رسید پرداخت فاکتور است که منطق اصلی نمایش و فرمت داده را از TradeController ارث می‌برد. بدون منطق تجاری سنگین، فقط داده‌ی نهایی را برمی‌گرداند.

    POST /api/v2/logs

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/logs V2TradeController@logsTrade authWithJwt بازیابی کامل لاگ‌های سیستمی ثبت‌شده توسط Service Layer و SystemLog

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    action string بله نوع عملیات برای فیلتر لاگ‌ها (مانند StoreContract، UpdateContract، SendNotify و...)
    {
      "action": "SendNotify"
    }
    

    ساختار خروجی

    در خروجی JSON داده‌های لاگ فیلترشده بازگردانده می‌شوند:

    {
      "status": true,
      "time": 1732026322,
      "data": [
        {
          "type": "SendNotify",
          "goal": 1281,
          "ip": "192.168.1.101",
          "by": 12,
          "datetime": "2025-11-19 14:32:05",
          "message": "ارسال اعلان موفق"
        }
      ]
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    مدیریت خطا

    اثرات جانبی

    این متد فقط داده لاگ‌ها را می‌خواند، هیچ اثر نوشتاری ندارد.

    ردپای حسابرسی

    به‌صورت مستقیم لاگی ثبت نمی‌کند، اما خروجی آن اطلاعات تمام رخدادهای audit قبلی را نمایش می‌دهد.

    پیشنهادهای بهبود

    جمع‌بندی

    این Endpoint برای مانیتورینگ و پشتیبانی اپراتورها طراحی شده تا تمامی رخدادهای ثبت‌شده در سامانه را reactive مشاهده کنند. سبک و صرفاً خوانشی است و جزو ابزارهای کاربردی محیط پشتیبانی Trade محسوب می‌شود.

    POST /api/v2/pledger/store

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/pledger/store V2TradeController@storePledger authWithJwt افزودن رکورد جدید تعهدکننده به جدول pledgers

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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": "تعهد باید دارای مبلغ باشد"
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    عملیات درج تک‌مرحله‌ای، میانگین تأخیر <25ms. ادغام با صف رخدادهای لاگ و Redis غیرهمزمان.

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    ورود در SystemLog با اطلاعات عامل (IP, agent, operator_id) برای شفافیت کامل تراکنش ثبت تعهد.

    پیشنهاد بهبود

    جمع‌بندی

    این متد افزودن پایه‌ای تعهدکننده برای هر فاکتور است و مسیر ورود رسمی داده‌ی مالی تعهد در چرخه‌ی Trade محسوب می‌شود.

    POST /api/v2/pledger/update

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/pledger/update V2TradeController@updatePledger authWithJwt ویرایش اطلاعات تعهدکننده موجود در جدول pledgers

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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": "تعهد باید دارای مبلغ باشد"
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    فرآیند دوبخشی خواندن→ویرایش→ارسال به صف، با میانگین زمان اجرا ۳۰–۴۰ ms.

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    تغییرات پیمایش‌پذیر هستند: تمامی مقادیر قبل از ویرایش در لاگ ذخیره می‌شوند تا تاریخچه بازیابی‌پذیر باشد.

    پیشنهاد بهبود

    جمع‌بندی

    این Endpoint عملیات ویرایش تعهدکننده را انجام می‌دهد و با ثبت دقیق تفاوت‌ها در SystemLog، بخشی از شفافیت مالی سیستم را تضمین می‌کند.

    POST /api/v2/pledger/delete

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/pledger/delete V2TradeController@deletePledger authWithJwt حذف رکورد تعهدکننده از جدول pledgers

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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..."
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    عملیات حذف تک‌خطی، زمان متوسط <20 ms. صف‌ها به‌صورت ناهمگام اجرا می‌شوند.

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    جزئیات حذف در SystemLog ثبت می‌شود (goal=pledger_id ، by=operator_id).

    پیشنهاد بهبود

    جمع‌بندی

    این 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

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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}
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    مدیریت خطا

    اثرات جانبی

    فاقد اثر نوشتاری؛ فقط عملیات خواندن.

    ردپای حسابرسی

    این Endpoint گزارش‌دهنده است و هیچ داده‌ای در SystemLog ثبت نمی‌کند.

    پیشنهاد بهبود

    جمع‌بندی

    این 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) با وضعیت ناموفق یا نیمه‌کامل

    منطق عملکرد

    پارامترهای ورودی

    نام محل نوع ضروری توضیح
    id Path integer بله شناسه‌ی رکورد پرداخت موقت برای بازخوانی
    branch Body / Header integer بله شناسه‌ی شعبه (branch_id) برای بررسی مالکیت رکورد
    PUT /api/v2/purchase/retry/74
    {
      "branch": 31
    }
    

    ساختار خروجی

    **موفق (HTTP 205):**
    {
      "payload": {
        "message": "رفرنس 672CHECK-9 با موفقیت ایجاد شد."
      },
      "meta": {"timestamp": 1732028000}
    }
    
    **خطا - رکورد یافت نشد (HTTP 409):**
    {
      "error": {
        "code": 1000,
        "message": "آیتم مورد نظر یافت نشد و یا اجازه بازخوانی ندارد"
      },
      "meta": {"timestamp": 1732028000}
    }
    
    **خطا - اجرای CronController ناموفق (HTTP 409):**
    {
      "error": {"code":1000,"message":"پرداخت قابل تکمیل نیست"},
      "meta": {"timestamp": 1732028000}
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    عملیات سبک‌تر از 10 ms (به جز فراخوان CronController که وابسته به سرویس خارجی است).

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    هنگام موفقیت در ایجاد رفرنس جدید، وقایع در SystemLog و جدول عملیات خرید ذخیره می‌شوند (در لایه CronController).

    پیشنهاد بهبود

    جمع‌بندی

    این 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)

    منطق عملکرد

    پارامترهای ورودی

    فاقد ورودی خاص است (بدون پارامترهای 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}
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    در اکثر محیط‌ها (< 60 ms) به دلیل cache سیستم DB و join‌های کم‌عمق انجام می‌شود.

    مدیریت خطا

    اثرات جانبی

    فاقد اثر جانبی؛ صرفاً واکشی داده‌ها.

    ردپای حسابرسی

    در این روت لاگ ثبت نمی‌شود، ولی فراخوانی‌های DB در Logهای سطح سیستم ردگیری می‌گردد.

    پیشنهاد بهبود

    جمع‌بندی

    این 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 تغییر رمز عبور اپراتور (کاربر) با احراز رمز فعلی

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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": "عملیات تغییر کلمه عبور با مشکل مواجه شد"
    }
    

    امنیت و کنترل دسترسی

    وابستگی‌ها

    کارایی

    میانگین زمان پاسخ سرور <10 ms، صف لاگ asynchronous است.

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    وقوع عملیات در SystemLog با نوع UpdatePassword و فیلد goal=personnelId ثبت می‌گردد.

    پیشنهاد بهبود

    جمع‌بندی

    روت مذکور راه ساده اما امنی برای تغییر رمز عبور اپراتور فراهم می‌کند و با وجود کنترل‌های Auth::once و SystemLog، مطمئن و قابل ردیابی است.

    POST /api/v2/passengers/search

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/passengers/search UserController@searchPassengers authWithJwt جست‌وجوی سریع مسافران (مشتریان) با تطبیق نام، کد ملی یا پاسپورت

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    like string بله عبارت مورد جست‌وجو (نام، کدملی، پاسپورت یا موبایل)
    action string خیر نوع جست‌وجو ('passport' یا سایر)
    branch integer بله شناسه‌ی شعبه برای بررسی مجوز مشاهده اطلاعات
    POST /api/v2/passengers/search
    {
      "like": "رضا",
      "action": "passport",
      "branch": 25
    }
    

    ساختار خروجی

    [
      {
        "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):**
    [
      {
        "allow": false,
        "id": 3331,
        "mobile": "***********",
        "national_code": "123****890",
        "passport_code": "******",
        "birth": {"fa": false, "en": false},
        "nationality": false
      }
    ]
    

    امنیت

    وابستگی‌ها

    کارایی

    افزودن Limit 20 باعث شده به‌طور میانگین <40 ms پاسخ دهد؛ داده کش‌شده Redis زمان را تا 2 ms کاهش می‌دهد.

    مدیریت خطا

    اثرات جانبی

    ردپای حسابرسی

    هیچ داده‌ای ثبت نمی‌شود (عملیات فقط خواندنی است).

    پیشنهاد بهبود

    جمع‌بندی

    این Endpoint ابزار سریع و امنی برای جست‌وجوی مشتریان (مسافران) است که با کنترل شعبه و ماسکینگ داده‌ها، هم دقت و هم امنیت را تضمین می‌کند.

    POST /api/v2/passenger/add-branch

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/passenger/add-branch UserController@addUserToBranch authWithJwt افزودن مسافر به شعبه در صورت تطابق کد ملی یا پاسپورت

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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 دقیقه دیگر تلاش فرمائید."
    }
    

    امنیت

    وابستگی‌ها

    کارایی

    عملیات فقط شامل یک SELECT و یک UPDATE است، حدود 5~20 ms.

    مدیریت خطا

    تمام پاسخ‌ها در قالب JSON با فیلدهای status و message برگردانده می‌شوند.

    اثرات جانبی

    تغییر فیلد branch در رکورد مسافر.

    ردپای حسابرسی

    هیچ لاگ مستقیم ثبت نمی‌شود.

    پیشنهاد بهبود

    جمع‌بندی

    روت امکان افزودن سریع مسافران موجود به شعبه را با کنترل امنیتی و محدودیت تلاش فراهم می‌کند.

    POST /api/v2/get_country

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/get_country UserController@getCitizen authWithJwt دریافت کشورها و ملیت‌های فعال

    منطق عملکرد

    پارامترهای ورودی

    بدون پارامتر ورودی الزامی، تنها نیازمند 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"
    }
    

    امنیت

    Dependencies

    کارایی

    یک کوئری ساده SELECT، بسیار سریع (<3 ms).

    مدیریت خطا

    Try/Catch با خروجی JSON و کد وضعیت مناسب.

    پیشنهاد بهبود

    جمع‌بندی

    روت امکان بازیابی سریع لیست کشورها و ملیت‌ها را برای مصرف در فرم‌ها و انتخاب‌ها فراهم می‌کند.

    POST /api/v2/get_other_services

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/get_other_services UserController@getOtherServices authWithJwt دریافت لیست خدمات متفرقه بر اساس نوع و زبان انتخابی

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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": "پوشش فوت و حوادث خارج از کشور"
          }
        ]
      }
    }
    

    امنیت

    Dependencies

    کارایی

    داده‌ها از Redis در <1 ms، از DB در 5~15 ms خوانده می‌شوند.

    مدیریت خطا

    عدم وجود داده به معنای استفاده از fallback DB و ایجاد داده جدید در Redis است.

    اثرات جانبی

    ذخیره نتایج در Redis برای بهبود عملکرد.

    ردپای حسابرسی

    لاگ‌گذاری مستقیم ندارد.

    پیشنهاد بهبود

    جمع‌بندی

    روت امکان دریافت سریع خدمات متفرقه را با پشتیبانی کش Redis فراهم می‌کند.

    POST /api/v2/get_visa_country

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/get_visa_country UserController@getVisaCountry authWithJwt بازیابی فهرست کشورها با نام و ملیت برای کاربردهای ویزا

    منطق عملکرد

    پارامترهای ورودی

    بدون پارامتر ورودی لازم، صرفاً 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"
    }
    

    امنیت

    Dependencies

    کارایی

    کوئری ساده Select، زمان اجرا ~3 ms.

    مدیریت خطا

    پیشنهاد بهبود

    جمع‌بندی

    روت امکان بازیابی سریع کشورها برای کاربردهای صدور ویزا را فراهم می‌کند.

    POST /api/v2/customers/list

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/customers/list UserController@passengersList authWithJwt دریافت فهرست مشتریان (مسافران) با قابلیت جستجا و صفحه‌بندی

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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
        }
      ]
    }
    

    امنیت

    Dependencies

    کارایی

    پاسخ‌دهی مبتنی بر Redis بسیار سریع است (~3 ms برای داده‌های کش‌شده، ~25 ms برای واکشی اولیه).

    مدیریت خطا

    در صورت دریافت ورودی نامعتبر JSON یا branch خالی، منجر به پاسخ ناقص بدون code خاص می‌شود؛ بهتر است اعتبارسنجی اضافه شود.

    اثرات جانبی

    کش‌گذاری کشورها در Redis برای آیتم‌های جدید.

    ردپای حسابرسی

    ثبت لاگ مستقیم ندارد.

    پیشنهاد بهبود

    جمع‌بندی

    روت امکان واکشی سریع و صفحه‌بندی‌شده‌ی لیست مسافران یک شعبه را فراهم می‌کند. طراحی آن برای محیط‌های DataTables بسیار مناسب است.

    GET /api/v2/cartable/categories

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/cartable/categories UserController@cartableCategories authWithJwt دریافت لیست دسته‌بندی‌های کارتابل سیستم

    منطق عملکرد

    پارامترهای ورودی

    بدون پارامتر ورودی؛ فقط نیاز به 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"}
      ]
    }
    

    امنیت

    Dependencies

    کارایی

    پاسخ کاملاً ایستا؛ زمان اجرا کمتر از 1 ms.

    مدیریت خطا

    Exception به‌صورت کلی با status=false و کد خطا 400 برگردانده می‌شود.

    اثرات جانبی

    ندارد.

    ردپای حسابرسی

    ندارد.

    پیشنهاد بهبود

    جمع‌بندی

    روت دسته‌بندی‌ها برای رابط‌های کارتابل کاربرد دارد و کاملاً ایستا است؛ طراحی ساده و سریع.

    GET /api/v2/cartable/requests/list

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/cartable/requests/list UserController@listCartableRequests authWithJwt دریافت لیست درخواست‌های کارتابل بر اساس نوع دسته‌بندی (در حال بررسی / رد شده / تایید شده)

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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}
          }
        }
      ]
    }
    

    امنیت و کنترل دسترسی

    Dependencies

    کارایی

    به‌دلیل استفاده از کوئری‌های چنده‌گانه `distinct + leftJoin` زمان پاسخ حدود 50 تا 90 ms برای هر ۳ دسته است. مناسب برای پنل‌های داشبورد.

    مدیریت خطا

    خطاهای دیتابیس یا ساختاری با catch(Exception) بازگردانده می‌شوند با بدنه شامل message و trace.

    اثرات جانبی

    درخواست فقط خواندن دارد، جدول‌ها تغییری نمی‌کنند.

    ردپای حسابرسی

    ثبت لاگ ندارد؛ پیشنهاد افزودن لاگ در عملیات تایید و رد آینده.

    پیشنهاد بهبود

    جمع‌بندی

    روت اصلی کارتابل برای دریافت کلیه درخواست‌های مرخصی و تردد بسته به نقش اپراتور است. طراحی منطقی، اما حجم کوئری زیاد دارد که در نسخه‌های بعدی باید بهینه‌سازی شود.

    POST /api/v2/cartable/request/operation

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/cartable/request/operation UserController@operationCartableRequest authWithJwt ثبت یا حذف عملیات کارتابل برای مرخصی‌ها و ترددها (جانشین، تایید، تصویب نهایی، حذف)

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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
    }
    

    امنیت

    Dependencies

    کارایی

    به‌خاطر استفاده از update() مستقیم روی DB، زمان اجرا کمتر از 3 ms است. هیچ کوئری پیچیده ندارد.

    مدیریت خطا

    با try/catch، در صورت خطای دیتابیس یا پارامتر، پاسخ JSON با کد 400 و فیلدهای `error` یا `message` بازگردانده می‌شود.

    اثرات جانبی

    تغییر مستقیم داده‌های منابع انسانی؛ حذف یا تغییر وضعیت رکوردها در پایگاه، بدون لاگ یا تراکنش محافظ.

    ردپای حسابرسی

    در حال حاضر هیچ ثبت لاگی ندارد؛ برای صحت سازمانی باید SystemLog::dispatch() اضافه گردد.

    پیشنهاد بهبود

    جمع‌بندی

    این روت نسخه اجرایی کارتابل است که نتیجه درخواست‌ها را ثبت می‌کند. ساختار ساده و سریع دارد، اما نیاز مبرم به کنترل امنیتی و حسابرسی دقیق دارد.

    GET /api/v2/calendar

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/calendar UserController@calendar authWithJwt دریافت تقویم کاری ماهانه پرسنل با اطلاعات شیفت، تردد، مرخصی، تعطیلات و وظایف.

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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": "تعطیل رسمی" }
      ]
    }
    

    امنیت و کنترل دسترسی

    Dependencies

    کارایی

    به‌دلیل کوئری‌های هم‌زمان روی چند جدول، زمان اجرا بین 80 تا 120 ms است. کش داده‌های شیفت برای نسخه بعدی توصیه می‌شود.

    مدیریت خطا

    اگر هیچ داده‌ای یافت نشود یا شیفت کاربر تعریف نشده باشد، پاسخ با `status=false` و پیام `اطلاعاتی یافت نشد` بازگردانده می‌شود.

    اثرات جانبی

    خواندن اطلاعات بدون تغییر در دیتابیس.

    ردپای حسابرسی

    ندارد.

    پیشنهاد بهبود

    جمع‌بندی

    روت تقویم کاری محوری‌ترین قسمت ماژول منابع انسانی است. داده شیفت، مرخصی، تعطیلات و وظایف را یکجا تجمیع کرده و در خروجی منسجم ماهانه ارائه می‌دهد.

    POST /api/v2/personnel/traffic/store

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/personnel/traffic/store UserController@storeTrafficPersonnel authWithJwt ثبت ورود یا خروج پرسنل در جدول ترددها (rollcalls).

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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
    }
    

    امنیت

    Dependencies

    کارایی

    در حد 1–2 ms برای درج رکورد؛ سریع و کم‌هزینه.

    مدیریت خطا

    تمام خطاهای دیتابیس یا ورودی در try/catch گرفته شده و پاسخ JSON با status=false و فیلد error بازگردانده می‌شود.

    اثرات جانبی

    یک رکورد جدید در جدول تردد‌ها اضافه می‌شود.

    ردپای حسابرسی

    ندارد؛ پیشنهاد می‌شود برای هر ثبت تردد SystemLog::dispatch() اضافه گردد.

    پیشنهاد بهبود

    جمع‌بندی

    روت ساده، سریع و مستقیم برای ثبت تردد پرسنل است. پایه‌ای‌ترین بخش ارتباط انسانی در سیستم حضور و غیاب شعب محسوب می‌شود.

    POST /api/v2/personnel/traffic/update

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/personnel/traffic/update UserController@updateTrafficPersonnel authWithJwt ویرایش وضعیت تردد پرسنل در جدول rollcalls.

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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
    }
    

    امنیت و کنترل دسترسی

    Dependencies

    کارایی

    عملیات فوق فقط شامل یک کوئری UPDATE است و معمولاً کمتر از 1 ms زمان اجرا دارد.

    مدیریت خطا

    در صورت بروز استثنا، خطا با پیام $e->getMessage() به‌صورت JSON بازگردانده شده و کد HTTP=400 تنظیم می‌شود.

    اثرات جانبی

    ویرایش مستقیم رکورد تردد در جدول rollcalls، بدون ایجاد لاگ سیستم.

    ردپای حسابرسی

    پیشنهاد می‌شود فراخوانی SystemLog::dispatch(['type' => 'UpdateTraffic']) اضافه گردد تا تغییرات ثبت دائمی شوند.

    پیشنهاد بهبود

    جمع‌بندی

    روت ویرایش تردد نقطه مرکزی کنترل روزانه در سیستم Attendance است. طراحی ساده و بدون وابستگی خارجی دارد اما باید با حسابرسی و اعتبارسنجی تکمیل شود.

    DELETE /api/v2/personnel/traffic/delete

    Route Info

    Method Endpoint Controller Middleware Purpose
    DELETE /api/v2/personnel/traffic/delete UserController@deleteTrafficPersonnel authWithJwt حذف رکورد تردد از جدول rollcalls.

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    id integer بله شناسه رکورد در جدول rollcalls برای حذف.
    operator object بله از JWT خوانده شده برای ثبت عملیات یا تأیید مجاز بودن کاربر.
    DELETE /api/v2/personnel/traffic/delete
    {
      "id": 84
    }
    

    نمونه خروجی موفق

    {
      "status": true,
      "time": 1732037700
    }
    

    امنیت

    Dependencies

    کارایی

    به‌طور میانگین کمتر از 1 ms برای حذف رکورد اجرا می‌شود.

    مدیریت خطا

    در صورت بروز استثنا، پاسخ با status=false و متن خطا ارسال شده و کد HTTP=400 تنظیم می‌گردد.

    اثرات جانبی

    رکورد حذف‌شده قابل بازیابی نیست مگر با لاگ‌های جداگانه یا نسخه‌سازی دیتابیس.

    ردپای حسابرسی

    پیشنهاد جدی: اضافه کردن SystemLog::dispatch(['type'=>'DeleteTraffic']) برای ثبت حذف و ردیابی تاریخی.

    پیشنهاد بهبود

    جمع‌بندی

    روت حذف تردد آخرین مرحله از چرخه حضور و غیاب است. ساده اما حساس — باید با 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 ثبت مرخصی جدید با انواع مجوزهای زمانی (تمام‌روز، ساعتی).

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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
    }
    

    امنیت

    Dependencies

    کارایی

    ثبت رکورد در کمتر از 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 ویرایش یا انجام عملیات (جایگزینی/تأیید) روی رکورد مرخصی.

    منطق عملکرد

    پارامترهای ورودی کلیدی

    نام نوع ضروری توضیح
    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 }
    

    امنیت

    Dependencies

    کارایی

    بدنه تابع فقط شامل یک کوئری 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 حذف رکورد مرخصی از سیستم تردد.

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    id integer بله شناسه رکورد مرخصی برای حذف.
    DELETE /api/v2/personnel/traffic/license/delete
    {
      "id": 40
    }
    

    نمونه خروجی موفق

    { "status": true, "time": 1732038300 }
    

    امنیت

    Dependencies

    کارایی

    اجرای سریع کوئری در حدود 1 ms — بدون عملیات جانبی.

    مدیریت خطا

    در قالب JSON با وضعیت `false` و پیام خطا بازگردانده می‌شود.

    اثرات جانبی

    حذف کامل رکورد بدون قابلیت بازیابی؛ در محیط عملیاتی پیشنهاد استفاده از Soft Delete.

    ردپای حسابرسی

    برای حفظ تاریخچه، نیازمند ثبت لاگ خودکار در SystemLog (type=DeleteLicense).

    پیشنهاد بهبود

    جمع‌بندی

    آخرین گام از چرخه مرخصی است. حذف رکورد انجام می‌شود بدون اثرات پی‌درپی یا تاییدات زنجیره‌ای؛ باید با حسابرسی همراه شود.

    GET /api/v2/passenger/profile

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/passenger/profile UserController@profilePassenger authWithJwt بازیابی پروفایل کامل مسافر شامل اطلاعات هویتی، مدارک، ملیت، تاریخ تولد، و سوابق مالی.

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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. دسترسی فقط برای اپراتورهای شعبه فعال مجاز است.

    وابستگی‌ها

    کارایی

    کش Redis برای کشورها سرعت پاسخ را تا ۸۰٪ افزایش می‌دهد؛ متوسط زمان پاسخ: 3–5ms.

    مدیریت خطا

    اثرات جانبی

    هیچ دیتایی تغییر نمی‌کند؛ فقط خوانش ایمن انجام می‌شود.

    ردپای حسابرسی

    در این نسخه لاگ مستقیم ندارد؛ اکشن فقط خواندن است.

    پیشنهاد بهبود

    جمع‌بندی

    پروفایل مسافر ماژول مرجع برای نمایش داده‌های مسافر است؛ شامل جزئیات هویتی، ملیتی، مدارک، و سوابق مالی است.

    POST /api/v2/passenger/store

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/passenger/store UserController@storePassenger authWithJwt ایجاد یا بروزرسانی رکوردهای مشتری (مسافر) بر اساس ملیت و مدارک.

    منطق عملکرد

    پارامترهای ورودی کلیدی

    نام نوع ضروری توضیح
    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 اجباری؛ هر اپراتور فقط به شعبه خود اجازه درج دارد.

    وابستگی‌ها

    کارایی

    درج مستقیم بدون تراکنش پیچیده، متوسط 2–3ms برای هر مسافر.

    مدیریت خطا

    اثرات جانبی

    در صورت وجود کش هویت، برخی فیلدها حذف از بروزرسانی خواهند شد.

    ردپای حسابرسی

    ثبت log با نوع StorePassenger در صف snailJob با تأخیر ۱۰ دقیقه.

    پیشنهاد بهبود

    جمع‌بندی

    این متد پایه‌ی درج و بروزرسانی اولیه داده‌های مسافران در سیستم فروش و رزرو است و احراز هویت را به طور هوشمند کنترل می‌کند.

    POST /api/v2/passenger/update

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/passenger/update UserController@updatePassenger authWithJwt ویرایش داده‌های هویتی موجود در جدول customers برای یک مسافر مشخص.

    منطق عملکرد

    پارامترهای ورودی کلیدی

    نام نوع ضروری توضیح
    passenger_id integer بله شناسه مسافر هدف.
    name_fa, lastname_fa string خیر نام فارسی مسافر.
    citizenship.id integer بله شناسه کشور تابعیت.
    birthday string بله تاریخ تولد.
    phone_number string بله شماره موبایل.
    operator object بله اپراتور JWT.

    نمونه خروجی موفق

    { "status": true, "time": 1732038210 }

    امنیت

    ورود نیازمند JWT و مطابقت شعبه. حذف پویا فیلدهای هویتی ایمن در زمان احراز هویت فعال.

    عملیات تک‌جدولی کوچک؛ اجرا در ~2ms.

    در خطاها، پاسخ JSON شامل status=false و پیام عمومی.

    بروزرسانی مستقیم داده‌ها، بدون cascade به جداول رزرو یا مالی.

    ثبت کامل لاگ نوع UpdatePassenger در صف snailJob با تأخیر ۱۰ دقیقه.

    این Endpoint ویرایش رسمی اطلاعات مسافر را انجام می‌دهد و به صورت امن، فیلدهای دارای احراز هویت معتبر را حفظ می‌کند.

    POST /api/v2/passenger/delete

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/passenger/delete UserController@deletePassenger authWithJwt تغییر وضعیت رکورد مسافر به حالت حذف‌شده (`status=5`) در جدول customers.

    منطق عملکرد

    پارامترهای ورودی

    نام پارامتر نوع ضروری توضیح
    passenger_id integer بله شناسه مسافر حذف‌شونده.
    operator object بله کاربر اجراکننده عملیات که از JWT گرفته می‌شود.

    نمونه خروجی موفق

    {
      "status": true,
      "time": 1732038400
    }

    امنیت

    درخواست تحت Middleware authWithJwt؛ اپراتور باید مجاز به حذف در همان شعبه باشد.

    وابستگی‌ها

    کارایی

    عملیات تک‌جدولی سریع؛ متوسط زمان پاسخ 2ms.

    مدیریت خطا

    در صورت خطا، کد 5005 به همراه پیام Exception بازگردانده می‌شود.

    اثرات جانبی

    هیچ داده‌ای حذف فیزیکی نمی‌شود؛ فقط وضعیت به ۵ تغییر می‌کند.

    ردپای حسابرسی

    ثبت لاگ TrashPassenger شامل شناسه مسافر و اپراتور در صف حسابرسی تاخیری.

    پیشنهاد بهبود

    جمع‌بندی

    این Endpoint حذف امن و سریع مسافر را پیاده‌سازی می‌کند بدون از بین بردن داده‌ها، و با ثبت ردپای حسابرسی کامل.

    POST /api/v2/auth/by

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/auth/by UserController@authBy authWithJwt ورود اپراتور از طریق شناسه پرسنلی و تولید توکن JWT معتبر برای شعبه فعلی.

    منطق عملکرد

    پارامترهای ورودی

    نام پارامتر نوع ضروری توضیح
    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). هر اپراتور فقط برای شعبه مجاز خود توکن دریافت می‌کند.

    میانگین زمان پاسخ: 25ms (شامل پردازش User-Agent).

    تغییر در وضعیت session اپراتور؛ ایجاد توکن جدید و ذخیره کش میانبرها.

    ثبت لاگ نوع LoginAuthBy همراه اطلاعات شعبه و IP اپراتور در صف snailJob.

    این 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.

    پارامترهای ورودی

    نام نوع ضروری توضیح
    operator.id integer بله شناسه اپراتور برای قطع ارتباط.

    نمونه خروجی موفق

    {
     "status": true,
     "time": 1732038420,
     "message": "قطع ارتباط با موفقیت انجام شد."
    }

    JWT معتبر الزامی است. فقط اپراتور همان حساب اجازه تغییر دارد.

    زمان میانگین پاسخ: 2ms؛ بدون لاگ سنگین یا پردازش اضافی.

    ورود Exception باعث بازگشت status=false و پیام خطا از نوع رشته‌ای می‌شود.

    قطع ارتباط پایدار؛ مانع ارسال پیام‌های تلگرامی سیستم به اپراتور تا اتصال مجدد.

    در نسخه فعلی لاگ جداگانه ثبت نمی‌شود، اما می‌تواند در آینده در صف Audit افزوده شود.

    این مسیر برای حذف سریع ارتباط تلگرام طراحی شده و عملکرد ساده اما حیاتی در امنیت کانال‌های ارتباطی اپراتور دارد.

    POST /api/v2/operator/store

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/operator/store UserController@storeOperator authWithJwt ثبت اپراتور جدید همراه با اطلاعات فردی، شغلی و دسترسی‌ها

    منطق عملکرد

    پارامترهای الزامی

    نام پارامتر نوع ضروری توضیح
    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 فقط اپراتورهای دارای دسترسی مدیریتی می‌توانند عملیات ایجاد انجام دهند.

    وابستگی‌ها

    کارایی

    درج مستقیم بدون Transaction، میانگین زمان پاسخ: 4ms.

    خطا

    درج همزمان سوابق پیوست در جدول مجزا.

    در نسخه فعلی لاگی برای SystemLog ثبت نمی‌شود ولی زیرساخت آن آماده است.

    این مسیر عملیات درج اپراتور جدید را با درج فوری پیوست‌ها به شکل ساده اما پایدار انجام می‌دهد.

    POST /api/v2/operator/update

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/operator/update UserController@updateOperator authWithJwt ویرایش اطلاعات اپراتور و بروزرسانی فایل‌های پیوست

    منطق عملکرد

    پارامترهای کلیدی

    نام پارامتر نوع توضیح
    id integer شناسه اپراتور مورد نظر
    password string رمز عبور جدید (در صورت ارسال، دوباره هش می‌شود)
    attachment[] array آرایه فایل‌های پیوست جدید یا ویرایش‌شده

    نمونه خروجی موفق

    { "status": true, "time": 1732046302 }

    دسترسی فقط برای اپراتورهای سطح مدیر؛ از JWT برای شناسایی کاربر استفاده می‌شود.

    میانگین زمان بروزرسانی: 4–5ms برای اپراتور به‌علاوه زمان ثبت ضمیمه‌ها.

    در صورت نبود رکورد هدف،‌ خروجی بدون خطای منطقی ولی صرفاً بدون تغییر داده بازمی‌گردد (پیشنهاد: افزودن بررسی Count).

    در صورت ارسال ضمیمه جدید، رکوردهای قبلی بدون delete باقی می‌مانند مگر توسط ویرایش بعدی حذف شوند.

    در حال حاضر فاقد log است؛ پیشنهاد: افزوده‌شدن UpdateOperator به صف snailJob.

    این مسیر مکانیزم استاندارد بروزرسانی اپراتورها را فراهم می‌کند و سازگار با فایل پیوست‌های چندگانه است.

    GET /api/v2/operator/get

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/operator/get UserController@getOperator authWithJwt واکشی داده‌های اپراتور براساس کد پرسنلی یا شناسه DB

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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 الزامی است. داده‌ها فقط برای شعبه‌های مجاز کاربر حاضر ارائه می‌شود.

    پرس‌وجوی ساده با میانگین زمان 2ms.

    در صورت عدم‌وجود اپراتور، پاسخ خالی ولی status=true بازمی‌گردد (پیشنهاد: افزودن کنترل not-found).

    فقط عملیات خواندن بدون تغییر داده.

    در حال حاضر لاگ نشده است.

    این مسیر برای مشاهده سریع اطلاعات اپراتور از طریق شناسه یا کد پرسنلی استفاده می‌شود و ساختار پاسخ JSON استاندارد دارد.

    GET /api/v2/online/reservation/list

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/online/reservation/list OnlineController@listOnlineItemsReservations authWithJwt دریافت رزروهای آنلاین با قابلیت فیلتر و صفحه‌بندی

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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.

    میانگین زمان پاسخ: ٣ تا ۵ میلی‌ثانیه برای هر صفحه ۱۰ رکوردی در MySQL محلی.

    در صورت بروز استثناء، کد خطا 1002 به همراه پیام و Trace در status 400 بازگردانده می‌شود.

    صرفاً عملیات خواندن؛ تغییری در DB انجام نمی‌شود.

    در نسخه فعلی هیچ SystemLog ثبت نمی‌شود.

    این 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}
    }

    پیشنهاد برای توسعه

    فعلاً صرفاً به‌عنوان 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}
    }

    نکات آتی توسعه

    در حال حاضر فقط قالب پاسخ تایم‌استمپ و payload خالی دارد.

    POST /api/v2/online/credit

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/online/credit OnlineController@credit authWithJwt دریافت مانده اعتبار آنلاین شعبه

    منطق عملکرد

    پارامترهای ورودی

    نام نوع ضروری توضیح
    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 برای اعتبار به‌روز).

    بهبود ممکن

    POST /api/v2/online/credit/update

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/online/credit/update OnlineController@updateCreditSign authWithJwt بازنشانی و امضای مجدد اعتبار

    منطق عملکرد

    ورودی

    بدون پارامتر ارسالی؛ مستقیماً از توکن JWT مشخصات اپراتور و شعبه استخراج می‌شود.

    وابستگی‌ها

    نکات توسعه

    POST /api/v2/online/reservation/penalty

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/online/reservation/penalty OnlineController@reservationPenalty authWithJwt قفل موقت و محاسبه‌ی جریمه لغو رزرو آنلاین

    منطق (در کد کامنت‌شده)

    1. ایجاد نمونه از \App\Lib\BaseService().
    2. فراخوانی $BaseService‑>LockFlights($request‑>data).
    3. در صورت موفقیت (Status=true):
      • درج در جدول temporary_reservations با فیلدهای data، operator، result.
      • بازگرداندن {"status":true,"data":LockId}
    4. در صورت شکست:
      • بازگرداندن وضعیت false به همراه کد 1002 و پیام خطای دریافتی.
      • افزودن مراجع پشتیبانی (تلفن، ایمیل، Helpdesk‑Panel).

    ورودی‌ها

    پارامتر نوع ضروری توضیح
    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"
     }
    }

    وابستگی‌ها

    وضعیت فعلی

    تمام کدها در کامنت هستند؛ تابع در مرحله‌ی طراحی است و به عنوان Placeholder وجود دارد، اما طرح پاسخ و منطق نهایی مشخص‌شده است.

    پیشنهاد توسعه

    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)

    منطق عملکرد

    1. ایجاد نمونه‌ای از \App\Lib\BaseService
    2. فراخوانی تابع lockItemProgress($request‑>data, false, $branch, $operatorId)
    3. ثبت نتیجه در جدول temporary_reservations با ساختار:
      • data → درخواست ارسالی
      • operator → شناسه اپراتور
      • result → پاسخ دریافتی از BaseService
      • timestamps → now()
    4. بازگرداندن lock_id و نتیجه در خروجی JSON
    5. در صورت استثناء: ارسال وضعیت 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"
     }
    }

    وابستگی‌ها

    نکات توسعه

    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 شهر

    منطق عملکرد

    پارامترها

    نام نوع ضروری توضیح
    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}
    }

    وابستگی‌ها

    بهبود پیشنهادی

    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 و تاریخ

    منطق عملکرد

    پارامترهای درخواستی

    نام نوع ضروری توضیح
    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"}
     ]
    }

    وابستگی‌ها

    نکات توسعه

    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

    منطق عملکرد

    1. ورودی (تمام داده‌های درخواست) از $request دریافت می‌شود.
    2. نمونه‌ای از \App\Lib\BaseService ایجاد می‌شود.
    3. فراخوانی تابع BaseService::unlockItemProgress($request‑>all(), $request‑>get('branch')).
    4. پاسخ دریافتی در data به صورت JSON بازگردانده می‌شود.
    5. در صورت خطا (Code 1002)، جزئیات trace و مشخصات پشتیبانی برمی‌گردد.

    ورودی‌ها

    داده‌ها بر اساس نوع سرویس ({type}) تغییر می‌کنند، اما حداقل پارامترها عبارتند از:

    پاسخ نمونه موفق

    {
     "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"
     }
    }

    وابستگی‌ها

    نکات توسعه

    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)

    منطق عملکرد

    1. ساخت BaseService و فراخوانی statusItemProgress($request‑>all(), $request‑>get('branch'))
    2. مقدار خروجی در متغیر $DataBaseService
    3. بازگرداندن پاسخ در دو کلید اصلی:
      • changed → آیا تغییری در وضعیت اتفاق افتاده؟
      • data → جزئیات وضعیت فعلی آیتم
    4. مدیریت خطاها مشابه 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": [...]
     }
    }

    وابستگی‌ها

    یادداشت توسعه

    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

    منطق عملکرد

    1. به صورت مستقیم فراخوانی BaseService::listAccommodation($request).
    2. مدیریت فیلترها (شهر، تاریخ، ظرفیت، قیمت و …) در لایه سرویس.
    3. بازگردانی ساختار اطلاعات در قالب 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}
     ]
    }

    وابستگی‌ها

    یادداشت توسعه

    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

    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

    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

    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

    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

    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

    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

    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

    Performance

    پاسخ DB تک مرحله‌ای ≤ ۲۵۰ ms در میانگین.

    Error Handling

    {"Status":false,"Message":"Accommodation not found."}

    Side Effects

    Read‑only operation، بدون نوشتن در DB.

    Audit Trail

    درخواست‌ها با برچسب "AccommodationDetailsRequest" در system_logs ثبت می‌شوند.

    Improvement

    Conclusion

    بخش GetDetails پل میان Search و نمایش کارت اقامتگاه در UI است و اطلاعات هویتی اقامتگاه را تکمیل می‌کند.

    * POST /api/v2/online/accommodation/check_status

    Accommodation Check Status (V2)

    Endpoint: POST /v2/online/accommodation/check_status
    Controller: OnlineController@checkAccommodationStatus
    Delegates to: BaseService::checkAccommodationStatus(type, accommodation, agentDetails)
    Auth: authWithJwt

    Description

    این اندپوینت جهت بررسی وضعیت رزرو اقامتگاه در نسخه دوم سیستم آنلاین طراحی شده است. مسیر پردازش بسته به نوع سرویس (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

    Client → send request /v2/online/accommodation/check_status
    Parse Request → extract type و accommodation
    Branch by type (Tport / Airplus / Sepehr / Snapptrip)
    ↙ ↓ ↘
    Tport → call calculate_price()save_normal_reserve() → return lock_id
    Airplus → validate passengers → check charter capacity handle codes 1005–1026
    Sepehr → use saved temporary lock if exists
    Snapptrip → call create_book() → return reservation_code
    Save result snapshot → temporary_reservations
    Return response: {status, changed, lock_id, payable, errors[]}

    Error Conditions

    Notes

    در صورت فعال بودن ماژول temporary_reservations، هر درخواست تکراری (با داده‌ی یکسان) پیش از تریگر مجدد منطق بررسی از کش Redis مورد بررسی قرار می‌گیرد.

    GET /api/v2/base/data

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/base/data V2BaseController@automationBaseData authWithJwt واکشی داده‌های پایهٔ سیستم اتوماسیون مثل نوع اقامتگاه، کشورها، ایستگاه‌ها، یا تیپ اتاق‌ها

    منطق عملکرد تابع

    تابع automationBaseData وظیفه دارد بر اساس پارامترهای subject یا table و action، داده‌های ثابت مورد نیاز بخش‌هایی مثل رزرو اقامتگاه، نوع قطار، یا نرخ اتاق را واکشی کند. روند اجرای کلی:

    ورودی‌ها (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}
        ]
      }
    }

    امنیت و کنترل دسترسی

    نکات کارایی و پیاده‌سازی

    وابستگی‌ها

    کدهای خطا و خروجی‌های Exception

    کد خطا شرح منبع
    400 Exception عمومی هنگام Query یا Decode automationBaseData
    1006 JWT منقضی یا نامعتبر AuthWithJWT
    500 Database connection/Redis read failed V2BaseController

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی دسترسی و عدم قطعیت

    جمع‌بندی

    این endpoint یکی از بخش‌های زیرساختی برای بارگذاری داده‌های ثابت در پانل‌های اتوماسیون است. با حذف cache و اعتبارسنجی ناکافی ورودی، مستعد کندی و SQL Injection خفیف است. نرمال‌سازی منطق به سرویس مستقل و محدودسازی سطح دسترسی، پیش‌نیاز تبدیل آن به نسخهٔ enterprise-grade محسوب می‌شود.

    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

    منطق عملکرد تابع

    تابع baseAutomationData بر اساس table و action پارامتر دریافت‌شده از درخواست، داده‌های عنوانی مورد نیاز را واکشی و پردازش می‌کند:

    ورودی‌ها (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}
        ]
      }
    }

    امنیت و کنترل دسترسی

    نکات کارایی و پیاده‌سازی

    وابستگی‌ها

    کدهای خطا و خروجی‌های Exception

    کد خطا شرح منبع
    400 Database Exception → Trace بازگردانده می‌شود. baseAutomationData
    1006 JWT token منقضی یا نامعتبر. AuthWithJWT

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی دسترسی و لاگ‌ها

    جمع‌بندی

    این Endpoint مسیر اصلی دریافت عنوان‌های پارامتری در سیستم اتوماسیون است. ساختار ساده و خروجی یکسان باعث سهولت مصرف در فرانت‌اند می‌شود، اما عدم وجود validation و cache منجر به ضعف امنیت و Performance در حجم بالا خواهد شد. توصیه می‌شود Refactor در لایهٔ Service و اعمال TTL روی داده‌های ثابت انجام گیرد.

    POST /api/v2/notif/simple/list

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/notif/simple/list V2BaseController@notifySimpleList authWithJwt واچش اعلان‌های ساده (Simple Notification) کاربر برای نمایش فوری در داشبورد یا موبایل

    منطق عملکرد تابع

    تابع notifySimpleList با دریافت شناسهٔ شعبه یا اپراتور کاربر، لیست اعلان‌های فعال را از جدول notifications واکشی می‌کند و آن‌را به ترتیب نزولی تاریخ مرتب می‌نماید. روند اجرایی اصلی:

    ورودی‌ها (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"}
      ]
    }

    امنیت و کنترل دسترسی

    نکات کارایی و سرعت

    وابستگی‌ها

    کدهای خطا و خروجی‌های Exception

    کد خطا شرح منبع
    400 Database یا Query Exception notifySimpleList
    1006 JWT نامعتبر یا منقضی شده authWithJwt
    404 عدم یافتن اعلان برای branch Query Result

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    Endpoint POST /notif/simple/list یکی از مسیرهای کلیدی مدیریتی برای رساندن اعلان‌های سریع به کاربران است. ساختار ساده و بدون Cache باعث افزایش بار در تکرار درخواست‌ها شده و نبود Audit باعث فقدان ردپای ثبت‌شدهٔ امنیتی است. توصیه می‌شود Redis و صفحه‌بندی برحسب زمان آخرین مشاهده در نسخهٔ بعدی اضافه گردد.

    POST /api/v2/notif/send

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/notif/send V2BaseController@notifySend authWithJwt ارسال اعلان جدید به کاربران یا اپراتورهای هدف در سیستم Base

    منطق عملکرد تابع

    تابع notifySend ورودی‌های اعلان را از درخواست دریافت کرده و با استفاده از مدل notifications در بانک اطلاعاتی ثبت می‌کند. منطق تابع به صورت خلاصه:

    ورودی‌های درخواست

    نام فیلد نوع داده الزامی توضیح
    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": "هشدار موجودی"
      }
    }

    نکات امنیتی و کنترل دسترسی

    نکات کارایی و عملکرد

    وابستگی‌ها

    کدهای خطا و پاسخ‌های غیرعادی

    کد خطا شرح خطا منبع
    400 Missing or invalid input data Validation مرحله ورود داده
    1006 توکن JWT منقضی یا نادرست auth middleware
    500 Database Exception هنگام ثبت اعلان DB::insert()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    این مسیر کلیدی برای مدیریت و ارسال اعلان در سیستم است اما نیازمند تقویت زیرساخت امنیتی و ممیزی است. در وضعیت فعلی مناسب برای پنل داخلی است ولی برای ارسال real-time یا حجم زیاد اعلان، باید از queue و caching استفاده شود.

    POST /api/v2/dashboard

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/dashboard V2BaseController@dashboard authWithJwt واکشی داده‌های تحلیلی و وضعیت سرویس‌های پایه برای صفحه داشبورد اپراتور

    منطق عملکرد تابع

    تابع dashboard داده‌های اصلی سامانه (وضعیت ارتباط با سرویس‌های وابسته، تعداد آیتم‌های فعال، هشدارهای مرکزی و زمان آخرین همگام‌سازی) را جمع‌آوری و بازگردانی می‌کند:

    ورودی‌ها (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"}]
    }

    نکات امنیتی و کنترل دسترسی

    نکات کارایی و Performance

    وابستگی‌ها

    کدهای خطا و پاسخ‌های Exception

    کد خطا شرح خطا منبع
    400 Database یا ارتباط ناموفق با سرویس مرکزی dashboard()
    1006 JWT غیر معتبر authWithJwt()
    500 Exception عمومی در runtime Exception handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع dashboard یکی از نقاط اصلی گزارش وضعیت سیستم است که ارتباط لحظه‌ای و تعداد آیتم‌های فعال را نمایش می‌دهد. ساختار خروجی به اندازه کافی خواناست؛ اما نبود ممیزی و کنترل سطح دسترسی، آن را در محیط‌های production ناایمن می‌سازد. توصیه می‌شود role-based limitation و audit logging برای پایداری enterprise لحاظ گردد.

    POST /api/v2/operator/details

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/operator/details V2BaseController@operatorDetails authWithJwt دریافت جزئیات اپراتور شامل اطلاعات شعبه، نقش‌ها، تنظیمات و وضعیت فعال‌سازی

    منطق عملکرد تابع

    تابع operatorDetails با استفاده از شناسه اپراتور از توکن JWT، اطلاعات دقیق اپراتور فعلی را از جداول اصلی استخراج می‌کند:

    ورودی‌ها (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" }
        ]
      }
    }

    نکات امنیتی

    عملکرد و کارایی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    1006 توکن JWT غیر معتبر یا منقضی شده authWithJwt
    400 داده ناقص یا branch نامعتبر Input Validation
    500 خطا در واکشی داده‌ها از DB operatorDetails()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /operator/details برای مدیریت داخلی طراحی شده و اطلاعات کلیدی اپراتور را بدون واسطه برمی‌گرداند. در محیط production نیاز به کنترل دقیق نقش‌ها و محدودسازی پاسخ دارد تا از افشای جزئیات مدیریتی جلوگیری شود.

    POST /api/v2/management/office

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/management/office V2BaseController@office authWithJwt واکشی مشخصات دفتر و سرویس‌های مرتبط با شعبهٔ فعلی برای نمایش در پنل مدیریت

    منطق عملکرد تابع

    تابع office برای تجمیع اطلاعات اولیهٔ دفتر در داشبورد مدیریتی طراحی شده است:

    ورودی‌ها (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
          }
        ]
      }
    }

    نکات امنیتی و احراز هویت

    عملکرد و Performance

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    1006 توکن JWT نامعتبر یا منقضی authWithJwt
    400 branch ارسال نشده Input Validation
    500 خطای داخلی DB یا Exception ناشناخته office()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /management/office بستری برای بررسی سریع وضعیت شعبه و سرویس‌های فعال است. منطق عملکرد ساده اما امنیت سطح پایین دارد؛ پیشنهاد قطعی اجرای **Role-based Access + Audit Logging** جهت جلوگیری از افشای داده‌های دفاتر دیگر در محیط production می‌باشد.

    GET /api/v2/panel/groups/get

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/panel/groups/get V2BaseController@smsPanelGetGroups authWithJwt دریافت گروه‌های دفترچه مخاطبین سرویس پیامک فعال (SMS Panel) برای شعبه فعلی

    منطق عملکرد تابع

    تابع smsPanelGetGroups برای اتصال به سرویس پیامک Melipayamak (API رسمی شرکت پرداخت اول) طراحی شده است:

    ورودی‌ها (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
        }
      }
    ]

    نکات امنیتی

    عملکرد و Performance

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    1006 توکن JWT نامعتبر یا منقضی شده authWithJwt
    404 تنظیمات سرویس پیامک برای این شعبه یافت نشد smsPanelGetGroups()
    500 خطای اتصال با سرویس Melipayamak یا پاسخ نامعتبر SOAP MelipayamakApi::contacts()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /panel/groups/get برای هماهنگی مستقیم با API پیامکی استفاده می‌شود. هرچند پاسخ سبک و خوانا دارد، اما به دلیل عدم کش داخلی (caching) در نسخه فعلی می‌تواند موجب افزایش latency سیستم شود. در نسخه‌ی حرفه‌ای باید caching و محدودیت فراخوانی Request Rate در هر شعبه اضافه گردد.

    GET /api/v2/panel/bulk/receptions

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/panel/bulk/receptions V2BaseController@smsPanelGetBulkReceptions authWithJwt دریافت لیست پیام‌های انبوه ارسال‌شده در پنل پیامک فعال برای شعبه فعلی

    منطق عملکرد تابع

    تابع smsPanelGetBulkReceptions به سرویس پیامکی متصل شده و داده‌های آماری پیام‌های گروهی ارسال‌شده را بازیابی می‌کند:

    ورودی‌ها (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
        }
      ]
    }

    نکات امنیتی

    عملکرد و کارایی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    1006 توکن JWT نامعتبر یا منقضی شده authWithJwt
    404 عدم یافتن تنظیمات سرویس پیامک برای شعبه smsPanelGetBulkReceptions()
    500 خطای پاسخ از سرویس Melipayamak یا SOAP Data Invalid MelipayamakApi::getBulkReceptions()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /panel/bulk/receptions به عنوان بخش گزارش پیام‌های انبوه پنل پیامکی عمل می‌کند. داده‌های خروجی ساده و بدون نقش‌بندی است و در نسخه فعلی به عنوان نقطه ضعف امنیتی شناخته می‌شود. در ساختار Enterprise لازم است احراز نقش و ساختار audit کامل افزوده گردد و caching دوره‌ای برای سنجش عملکرد سرویس پیامک فعال شود.

    POST /api/v2/panel/bulk/add

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/panel/bulk/add V2BaseController@smsPanelAddBulk authWithJwt ارسال پیامک‌های انبوه به گروه یا فهرست مخاطبین تعیین‌شده از سمت پنل پیامکی شعبه

    منطق عملکرد تابع

    تابع smsPanelAddBulk از طریق اتصال به API سرویس Melipayamak فرآیند ارسال انبوه پیامک را انجام می‌دهد:

    ورودی‌ها (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
      }
    }

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    1006 توکن JWT نامعتبر یا منقضی شده authWithJwt
    2001 درخواست فاقد شماره ارسال معتبر smsPanelAddBulk()
    500 خطای ارتباط یا پاسخ نامعتبر از سرویس Melipayamak MelipayamakApi::bulkSend()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /panel/bulk/add نقطه ورود برای ارسال کمپین‌های پیامکی است که با وجود عملکرد ساده، به دلیل عدم وجود نقش‌بندی و ممیزی می‌تواند به سوء‌استفاده عملیاتی منجر شود. نسخه‌ای که در Enterprise اجرا شود باید حتماً شامل تأیید سطح دسترسی، audit log لحظه‌ای و ثبت پیام‌های ارسالی به‌صورت رمزگذاری‌شده باشد.

    POST /countries-cities/get

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/countries-cities/get V2BaseController@getCountiesOrCities authWithJwt دریافت لیست کشورها یا شهرها براساس نوع درخواست (`type`)، با امکان جست‌وجو و فیلتر وابسته به شناسه والد

    منطق عملکرد تابع

    تابع getCountiesOrCities مسئول واکشی اطلاعات جغرافیایی کشورها و شهرها است و رفتار آن براساس فیلد type (مقدار country یا city) تغییر می‌کند:

    ورودی‌ها (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 }
    }

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    1006 توکن JWT منقضی یا نامعتبر authWithJwt
    1001 پارامتر type نامعتبر یا خالی است getCountiesOrCities()
    500 خطا در واکشی داده‌ها از پایگاه‌داده DB::table('countries')

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /countries-cities/get سازوکار اصلی دریافت داده‌های جغرافیایی است. با وجود سادگی، نبود کنترل سطح دسترسی بین شعب می‌تواند نقطه ضعف امنیتی باشد. در نسخهٔ Enterprise لازم است مکانیزم‌های cache و rate-limit، audit log و کنترل اعتبار parent_id حتماً فعال شوند تا پاسخ سریع و ایمن بماند.

    GET /api/v2/settings/index/{type}

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/settings/index/{type} V2BaseController@settingsIndex authWithJwt دریافت تنظیمات شاخه، شامل کشور، استان و شهر مربوط به شعبه، براساس نوع تنظیم {type}.

    منطق عملکرد تابع

    تابع settingsIndex پارامتر {type} را از URL دریافت کرده و بر اساس آن، تنظیمات اصلی شعبه را از جدول offices واکشی می‌کند. اگر مقدار type برابر با “branch” باشد، سه سطح داده جغرافیایی کشور، استان و شهر از جداول پایگاه‌داده خوانده می‌شود:

    پارامترهای ورودی

    نام پارامتر نوع داده منبع الزامی توضیح
    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 }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    1006 توکن JWT نامعتبر یا منقضی شده authWithJwt
    1002 نوع تنظیم {type} نامعتبر یا پشتیبانی‌نشده است settingsIndex()
    500 خطا در اتصال به Redis یا پایگاه‌داده DB/Redis

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /settings/index/{type} برای دریافت داده‌های تنظیمات جغرافیایی شعبه استفاده می‌شود و یکی از پایه‌های ساخت فرم پیکربندی در داشبورد مدیریتی است. ضعف اصلی در حال حاضر نبود تفکیک Role بین شعب است. در نسخه Enterprise نیاز است Redis caching و Audit log فعال و کنترل سطح دسترسی دقیق اعمال گردد تا فرایند پاسخ‌دهی هم سریع‌تر و هم ایمن‌تر شود.

    PUT /settings/index/{type}

    Route Info

    Method Endpoint Controller Middleware Purpose
    PUT /api/v2/settings/index/{type} V2BaseController@settingsUpdate authWithJwt به‌روزرسانی تنظیمات شاخه موردنظر (نوع مشخص‌شده در پارامتر {type}) و ذخیره در دیتابیس مربوطه.

    منطق عملکرد تابع

    تابع settingsUpdate با دریافت {type} از URL مسیر، داده جدید تنظیمات شاخه (Branch) را بر اساس نوع تنظیم ذخیره می‌کند. منطق داخلی تابع بر پایه نوع تنظیم به سه شاخه تقسیم می‌شود:
    • type = "application" → بروزرسانی تنظیمات UI/Theme شعبه در جدول offices.
    • type = "accounting" → ثبت داده‌های مالی و محدودیت‌های اعتبار در جدول accounting_titles.
    • type = "hub" → به‌روزرسانی مارکاپ‌های سطحی در جدول hub_markups با استفاده از متد updateOrInsert().
    در آخر، داده‌ها پس از تغییر در DB با کلید شعبه (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
        }
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    404 نوع تنظیم {type} نامعتبر یا داده‌های ارسالی ناقص است. settingsUpdate()
    500 خطا در اتصال به Redis یا در عملیات DB. DB Facade
    400 درخواست فاقد احراز هویت JWT معتبر. authWithJwt Middleware

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر PUT /settings/index/{type} مسئول بروزرسانی تنظیمات شاخه‌هاست و سه نوع تنظیمات سیستم Interface، مالی، و مرکز پرواز (hub) را شامل می‌شود. برای محیط‌های Production باید مکانیزم cache و audit دقیقی فعال شود تا عملکرد سریع و قابل‌ردگیری تضمین گردد.

    GET /base/accommodations/list

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/base/accommodations/list V2BaseController@accommodationsList authWithJwt دریافت لیست اقامتگاه‌ها (هتل‌ها) براساس فیلترهای جغرافیایی و ویژگی‌های پیشرفته، همراه با جزئیات تأمین‌کننده و رسانه.

    منطق عملکرد تابع

    تابع accommodationsList از پارامتر JSON در بدنه درخواست استفاده می‌کند تا جزئیات فیلتر و صفحه‌بندی را تعیین کند. سپس فیلترهایی بر اساس کشور، استان، شهر، امتیاز و جستجوی عمومی (با کلید 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 }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 فرمت نادرست JSON یا فیلدهای ناقص. Decoder JSON
    404 هیچ اقامتگاهی برای فیلترهای واردشده یافت نشد. Query hotels
    500 خطا در اتصال Redis یا پایگاه داده. DB Facade / Redis

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /base/accommodations/list یکی از اصلی‌ترین نقاط برای واکشی اقامتگاه‌های سیستم است. با تکیه بر Redis، سیستم بهینه‌تر عمل می‌کند ولی برای نسخه Enterprise نیاز به توسعه کش شهر/استان و امنیت سطح دسترسی دارد. پاسخ استاندارد JSON‌ شامل متادیتا زمان و شمارنده‌های صفحه‌بندی، ساختار کاملاً تمیز و قابل‌درک برای فرانت‌اند فراهم می‌کند.

    POST /base/accommodation/rooms/delete

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/base/accommodation/rooms/delete V2BaseController@accommodationRoomsDelete authWithJwt حذف گروهی انواع اتاق‌های اقامتگاه و نگاشت‌های مربوطه از جداول سیستم.

    منطق عملکرد تابع

    تابع accommodationRoomsDelete مسئول حذف تمام انواع اتاق‌های وابسته به یک اقامتگاه (هتل) است. این تابع ابتدا شناسه‌های اتاق‌های وابسته را بر اساس پارامتر ورودی 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    404 شناسه اقامتگاه وجود ندارد یا خالی است. accommodationRoomsDelete()
    403 کاربر مجاز به حذف رکورد نیست. authWithJwt
    500 خطا در حذف رکورد از DB. DB Facade

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر POST /base/accommodation/rooms/delete نقطه اصلی برای پاک‌سازی انواع اتاق‌های اقامتگاه است. حذف هم‌زمان نگاشت‌ها و اتاق‌ها بدون Transaction انجام می‌شود که باید در نسخه بعدی اصلاح گردد. پیشنهاد می‌شود پس از حذف، کلیدهای Redis مربوطه آزاد (evict) شوند تا وضعیت لحظه‌ای سرویس‌های اتاق‌دار (مانند Snapptrip) با منبع هماهنگ بماند.

    POST /base/accommodation/room/update

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/base/accommodation/rooms/update V2BaseController@accommodationRoomsUpdate authWithJwt بروزرسانی نگاشت (Mapping) و اطلاعات اتاق‌های اقامتگاه از طریق سرویس‌های خارجی نظیر SnappTrip.

    منطق عملکرد تابع

    تابع accommodationRoomsUpdate وظیفه دارد لیست اتاق‌های متصل به اقامتگاه را بازسازی کند. این مسیر فقط برای سرویس‌های خاص فعال است—به‌طور پیش‌فرض برای سرویس 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    404 سرویس واردشده پشتیبانی نمی‌شود. accommodationRoomsUpdate()
    500 خرابی در فراخوانی CronController یا ارتباط Redis/DB. CronController / DB Facade

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /base/accommodation/rooms/update عملیات حیاتی بروزرسانی نگاشت اتاق‌ها با سرویس‌های خارجی مانند SnappTrip را انجام می‌دهد. در فعلی‌ترین پیاده‌سازی فقط برای SnappTrip فعال است؛ حذف و بازسازی همه‌ی داده‌های مربوطه همراه با پاک‌سازی Redis انجام می‌شود. در نسخه بعدی این سیستم باید مفهوم Transactional Safety و Service Expandability اضافه گردد تا برای سایر سرویس‌ها نیز قابل توسعه باشد.

    GET /api/v2/base/certificates

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/base/certificates V2BaseController@indexCertificates authWithJwt نمایش لیست گواهی‌نامه‌های شعبه شامل فایل‌ها و پیش‌نمایش محتوای آن‌ها.

    منطق عملکرد تابع

    تابع indexCertificates سطرهای جدول 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 خطا در اجرای کوئری پایگاه‌داده. Catch(Exception)
    404 هیچ گواهی‌نامه‌ای برای این شعبه یافت نشد. indexCertificates()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    متد indexCertificates داده‌های ساده و استاتیک گواهی‌نامه‌های شعبه را واکشی می‌کند و پیش‌نمایش‌های خودکار از فایل یا تصویر ایجاد می‌کند. پیشنهاد می‌شود برای بهبود سرعت در بارگذاری داشبوردها، خروجی در Redis cache ذخیره شود و در نسخه بعدی قابلیت جستجوی پویا اضافه گردد.

    DELETE /api/v2/base/certificate

    Route Info

    Method Endpoint Controller Middleware Purpose
    DELETE /api/v2/base/certificate V2BaseController@deleteCertificate authWithJwt حذف گواهی‌نامه مشخص از جدول certificates براساس شناسه و شعبه درخواستی.

    منطق عملکرد تابع

    تابع deleteCertificate برای حذف یک گواهی‌نامه خاص با توجه به شعبه و شناسه گواهی عمل می‌کند. این روش، مستقیماً جدول 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": [...]
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 خطای اجرای کوئری یا Exception داخلی هنگام حذف. Catch(Exception)
    404 شناسه مدرک یافت نشد یا شعبه با آن مطابقت ندارد. deleteCertificate()
    500 خطای ارتباط یا I/O در پایگاه داده. DB Facade

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر DELETE /base/certificate عملیات حذف ساده ولی حساس گواهی‌نامه‌ها را انجام می‌دهد. به دلیل ماهیت destructive عمل، لازم است کنترل سطح دسترسی و ممیزی دقیق فعال باشد. در نسخه بعدی، استفاده از SoftDelete و ثبت رویداد حذف در Redis توصیه می‌شود.

    GET /api/v2/base/certificate

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /api/v2/base/certificate V2BaseController@showCertificate authWithJwt واکشی مشخصات گواهی‌نامه‌ی خاص بر اساس id و شعبه کاربر.

    منطق عملکرد تابع

    تابع showCertificate داده‌ی یک گواهی‌نامه خاص را از جدول 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": "گواهی معتبر رزرو اقامتگاه سطح دو."
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 خطا در اجرای کوئری یا Exception داخلی. Catch(Exception)
    404 گواهی‌نامه با این شناسه یافت نشد. DB::first()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    این مسیر برای واکشی دقیق اطلاعات هر گواهی‌نامه طراحی شده و پاسخ سبک و تمیز JSON با کلید 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"
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح
    400 داده ناقص یا فایل نامعتبر.
    500 خطا در درج رکورد در پایگاه داده.

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی

    نوع لاگ: 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() در همان رکورد انجام می‌شود.

    ورودی‌ها

    خروجی

    {
      "meta": {"timestamp": 1750669277},
      "payload": true
    }
    

    امنیت

    کارایی

    وابستگی‌ها

    خطاها

    کد شرح
    404 گواهی‌نامه یافت نشد.
    400 داده نامعتبر یا پارامتر ناقص.

    پیشنهاد امنیتی

    پیشنهاد عملکردی

    ممیزی

    نوع لاگ: 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}
    }
    

    امنیت

    کارایی

    وابستگی‌ها

    خطاها

    کد شرح
    400 ورودی JSON نامعتبر.
    404 اقامتگاهی مطابق فیلتر یافت نشد.

    پیشنهاد امنیتی

    پیشنهاد عملکردی

    ممیزی

    نوع لاگ: 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"
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی JSON نامعتبر یا پارامتر ناقص. json_decode()
    500 خطا در واکشی داده از پایگاه داده. DB Query

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    این مسیر برای واکشی فهرست بدهکار/بستانکار طراحی شده است. با احراز 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی JSON ناقص یا فرمت اشتباه در دوره مالی. json_decode()
    404 داده مالی یا شناسه همکار یافت نشد. DB Query
    500 خطا در محاسبه مجموع بدهی/بستانکاری. getFinancialPasts()

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    این مسیر خلاصه‌ای از وضعیت مالی بدهکار و بستانکار را بر اساس داده‌های سال مالی و تراکنش‌های ثبت‌شده برمی‌گرداند. با ترکیب کش 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
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی JSON نامعتبر یا پارامتر ناقص. json_decode()
    403 کاربر بدون مجوز برای انجام نوع تراکنش. authWithJwt
    500 خطا در ثبت گروهی یا بروزرسانی کش Redis. DB/Redis

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    این مسیر مسئول ثبت تراکنش‌های پرداخت و دریافتی و بروزرسانی مالی 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
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی JSON ناقص یا فرمت نامعتبر. json_decode()
    404 تراکنش با شناسه داده‌شده یافت نشد. DB Query
    500 خطای سیستم در بروزرسانی یا بازسازی کش Redis. DB/Redis

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    این مسیر برای بروزرسانی امن و سریع تراکنش‌های پرداخت طراحی شده است. با احراز 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
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    5007 دوره حسابداری بسته است؛ حذف سند ممکن نیست. officeConfig()
    5005 خطا در عملیات حذف یا Redis. catch(Exception $e)
    403 عدم تطابق شعبه با دوره مالی جاری. authWithJwt

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی DataTables نامعتبر یا ناقص. Validator
    403 کاربر دسترسی مشاهده ندارد. authWithJwt
    500 خطا در Redis یا Query پایگاه داده. DB Query

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 نوع حساب نامعتبر یا فیلد type خالی است. Validator
    403 کاربر دسترسی مشاهده ندارد. Middleware JWT
    500 خطا در واکشی داده‌ها. DB/Redis

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامترهای لیست ناقص یا نامعتبر. Validator
    403 کاربر مجاز به مشاهده چک‌ها نیست. authWithJwt
    500 خطا در Query یا کش Redis. DB Layer

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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"
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 داده‌های چک ناقص یا نامعتبر. Validator
    403 دسترسی مجاز برای ثبت چک وجود ندارد. authWithJwt
    500 خطا در درج داده‌ها یا بروزرسانی کش Redis. DB Layer / Redis

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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": "وضعیت چک با موفقیت بروزرسانی شد"
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامترها ناقص یا نوع عملیات نامعتبر است. Validator
    403 کاربر مجاز به انجام این عملیات نیست. authWithJwt
    404 چک مورد نظر یافت نشد. DB
    500 خطای داخلی پایگاه داده یا بروزرسانی کش. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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": "جزئیات عملیات چک با موفقیت بروزرسانی شد"
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی‌ها ناقص یا نامعتبر. Validator
    403 دسترسی غیرمجاز برای بروزرسانی عملیات چک. authWithJwt
    404 شناسه عملیات یافت نشد. DB
    500 خطا در به‌روزرسانی یا بازسازی کش. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامتر type نامعتبر یا خالی. Validator
    403 عدم مجوز مشاهده ساختار مالی. Auth Middleware
    500 خطای داخلی دیتابیس یا Redis. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامتر branch نامعتبر. Validator
    403 عدم دسترسی به موجودی شعبه. Auth Middleware
    500 خطا در واکشی داده‌ها یا کش Redis. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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"
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی ناقص یا نامعتبر DataTables. Validator
    403 عدم دسترسی به اعلان‌های شعبه دیگر. Auth Middleware
    500 خطای داخلی دیتابیس یا کش Redis. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر 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"
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی ناقص یا نامعتبر DataTables. Validator
    403 کاربر فاقد سطح دسترسی سازمانی. Auth Middleware
    500 مشکل در واکشی داده‌ها یا کش Redis. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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"
        }
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامتر id ارسال نشده یا نامعتبر است. Validator
    403 کاربر به شعبهٔ اعلان دسترسی ندارد. Auth Middleware
    404 اعلان یافت نشد. DB Query
    500 خطای داخلی در بازیابی داده از دیتابیس. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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"
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامتر type یا object_id ارسال نشده است. Validator
    403 دسترسی نقش به ویرایش اعلان‌ها محدود است. RBAC
    404 رکورد ارسالی برای ویرایش یافت نشد. DB Query
    500 خطای داخلی در عملیات درج یا بروزرسانی. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 شناسه یا پارامترهای ورودی ناقص است. Validator
    403 کاربر مجوز ویرایش این اعلان را ندارد. RBAC
    404 اعلان یافت نشد یا از شعبه دیگر است. Query
    423 اعلان بسته است و قابل ویرایش نیست. Business Rule
    500 خطای داخلی سرور در هنگام ذخیره. Exception

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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"
          }
        ]
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامتر id ارسال نشده یا نامعتبر است. Validator
    403 کاربر مجاز به مشاهده اعلان در شعبهٔ دیگر نیست. Auth Middleware
    404 اعلان یافت نشد. DB Query
    500 خطای داخلی در بازیابی اطلاعات یا join داده‌های وابسته. Exception

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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"
          }
        ]
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 ورودی‌های زمانی یا نوع اعلان نادرست است. Validator
    403 کاربر مجاز به مشاهده‌ی بیانیه شعبه نیست. Auth Middleware
    404 داده‌ای در بازهٔ زمانی موردنظر یافت نشد. Query
    500 خطای داخلی سرور در پردازش تجمیعی. Exception

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح منبع
    400 تاریخ یا پارامتر گزارش ارسال نشده است. Validator
    403 کاربر اجازه مشاهده دفترکل این شعبه را ندارد. RBAC
    404 هیچ تراکنشی در بازهٔ مورد نظر پیدا نشد. Query
    500 خطای داخلی سرور. Exception

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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"
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح منبع
    400 پارامتر زمانی یا نوع اعلان نادرست است. Validator
    403 دسترسی کافی برای بازمحاسبه وجود ندارد. RBAC
    404 هیچ اعلان فعالی در بازهٔ مورد نظر یافت نشد. Query
    500 خطای داخلی هنگام آپدیت تراز. Transaction

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامتر یا دادهٔ درگاه نامعتبر است. Validator
    403 کاربر مجاز به تولید فاکتور این آیتم نیست. Auth Middleware
    404 آیتم مالی یافت نشد. Query
    500 خطای سرور هنگام برقراری ارتباط با درگاه پرداخت. Exception

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    400 پارامتر نامعتبر یا دادهٔ ناقص در Callback. Validator
    403 کاربر مجاز به تأیید این فاکتور نیست. Auth Middleware
    404 رکورد فاکتور یافت نشد. Query
    409 تأیید تکراری برای همان فاکتور انجام شد. Duplicate Prevention
    500 پاسخ نامعتبر از سرور درگاه. Gateway Error

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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 }
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    404 رکورد سریال پرداخت یافت نشد. DB::first()
    422 پرداخت ناموفق یا ناتمام. پردازش درگاه
    1000 پیام پیش‌فرض خطای عمومی پرداخت در پاسخ JSON. Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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"
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    401 توکن JWT نامعتبر یا منقضی شده است. authWithJwt
    422 ساختار دادهٔ ارسالی اشتباه است. JSON Parse Request
    500 خطا در پردازش داده‌ها از Redis یا پایگاه داده. DB/Routing

    پیشنهادهای امنیتی

    پیشنهادهای بهبود

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر 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 همکار و بازه تاریخی درخواست‌شده برمی‌گرداند. الگوریتم شامل مراحل زیر است:

    1. خواندن ساختار JSON ورودی شامل فیلترها (from, to, r, status, lbalance, fpopen) برای محدودسازی بازهٔ تراکنش.
    2. بازیابی فاکتورهای صادرشده از جداول مرتبط (فروش، خدمات و رزرو) و سپس عملیات پرداخت یا دریافت از جدول pays.
    3. تشخیص خودکار نوع عملیات مالی (پرداخت، دریافت، چک، سند دستی، کارمزد، افتتاحیه، اختتامیه).
    4. محاسبه مجموع بستانکاری و بدهکاری برای هر فاکتور و تولید خروجی یکپارچه شامل عنوان، تاریخ، توضیحات HTML و نوع سند.
    5. در صورت فعال بودن گزینه «lbalance»، مانده دوره قبل با عنوان «مانده دوره قبل تا تاریخ X/X/X» در بالای خروجی درج می‌شود.
    6. تمامی داده‌ها قبل از بازگردانی، بر اساس زمان شمسی (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
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    401 توکن JWT نامعتبر یا منقضی. authWithJwt
    404 یافت نشدن همکار. Database Query
    500 خطا در پردازش تاریخ یا تجمع داده‌ها (Jalalian/Carbon). Logic Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع colleagueBill یکی از سنگین‌ترین و حیاتی‌ترین مسیرهای مالی در سیستم است که ترکیبی از پرداخت‌ها، چک‌ها، اسناد دستی و مانده دوره را در قالب یک جمع‌بندی زمانی بازمی‌گرداند. امنیت و صحت زمان‌بندی محاسباتی نقش کلیدی در جلوگیری از ناسازگاری‌های حسابداری دارد.

    POST /colleagues/ledger-accounts

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/colleagues/ledger-accounts V2ColleaguesController@colleagueLedgerAccounts authWithJwt دریافت فهرست حساب‌های معین، کل و تفصیلی مرتبط با هر همکار (Colleague) برای مقاصد گزارش‌گیری حسابداری.

    منطق عملکرد تابع

    تابع colleagueLedgerAccounts وظیفه دارد حساب‌های معین مرتبط با همکار مشخص را از ساختار حسابداری استخراج کند. این تابع معمولاً هنگام تهیه گزارش دفتر کل یا نمایش جزئیات مالی یک همکار فراخوانی می‌شود. عملکرد کلی:

    1. دریافت شناسهٔ همکار (colleague_id) از درخواست و بررسی صحت آن.
    2. بازیابی حساب‌های معین (moeen) که در جداول accounting_moeens به همکار اشاره دارند.
    3. اتصال به جداول accounting_generals و accounting_groups برای تکمیل سلسله‌مراتب حساب.
    4. ساخت خروجی با ساختار سه‌سطحی (گروه – کل – معین) جهت نمایش در UI یا محاسبه.
    5. بازگردانی نتایج در قالب 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"
        }
      }
    ]
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح منبع
    400 پارامتر colleague_id ارسال نشده است. Validation
    404 هیچ حسابی برای همکار یافت نشد. Query Result
    500 خطای داخلی در پردازش داده‌ها یا joins. DB Join Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    مسیر /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) بر اساس آخرین تراکنش‌ها محاسبه مجدد شود. منطق کلی آن:

    1. خواندن اطلاعات درخواستی از کلاینت (درخواست عمومی یا برای همکار خاص).
    2. دریافت آخرین ماندهٔ مالی از جداول pays، factors، و manual_documents.
    3. محاسبهٔ خالص بدهی (Debit) و بستانکاری (Credit) برای هر همکار.
    4. تعیین Diagnosis هر همکار (Debtor, Creditor, Neutral).
    5. به‌روزرسانی فیلدهای مالی در جدول colleagues و ثبت زمان آخرین آپدیت در Redis Cache تحت کلید TIME:colleagues:general_billing.
    6. برگشت خروجی شامل وضعیت کلی هر همکار پس از به‌روزرسانی.

    پارامترهای ورودی

    نام نوع محل الزامی توضیح
    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
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    Redis::set('TIME:colleagues:general_billing', Carbon::now()->toString());

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    401 توکن JWT نامعتبر authWithJwt
    422 پارامتر نامعتبر در json Validation
    500 خطای داخلی پایگاه داده هنگام تجمیع داده DB::Transaction

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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)‌ را به‌روزرسانی می‌کند. هدف آن حفظ یکپارچگی اطلاعات مالی همکاران در سامانه است.

    1. دریافت پارامترهای فیلتر (id خاص یا به‌روزرسانی جمعی)،
    2. اجرای محاسبات بدهی/بستانکاری هر همکار از creditDebit، factor، pays و manual_documents.
    3. تعیین تشخیص وضعیت مالی (Diagnosis): Creditor، Debtor یا Neutral.
    4. ذخیره مجموع‌ها در جدول colleagues (فیلد balance و diagnosis).
    5. به‌روزرسانی مهر زمانی در Redis: TIMESTAMP = 'TIME:colleagues:general_billing'
    6. بازگرداندن گزارش کلی از همکاران به‌روزشده.

    پارامترهای ورودی

    نام نوع محل الزامی توضیح
    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": "شرکت داخلی"
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    وابستگی‌ها

    کدهای خطا

    کد شرح خطا منبع
    401 عدم احراز هویت JWT Middleware
    422 پارامتر ناقص یا JSON نامعتبر Validation
    500 خطا در اجرای کوئری محاسبات مالی DB Transaction

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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 را به‌روزرسانی می‌کند.

    1. دریافت پارامترهای JSON از بدنه درخواست.
    2. بررسی مقدار فیلد action (create/update/delete).
    3. در صورت create → درج رکورد جدید در جدول اصلی و جداول ارتباطی.
    4. در صورت update → اعمال تغییرات در ردیف مشخص‌شده و ثبت تاریخ بروزرسانی.
    5. در صورت delete → حذف نرم (soft delete) رکورد همکار و غیرفعالسازی در Redis.
    6. به‌روزرسانی کش با job UpdateRedis برای شاخص colleague:{id}.
    7. بازگرداندن نتیجه همراه با وضعیت و زمان انجام عملیات.

    پارامترهای ورودی

    نام نوع محل الزامی توضیح
    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"
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد توضیح منبع
    400 پارامترهای ضروری ارسال نشده‌اند Validation
    401 توکن احراز هویت اعتبار ندارد Middleware
    404 همکار مورد نظر یافت نشد Model Query
    500 خطای داخلی پایگاه داده در زمان ثبت یا بروزرسانی DB Transaction

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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 ذخیره می‌کند.

    1. خواندن پارامتر id از Query string.
    2. بررسی موجود بودن cache (کلید: colleague:{id}) در Redis.
    3. در صورت وجود → بارگذاری داده از cache، در غیر این صورت:
      • دریافت رکورد اصلی از جدول colleagues.
      • پیوستن دادهٔ اضافی از colleague_additional.
      • دریافت روابط از mapping_colleagues (در حوزه شرکت اصلی).
      • تبدیل فیلدهای ساختار‌یافته (dates, phone, category, type) به قالب خروجی استاندارد UI.
    4. ارسال نتیجهٔ نهایی به صورت 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
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامتر id ارسال نشده Validation
    401 توکن JWT نامعتبر یا منقضی است Middleware
    404 همکار در سیستم یافت نشد DB Query
    500 خطای داخلی پایگاه داده یا Redis Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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 باکس‌های انتخاب همکار استفاده می‌شود.

    1. دریافت پارامتر query از Query String.
    2. درخواست از کش Redis (کلید: colleagues:list:light) در صورت فعال بودن برای پاسخ فوری.
    3. اگر در کش موجود نباشد:
      • یافتن رکوردها در جدول colleagues با فیلترهای فازی روی فیلدهای office، first_name، last_name و financial_code.
      • ترکیب نتایج با جدول colleague_additional برای تکمیل email/phone.
      • محدودسازی خروجی به ۲۰ رکورد برای کارایی.
    4. بازگرداندن آرایه‌ای از نتایج به فرمت استاندارد 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
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 عبارت جست‌وجو خالی یا غیرمجاز است Validation
    401 توکن JWT منقضی یا نامعتبر است Middleware
    429 تعداد درخواست بیش از حد مجاز (Rate Limit) Throttle
    500 خطای داخلی SQL یا Redis DB/Redis

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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 برای تجمیع اطلاعات استفاده می‌کند.

    1. دریافت شناسهٔ همکار از Query String (colleague_id).
    2. خواندن رکوردهای فعال از جدول colleague_users.
    3. اتصال با جدول کاربران برای دریافت نام، ایمیل، وضعیت و نقش.
    4. بازگرداندن آرایه‌ای از کاربران در ساختار سبک جهت نمایش 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"
       },
       ...
     ]
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامتر colleague_id ارسال نشده یا نامعتبر است Validation
    401 توکن احراز هویت فاقد مجوز دسترسی به این منبع است Middleware
    404 هیچ کاربری برای همکار مشخص یافت نشد DB Query
    500 خطای پایگاه داده یا Redis Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع indexColleagueUser نمای کلی تمامی کاربران وابسته به یک همکار را بر می‌گرداند و به‌عنوان منبع اصلی نمایش استاندارد در ماژول مدیریت همکاران است. پاسخ JSON آن به‌طور بهینه و با حفظ امنیت اطلاعات تعریف شده است.

    PUT /colleague/user

    Route Info

    Method Endpoint Controller Middleware Purpose
    PUT /api/v2/colleague/user V2ColleaguesController@updateColleagueUser authWithJwt ویرایش اطلاعات یکی از کاربران ثبت‌شده زیرمجموعه همکار

    منطق عملکرد تابع

    تابع updateColleagueUser برای به‌روزرسانی داده‌های کاربر متصل به یک colleague خاص است. سامانه بررسی می‌کند که کاربر ارسالی متعلق به همان همکار باشد تا از تغییر دسترسی اشتباه جلوگیری شود.

    1. دریافت شناسهٔ کاربر با id از بدنهٔ درخواست.
    2. اعتبارسنجی فیلدهای حیاتی (نام، ایمیل، شماره تماس، نقش، وضعیت).
    3. بررسی تعلق کاربر به همکار جاری از طریق جدول colleague_users.
    4. به‌روزرسانی رکورد در جداول users و colleague_users در یک تراکنش.
    5. پاک کردن کش Redis مربوط به colleague:{id}:users تا اطلاعات جدید در فراخوانی بعدی بازسازی شود.
    6. بازگرداندن خروجی موفقیت شامل اطلاعات تازهٔ کاربر.

    پارامترهای ورودی

    نام نوع محل الزامی توضیح
    id integer body بله شناسه کاربر همکار برای ویرایش
    colleague_id integer body بله شناسه همکار مادر
    name string body خیر نام کامل (در صورت تغییر)
    email 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"
     }
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامترهای ناقص یا خطای اعتبارسنجی ورودی Validation
    401 عدم احراز هویت یا توکن منقضی‌شده Middleware
    403 کاربر دسترسی تغییر کاربران همکار ندارد Policy
    404 کاربر یافت نشد یا متعلق به این همکار نیست DB Query
    500 خطای پایگاه داده یا Rollback تراکنش Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع updateColleagueUser پایهٔ اصلی مدیریت کاربران همکاران است که با رعایت atomicity، احراز هویت سخت‌گیرانه و refresh خودکار cache عمل می‌کند. تمامی تغییرات در لاگ سیستمی ثبت می‌شود تا قابلیت ممیزی کامل داشته باشد.

    POST /colleague/user

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /api/v2/colleague/user V2ColleaguesController@storeColleagueUser authWithJwt افزودن کاربر جدید به زیرمجموعه یک همکار در سیستم مالی/سازمانی

    منطق عملکرد تابع

    تابع storeColleagueUser برای ایجاد یک کاربر جدید در زیرمجموعه همکار استفاده می‌شود. این تابع در ماژول مدیریت همکاران مورد استفاده مدیران مالی و مدیران همکار قرار می‌گیرد تا بتوانند کارمندان یا مسئولین دسترسی را تعریف کنند.

    1. اعتبارسنجی فیلدهای ورودی (نام، ایمیل، شماره تماس، رمز عبور، همکار ID و نقش).
    2. بررسی وجود ایمیل یا موبایل تکراری در جدول users.
    3. ثبت کاربر در جدول users و ایجاد رکورد متناظر در جدول colleague_users با شناسه همکار.
    4. تخصیص نقش و سطح دسترسی (RoleAssignment) مطابق policy داخلی.
    5. پاک کردن کش Redis (colleague:{id}:users) برای به‌روزرسانی لیست کاربران فعال.
    6. بازگرداندن پاسخ حاوی اطلاعات کاربر تازه ایجاد شده.

    پارامترهای ورودی

    نام نوع محل الزامی توضیح
    colleague_id integer body بله شناسه همکار مادر
    name string body بله نام کامل کاربر
    email 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"
     }
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 دادهٔ ورودی ناقص یا نامعتبر Validation
    401 توکن احراز هویت نامعتبر یا پایان‌یافته Middleware
    403 کاربر حق ایجاد کاربران همکار ندارد Policy
    409 ایمیل یا موبایل تکراری است DB Unique
    500 خطای عمومی در ثبت کاربر یا تراکنش Rollback Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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).

    1. دریافت شناسه id و colleague_id از درخواست.
    2. اعتبارسنجی مالکیت: بررسی اینکه کاربر هدف به همان colleague متعلق دارد.
    3. پیدا کردن رکورد در colleague_users و users و بررسی عدم تعامل فعال (مثل فاکتور باز).
    4. در صورتی که هیچ dependency فعال وجود ندارد، soft‑delete رکورد از هر دو جدول.
    5. انجام rollback در صورت وجود خطا در هر مرحله و ثبت لاگ در system_logs.
    6. پاک‌سازی کش Redis کلید colleague:{id}:users.
    7. بازگرداندن پاسخ موفقیت با جزییات کاربر حذف‌شده.

    پارامترهای ورودی

    نام نوع محل الزامی توضیح
    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"
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامترهای ورودی ناقص Validation
    401 توکن نامعتبر یا منقضی Middleware
    403 عدم مجوز حذف کاربر Policy
    404 کاربر یافت نشد یا متعلق به این همکار نیست DB Lookup
    409 کاربر در فرآیند فعال است (فاکتور/پرداخت باز) Business Rule
    500 Rollback تراکنش به دلیل Exception DB

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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 بازگردانده می‌شود.

    1. دریافت پارامترهایی شامل branch، type، (اختیاری) mimes و size.
    2. ساخت پیکربندی اعتبارسنجی: انواع مجاز و حدّ اکثر سایز.
    3. اعتبارسنجی فایل ورودی با قانون Laravel (required|file|mimes|max).
    4. نام‌گذاری فایل به‌صورت ترکیب تاریخ + microtime برای یونیک بودن.
    5. ذخیره‌سازی در مسیر uploads/{branch}/{type}/{year}/{month}/{extension}/.
    6. بازگرداندن پاسخ با وضعیت موفق و مسیر نهایی فایل.
    7. در صورت رخداد استثنا (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."
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 عدم اعتبار فایل ارسالی یا خطای upload Validation / Exception
    401 توکن JWT نامعتبر یا منقضی Middleware
    403 کاربر مجوز upload ندارد Policy (در صورت فعال)
    422 فیلد اجباری ناقص یا فرمت نامعتبر Laravel Validator

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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) را بر می‌گرداند. در صورت بروز خطا، پاسخی با کد ۵۰۰ و پیام مناسب بر می‌گرداند.

    1. فراخوانی تابع Ami::getStats() برای دریافت جزییات ارتباط.
    2. در موفقیت، بازگشت پاسخ JSON با status=true و فیلد data حاوی آمار AMI.
    3. در خطا، بازگشت پاسخ 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
      }
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 توکن JWT نامعتبر یا ناحاضر Middleware
    500 عدم دسترسی به سرور Asterisk یا timeout AMIClient Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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 بازگردانده می‌شود.

    1. برقراری کانکشن با AMI از طریق Ami::getConnection().
    2. ارسال دستور CoreShowChannels و دریافت پاسخ.
    3. پارْس داده‌ها و ساخت لیستی از channel های فعال (Active Call Sessions).
    4. بازگرداندن پاسخ با فرمت 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"
        }
      ]
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 توکن JWT نامعتبر Middleware
    504 Timeout در پاسخ AMI AmiClient
    500 خطای داخلی سرور Asterisk Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    تابع 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) را بازمی‌گرداند.

    1. خواندن پارامتر channel از query string.
    2. فراخوانی Ami::sendAction('ChannelStatus', ['Channel' => $channel]).
    3. در صورت یافتن کانال فعال، پاسخ ساخته می‌شود با فیلدهای State، CallerID، Duration و Application.
    4. در صورت نبود کانال یا خطا، پاسخ با 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"
    }
    

    نکات امنیتی

    نکات عملکردی

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 توکن JWT نامعتبر Middleware
    404 کانال پیدا نشد AMI Response
    500 خطای ارتباط با AMI Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    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 و اطلاعات پایه تماس برگردانده می‌شود.

    1. دریافت پارامترهای source (داخلی مبدأ)، destination (شماره مقصد)، context، priority.
    2. اعتبارسنجی ورودی‌ها و بررسی فرمت شماره تلفن.
    3. ایجاد Action به‌صورت:
      Originate Channel=SIP/{source}, Exten={destination}, Context=from-internal, Priority=1, CallerID={cid}
    4. ارسال به AMI از طریق Ami::sendAction().
    5. بررسی پاسخ و بازگرداندن وضعیت تماس با کد ۲۰۰ در صورت موفقیت.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
      }
    }
    

    نکات امنیتی

    عملکرد سیستم

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 ورودی نامعتبر (شماره داخلی یا تلفن) Validator
    403 دسترسی مجاز برای برقراری تماس ندارید Middleware
    504 Timeout در AMI یا عدم پاسخ AmiClient
    500 خطای نامشخص در ارسال Originate Exception

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    متد 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 به سرور ارسال شده و در صورت موفقیت، پیغام تأیید برمی‌گردد.

    1. بررسی پارامترهای درخواست (channel یا uniqueid).
    2. در صورت عدم ورود هیچکدام، پاسخ 400 برگردانده می‌شود.
    3. ایجاد Action Hangup و ارسال به AMI از طریق Ami::sendAction().
    4. بررسی پاسخ در صورت موفقیت (Response: Success).
    5. ثبت رویداد در سیستم ممیزی.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 نبود پارامتر مورد نیاز: channel یا uniqueid Validation
    401 توکن JWT نامعتبر Middleware
    403 مجوز قطع تماس ندارید Authorization
    404 کانال مورد نظر یافت نشد AMI Response
    504 Timeout در ارتباط با AMI AmiClient

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    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 ثبت می‌شود.

    1. اعتبارسنجی پارامترهای ورودی (number و message).
    2. تشخیص دستگاه ارسال (device) و بررسی اتصال به AMI.
    3. ساخت دستور AMI از نوع DongleSendSms.
    4. ارسال و دریافت نتیجه (بررسی Response: Success).
    5. ثبت رویداد در وب‌سوکت و ذخیره در پایگاه داده.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامترهای ورودی نامعتبر Validation
    403 عدم مجوز ارسال پیامک Authorization
    500 خطای پاسخ AMI AMI Service
    504 Timeout در دریافت پاسخ از Asterisk Gateway

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    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 به کاربر منتشر می‌شود.

    1. اعتبارسنجی پارامترهای code و device.
    2. بررسی اتصال به AMI و دسترس بودن دستگاه مورد نظر.
    3. ارسال دستور USSD از طریق Action DongleSendUssd.
    4. ثبت در ami_ussd_logs، تماس با وب‌سوکت برای اعلان.
    5. بازگرداندن نتیجه به صورت 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"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 ورودی نامعتبر یا کد USSD غیراستاندارد Validation
    401 توکن JWT نامعتبر Authentication
    403 عدم مجوز ارسال USSD Authorization
    500 عدم پاسخ از AMI AMI Gateway
    504 Timeout در پاسخ شبکه USSD Network

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    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 ثبت می‌نماید.

    1. اعتبارسنجی وجود پارامتر action.
    2. بررسی حق دسترسی کاربر (فقط admin/developer).
    3. ارسال درخواست به AMI با متد Service Ami::action().
    4. دریافت پاسخ و تبدیل به آرایه قابل خواندن.
    5. ثبت در لاگ در صورت فعال بودن گزینه‌ی 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"
          ]
        }
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامتر action ارسال نشده است Validation
    403 عدم دسترسی به دستور درخواستی Authorization
    422 Action نامعتبر یا غیرفعال در AMI AMI Validation
    500 خطای داخلی AMI در حین اجرا AMI Service

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    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 طراحی شده است. فرایند به صورت زیر انجام می‌شود:

    1. ارسال پینگ (پیام ping:test) به کانال داخلی asterisk_socket.
    2. دریافت پاسخ از سرویس (پیام pong) و محاسبه‌ی اختلاف زمان بین ارسال و دریافت.
    3. بازگرداندن نتیجه به صورت JSON همراه با فیلد latency_ms برحسب میلی‌ثانیه.
    4. در صورت خطا در اتصال وب‌سوکت، کد ۵۰۴ برگردانده می‌شود.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 توکن JWT نامعتبر یا منقضی Authentication
    403 کاربر فاقد مجوز برای دسترسی به تست وب‌سوکت Authorization
    504 عدم پاسخ از وب‌سوکت در بازه‌ی ۲ ثانیه Connection Timeout

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    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 با مشخصات ارسال‌کننده ثبت می‌نماید.

    1. اعتبارسنجی وجود پارامترهای type و data.
    2. ساخت Payload شامل نوع و داده‌ی رویداد.
    3. انتشار رویداد در کانال آزمایشی (پیش‌فرض: test_notification_channel).
    4. ثبت در پایگاه داده ami_notifications_log به همراه user_id.
    5. بازگرداندن نتیجه برای تأیید انتشار.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 ارسال پارامترهای ناقص (نبود type یا data) Validation
    401 توکن JWT نامعتبر یا منقضی Authentication
    403 سطح دسترسی ناکافی برای تست نوتیفیکیشن Authorization
    500 خطای داخلی در ارسال به سرویس WebSocket/Redis Notification Gateway

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و لاگ‌ها

    جمع‌بندی

    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) طراحی شده است. نحوه‌ی عملکرد:

    1. تعیین مسیر (path) بر اساس location، type، سال و ماه فعلی.
    2. پیکربندی فایل (اندازه، فرمت‌های مجاز، مسیر نهایی).
    3. اعتبارسنجی نوع و حجم فایل ارسالی بر مبنای mimes و max.
    4. تشخیص پسوند فایل به‌صورت پله‌ای (بررسی فرم، نام فایل، و آخرین نقطه).
    5. تعیین نام نهایی (fileName) بر اساس نام فرستاده‌شده یا زمان سیستم.
    6. آپلود فایل در Disk با کلید liara از طریق Storage Facade لاراول.
    7. بازگرداندن پاسخ 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
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامتر فایل ناقص یا مغایرت در فرمت/حجم Validation
    401 توکن JWT نامعتبر یا منقضی شده Authentication
    500 خطای شبکه در هنگام تماس با S3 Storage Liara/S3 Adapter

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    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 طراحی شده است.

    1. بررسی وجود پارامترهای related_id، id و path در درخواست.
    2. جستجوی مدیاهای مطابق با یکی از شرایط:
      • بر اساس path
      • بر اساس id
      • بر اساس related_category و related_id (همراه با related_type)
    3. بررسی وجود فایل در Storage (disk:liara)، در صورت وجود حذف فیزیکی.
    4. ثبت پیام برای هر فایل حذف شده یا یافت نشده در آرایه deleted.
    5. حذف رکورد مدیا از جدول media.
    6. برگرداندن نتیجه عملیات به همراه زمان و آرایه جزئیات حذف.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
      ]
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 عدم ارسال پارامترهای الزامی Validation
    401 توکن JWT نامعتبر یا منقضی Authentication

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    deleteMedia یک مسیر حذف ایمن برای مدیریت فایل‌های فضای S3 در سامانه است که هم حذف فیزیکی فایل را انجام می‌دهد و هم پاکسازی بانک اطلاعاتی را؛ در صورت پیاده‌سازی لاگ و کنترل مالکیت، این فرایند می‌تواند ضد‌گلوله شود.

    GET /personnel/attendance

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /personnel/attendance OfficialController@attendancePersonnel authWithJwt دریافت لیست حضور و غیاب پرسنل با امکان فیلتر پیشرفته و جزئیات مالی

    منطق عملکرد تابع

    متد attendancePersonnel پس از تأیید احراز هویت، اطلاعات حضور و غیاب پرسنل را با پارامترهای فیلتر متنوع برگردانده و در خروجی جزئیات مالی مرتبط را بهمراه اسناد ثبت شده نمایش می‌دهد.

    1. دریافت داده ورودی از Request شامل فیلترها، تاریخ‌ها و شناسه پرسنل.
    2. جستجوی رکورد پرسنل در جدول مرتبط.
    3. بر اساس شرایط زمان‌بندی یا کلید جستجوی پیشرفته (advanced)، استخراج حضورها از دیتابیس.
    4. ساخت آرایه Items شامل اطلاعات هر رویداد:
      • شماره سریال و شناسه سیستم
      • تاریخ و ساعت ثبت حضور
      • جزئیات عنوان، نوع سند، مبلغ بستانکاری/بدهکاری، تشخیص وضعیت
    5. مرتب‌سازی اقلام بر اساس زمان به صورت صعودی.
    6. در صورت نیاز، افزودن سند افتتاحیه از جدول financial_pasts به ابتدای لیست.
    7. بازگرداندن خروجی همراه با جزئیات پرسنل، محدوده فیلتر شده، و داده‌ها.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
        }
      ]
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    404 پرسنل یافت نشد Query DB
    401 توکن نامعتبر AuthWithJwt
    400 پارامترهای ورودی نامعتبر Validation

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    attendancePersonnel مسیر پایه برای مشاهده وضعیت حضور پرسنل با جزئیات مالی است. اگر کنترل سطح دسترسی و ثبت رویدادها به‌درستی اجرا شود، مسیر قابلیت استفاده در محیط‌های حساس را دارد.

    POST /personnel/attendance

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /personnel/attendance OfficialController@attendancePersonnelStore authWithJwt ثبت حضور و غیاب روزانه‌ی پرسنل یا اصلاح رکوردهای ثبت‌شده

    منطق عملکرد تابع

    تابع attendancePersonnelStore برای ثبت یا به‌روزرسانی سوابق حضور و غیاب پرسنل استفاده می‌شود. ورودی شامل شناسه‌ی پرسنل، بازه‌ی تاریخ، وضعیت کاری، و نوع حضور است. در حالت کلی:

    1. دریافت پارامترهای ارسالی از درخواست و بررسی وجود کاربر هدف در جدول personnel.
    2. در صورت ثبت اولیه، رکورد جدیدی در جدول personnel_attendance با تاریخ روز و ساعت فعلی درج می‌شود.
    3. در حالت ویرایش، داده‌ی موجود با اطلاعات جدید (مانند ساعت ورود/خروج) به‌روزرسانی می‌شود.
    4. در صورت ارسال وضعیت مرخصی یا تعطیلی، رکورد در جدول مجزای personnel_permissions ثبت می‌گردد.
    5. در انتها خروجی شامل وضعیت عملیات و شناسه‌ی رکورد مربوطه بازگردانده می‌شود.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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": "ثبت اتوماتیک حضور"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    404 پرسنل یافت نشد Validation
    422 تاریخ یا وضعیت نامعتبر است Validation
    401 توکن احراز هویت منقضی شده AuthWithJwt
    500 اشکال در ثبت رکورد در دیتابیس DB Transaction

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    attendancePersonnelStore وظیفه‌ی ثبت و به‌روزرسانی سوابق روزانه حضور کارکنان را با سطح دسترسی کنترل‌شده بر عهده دارد. این مسیر پایه‌ی زیرساخت KPI و محاسبات حقوق و دستمزد پرسنل محسوب می‌شود.

    GET /personnel/attendance/permission

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /personnel/attendance/permission OfficialController@attendancePersonnelPermission authWithJwt دریافت نوع و وضعیت مجوز حضور و غیاب برای پرسنل در شعبهٔ فعلی

    منطق عملکرد تابع

    این تابع تعیین می‌کند که کاربر جاری در هنگام ثبت تردد در چه شرایطی مجاز به انجام حضور و غیاب است. تنظیمات بسته به مقدار ذخیره‌شده در کلید ATTENDANCE جدول office_config مشخص می‌شوند.

    1. واخوانی مقدار تنظیم حضور و غیاب شعبه جاری از جدول office_config.
    2. در صورت نبود تنظیم، پاسخ خطای ۴۰۹ با پیام «تنظیمات تعریف نشده است» بازگردانده می‌شود.
    3. در غیر این صورت، مقدار کلید بررسی و متن نمایشی فارسی آن تنظیم می‌گردد (مثلاً full → «تصویر / موقعیت»).
    4. اگر نوع تنظیم face یا full باشد، داده چهرهٔ کاربر از جدول attendance_face_encoding واکشی می‌شود تا مشخص گردد آیا اجازهٔ ثبت چهره دارد یا نه.
    5. در نهایت پاسخ 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 }
    }
    

    نکات امنیتی

    عملکرد و بهینه‌سازی

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 JWT نامعتبر یا منقضی AuthWithJwt
    409 تنظیمات حضور برای دفتر تعریف نشده DB query
    500 اشکال داخلی یا استثناء دیتابیس Exception handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    attendancePersonnelPermission پایه‌ای‌ترین متد زیرساختی برای سیستم حضور و غیاب است که مشخص می‌کند آیا کاربر می‌تواند بر اساس نوع تعریف‌شده دفتر (موقعیت، تصویر یا هر دو) اقدام به ثبت تردد کند یا خیر.

    POST /personnel/list

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /personnel/list OfficialController@personnelList authWithJwt دریافت لیست کامل پرسنل با امکان فیلتر بر اساس وضعیت، شعبه، نقش و بازه زمانی

    منطق عملکرد تابع

    تابع personnelList مجموعه‌ای از رکوردهای پرسنل را از پایگاه داده استخراج می‌کند و فیلترهایی که در درخواست آمده را اعمال می‌نماید.

    1. پارسه کردن ورودی‌ها (branch, status, category, search, from, to).
    2. افزودن شرط‌های where براساس پارامترها (مثلاً فیلتر شعبه فقط اگر ارسال شده باشد).
    3. پیوستن جداول مرتبط برای افزودن نقش، دسته و سقف مالی.
    4. مرتب‌سازی نتایج بر اساس اولویت و نام.
    5. بازگرداندن داده با ساختار 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
        }
      ]
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 توکن معتبر نیست یا منقضی شده AuthWithJwt
    403 عدم دسترسی Role Check
    500 اشکال داخلی Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    personnelList ابزاری قدرتمند برای مدیران و HR جهت مدیریت و پایش پرسنل با فیلترهای متنوع است.

    POST /personnel/bill

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /personnel/bill OfficialController@personnelBill authWithJwt بازیابی و نمایش صورت‌حساب مالی پرسنل با جزییات اسناد و مانده

    منطق عملکرد تابع

    این متد بر اساس شناسه پرسنل داده‌شده، تمام تراکنش‌های مالی مرتبط (دریافت، پرداخت، اسناد دستی، افتتاحیه/اختتامیه) را واکشی می‌کند، آن‌ها را بر اساس تاریخ مرتب می‌نماید و در قالب ساختار صورت‌حساب یکپارچه بازمی‌گرداند.

    1. دریافت پارامترهای جستجو از $Data->advanced و اعمال فیلتر بازه تاریخ یا شماره سند.
    2. واخوانی فاکتورهای پرداخت (pay) از جدول pays با شرایط مربوط به پرسنل (functor/object).
    3. تشخیص نوع سند: سند عملیات رفرنس، سند کارمزدی، سند دستی، یا عملیات دریافت/پرداخت.
    4. ساخت آبجکت‌ جزئیات شامل عنوان، نوع سند، مبلغ بدهکار/بستانکار و تشخیص وضعیت (diagnosis).
    5. مرتب‌سازی آیتم‌ها بر اساس زمان.
    6. افزودن سند افتتاحیه از جدول financial_pasts اگر موجود بود و شرایط اجازه می‌داد.
    7. بازگرداندن پاسخ شامل داده‌ها، جزئیات شخص، و اطلاعات فیلتر.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
        }
      ]
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    404 پرسنل یافت نشد DB query
    500 خطای داخلی دیتابیس/پردازش Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    personnelBill امکان مشاهده جامع تاریخچه مالی پرسنل را فراهم می‌کند و داده‌ها را در قالب ساختار سندی استاندارد بازمی‌گرداند.

    POST /personnel/operation

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /personnel/operation OfficialController@operationPersonnel authWithJwt اجرای عملیات مدیریتی روی پرونده پرسنل (بروزرسانی، تغییر وضعیت، مسدودسازی موقت)

    منطق عملکرد تابع

    این متد عملیاتی را که از طریق کلید action مشخص می‌شود، روی پرسنل اجرا می‌کند:

    در پایان، نتیجه اجرای عملیات به‌صورت 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": [ ... ]
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    500 خطای عمومی پایگاه داده یا منطق Exception
    400 ورودی نامعتبر Validation layer

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    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) استفاده شود.

    1. تشکیل آرایه‌ای از گروه‌های اصلی (مثل منابع انسانی، مالی، پروژه).
    2. درج زیرماژول‌ها به صورت children در هر گروه.
    3. بازگرداندن داده با کلید 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"}
          }
        }
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 عدم احراز هویت Middleware
    500 خطای سیستم در هنگام تولید ساختار دسترسی Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    personnelAccessList ساختار کامل ماژول‌ها و صفحات قابل‌دسترسی سیستم را تولید می‌کند و پایه‌ای برای سیستم مدیریت مجوزهاست. به دلیل استاتیک بودن داده، کارایی بالا و ریسک امنیتی پایین دارد.

    GET /personnel

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /personnel OfficialController@personnelIndex authWithJwt دریافت فهرست یا اطلاعات کلی همه پرسنل برای نمایش در داشبورد مدیریت

    منطق عملکرد تابع

    این متد بدون نیاز به ورودی جزئی، لیست کامل پرسنل یا اطلاعات پایه آن‌ها را از دیتابیس واکشی می‌کند. بسته به نقش و سطح دسترسی کاربر، ممکن است فیلترهایی روی لیست اعمال شود (مثلاً فقط پرسنل فعال یا پرسنل زیرمجموعه یک شعبه خاص).

    1. بررسی نقش کاربر از روی JWT برای تعیین محدوده دسترسی به داده‌ها.
    2. واکشی رکوردها از جدول operators یا جداول مرتبط.
    3. بارگذاری داده‌های مرتبط مانند وضعیت، شعبه، نقش، اطلاعات تماس.
    4. بازگرداندن خروجی 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"
        },
        ...
      ]
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 توکن JWT نامعتبر یا منقضی شده Middleware
    500 خطای داخلی سرور Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    personnelIndex نقطه شروع واکشی داده‌های پرسنل است و باید‌ سریع، امن و منطبق با نقش کاربر اجرا شود.

    POST /personnel

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /personnel OfficialController@personnelStore authWithJwt ثبت اطلاعات و ایجاد کاربر (پرسنل) جدید در سیستم

    منطق عملکرد تابع

    تابع personnelStore ورودی JSON شامل جزئیات پرسنل را دریافت و صحت‌سنجی می‌کند، سپس رکورد جدیدی در دیتابیس ایجاد می‌کند. بعد از ذخیره، شناسه و اطلاعات پایه کاربر جدید در خروجی برگردانده می‌شود.

    1. بررسی نقش کاربر جاری (دسترسی ایجاد پرسنل).
    2. اعتبارسنجی فیلدهای ضروری مثل نام، نام کاربری، شعبه، نقش.
    3. درج رکورد در جدول operators به همراه هش کردن رمز عبور.
    4. اختصاص نقش و تنظیمات پیش‌فرض دسترسی.
    5. بازگرداندن خروجی موفق شامل شناسه و زمان ایجاد.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    name Body string بله نام کامل پرسنل
    username Body string بله نام کاربری (یونیک)
    password Body string بله رمز عبور (پیش از ذخیره هش می‌شود)
    branch Body integer بله شناسه شعبه
    role Body integer بله شناسه نقش سازمانی
    email 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"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 ورودی ناقص یا نامعتبر Validation
    409 نام کاربری تکراری DB Unique Constraint
    500 خطای داخلی سرور Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    personnelStore نقطه ورود ایجاد پرسنل جدید است و باید با کنترل‌های امنیتی و اعتبارسنجی دقیق همراه باشد.

    PUT /personnel

    Route Info

    Method Endpoint Controller Middleware Purpose
    PUT /personnel OfficialController@personnelUpdate authWithJwt ویرایش اطلاعات پرسنل موجود (نام، نقش، شعبه، وضعیت و ...)

    منطق عملکرد تابع

    تابع personnelUpdate ابتدا بررسی می‌کند که کاربر جاری اجازه ویرایش دارد. سپس داده‌های جدید را از درخواست دریافت کرده، اعتبارسنجی می‌کند و رکورد مربوطه را در جدول operators یا جداول مرتبط به‌روزرسانی می‌نماید. در پایان، خروجی موفقیت‌آمیز برمی‌گرداند.

    1. اعتبارسنجی JWT و نقش کاربر.
    2. بررسی وجود شناسه (id) پرسنل.
    3. دریافت فیلدهای جدید مانند نام، نقش، شعبه، وضعیت.
    4. اجرای کوئری UPDATE متناسب با فیلدهای ارسالی.
    5. بازگرداندن پیام موفقیت همراه با زمان به‌روزرسانی.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 ورودی ناقص یا نامعتبر Validation
    404 پرسنل یافت نشد DB Lookup
    500 خطای داخلی سرور Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    personnelUpdate برای به‌روزرسانی امن و سریع اطلاعات پرسنل استفاده می‌شود و باید با کنترل دقیق مجوز اجرا گردد.

    GET /personnel/shift/list

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /personnel/shift/list OfficialController@shiftWorkList authWithJwt نمایش لیست همه شیفت‌های موجود و تعریف‌شده سیستم

    منطق عملکرد تابع

    تابع shiftWorkList داده‌های تمام شیفت‌های فعال را از پایگاه‌داده واکشی کرده و آن‌ها را در قالب ساختاریافته (با جزئیات زمان شروع، پایان، نوع و توضیحات) بازمی‌گرداند. این خروجی معمولاً برای انتخاب سریع شیفت هنگام تخصیص به پرسنل استفاده می‌شود.

    1. دریافت درخواست GET از کلاینت با توکن معتبر JWT.
    2. اجرای کوئری روی جدول shift_works یا مدل مشابه برای استخراج لیست شیفت‌ها.
    3. مرتب‌سازی خروجی بر اساس شماره یا عنوان شیفت.
    4. بازگشت پاسخ 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": "شیفت عصرگاهی"
        }
      ]
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    401 توکن نامعتبر یا منقضی‌شده Middleware
    404 هیچ شیفتی یافت نشد DB Query
    500 خطای داخلی سرور Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    shiftWorkList نقطهٔ ورودی دریافت لیست کامل شیفت‌هاست. با بازگرداندن جزئیات دقیق زمان‌ها، این مسیر پایهٔ مدیریت و تنظیم شیفت‌های پرسنل در سامانه است.

    GET /personnel/shift

    Route Info

    Method Endpoint Controller Middleware Purpose
    GET /personnel/shift OfficialController@shiftWorkIndex authWithJwt نمایش لیست و جزئیات شیفت‌های فعال به همراه پرسنل مربوطه

    منطق عملکرد تابع

    تابع shiftWorkIndex برای واکشی نمای کلی از شیفت‌های در حال اجرا طراحی شده است. داده‌های جدول شیفت (مثلاً shift_works یا مشابه) را دریافت کرده، سپس نام پرسنل منتسب، زمان‌بندی شروع و پایان، نوع شیفت، و وضعیت فعال یا غیرفعال را بازمی‌گرداند.

    1. اعتبارسنجی توکن JWT و تشخیص نقش کاربر.
    2. واخوانی داده‌ها از جدول شیفت‌ها با روابط مربوط به پرسنل (LEFT JOIN).
    3. در صورت ارسال شناسهٔ خاص، فقط شیفت موردنظر بازگردانده می‌شود.
    4. بازگشت پاسخ 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
        }
      ]
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامتر جستجوی نامعتبر Validation
    404 شیفت مورد نظر یافت نشد DB Lookup
    500 خطای داخلی سرور Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    shiftWorkIndex برای نمایش وضعیت فعلی شیفت‌ها در سیستم طراحی شده و پایهٔ داشبورد مدیریتی بخش پرسنل محسوب می‌شود.

    POST /personnel/shift

    Route Info

    Method Endpoint Controller Middleware Purpose
    POST /personnel/shift OfficialController@shiftWorkStore authWithJwt ایجاد و ثبت شیفت کاری جدید برای پرسنل

    منطق عملکرد تابع

    تابع shiftWorkStore‌ مسئول درج رکورد جدید شیفت در پایگاه‌داده است. پس از اعتبارسنجی مقادیر ورودی و بررسی تداخل زمانی با شیفت‌های قبلی، اطلاعات ثبت و پاسخ JSON بازگردانده می‌شود.

    1. بررسی صحت احراز هویت JWT و نقش کاربر (Admin یا HR).
    2. اعتبارسنجی وجود پارامترهای ضروری (عنوان، زمان شروع، زمان پایان).
    3. اطمینان از نبود تداخل زمانی با سایر شیفت‌ها.
    4. ایجاد رکورد جدید در جدول shift_works با وضعیت فعال.
    5. برگرداندن پاسخ شامل شناسه و جزئیات شیفت تازه‌ثبت‌شده.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    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"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامتر ورودی ناقص یا نامعتبر Validation
    409 تداخل زمانی با شیفت دیگر Business Logic
    500 خطای عمومی سرور Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    shiftWorkStore هسته فرآیند ایجاد شیفت کاری است. این مسیر توسط تیم منابع انسانی برای تعریف ساختار کاری واحدها استفاده می‌شود و جزئیات ضروری هر شیفت را ذخیره می‌کند.

    PUT /personnel/shift

    Route Info

    Method Endpoint Controller Middleware Purpose
    PUT /personnel/shift OfficialController@shiftWorkUpdate authWithJwt ویرایش اطلاعات یک شیفت کاری موجود

    منطق عملکرد تابع

    تابع shiftWorkUpdate برای به‌روزرسانی داده‌های شیفت قبلی طراحی شده است. این تابع بررسی می‌کند که شیفت با id ارسالی وجود دارد و سپس اطلاعات جدید را در همان رکورد ذخیره می‌کند.

    1. بررسی وجود مجوز دسترسی (Admin / HR).
    2. یافتن رکورد شیفت بر اساس شناسه ورودی.
    3. اعتبارسنجی ورودی‌ها و کنترل تداخل زمانی احتمالی با سایر شیفت‌ها.
    4. اعمال تغییرات در دیتابیس و تولید پاسخ استاندارد 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"
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامتر الزامی ارسال نشده Validation
    404 شیفت یافت نشد Database
    409 تداخل زمانی با شیفت دیگر Logic Checker
    500 خطای داخلی سرور Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    shiftWorkUpdate مسیر استاندارد به‌روزرسانی شیفت‌هاست و دقت بالایی در کنترل تداخل و اعتبار داده‌ها دارد. این متد هسته مدیریت اصلاحات شیفت در ماژول منابع انسانی است.

    DELETE /personnel/shift

    Route Info

    Method Endpoint Controller Middleware Purpose
    DELETE /personnel/shift OfficialController@shiftWorkDestroy authWithJwt حذف (یا غیرفعال‌سازی) یک شیفت کاری موجود

    منطق عملکرد تابع

    تابع shiftWorkDestroy‌ بسته به نوع درخواست دریافتی، یا شیفت را به‌صورت کامل از جدول حذف می‌کند یا با تغییر وضعیت آن به حالت غیرفعال، حذف منطقی انجام می‌دهد. این کنترل ضمن بررسی نقش کاربر، اطمینان حاصل می‌کند که شیفت درحال استفاده (مثلاً تخصیص‌داده‌شده به پرسنل) قابل حذف نیست.

    1. دریافت شناسه شیفت از پارامتر ورودی.
    2. بررسی وجود شیفت در دیتابیس.
    3. در صورت فعال بودن پرچم force → حذف فیزیکی رکورد.
    4. در غیر این صورت → تغییر فیلد status به ۰ (غیرفعال).
    5. بازگرداندن پاسخ JSON بر اساس نتیجه عملیات.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    id Body integer بله شناسه شیفت قابل حذف
    force Body boolean خیر در صورت true، رکورد به‌صورت دائمی حذف می‌شود

    ساختار خروجی

    {
      "status": true,
      "time": 1732369925,
      "data": {
        "id": 24,
        "deleted_type": "soft",
        "message": "شیفت با موفقیت غیرفعال شد."
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    404 شیفت یافت نشد Database
    409 شیفت دارای وابستگی است (قابل حذف نیست) Business Rule
    500 خطای داخلی سرور Exception Handler

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    shiftWorkDestroy مسیر رسمی حذف شیفت است که قابلیت حذف منطقی و دائمی را فراهم می‌کند. حذف ایمن با کنترل وابستگی، و ورود لاگ کامل در سامانه حسابرسی از ویژگی‌های کلیدی آن محسوب می‌شود.

    * PATCH /personnel/shift/reset

    Route Info

    Method Endpoint Controller Middleware Purpose
    PATCH /personnel/shift/reset OfficialController@shiftWorkReset authWithJwt بازنشانی کلیه‌ی شیفت‌ها به وضعیت اولیه

    منطق عملکرد تابع

    تابع shiftWorkReset کلیه رکوردهای شیفت را از نظر وضعیت، زمان‌بندی و تخصیص‌ها بازنشانی می‌کند. معمولاً جهت پاکسازی داده‌های تستی یا آماده‌سازی دوره جدید کاری به‌کار می‌رود.

    1. احراز هویت JWT و بررسی نقش HR یا Admin.
    2. ثبت عملیات در جدول ممیزی (system_audit).
    3. به‌روزرسانی فیلدهای status، start، end و برگرداندن به مقادیر پیش‌فرض.
    4. در صورت وجود وابستگی (مانند حضور کارمندان)، ابتدا درج لاگ هشدار در audit.
    5. بازگرداندن نتیجه عملیات همراه با تعداد رکوردهای تغییر یافته.

    پارامترهای ورودی

    نام محل نوع الزامی توضیح
    scope Body string خیر محدوده بازنشانی (مثلاً all یا current_month)
    confirm Body boolean بله تأیید نهایی برای انجام بازنشانی (پیش‌فرض false)

    ساختار خروجی

    {
      "status": true,
      "time": 1732370189,
      "data": {
        "reset_scope": "all",
        "affected_rows": 46,
        "message": "شیفت‌ها با موفقیت به وضعیت اولیه بازگردانده شدند."
      }
    }
    

    نکات امنیتی

    عملکرد

    Dependencies

    کدهای خطا

    کد شرح منبع
    400 پارامتر تأیید (`confirm`) ارسال نشده Validation
    403 دسترسی غیرمجاز برای بازنشانی Middleware
    500 خطای داخلی پایگاه داده Server Error

    پیشنهادهای امنیتی

    پیشنهادهای توسعه‌ای

    ممیزی و ثبت وقایع

    جمع‌بندی

    shiftWorkReset آخرین مسیر از مجموعه شیفت‌هاست و هدفش بازگرداندن داده‌ها به حالت پایه‌ای و جلوگیری از انباشت خطای انسانی در تنظیمات زمان‌بندی است. این مسیر با اطمینان بالا، کنترل دسترسی دقیق و پشتیبانی کامل لاگ حسابرسی پیاده‌سازی شده است.

    POST /api/v2/tasks/category/operation

    Route Info

    Method Endpoint Controller
    POST /api/v2/tasks/category/operation OfficialController@operationTasksCategory

    شرح عملکرد (Functionality)

    این متد مدیریت کامل (CRUD) دسته‌بندی‌های تسک را بر عهده دارد و بر اساس پارامتر action رفتار متفاوتی نشان می‌دهد:

    پارامترهای ورودی (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") واکشی می‌کند. منطق تفکیک و پردازش داده‌ها به شرح زیر است:

    پارامترهای ورودی (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 استاتیک را فراخوانی می‌کند:

    1. Operator Resolution (متد StaticController::getOperators):

      شناسه‌های کاربران (چه سازنده و چه اپراتورهای مسئول) به این تابع ارسال می‌شوند. تابع ابتدا ورودی را نرمال‌سازی کرده (تبدیل رشته JSON یا عدد به آرایه)، سپس با کوئری whereIn از جدول operators اطلاعات را واکشی می‌کند. خروجی شامل یک رشته فرمت‌شده (text) برای نمایش سریع و یک آبجکت query حاوی تمام فیلدهای هویتی و سازمانی کاربر است.

    2. 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 عملیات‌های مختلفی را روی دیتابیس اجرا می‌کند:

    پارامترهای ورودی (Body Parameters)

    نام پارامتر نوع الزامی؟ توضیحات
    action String بله تعیین نوع عملیات: store, update, delete, store_note, update_order, update_status
    id Integer شرطی شناسه تسک (در متدهای ویرایشی و حذفی الزامی است).
    data Array بله حاوی جزئیات عملیات (طبق ساختار زیر).

    ساختار آبجکت data:

    نمونه خروجی (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) است، بازیابی می‌کند. منطق عملکرد به شرح زیر است:

    پارامترهای ورودی (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)

    این متد لیست دپارتمان‌های پشتیبانی فعال را جهت نمایش به کاربر برمی‌گرداند. نکات فنی و منطقی این روت عبارتند از:

    پارامترهای ورودی (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)

    این متد لیست اعضای (پاسخ‌دهندگان) یک دپارتمان پشتیبانی خاص را برمی‌گرداند. فرآیند پردازش به شرح زیر است:

    پارامترهای ورودی (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)

    این متد لیست سوالات پیش‌فرض (یا موضوعات تیکت) مربوط به یک دپارتمان خاص را برمی‌گرداند. کوئری دیتابیس شامل فیلترهای زیر است:

    نکته: خروجی مستقیماً تمام ستون‌های جدول 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)

    این متد لیست تیکت‌های پشتیبانی را بر اساس نقش کاربر (درخواست‌کننده یا پاسخ‌دهنده) مدیریت می‌کند. نکات کلیدی منطق آن عبارتند از:

    پارامترهای ورودی (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 است، به این معنی که در هر درخواست تنها یکی از عملیات زیر بر اساس اولویت پارامتر ارسالی انجام می‌شود:

    1. تغییر وضعیت (Status): اگر پارامتر status ارسال شود:
      • وضعیت تیکت آپدیت می‌شود.
      • یک یادداشت خودکار (System Note) متناسب با وضعیت جدید (مثلاً: "مورد شما در حال بررسی می‌باشد...") توسط اپراتور جاری در تیکت درج می‌شود.
      • اگر تیکت هنوز اپراتوری نداشت (operator IS NULL)، اپراتور جاری به عنوان مسئول تیکت ثبت می‌شود.
    2. ثبت امتیاز (Score): اگر پارامتر score ارسال شود، امتیاز کاربر به تیکت ثبت می‌شود.
    3. ارجاع به دپارتمان (Department): اگر پارامتر department ارسال شود، تیکت به دپارتمان دیگری منتقل می‌شود.
    4. تخصیص کارشناس (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) جهت اطلاع‌رسانی درباره پاسخ داده شدن به تیکت استفاده می‌شود. فرآیند به شرح زیر است:

    پارامترهای ورودی (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) آن را بر عهده دارد. همچنین به صورت همزمان عملیات "خوانده شدن" پیام‌ها را انجام می‌دهد. نکات فنی و منطقی آن عبارتند از:

    پارامترهای ورودی (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)

    این متد وظیفه حذف فیزیکی و کامل یک تیکت و تمامی سوابق گفتگوهای آن را بر عهده دارد. عملیات به صورت غیرقابل بازگشت انجام می‌شود:

    پارامترهای ورودی (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)

    این متد لیست تمامی آزمون‌های فعال مرتبط با شعبه جاری را استخراج کرده و آماری از وضعیت هر آزمون ارائه می‌دهد:

    پارامترهای ورودی (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) استفاده می‌شود. کاربرد اصلی آن احتمالا علامت‌گذاری پاسخ‌نامه‌ها برای بررسی مجدد یا تغییر وضعیت رسیدگی به آن‌ها توسط مدیریت است.

    پارامترهای ورودی (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)

    این متد وظیفه تحلیل آماری و تجمیع پاسخ‌های داده شده به یک آزمون خاص را بر عهده دارد. سیستم تمام سوالات فعال آزمون را استخراج کرده و برای هر سوال، پاسخ‌های کاربران را بر اساس نوع سوال پردازش می‌کند:

    پارامترهای ورودی (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)

    این متد اطلاعات مربوط به تعهدات مالی و پارامترهای محاسباتی حقوق و دستمزد (مانند پایه حقوق، سقف مالیاتی، حق مسکن و...) را برای یک سال مالی مشخص بازیابی می‌کند.

    پارامترهای ورودی (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 (تغییر فرمت) را روی داده‌ها انجام می‌دهد.

    مراحل اجرای کد:

    1. Database Query: اتصال به جدول scheduled_notifications و دریافت تمام ستون‌ها.
      • Scope Filtering: نتایج بر اساس مقدار branch فیلتر می‌شوند. این مقدار از آبجکت $request خوانده می‌شود (که معمولاً توسط میدل‌ور پس از احراز هویت ست شده است).
    2. Data Transformation Loop (Map): روی تک‌تک رکوردهای دریافتی یک حلقه اجرا شده و تغییرات زیر اعمال می‌شود:
      • Unsetting Fields: برای امنیت و کاهش حجم Payload، فیلدهای سیستمی زیر حذف می‌شوند:
        • created_at (تاریخ ایجاد رکورد)
        • updated_at (تاریخ ویرایش رکورد)
        • branch (شناسه شعبه - چون همه خروجی‌ها متعلق به یک شعبه هستند، تکرار آن غیرضروری است).
      • JSON Decoding: فیلدهای زیر که در دیتابیس به صورت رشته (String/Text) ذخیره شده‌اند، به آبجکت یا آرایه PHP تبدیل می‌شوند تا در خروجی JSON نهایی به درستی نمایش داده شوند:
        • recipients
        • devices
        • mobiles
    3. Response Formatting: داده‌های پردازش شده در قالب استاندارد پاسخ موفقیت‌آمیز قرار می‌گیرند.
    4. 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) استفاده می‌کند که سربار کمتری دارد.

    مراحل اجرای کد:

    1. Data Serialization (سریال‌سازی داده‌ها):
      ورودی‌های آرایه‌ای یا آبجکت مانند recipients (گیرندگان)، devices (دستگاه‌ها) و mobiles (شماره‌های همراه) قبل از ذخیره شدن در دیتابیس، توسط تابع json_encode به رشته JSON تبدیل می‌شوند.
    2. Auth Context Injection:
      شناسه شعبه (branch) و شناسه اپراتور ثبت‌کننده (operator->id) مستقیماً از آبجکت $request استخراج می‌شوند. این یعنی این مقادیر توسط میدل‌ور authWithJwt در ریکوئست تزریق شده‌اند و کاربر نمی‌تواند آنها را جعل کند.
    3. 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 نیز بازنویسی می‌شوند.

    منطق دقیق کد:

    1. استخراج وابستگی‌های سیستمی (System Dependencies):
      متد مقادیر branch و operator را از آبجکت $request می‌خواند.
      ⚠️ نکته امنیتی: این مقادیر معمولاً توسط میدل‌ور authWithJwt تزریق می‌شوند، اما کد کنترلر کورکورانه آن‌ها را آپدیت می‌کند ($request->get('operator')->id). این یعنی مالکیت رکورد به "اپراتور و شعبه جاری" تغییر می‌کند.
    2. تبدیل داده‌ها (Data Transformation):
      فیلدهای recipients و devices الزاماً با json_encode تبدیل می‌شوند. فیلد mobiles منطق شرطی دارد: اگر مقدار داشته باشد تبدیل به JSON می‌شود، در غیر این صورت NULL ذخیره می‌شود.
    3. مدیریت زمان (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):

    ساختار پاسخ‌ها (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) را برای یک حساب خاص (گروه، کل، معین یا تفضیلی) بر عهده دارد. این تابع تراکنش‌های مالی را از منابع مختلف (اسناد پرداخت، چک‌ها، ارزش افزوده و اسناد افتتاحیه/اختتامیه) جمع‌آوری کرده، بدهکار/بستانکار بودن را محاسبه نموده و خروجی را جهت نمایش در جداول مالی آماده می‌کند. نکات فنی و منطقی آن عبارتند از:

    پارامترهای ورودی (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)

    این متد صورت‌حساب، برخلاف نسخه ساده قبلی، دارای منطق "تفسیر و تجمیع" است که وابستگی شدیدی به توابع استاتیک کمکی دارد:

    ساختار خروجی (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)

    این متد وظیفه ارائه لیست ارزهای موجود در سیستم را دارد و داده‌های استاتیک دیتابیس را با داده‌های لحظه‌ای (نرخ بازار) ترکیب می‌کند.

    پارامترهای ورودی (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)

    این متد لیست حساب‌های بانکی/مالی فعال مربوط به شعبه کاربر جاری را برمی‌گرداند و اطلاعات حساب را با اطلاعات بانک عامل ادغام می‌کند.

    ساختار خروجی (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) داده‌ها و دسته‌بندی اطلاعات در آبجکت‌های تو در تو است.

    پارامترهای ورودی (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) می‌کند.

    پارامترهای ورودی (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) دریافت می‌شود.

    پارامترهای ورودی (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 را دارد.

    پارامترهای ورودی (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) را محاسبه می‌کند.

    پارامترهای ورودی (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 یا آبجکت وضعیت برمی‌گرداند که آیا تراکنش با مبلغ مشخص شده مجاز است یا خیر.

    پارامترهای ورودی (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، لینک‌دهی رزرو و فاکتور، و ساخت ساختار نهایی) برمی‌گرداند.

    پارامترهای ورودی (Query + Body)

    Query Params

    ?branch=101
    

    Body Params (paginate)

    {
      "paginate": {
        "start": 0,
        "length": 20
      }
    }
    

    خروجی (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 (مثلاً موارد زیر):

    {
      "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) و گروه‌بندی چندین رکورد مالی مختلف (مثل سندهای پرداختی، اقلام فاکتور، چک‌ها و سوابق مالی) تحت یک شناسه واحد را دارد.

    1. ابتدا یک رکورد جدید در جدول connections ایجاد می‌کند.
      • شماره سریال (serial) با استفاده از تابع کمکی StaticController::getSerialId تولید می‌شود (این تابع آخرین سریال شعبه را می‌خواند و یکی اضافه می‌کند).
    2. سپس روی آرایه ورودی items حلقه می‌زند و بر اساس type هر آیتم، جدول مربوطه را آپدیت می‌کند:
      • اگر نوع pay, wage, manual_document باشد → جدول pays آپدیت می‌شود.
      • اگر نوع fitem باشد → جدول factor_items آپدیت می‌شود.
      • اگر نوع financial_past باشد → جدول financial_pasts آپدیت می‌شود.
      • اگر نوع check باشد → جدول check_operations آپدیت می‌شود.
    3. در رکوردهای هدف، فیلد 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" }
      ]
    }
    

    خروجی (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 موجود استفاده می‌شود. نوع عملیات وابسته به دو آرایه است:

    برای هر آیتم، فیلد relationship در جدول مربوطه یا به مقدار connection_id آپدیت می‌شود یا null می‌گردد.

    نوع هر آیتم، جدول مقصد را تعیین می‌کند:

    پارامترهای ورودی (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 }
      ]
    }
    
    

    فیلدهای اجباری:

    خروجی (Response)

    موفق (Success)

    {
      "status": true,
      "time": 1718450000
    }
    

    خطا (Error)

    اینجا هیچ مدیریت خطا، اعتبارسنجی یا کنترل وجود ندارد. اگر:

    باز هم متد بدون هیچ هشدار یا Exception، پاسخ موفق بازمی‌گرداند. (Silent Failure)

    POST /v2/accounting/connections/trash

    Route Info

    Method Endpoint Controller
    POST /v2/accounting/connections/trash AccountingController@trashConnection

    شرح عملکرد (Functionality)

    این متد برای حذف نرم (Soft Delete) یک Connection استفاده می‌شود. در این عملیات:

    این عملیات هیچ رکوردی را حذف نمی‌کند و فقط ارتباط آن‌ها با Connection را قطع می‌کند.

    پارامترهای ورودی (JSON Body)

    {
      "connection_id": 42
    }
    

    خروجی (Response)

    موفق (Success)

    {
      "status": true,
      "time": 1718450000
    }
    

    خطاها (Errors)

    برای این مسیر مدیریت خطای اختصاصی پیاده‌سازی نشده است.

    POST /v2/accounting/connections/trash

    Route Info

    Method Endpoint Controller
    POST /v2/accounting/connections/trash AccountingController@trashConnection

    شرح عملکرد (Functionality)

    این متد برای حذف نرم (Soft Delete) یک Connection استفاده می‌شود. در این عملیات:

    این عملیات هیچ رکوردی را حذف نمی‌کند و فقط ارتباط آن‌ها با Connection را قطع می‌کند.

    پارامترهای ورودی (JSON Body)

    {
      "connection_id": 42
    }
    

    خروجی (Response)

    موفق (Success)

    {
      "status": true,
      "time": 1718450000
    }
    

    خطاها (Errors)

    برای این مسیر مدیریت خطای اختصاصی پیاده‌سازی نشده است.

    POST /v2/accounting/connections/view

    Route Info

    Method Endpoint Controller
    POST /v2/accounting/connections/view AccountingController@viewConnection

    شرح عملکرد (Functionality)

    این مسیر برای نمایش جزئیات کامل یک Connection استفاده می‌شود. در این متد، نوع اتصال بررسی می‌شود و در صورتی که اتصال از نوع leger_account باشد، عملیات زیر انجام می‌شود:

    خروجی شامل لیست نهایی اسناد (Bills) است که برای نمایش جزئیات دفاتر حسابداری استفاده می‌شود.

    پارامترهای ورودی (JSON Body)

    {
      "connection_id": 42
    }
    

    خروجی (Response)

    موفق (Success)

    {
      "status": true,
      "time": 1718450000,
      "data": [
         { ... ledger items ... }
      ]
    }
    

    فیلد data شامل آرایه‌ای از تمام آیتم‌های دفتر حساب (Ledger) است. هر آیتم شامل فیلدهای:

    خطاها (Errors)

    این مسیر مدیریت خطای اختصاصی ندارد.

    POST /v2/accounting/connections/merge

    Route Info

    Method Endpoint Controller
    POST /v2/accounting/connections/merge AccountingController@mergeConnection

    شرح عملکرد (Functionality)

    این مسیر برای ادغام دو Connection مورد استفاده قرار می‌گیرد. عملیات به این صورت انجام می‌شود:

    در نهایت، مسیر صرفاً تأیید انجام عملیات را بازمی‌گرداند و اطلاعات اضافی ارسال نمی‌شود.

    پارامترهای ورودی (JSON Body)

    {
      "connection_last_id": 14,
      "connection_current_id": 7
    }
    

    خروجی (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 فعال استفاده می‌شود. امکان اعمال دو نوع فیلتر ورودی وجود دارد:

    پس از اعمال فیلترها، فقط رکوردهایی با 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)

    این مسیر مدیریت خطای اختصاصی ندارد.

    POST /v2/accounting/account/update

    Route Info

    Method Endpoint Controller
    POST /v2/accounting/account/update AccountingController@updateAccountInTreeView

    شرح عملکرد (Functionality)

    این مسیر برای ایجاد (store) یا ویرایش (update) اطلاعات حساب‌های درخت حسابداری مورد استفاده قرار می‌گیرد. در این متد سه نوع ساختار حسابداری پشتیبانی می‌شود:

    نوع عملیات از طریق پارامتر action مشخص می‌شود:

    ورودی‌ها در کلید data قرار می‌گیرند و بر اساس نوع حساب (type) عمل درج یا ویرایش روی یکی از جداول زیر انجام می‌شود:

    در صورت بروز خطا، پیام و 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

    فیلدهای تکمیلی برای نوع Moeen

    خروجی موفق (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 باشد، اطلاعات از جداول مرتبط استخراج شده و ساختار داده متناسب بازگردانده می‌شود.

    اطلاعات بازگشتی شامل:

    پارامترهای ورودی (Query Params)

    /v2/accounting/account/get?type=TYPE&id=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) استفاده می‌شود. روش کار متد بطور خلاصه:

    در هر دو حالت، ثبت لاگ در 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
    }
    

    توضیحات پارامترهای کلیدی

    خروجی موفق (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) استفاده می‌شود. فرآیند عملکرد به صورت زیر است:

    ورودی‌ها (Request Inputs)

    Body Parameters (JSON/Form):

    {
      "id": 1542
    }
    

    خروجی موفق (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)

    POST /v2/manual-document/list

    Route Info

    Method Endpoint Controller
    POST /v2/manual-document/list AccountingController@listManualDocument

    شرح عملکرد (Functionality)

    این مسیر برای لیست‌گیری، جستجو، فیلتر و صفحه‌بندی اسناد دستی در جدول manual_documents استفاده می‌شود. عملکرد متد به صورت زیر است:

    پارامترهای ورودی (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": ""
      }
    }
    

    خروجی موفق (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)

    POST /v2/manual-document/view

    Route Info

    Method Endpoint Controller
    POST /v2/manual-document/view AccountingController@viewManualDocument

    شرح عملکرد (Functionality)

    این مسیر برای نمایش جزئیات کامل یک سند دستی (Manual Document) استفاده می‌شود. پس از دریافت شناسه سند:

    پارامترهای ورودی (Request Inputs)

    Body (JSON یا Form-Data):

    {
      "id": 1524
    }
    

    خروجی موفق (Success Response)

    ساختار خروجی شامل:

    {
      "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

    بسته به نوع سند:

    در پایان:

    POST /v2/manual-document/get

    Route Info

    Method Endpoint Controller
    POST /v2/manual-document/get AccountingController@getManualDocument

    شرح عملکرد (Functionality)

    این مسیر جهت دریافت تمامی جزئیات یک سند دستی و تمام ردیف‌های وابسته به آن استفاده می‌شود.

    فرآیند متد:

    پارامترهای ورودی (JSON Body)

    Body:

    {
      "id": 1524
    }
    

    خروجی موفق (Success Response)

    خروجی شامل سه بخش اصلی است:

    {
      "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": [ ... ]
    }
    

    نکات داخلی اجرای متد

    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)، شماره سیستمی، و عنوان نهایی سند است.

    پارامترهای مسیر (Path Parameters)

    نام نوع توضیح
    type string نوع سند (manual / opening / closing)

    پارامترهای ورودی (JSON Body)

    Body:

    {
      "json": "{\"search\": {\"value\": \"1403/01\"}}",
      "branch": 1
    }
    

    خروجی موفق (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)

    الگوی خروجی عنوان براساس نوع سند:

    در این Route فقط سه نوع زیر بازگردانده می‌شود:

    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) استفاده می‌شود. ورودی شامل آرایه‌ای از رکوردها است که هر کدام شامل:

    این متد با منطق زیر کار می‌کند:

    پارامترهای ورودی (JSON Body)

    Body:

    {
      "data": [
        {
          "moeen": "110203",
          "preference": 120000145
        }
      ],
      "branch": 1
    }
    

    خروجی موفق (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:

    این ۳ بخش به‌ترتیب روی جداول زیر اعتبارسنجی می‌شوند:

    تشخیص نوع تفصیل بر اساس ۲ رقم اول کد

    پیشوند نوع جدول مرتبط
    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

    تمام پاسخ‌ها شامل:

    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)

    1. دریافت شناسه شعبه با کلید branch از Query String یا Token.
    2. فراخوانی تابع officeConfig(branch, 'END_OF_FINANCIAL_PERIOD_CLOSING_ACCOUNTS').
    3. اگر مقدار موجود بود، رشته خروجی مثلاً 14040301 به قالب JSON تبدیل می‌شود:
      • year = 1404
      • month = 03
    4. در غیر این صورت مقدار 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)

    POST /v2/accounting/closing

    Route Info

    Method Endpoint Controller Middleware
    POST /v2/accounting/closing AccountingController@updateClosingAccount authWithJwt

    شرح عملکرد (Functionality)

    این مسیر برای بستن دوره مالی در یک شعبه استفاده می‌شود. قبل از اعمال بستن دوره، سیستم باید اطمینان پیدا کند که در بازه انتخاب‌شده هیچ سند قطعی‌نشده وجود ندارد. این بررسی شامل ۳ دسته سند اصلی است:

    اگر هرکدام از این موارد قطعی نشده باشند، مسیر با خطای 422 و پیام مناسب بازمی‌گردد. در صورت پاک بودن همه اسناد، مقدار جدید END_OF_FINANCIAL_PERIOD_CLOSING_ACCOUNTS برای شعبه در جدول office_config ثبت یا به‌روزرسانی می‌شود.

    پارامترهای ورودی (JSON Body)

    {
      "year": "1404",
      "month": "03",
      "branch": 1
    }
    

    منطق بررسی اسناد قطعی‌نشده (checkDocumentsForClosingAccount)

    این تابع ۳ نوع سند را بررسی می‌کند:

    اسناد فروش (factors)

    هر سندی که:

    اسناد دریافت/پرداخت (pays)

    منطق حساس و دوگانه:

    اسناد دستی (manual_documents)

    خروجی تابع:

    {
      "references": true | false,
      "payments": true | false,
      "manual_documents": true | false,
      "salary": false,
      "treasury_bills": false
    }
    

    پاسخ خطا (422)

    اگر هرگونه سند قطعی‌نشده وجود داشته باشد:

    {
      "error": {
        "code": 1000,
        "message": "اسناد فروش روزانه | دارای سند قطعی نشده می باشد. قبل از بستن حساب ها، تمامی اسناد باید قطعی شوند."
      },
      "meta": {
        "timestamp": 1733056000
      }
    }
    

    *نوع پیام بسته به اولین پرچم فعال در خروجی checkDocumentsForClosingAccount است:

    پاسخ موفق (Success Response)

    اگر هیچ سندی مانع بستن دوره نباشد:

    پاسخ:

    Status: 204 No Content
    Body: ""
    

    وابستگی دیتابیس

    Table Description
    office_config ذخیره مقدار پایان دوره مالی (فرمت: YYYYMM)
    factors اسناد فروش روزانه
    pays اسناد دریافت و پرداخت
    manual_documents سندهای دستی

    META

    PUT /v2/accounting/closing/definite/{type}

    Route Info

    Method Endpoint Controller Middleware
    PUT /v2/accounting/closing/definite/{type} AccountingController@definiteDocumentsInClosingAccount authWithJwt

    شرح عملکرد (Functionality)

    این مسیر برای قطعی‌کردن دسته‌ای اسناد هنگام بستن دوره مالی استفاده می‌شود. نوع سند از پارامتر {type} تعیین می‌شود و شامل موارد زیر است:

    هر نوع سند با شرط‌های خاص زمانی (شمسی/میلادی) و وضعیت فعلی، به وضعیت 3 (قطعی) به‌روزرسانی می‌شود. این API بخشی از پیوستگی عملیات بستن حساب است و معمولاً پس از بررسی عدم وجود سند باز انجام می‌شود.

    پارامترهای ورودی (Input)

    Path Parameter

    Body (JSON):

    {
      "year": "1404",
      "month": "03",
      "branch": 1
    }
    

    منطق پردازش (Logic)

    ابتدا سال/ماه شمسی به میلادی تبدیل می‌شوند:

    قطعی‌کردن اسناد فروش روزانه (type = daily_sales)

    فیلترها: عملیات:
    UPDATE factors SET status = 3

    قطعی‌کردن دریافت/پرداخت روزانه (type = daily_credit_debit)

    شرط‌ها: فیلتر تاریخ: عملیات:
    UPDATE pays SET status = 3

    قطعی‌کردن اسناد دستی (type = manual_documents)

    شرط‌ها: عملیات:
    UPDATE manual_documents SET status = 3

    موارد رزرو (به‌صورت NO-OP)

    اگر type نامعتبر باشد:
    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

    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، سیستم به‌صورت خودکار مقدار پیش‌فرض زیر را اعمال می‌کند:

    محاسبه شماره صفحه بر اساس فرمول زیر انجام می‌شود:

    page = (start == 0 ? length : start + length) / length
    

    پارامترهای ورودی (Query Parameters)

    پاسخ موفق (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)

    وابستگی دیتابیس

    Table Description
    mapping_accounting_preferences جدول نگاشت حسابداری برای انواع موجودیت‌ها

    Meta

    POST /v2/accounting/preference

    Route Info

    Method Endpoint Controller Middleware
    POST /v2/accounting/preference AccountingController@storeMappingPreferences authWithJwt

    شرح عملکرد (Functionality)

    این API برای ثبت نگاشت‌های حسابداری در جدول mapping_accounting_preferences استفاده می‌شود. ورودی به‌صورت مجموعه‌ای از آیتم‌ها در فیلد items ارسال می‌شود. برای هر آیتم:

    در انتها، اگر هیچ خطایی وجود نداشته باشد، همه رکوردهای جدید به صورت Batch درج می‌شوند.

    ورودی مورد نیاز (JSON Body)

    ساختار کلی:

    {
      "branch": 1,
      "items": [
        {
          "id": "customer-24",
          "text": "24 - مشتری عمده",
          "code": "110201"
        },
        {
          "id": "office-3",
          "text": "301 - دفتر مرکزی",
          "code": "880030"
        }
      ]
    }
    

    جزئیات فیلدها:

    ساختاردهی رکورد درج‌شونده

    برای هر آیتم غیرتکراری:

    پاسخ موفق (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

    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)

    پاسخ موفق (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)

    وابستگی دیتابیس

    Table Description
    mapping_accounting_preferences ذخیره‌ساز نگاشت‌های حسابداری

    Meta

    PUT /v2/accounting/preference/{id}

    Route Info

    Method Endpoint Controller Middleware
    PUT /v2/accounting/preference/{id} AccountingController@updateMappingPreferences authWithJwt

    شرح عملکرد (Functionality)

    این API برای ویرایش یک نگاشت حسابداری در جدول mapping_accounting_preferences استفاده می‌شود.

    منطق مسیر شامل مراحل زیر است:

    ساختار ورودی (JSON Body)

    {
      "branch": 1,
      "id": "customer-24",
      "text": "24 - مشتری عمده",
      "code": "110201"
    }
    

    توضیح فیلدها

    اعتبارسنجی

    قبل از ذخیره، بررسی می‌شود که آیا کد جدید در همان شعبه قبلاً استفاده شده است یا خیر:

    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

    POST /v2/account-history/calculate-daily

    Route Info

    Method Endpoint Controller Middleware
    POST /v2/account-history/calculate-daily AccountHistoryController@calculateDailyBalanceForYear authWithJwt

    شرح عملکرد (Functionality)

    این API برای محاسبه و ذخیره مانده‌های روزانه یک همکار (Colleague) استفاده می‌شود. فرآیند محاسبه می‌تواند:

    محاسبات امکان اجرا به صورت:

    نتیجه‌های محاسبه شده در 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
    }
    

    قوانین اعتبارسنجی

    منطق اجرا (Execution Logic)

    مشخصات 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

    POST /v2/account-history/calculate-monthly

    Route Info

    Method Endpoint Controller Middleware
    POST /v2/account-history/calculate-monthly AccountHistoryController@calculateMonthlyBalanceForYear authWithJwt

    شرح عملکرد (Functionality)

    این API وظیفه محاسبه و ذخیره مانده‌های ماهانه برای یک همکار (Colleague) را دارد. محاسبه می‌تواند بر اساس:

    محاسبات می‌توانند به صورت:

    در حالت 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)
    }
    

    قوانین اعتبارسنجی

    منطق اجرا (Execution Logic)

    مشخصات 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

    POST /v2/account-history/calculate-complete

    Route Info

    Method Endpoint Controller Middleware
    POST /v2/account-history/calculate-complete AccountHistoryController@calculateCompleteHistory authWithJwt

    شرح عملکرد (Functionality)

    این API برای محاسبه کامل مانده‌ها استفاده می‌شود؛ یعنی:

    محاسبه‌ها می‌توانند بر اساس:

    روش اجرا:

    هر دو محاسبه از طریق سرویس AccountHistoryService انجام می‌شوند.

    ورودی (Request Body)

    {
      "colleague_id": 108,
      "year": 1403,           // optional
      "from": "1403-01-01",   // optional
      "to": "1403-12-29",     // optional
      "async": true           // optional (default = true)
    }
    

    قوانین اعتبارسنجی

    منطق اجرا (Execution Logic)

    مشخصات 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

    GET /v2/account-history/daily

    Route Info

    Method Endpoint Controller Middleware
    GET /v2/account-history/daily AccountHistoryController@getDailyBalance authWithJwt

    شرح عملکرد (Functionality)

    این API صرفاً برای خواندن سریع اطلاعات محاسبه‌شده از قبل استفاده می‌شود.

    ورودی (Query Parameters)

    پارامترها به صورت Query String ارسال می‌شوند:

    ?colleague_id=108&year=1403&date=1403-01-05
    

    قوانین اعتبارسنجی (Validation Rules)

    منطق اجرا (Execution Logic)

    پاسخ موفق (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"
      }
    }
    
    * ساختار payload بسته به خروجی سرویس ممکن است دقیقاً به این شکل باشد.

    پاسخ‌های خطا (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)

    GET /v2/account-history/monthly

    Route Info

    Method Endpoint Controller Middleware
    GET /v2/account-history/monthly AccountHistoryController@getMonthlyBalance authWithJwt

    شرح عملکرد (Functionality)

    این API برای دریافت مانده حساب ماهانه طراحی شده است و دارای مکانیزم هوشمند (Read-Through Cache) است:

    1. بررسی کش: ابتدا سعی می‌کند داده را از Redis بخواند.
    2. محاسبه خودکار (Auto-Calculation): اگر داده در کش نباشد، سیستم به طور خودکار متد calculateAndStoreMonthlyBalance را اجرا می‌کند تا داده‌های آن سال تولید و ذخیره شوند.
    3. بازخوانی: پس از محاسبه، مجدداً داده را از کش می‌خواند و برمی‌گرداند.

    توجه: اگر کش خالی باشد، پاسخ این سرویس ممکن است کمی طول بکشد (به اندازه زمان محاسبه مانده سالانه)

    ورودی (Query Parameters)

    ?colleague_id=108&year=1403&month=2
    

    قوانین اعتبارسنجی (Validation Rules)

    منطق اجرا (Execution Logic)

    پاسخ موفق (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 برای دریافت یکجای تمام سوابق روزانه یک سال خاص استفاده می‌شود (مثلاً برای رسم نمودار تغییرات موجودی در طول سال).

    ورودی (Query Parameters)

    ?colleague_id=108&year=1403
    

    قوانین اعتبارسنجی (Validation Rules)

    منطق اجرا (Execution Logic)

    پاسخ موفق (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)

    GET /v2/account-history/all-monthly

    Route Info

    Method Endpoint Controller Middleware
    GET /v2/account-history/all-monthly AccountHistoryController@getAllMonthlyBalances authWithJwt

    شرح عملکرد (Functionality)

    این API برای دریافت یکجای تمام مانده‌های ماهانه یک سال مشخص استفاده می‌شود.

    ورودی (Query Parameters)

    ?colleague_id=108&year=1403
    

    قوانین اعتبارسنجی (Validation Rules)

    منطق اجرا (Execution Logic)

    پاسخ موفق (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"
      }
    }
    
    * در مثال بالا فقط ماه‌های 1، 2 و 12 محاسبه شده و در کش بوده‌اند.

    پاسخ‌های خطا (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)

    DELETE /v2/account-history/cache

    Route Info

    Method Endpoint Controller Middleware
    DELETE /v2/account-history/cache AccountHistoryController@clearCache authWithJwt

    شرح عملکرد (Functionality)

    این API برای پاک‌سازی دستی کش (Invalidation) استفاده می‌شود. زمانی که داده‌های زیرساختی تغییر کرده‌اند و نیاز است محاسبات مجدداً از صفر انجام شوند، این متد صدا زده می‌شود.

    ورودی‌ها (Input Parameters)

    پارامترها می‌توانند در Query String یا Body (JSON) ارسال شوند.

    {
      "colleague_id": 108,
      "year": 1403
    }
    

    قوانین اعتبارسنجی (Validation Rules)

    منطق اجرا (Execution Logic)

    پاسخ موفق (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)

    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 اجرا می‌شود و این روت فقط فراخوانی و مدیریت پاسخ را انجام می‌دهد.

    ورودی‌ها (Input Parameters)

    این روت هیچ پارامتر ورودی‌ای در Query یا Body دریافت نمی‌کند.

    منطق اجرا (Execution Logic)

    پاسخ موفق (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)

    POST /v2/redis-accounting/create-missing-documents

    Redis Accounting Sync

    مستندات فنی سرویس حسابداری - نسخه 2.0


    POST /v2/redis-accounting/create-missing-documents
    این متد برای بازیافت اطلاعات (Data Recovery) استفاده می‌شود. سیستم تمامی کلیدهای موجود در Redis را پیمایش کرده و با دیتابیس SQL مقایسه می‌کند. اگر سندی در Redis باشد اما در دیتابیس اصلی نباشد، آن را ایجاد می‌کند.


    مسیر پردازش داده (Data Flow)

    Client Request

    Middleware (Auth & Date)

    Service: Scan Redis Keys

    Exists in DB? Skip
    Missing in DB? Create Doc

    Return Response JSON

    Header Requirements

    Key Value وضعیت
    Authorization Bearer {Token} الزامی
    Accept application/json الزامی

    نمونه پاسخ‌ها (Responses)

    JSON Response 200 OK
    {
        "success": true,
        "data": {
            "total_scanned": 1540,
            "total_processed": 12,
            "details": [
                "Document #8891 synced from Redis."
            ]
        },
        "message": "پردازش کامل شد. 12 سند ایجاد شد."
    }

    JSON Response 500 Server Error
    {
        "success": false,
        "message": "خطا در ایجاد اسناد: Redis connection timed out."
    }

    ⚠️
    هشدار پرفورمنس (Performance):
    این عملیات Heavy I/O است. در صورتی که تعداد کلیدهای ردیس بسیار زیاد باشد (مثلاً بیش از ۱۰,۰۰۰ رکورد)، ممکن است اجرای این درخواست باعث کندی سیستم یا خطای Timeout شود. پیشنهاد می‌شود فقط در زمان‌های کم ترافیک اجرا شود.



    POST /v2/redis-accounting/create-missing-documents

    Create Missing Documents

    این اندپوینت در سرویس Redis Accounting وظیفه دارد همه مدارکی که باید وجود داشته باشند اما در Redis ثبت نشده‌اند را شناسایی و ایجاد کند. این عملیات معمولاً در موارد زیر استفاده می‌شود:

    Request Overview

    URL: /v2/redis-accounting/create-missing-documents
    Method: POST
    Auth: Bearer Token (Required)
    Content-Type: application/json

    Request 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

    Start Request
    Load organization_id
    Compare Database vs Redis
    Identify Missing Documents
    dry_run ? return diff : create documents
    Return Response

    POST /v2/redis-accounting/save-manual-document

    Save Manual Document

    این اندپوینت یک سند دستی (Manual Document) را از دیتابیس بارگذاری کرده و ردیف‌های آن را در Redis ذخیره می‌کند. این عملیات زمانی استفاده می‌شود که بخواهیم وضعیت یک سند دستی در Redis بازسازی یا هماهنگ‌سازی شود.

    Request Overview

    URL: /v2/redis-accounting/save-manual-document
    Method: POST
    Auth: Bearer Token (Required)
    Content-Type: application/json

    Request 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

    Receive Request (id)
    Validate id
    Load Manual Document Details
    Loop over balance rows
    saveDocument(...) for each row
    Return result with saved count

    POST /v2/redis-accounting/save-document

    Save Document

    این اندپوینت یک سند حسابداری را در هسته حسابداری Redis ذخیره می‌کند. عملیات شامل ایجاد Hash سند، ساخت ایندکس‌های زمانی و حسابداری، و به‌روزرسانی مانده‌ها در سه سطح گروه/کل/حساب است. این متد ستون فقرات سیستم حسابداری Redis محسوب می‌شود.

    Request Overview

    URL: /v2/redis-accounting/save-document
    Method: POST
    Auth: Bearer Token (Required)
    Content-Type: application/json

    Request 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

    Receive Request
    Validate Input
    Convert Jalali → Gregorian
    Normalize Debtor/Creditor
    Generate Serial (if needed)
    Create Hash & Indexes in Redis
    Update Balances
    Return Final Response

    DELETE /v2/redis-accounting/delete-document

    Delete Document

    این اندپوینت یک سند حسابداری را از هسته Redis حذف می‌کند. عملیات شامل حذف Hash سند، حذف شناسه از تمامی ایندکس‌ها، و پاک‌سازی کامل کلیدهای بالانس مرتبط با گروه/کل/معین/تفضیلی است. توجه: در منطق فعلی سیستم، بالانس‌ها recalculate نمی‌شوند و به‌جای آن حذف کامل می‌گردند.

    Request Overview

    URL: /v2/redis-accounting/delete-document
    Method: DELETE
    Auth: Bearer Token (Required)
    Content-Type: application/json

    Request 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

    Receive Request
    Validate Input (id Required)

    DELETE /v2/redis-accounting/clear-all

    Clear All Documents

    این اندپوینت تمام اسناد حسابداری ذخیره‌شده در Redis را حذف می‌کند. عملیات شامل حذف تمامی Hashها، پاک‌سازی کامل تمام ایندکس‌ها (Set / Sorted Set)، و حذف همه کلیدهای بالانس در سطوح گروه، کل، معین و تفضیلی است. این عملیات یک عملیات مخرب و غیرقابل بازگشت محسوب می‌شود.

    Request Overview

    URL: /v2/redis-accounting/clear-all
    Method: DELETE
    Auth: Bearer Token (Required)
    Content-Type: application/json

    Request 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

    Receive Request
    Call redisAccountingService.clearAllDocuments()
    Delete All Document Hashes
    Delete All Indexes (Sets & ZSets)
    Delete All Balance Keys
    Return Final Response

    GET /v2/redis-accounting/documents/subsidiary

    Get Documents by Subsidiary

    این اندپوینت لیست اسناد حسابداری مربوط به یک کد تفضیلی را از Redis برمی‌گرداند. دیتای خروجی شامل اسناد، مانده فعلی، تعداد کل اسناد، و در صورت درخواست، مانده تجمیعی از ابتدای سال تا تاریخ مشخص است. عملیات بر اساس ایندکس Redis accounting:docs:subsidiary:{code} انجام می‌شود.

    Request Overview

    URL: /v2/redis-accounting/documents/subsidiary
    Method: GET
    Auth: Bearer Token (Required)
    Content-Type: application/json

    Query 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

    Receive Request + Validate Inputs
    Fetch IDs from Redis Set (accounting:docs:subsidiary:{code})
    Load Each Document Hash (accounting:doc:{id})
    Sort Documents by Date
    Load Current Balance (balance:subsidiary:{code})
    If from_year_start = true → Calculate Year-Start Balance
    Return Final JSON Response

    GET /v2/redis-accounting/documents/account

    Get Documents by Account

    این اندپوینت لیست اسناد حسابداری مربوط به یک کد معین را از Redis بازیابی می‌کند. خروجی شامل اسناد، مانده فعلی، تعداد اسناد، و در صورت فعال بودن، ماندهٔ تجمیعی از ابتدای سال است. عملیات بر اساس ایندکس Redis accounting:docs:account:{code} انجام می‌شود.

    Request Overview

    URL: /v2/redis-accounting/documents/account
    Method: GET
    Auth: Bearer Token (Required)
    Content-Type: application/json

    Query 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

    Receive Request + Validate Inputs
    Load Document IDs (accounting:docs:account:{code})
    Fetch Every Document Hash (accounting:doc:{id})
    Sort Documents by Date
    Read Current Balance (balance:account:{code})
    If from_year_start=true → compute getBalanceFromYearStart()
    Return Final JSON Response

    GET /v2/redis-accounting/documents/general

    Get Documents by General

    این اندپوینت اسناد حسابداری مرتبط با یک کد کل (General Code) را از Redis بازیابی می‌کند. خروجی شامل: لیست اسناد، مانده فعلی، تعداد اسناد، و اگر درخواست شده باشد، مانده تجمیعی از ابتدای سال است. داده‌ها از ایندکس Redis با الگو accounting:docs:general:{code} استخراج می‌شوند.

    Request Overview

    URL: /v2/redis-accounting/documents/general
    Method: GET
    Auth: Bearer Token (Required)
    Middleware: shamsiDate

    Query 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

    Validate Inputs
    Load IDs (accounting:docs:general:{code})
    Fetch Documents (accounting:doc:{id})
    Sort by Document Date
    Get Balance (balance:general:{code})
    If from_year_start=true → Compute Year-Start Balance
    Return JSON Response

    GET /v2/redis-accounting/documents/group

    Get Documents by Group

    این اندپوینت وظیفه دارد تمام اسناد حسابداری مرتبط با یک کد گروه (Group Code) را از Redis بازیابی کند. داده‌ها شامل اسناد، مانده فعلی، تعداد اسناد، و در صورت درخواست، مانده تجمیعی از ابتدای سال شمسی است. استخراج داده از Redis بر اساس الگوی زیر انجام می‌شود: accounting:docs:group:{code}.

    Request Overview

    URL: /v2/redis-accounting/documents/group
    Method: GET
    Middleware: authWithJwt, shamsiDate

    Query 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

    Validate Inputs
    Fetch IDs (accounting:docs:group:{code})
    Load Each Document (accounting:doc:{id})
    Sort by Date Field
    Get Redis Balance (balance:group:{code})
    If requested → Calculate Year-Start Balance
    Return Final JSON Payload

    GET /v2/redis-accounting/documents/group

    Get Documents by Group

    این اندپوینت وظیفه دارد تمام اسناد حسابداری مرتبط با یک کد گروه (Group Code) را از Redis بازیابی کند. داده‌ها شامل اسناد، مانده فعلی، تعداد اسناد، و در صورت درخواست، مانده تجمیعی از ابتدای سال شمسی است. استخراج داده از Redis بر اساس الگوی زیر انجام می‌شود: accounting:docs:group:{code}.

    Request Overview

    URL: /v2/redis-accounting/documents/group
    Method: GET
    Middleware: authWithJwt, shamsiDate

    Query 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

    Validate Inputs
    Fetch IDs (accounting:docs:group:{code})
    Load Each Document (accounting:doc:{id})
    Sort by Date Field
    Get Redis Balance (balance:group:{code})
    If requested → Calculate Year-Start Balance
    Return Final JSON Payload

    GET /v2/redis-accounting/documents/date-range

    Get Documents By Date Range

    این اندپوینت اسناد حسابداری را بر اساس یک بازهٔ تاریخ شمسی (Jalali) از Redis استخراج می‌کند. استخراج اسناد بر اساس ایندکس تاریخ شمسی accounting:docs:by_jalali_date انجام شده و خروجی شامل لیست اسناد، تعداد کل اسناد و اطلاعات معتبر از بازهٔ ارسال‌شده است.

    Request Overview

    URL: /v2/redis-accounting/documents/date-range
    Method: GET
    Middleware: authWithJwt, shamsiDate

    Query 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

    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

    Validate Input Format (Regex)
    Parse Jalali Dates (Jalalian)
    Validate start_date <= end_date
    Convert YYYY-MM-DD → YYYYMMDD scores
    Fetch IDs from ZSET by Score Range
    Load Each Document via HGETALL
    Sort Docs by Jalali Date
    Return Final JSON Payload

    GET /v2/redis-accounting/balance

    Get Redis Balance

    این اندپوینت مانده حساب را بر اساس نوع و کد حساب از Redis بازیابی می‌کند. مانده‌ها در کلیدهای balance:{type}:{code} ذخیره می‌شوند و شامل سه مقدار debit، credit و remaining هستند.

    Request Overview

    URL: /v2/redis-accounting/balance
    Method: GET
    Middleware: authWithJwt, shamsiDate

    Query 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 ساختار مانده حساب
    • debit
    • credit
    • remaining = debit - credit

    Process Flow

    Validate Inputs (type, code)
    Read from Redis (HGETALL)
    Convert Fields to float
    Compute remaining = debit - credit
    Return JSON Payload

    GET /v2/redis-accounting/stats

    Get Redis Statistics

    این اندپوینت آمار کلی Redis را در حوزه سیستم حسابداری استخراج می‌کند. شامل تعداد اسناد، تعداد ایندکس‌ها، تعداد مانده‌ها و میزان حافظه مصرف شده Redis. این روت برای استفاده داخلی (Ops, Engineering, Monitoring) طراحی شده است.

    Request Overview

    URL: /v2/redis-accounting/stats
    Method: GET
    Middleware: authWithJwt, shamsiDate
    Controller: RedisAccountingController@getRedisStats

    Authentication

    این اندپوینت کاملاً محافظت‌شده است. فقط کاربران احراز هویت شده و دارای 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

    URL: /v2/batch-accounting/process/date-range
    Method: POST
    Middleware: authWithJwt, shamsiDate
    Controller: BatchAccountingController@processByDateRange

    Authentication

    این اندپوینت کاملاً محافظت‌شده بوده و فقط کاربران دارای 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

    Validate JWT Authentication
    Validate Shamsi Dates (middleware)
    Request Validation (start_date, end_date, batch_size)
    Query DB: manual_documents (date range, status != 5)
    Chunk Documents (batch_size)
    Loop Through Chunks
    Process Each Batch (processBatch)
    Update Redis: documents + indexes + balances
    usleep(100ms) Throttle
    Aggregate Results (processed/errors)
    Return Final JSON Response

    POST /v2/batch-accounting/process/month

    Process Batch Documents By Month

    این اندپوینت اسناد حسابداری را بر اساس یک ماه شمسی مشخص دریافت کرده و آن‌ها را به صورت دسته‌ای (Batch Processing) پردازش می‌کند. ابتدا از تاریخ ورودی برای تعیین محدودهٔ کامل ماه استفاده می‌شود سپس عملیات پردازش دسته‌ای بر اساس منطق processBatchByDateRange اجرا می‌گردد. خروجی شامل تعداد کل اسناد پردازش‌شده، اطلاعات هر Batch و پیام نهایی مربوط به نام فارسی ماه است.

    Request Overview

    URL: /v2/batch-accounting/process/month
    Method: POST
    Middleware: authWithJwt, shamsiDate

    Body 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

    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

    Validate JWT Token
    Validate Shamsi Date (middleware)
    Validate Request Body (month_date + batch_size)
    Extract startOfMonth & endOfMonth (ShamsiDateHelper)
    Call processBatchByDateRange(start, end, batchSize)
    Execute Batch Processing (chunking + processing + throttling)
    Aggregate Results (processed + errors)
    Get Month Name (getMonthName)
    Return Final Response

    POST /v2/batch-accounting/process/month

    Process Batch Documents By Month

    این اندپوینت اسناد حسابداری را بر اساس یک ماه شمسی مشخص دریافت کرده و آن‌ها را به صورت دسته‌ای (Batch Processing) پردازش می‌کند. ابتدا از تاریخ ورودی برای تعیین محدودهٔ کامل ماه استفاده می‌شود سپس عملیات پردازش دسته‌ای بر اساس منطق processBatchByDateRange اجرا می‌گردد. خروجی شامل تعداد کل اسناد پردازش‌شده، اطلاعات هر Batch و پیام نهایی مربوط به نام فارسی ماه است.

    Request Overview

    URL: /v2/batch-accounting/process/month
    Method: POST
    Middleware: authWithJwt, shamsiDate

    Body 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

    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

    Validate JWT Token
    Validate Shamsi Date (middleware)
    Validate Request Body (month_date + batch_size)
    Extract startOfMonth & endOfMonth (ShamsiDateHelper)
    Call processBatchByDateRange(start, end, batchSize)
    Execute Batch Processing (chunking + processing + throttling)
    Aggregate Results (processed + errors)
    Get Month Name (getMonthName)
    Return Final Response

    POST /v2/batch-accounting/process/year

    Process Batch Documents By Year

    این اندپوینت اسناد حسابداری را بر اساس یک سال شمسی مشخص پردازش می‌کند. ابتدا تاریخ شروع سال و پایان سال شمسی محاسبه می‌شود، سپس عملیات دسته‌ای (Batch Processing) توسط متد processBatchByDateRange اجرا می‌گردد. نتیجه نهایی شامل تعداد اسناد پردازش‌شده، تعداد خطاها و مجموع Batchهای اجرا شده است.

    Request Overview

    URL: /v2/batch-accounting/process/year
    Method: POST
    Middleware: authWithJwt, shamsiDate

    Body 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

    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

    Validate JWT Token
    Validate Request Body (year + batch_size)
    Compute startOfYear = {year}-01-01
    Compute endOfYear via ShamsiDateHelper::endOfYear
    Call processBatchByDateRange(startOfYear, endOfYear, batchSize)
    Execute Batch Processing (chunking + throttling + batch execution)
    Aggregate Results (processed + errors + batch_count)
    Return Final JSON Response

    POST /v2/batch-accounting/process/current-month

    Process Batch Documents of Current Month

    این اندپوینت ماه جاری شمسی را به صورت خودکار تشخیص داده و اسناد آن را با استفاده از پردازش دسته‌ای (Batch Processing) پردازش می‌کند. تاریخ روز جاری از طریق ShamsiDateHelper::today() استخراج می‌شود و سپس عملیات ماه جاری مانند متد processBatchByMonth انجام می‌گیرد. پیام نهایی شامل نام فارسی ماه جاری است.

    Request Overview

    URL: /v2/batch-accounting/process/current-month
    Method: POST
    Middleware: authWithJwt, shamsiDate

    Body 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

    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

    Validate JWT Token
    Extract Current Date (ShamsiDateHelper::today)
    Compute Start/End of Month via startOfMonth & endOfMonth
    Call processBatchByMonth(currentDate, batchSize)
    processBatchByDateRange(start, end, batchSize)
    Execute Batch Processing (chunking + throttling)
    Aggregate Results (processed + errors + batch_count)
    Get Current Month Name (getMonthName)
    Return JSON Response

    POST /v2/batch-accounting/process/current-year

    Process Batch Documents of Current Year

    این اندپوینت سال جاری شمسی را به صورت خودکار تشخیص داده و اسناد آن را از طریق پردازش دسته‌ای (Batch Processing) پردازش می‌کند. سال جاری از تاریخ کامل امروز، که توسط ShamsiDateHelper::today() تولید می‌شود، استخراج شده و سپس متد processBatchByYear فراخوانی می‌شود.

    Request Overview

    URL: /v2/batch-accounting/process/current-year
    Method: POST
    Middleware: authWithJwt, shamsiDate

    Body 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

    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

    Validate JWT Token
    Get Today (ShamsiDateHelper::today)
    Extract Current Year (YYYY)
    Call processBatchByYear(currentYear, batchSize)
    Compute startOfYear / endOfYear
    Execute Batch Processing (chunking + throttling)
    Aggregate Results (processed + errors + batch_count)
    Return Final JSON Response

    POST /v2/batch-accounting/rebuild-indexes

    Rebuild All Redis Indexes

    این اندپوینت یک عملیات بسیار سنگین و مخرب (destructive) برای بازسازی کامل تمامی ایندکس‌های Redis اجرا می‌کند. قبل از شروع، تمام داده‌های موجود در Redis شامل اسناد، ایندکس‌ها و مانده‌ها حذف می‌شوند و سپس بر اساس رکوردهای فعال جدول manual_documents، ایندکس‌ها از صفر ساخته می‌شوند. برای جلوگیری از اجرای اشتباه، فیلد confirm اجباری است.

    Request Overview

    URL: /v2/batch-accounting/rebuild-indexes
    Method: POST
    Middleware: authWithJwt, shamsiDate

    Body 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

    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

    Validate JWT Token
    Validate Input (batch_size, confirm)
    Clear All Redis Documents (clearAllDocuments)
    Count Active DB Documents (status != 5)
    Fetch Documents in Batches (chunk)
    Process Each Batch (processBatch)
    Update Progress Log
    Aggregate Final Stats (processed, errors)
    Return JSON Response

    POST /v2/batch-accounting/optimize-memory

    Optimize Redis Memory

    این اندپوینت عملیات بهینه‌سازی حافظه Redis را به صورت کامل و ساختاریافته انجام می‌دهد. هدف عملیات شامل: - اندازه‌گیری حافظه فعلی، - حذف کلیدهای غیرمعتبر، - اجرای فشرده‌سازی AOF از طریق bgrewriteaof, - و ارائه گزارش میزان حافظه آزادشده است. از آنجا که این عملیات می‌تواند بر عملکرد موقتی Redis اثر بگذارد، معمولاً در محیط‌های Production با احتیاط فراخوانی می‌شود.

    Request Overview

    URL: /v2/batch-accounting/optimize-memory
    Method: POST
    Middleware: authWithJwt, shamsiDate

    Body 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

    Validate JWT Token
    Get Initial Memory Usage
    Execute Lua Script for Orphan/Invalid Keys
    Run Redis BGREWRITEAOF
    Measure Final Memory Usage
    Calculate Saved Memory & Percentage
    Return Final JSON Report

    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

    URL: /v2/batch-accounting/report/comprehensive
    Method: GET
    Middleware: authWithJwt, shamsiDate

    Query 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

    Validate JWT Token
    Fetch Redis General Stats
    Analyze Documents by Month
    Calculate Top Subsidiaries
    Summarize Balances
    Read Memory Detailed Info
    Collect Performance Metrics
    Return Final Comprehensive Report

    POST /v2/batch-accounting/preview

    Preview Batch Processing (Dry‑Run)

    این اندپوینت یک پیش‌نمایش کامل از عملیات پردازش Batch را ارائه می‌دهد، بدون اینکه هیچ پردازش واقعی روی Redis یا دیتابیس انجام شود. این پیش‌نمایش برای تصمیم‌گیری مهندسی قبل از اجرای پردازش‌های سنگین استفاده می‌شود و شامل: - تعداد اسناد قابل پردازش، - تعداد Batch مورد نیاز، - زمان تخمینی، - تعداد روزهای شمسی در بازه، - و اطلاعات کامل بازه تاریخ می‌باشد.

    Request Overview

    URL: /v2/batch-accounting/preview
    Method: POST
    Middleware: authWithJwt, shamsiDate

    Body 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

    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

    Validate JWT Token
    Validate Input (start_date, end_date, batch_size)
    Count DB Documents (manual_documents)
    Compute Estimated Batches
    Compute Estimated Time
    Generate Shamsi Date Range
    Return Preview JSON

    POST /v2/core/offices/list

    Core Offices List

    این اندپوینت لیست کامل دفاتر (Offices) سیستم را از جدول offices بازیابی کرده و برای استفاده در پنل‌های مدیریتی Core Center برگشت می‌دهد. دسترسی به این اندپوینت کاملاً ویژه است و فقط کاربرانی که Branch آن‌ها دقیقاً مقدار "[0]" باشد می‌توانند وارد شوند.

    Request Overview

    URL: /v2/core/offices/list
    Method: POST
    Controller: CoreController@officesList
    Middleware Stack: authWithJwt → core

    Authentication

    لازم است هدر زیر حتماً ارسال شود:

    Authorization: Bearer <JWT_TOKEN>

    میدلور AuthWithJWT موارد زیر را احراز می‌کند:

    سپس میدلور 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

    Check Bearer Token
    Decode JWT (HS256)
    Load User From Table (operators/customers/colleague)
    Check user.status == 1
    CoreAccess → Check operator.branch == "[0]"
    Query DB: offices table
    Return JSON Response

    POST /v2/core/accommodations/list

    Core Accommodations List

    این اندپوینت لیست اقامتگاه‌ها (هتل‌ها) را با پشتیبانی از فیلترهای پیشرفته، صفحه‌بندی پویا، بارگذاری داده‌های تکمیلی از Redis، اطلاعات جغرافیایی از دیتابیس، و استخراج Supplier Mapping برگشت می‌دهد. این سرویس صرفاً در اختیار کاربرانی قرار می‌گیرد که دسترسی Core داشته باشند (operator.branch == "[0]").

    Request Overview

    URL: /v2/core/accommodations/list
    Method: POST
    Controller: CoreController@accommodationsList
    Middleware Stack: authWithJwt → core

    Authentication & Access Control

    مانند سایر سرویس‌های هسته Core:

    در صورت عدم دسترسی:

    {
      "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 اعمال می‌شود:

    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

    AuthWithJWT → Validate Token
    CoreAccess → Check branch == "[0]"
    Decode Body.json
    Compute Pagination (start/length)
    Query hotels with Advanced Filters
    For Each Hotel → Enrichment Pipeline (Redis Country, City join, Mapping, Media)
    Return Final JSON Payload

    POST /v2/core/accommodation/store

    Core Accommodation Store

    این اندپوینت یک اقامتگاه جدید (Hotel / Accommodation) را در سیستم ثبت می‌کند. داده‌ها از کلید data در بدنه درخواست دریافت شده و به‌صورت کامل روی جدول hotels درج می‌شوند. سپس عملیات‌های جانبی شامل ذخیره لوگو و گالری رسانه، ثبت Mapping در جدول mapping_accommodations، و اجرای سه Job (UpdateRedis، SystemLog و RunCron) در صف انجام می‌شود.

    Request Overview

    URL: /v2/core/accommodation/store
    Method: POST
    Controller: CoreController@storeAccommodation
    Middleware Stack: authWithJwt → core

    Access Control

    دسترسی فقط در صورتی مجاز است که:

    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 وب‌سایت
    email 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

    عملیات اصلی درج:

    id = DB::table('hotels')->insertGetId([...])
      

    Media Insertion Rules

    هر فایل مسیر مانند

    "uploads/hotels/abc.jpg"

    به شکل زیر پردازش می‌شود:

    Mapping Insertion

    شیء mapping می‌تواند شامل کلیدهای زیر باشد. هر مقدار خالی (null / "") → در DB مقدار null ذخیره می‌شود.

    در جدول 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

    AuthWithJWT → Validate Token
    CoreAccess → operator.branch == "[0]"
    Extract data[] From Request
    Insert Main Record Into hotels
    If logo → Insert Into Media
    If media[] → Insert Each Into Media
    If mapping → Insert Into mapping_accommodations
    Dispatch 3 Background Jobs
    Return Success JSON

    POST /v2/core/accommodation/update

    Core Accommodation Update

    این اندپوینت برای به‌روزرسانی اطلاعات یک اقامتگاه موجود استفاده می‌شود. رفتار اندپوینت در دو حالت انجام می‌شود:
    method = "update" → به‌روزرسانی کامل رکورد اصلی (hotels)، ثبت لوگو و رسانه، به‌روزرسانی یا ایجاد Mapping، و اجرای Jobها.
    method = "board" → فقط آپدیت فیلد Board و اجرای Jobهای مرتبط.

    Request Overview

    URL: /v2/core/accommodation/update
    Method: POST
    Controller: CoreController@updateAccommodation
    Middleware Stack: authWithJwt → core

    Access Control

    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 وب‌سایت
    email 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")

    Media Update Rules

    Mapping Logic

    Update Logic (method = "board")

    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

    Validate JWT → Validate Core Access
    Read method / id / data
    IF method = update
    Update hotels Record
    Insert logo if new
    Insert media images if new
    Insert/Update Supplier Mapping
    Dispatch UpdateRedis + SystemLog
    Return Success


    IF method = board
    Update board Field Only
    Dispatch UpdateRedis(board) + SystemLog
    Return Success

    DELETE /v2/core/accommodation/delete

    Core Accommodation Delete

    این اندپوینت برای حذف کامل یک اقامتگاه (Hotel / Accommodation) از سیستم استفاده می‌شود. عملیات حذف فقط رکورد اصلی جدول hotels را پاک می‌کند و هیچ عمل پاکسازی روی Media یا Mapping انجام نمی‌دهد. پس از حذف، یک SystemLog با تأخیر ۱۰ دقیقه به صف snailJob ارسال می‌شود.

    Request Overview

    URL: /v2/core/accommodation/delete
    Method: DELETE
    Controller: CoreController@deleteAccommodation
    Middleware Stack: authWithJwt → core

    Access Control

    Request Parameters

    Field Type Required Description
    id integer yes ID اقامتگاه برای حذف

    پارامتر id باید به‌صورت QueryParam یا BodyParam (بسته به ساختار درخواست) ارسال شود.

    Delete Logic

    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

    AuthWithJWT → Validate Token
    Core Access Check → operator.branch == "[0]"
    Read id From Request
    DELETE FROM hotels WHERE id = ?
    Dispatch SystemLog(DeleteAccommodation) + delay(10m)
    Return Success JSON

    GET /v2/core/accommodation/view

    Core Accommodation View

    این اندپوینت اطلاعات کامل یک اقامتگاه را بر اساس شناسه ارسال‌شده بازگردانی می‌کند. داده‌های هتل، اطلاعات کشور/استان/شهر، مدیا (لوگو + گالری)، جزئیات، امکانات، برد، اطلاعات مدیریت و Mapping Supplierها به‌صورت کامل تجمیع و استانداردسازی می‌شود.

    Request Overview

    URL: /v2/core/accommodation/view
    Method: GET
    Controller: CoreController@viewAccommodation
    Middleware Stack: authWithJwt → core

    Access Control

    Request Parameters

    Field Type Required Description
    id integer yes ID اقامتگاه برای مشاهده

    Lookup Logic

    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

    Validate JWT & Core Access
    Read id
    SELECT * FROM hotels WHERE id=?
    Fetch & Normalize Country
    Fetch State & City
    Fetch Mapping → Convert null→false
    Fetch Media → logo + media[]
    Decode details/possibilities/board
    Assemble Response Payload
    Return JSON

    POST /v2/core/accommodation/supplier/store

    Core Accommodation Supplier Store

    این اندپوینت برای ایجاد یک Supplier جدید (نوع colleague) و اتصال آن به یک اقامتگاه استفاده می‌شود. این عملیات شامل ایجاد رکورد جدید در جدول colleagues با سریال اختصاصی و سپس ثبت ارتباط در mapping_colleagues است.

    Request Overview

    URL: /v2/core/accommodation/supplier/store
    Method: POST
    Controller: CoreController@supplierAccommodation
    Middleware Stack: authWithJwt → core

    Access Control

    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

    2. درج در mapping_colleagues

    Serial Generation Logic (colleague)

    فراخوانی زیر انجام می‌شود:

    StaticController::getSerialId('colleague', 2)

    منطق:

    Response (Success)

    {
      "status": true,
      "time": 1710000000,
      "message": "1023 | Supplier stored!"
    }
      

    Response (Error)

    {
      "status": false,
      "code": "1003",
      "message": "(Exception message)",
      "trace": [...]
    }
      

    Flowchart

    Validate JWT & Core Access
    Read data + accommodation_id
    Generate colleague serial (getSerialId)
    Insert colleague into colleagues table
    Insert mapping to mapping_colleagues
    Return success JSON

    POST /v2/core/airlines/list

    Core Airlines List

    این اندپوینت لیست خطوط هوایی را با قابلیت فیلترینگ پیشرفته، Pagination سفارشی و Pull اطلاعات کشور از Redis یا Database باز می‌گرداند. منطق Pagination به صورت DataTables‑Style انجام شده و ساختار پاسخ دقیقاً با نیازهای فرانت هماهنگ است.

    Request Overview

    URL: /v2/core/airlines/list
    Method: POST
    Controller: CoreController@airlinesList
    Middleware Stack: authWithJwt → core

    Access Control

    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

    Filtering Logic

    تمام فیلترها داخل یک Closure در where قرار گرفته‌اند:

    Country Fetch Logic (Redis + DB Fallback)

    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

    Validate JWT & Core Access
    Parse json → Data object
    Compute Pagination (start / length)
    Apply Filters (country, iata, icao, r)
    Query airlines with paginate
    For each airline → Fetch country (Redis → DB)
    Assemble Response Rows
    Return JSON with draw, recordsTotal, data

    POST /v2/core/airports/list

    Core Airports List

    این اندپوینت برای دریافت لیست فرودگاه‌ها با فیلترهای پیشرفته، Pagination مشابه DataTables، و غنی‌سازی داده‌ها (Country از Redis/DB و City از DB) استفاده می‌شود. خروجی کاملاً استاندارد و سازگار با سیستم‌های مدیریت داده (DataTables‑Compatible Response) است.

    Request Overview

    URL: /v2/core/airports/list
    Method: POST
    Controller: CoreController@airportsList
    Middleware Stack: authWithJwt → core

    Access Control

    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

    Filtering Logic

    تمام فیلترها داخل Closure:

    Country & City Enrichment

    Country (Redis → DB Fallback)

    City Resolution

    Lookup مستقیم DB:

    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

    Validate JWT & Core Access
    Parse json → Data object
    Compute Pagination (start / length)
    Apply Filters (country/state/city/iata/type/r)
    Query airports with paginate
    Resolve Country (Redis → DB)
    Resolve City + State from tables
    Assemble Response Array
    Return DataTables-Compatible JSON

    POST /v2/core/socket/send

    Core Socket: Send Message

    این اندپوینت یک پیام را از طریق سیستم Broadcasting لاراول ارسال می‌کند. پیام به Event TradeOperation پاس داده می‌شود و با toOthers() فقط به کلاینت‌های دیگر (غیر از فرستنده) Broadcast می‌شود.

    Request Overview

    URL: /v2/core/socket/send
    Method: POST
    Controller: CoreController@socketSendMessage
    Middleware Stack: authWithJwt → core

    Access Control

    Request Body Schema

    Field Type Required Description
    message string yes متن پیام که باید از طریق WebSocket (Broadcast) ارسال شود

    Broadcast Logic

    پس از دریافت message:

    broadcast(new TradeOperation($message))
    ->toOthers()

    Response (Success)

    {
      "status": "Message Sent!"
    }
      

    Response (Error)

    در این نسخه از کد هندل‌کردن Exception وجود ندارد، اما در صورت بروز خطا در سطح سیستم (مثلاً عدم کارکرد Broadcast Driver)، لاراول پاسخ خطا برمی‌گرداند.

    Flowchart

    Validate JWT & Core Middleware
    Extract message from request
    broadcast(new TradeOperation(message))
    Send to Others (WebSocket Clients)
    Return JSON: "Message Sent!"

    GET /v2/core/changelogs

    Core System Changelogs

    این اندپوینت وظیفه ارائه نسخه‌بندی کامل تغییرات سیستم (Change Log) را بر اساس ساختار SemVer توسعه‌یافته (major.minor.patch) بر عهده دارد. داده‌ها از جدول change_logs دریافت می‌شوند، سپس در قالب گروه‌بندی‌شده بر اساس نسخه، بازگردانده می‌شوند.

    Request Overview

    URL: /v2/core/changelogs
    Method: GET
    Controller: CoreController@changeLogs
    Middleware Stack: authWithJwt → core

    Access Control

    Database Query Logic

    دریافت تمامی لاگ‌ها از جدول change_logs با مرتب‌سازی دقیق نسخه:

    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).

    نسخه نهایی با صفرهای سمت چپ فرمت می‌شود:

    $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

    Validate JWT + Core Access
    Query change_logs (ordered)
    Group by Version (major/minor/patch)
    Format Version & Date
    Return Structured JSON

    GET /v2/core/accommodation/view

    Core Accommodation: View Details

    این اندپوینت برای دریافت جزئیات کامل یک هتل در سیستم Core استفاده می‌شود. داده‌ها از جداول اصلی (hotels، countries، states، cities)، جدول mapping_accommodations و جدول media بارگذاری شده و سپس در یک ساختار استاندارد و نرمال‌سازی شده بازگردانده می‌شوند.

    Request Overview

    URL: /v2/core/accommodation/view
    Method: GET
    Controller: CoreController@viewAccommodation
    Middleware Stack: authWithJwt (بدون core)

    Access Control

    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 Resolution

    کشور با شرط‌های زیر لود می‌شود:

    [
      "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

    Validate JWT
    Load hotel record
    Resolve country/state/city
    Load mapping_accommodations
    Load media (logo + gallery)
    Assemble standard response
    Return final JSON

    POST /v2/core/system/report

    Core System: Add System Report

    این اندپوینت برای ثبت گزارش سیستمی (System Report) در بخش Core استفاده می‌شود. داده‌ها به متد Visa::AddSystemReport() ارسال شده و پس از ثبت، یک کد رهگیری (tracking_code) تولید و برگردانده می‌شود.

    Request Overview

    URL: /v2/core/system/report
    Method: POST
    Controller: CoreController@addSystemReport
    Middleware Stack: authWithJwt

    Access Control

    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
    );
      

    Response (Success)

    کد وضعیت HTTP: 201 Created

    {
      "payload": {
        "tracking_code": "ABC123456"
      },
      "meta": {
        "timestamp": 1710000000
      }
    }
      

    Response (Error)

    در این متد هیچ try/catch وجود ندارد، بنابراین هرگونه Exception از طرف Visa::AddSystemReport یا دیتابیس → خطای 500 بازگشت داده می‌شود.

    Flowchart

    Validate JWT
    Extract: data + operator.id
    Call Visa::AddSystemReport(data, operator_id)
    Receive tracking_code (insert['Code'])
    Return JSON (201 Created)

    GET /v2/charter (Multi‑Mode Charter Loader)

    Charter: Get Charter(s) Details

    این اندپوینت، هسته اصلی بازیابی اطلاعات چارترهاست. بسته به ورودی، می‌تواند یک چارتر واحد را برگرداند، یا چندین چارتر را به‌صورت آرایه، و همچنین بسته به مقدار action می‌تواند چهار سطح مختلف از داده‌ها را برگرداند: all / list / base / calculations / capacity. این Endpoint از کلاس پیچیده CharterResource استفاده می‌کند که شامل غنی‌سازی بسیار سنگین دیتاست.

    Request Overview

    URL: /v2/charter
    Method: GET
    Controller: CharterController@indexCharter
    Middleware: authWithJwt

    Behavior Summary

    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)

    Shared Data Loaders (Resource Level)

    sortItems() Logic

    این تابع یکی از سنگین‌ترین بخش‌هاست و بسته به method/submethod آیتم دیتا را enrich می‌کند:

    جزئیات شامل:

    sortCalculations() Logic

    این قسمت شامل:

    getInformation() Logic

    بسته به type → title و blocks متفاوت تولید می‌کند. مقادیر در Redis کش می‌شوند تحت کلید:

    charter:{id}:information:title
      

    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

    Validate JWT
    Check id type (numeric | array)
    Load charter(s)
    If empty → return 'item not found'
    For each → new CharterResource()
    Inside Resource → action switch
    Load items, rules, calculations, information
    SortItems() + SortCalculations()
    Return Final JSON

    POST /v2/charter

    Charter: Create Inventory (Store)

    این اندپوینت قلب تپنده سیستم تعریف موجودی (Inventory) است. وظیفه آن دریافت یک الگوی زمانی و قیمتی، و تبدیل آن به صدها رکورد فیزیکی در دیتابیس است. این متد از یک Replication Engine داخلی استفاده می‌کند تا تاریخ‌های پرواز یا رزرو هتل را بر اساس الگوهای هفتگی، دوره‌ای یا تاریخ‌های خاص تولید کرده و تمام وابستگی‌های مالی و قانونی را در قالب تراکنش‌های اتمیک ذخیره کند.

    Request Overview

    URL: /v2/charter
    Method: POST
    Controller: CharterController@storeCharter
    Middleware: authWithJwt

    Key Features & Behavior

    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 وجود داشته باشد:

    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

    Start Request
    Resolve Cities & Objects
    Replication Engine
    Generate List of Dates
    Main Loop (Foreach Date)
    Begin Transaction
    Insert 'Charters' (Header)
    Insert 'Scheduled Notifications'
    Insert 'Charter Items'
    Financial Loop
    Calc / Tax / Markup / Rooms
    Insert Rules (Refund/Public)
    Commit Transaction
    Return Success Response

    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

    URL: /v2/charter
    Method: PUT
    Controller: CharterController@operationCharter
    Middleware: authWithJwt

    Key Features & Behavior

    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

    مدیریت کلاس‌های نرخی، قیمت‌ها، ظرفیت اتاق‌ها، مالیات‌ها و قوانین کنسلی.

    {
      "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

    عملیات کپی یک چارتر شامل:

    {
      "action": "copy",
      "id": 150
    }
      

    Execution Flowchart

    Start (PUT)
    Validate Action
    Switch(action)
    Execute Base | Calc | Copy
    Commit Transaction
    Return Response

    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

    URL: /v2/charter
    Method: PATCH
    Controller: CharterController@updateCharter
    Middleware Stack: authWithJwt

    Access Control

    Request Parameters

    Field Type Required Description
    id integer yes شناسه رکورد چارتر
    action string yes مقادیر مجاز: status یا sell
    data object yes حاوی فیلدهای مرتبط با اکشن انتخابی

    Logic: Action "status"

    برای تغییر وضعیت چارتر (فعال/غیرفعال/حذف) استفاده می‌شود.

    data.field Type Description
    status integer وضعیت جدید (مثلاً 4 برای حذف)

    Logic: Action "sell"

    برای کنترل سوئیچ‌های فروش استفاده می‌شود.

    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

    Start (PATCH /v2/charter)
    Find Charter by ID
    Check Action Type
    Action: Status
    Is Status = 4 (Delete)?
    Check Capacity (Reservations)
    If Has Reserve → Error
    Update Status
    Action: Sell
    Update colleague_sell / hub_sell
    Clear Redis Cache
    Return 204 No Content

    DELETE /v2/charter

    Charter: Delete (Soft Deactivate)

    این اندپوینت مسئول حذف نرم (Soft Delete) یا غیرفعال‌سازی یک چارتر است. توجه داشته باشید که این متد رکورد را از دیتابیس پاک نمی‌کند، بلکه وضعیت (Status) آن را به مقدار 2 تغییر می‌دهد (که معمولاً به معنای غیرفعال یا بایگانی است).

    Request Overview

    URL: /v2/charter
    Method: DELETE
    Controller: CharterController@deleteCharter
    Middleware Stack: authWithJwt

    Access Control

    Request Parameters

    Field Type Required Description
    id integer yes شناسه چارتری که باید غیرفعال شود

    Logic Details

    Response (Success)

    {
      "status": true,
      "time": 1710000000
    }
      

    Response (Exception Error)

    {
      "status": false,
      "time": 1710000000,
      "message": "SQLSTATE[...]: Integrity constraint violation...",
      "trace": [...]
    }
      

    Flowchart

    Start (DELETE /v2/charter)
    Get Request ID
    DB Transaction: Update `charters` SET `status` = 2
    Success
    Return { status: true }
    Exception
    Return { status: false, message... }

    GET /v2/charter/list

    Charter: List & Search

    این اندپوینت جهت دریافت لیست چارترها با قابلیت فیلتر کردن پیشرفته، جستجو بر اساس تاریخ (با منطق متفاوت برای اقامتگاه و پرواز)، مسیرهای دوطرفه و وضعیت‌های مختلف استفاده می‌شود.

    Request Overview

    URL: /v2/charter/list
    Method: GET
    Controller: CharterController@listCharter
    Middleware Stack: authWithJwt

    Access Control

    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 و 3
    all: همه وضعیت‌ها (غیر از 1)

    Complex Logic Details

    ۱. منطق تاریخ (Date Logic)

    ۲. جستجوی دوطرفه (Roundtrip)

    اگر advanced[roundtrip] برابر true باشد و مبدا و مقصد مشخص شده باشند، کوئری به صورت OR اجرا می‌شود:
    (مبدا = Origin AND مقصد = Dest) یا (مبدا = Dest AND مقصد = Origin).

    ۳. صفحه‌بندی و مرتب‌سازی

    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

    Start (GET /v2/charter/list)
    Set Default Pagination (30 items)
    Apply Basic Filters (Serial, Type)
    Advanced Filters Logic
    Date Logic
    If Accomm: Check Overlap
    Else: Check Start Date
    Roundtrip Logic
    If true: (A→B) OR (B→A)
    Else: (A→B)
    Check Branch (Filter if not ID 1)
    Execute Query & Paginate
    Action = 'list'?
    Yes
    Reverse Items Collection
    No
    Keep Order
    Return JSON Resource

    GET /v2/charter/communications

    Charter: List Communications

    این اندپوینت لیست "ارتباطات" (Communications) تعریف شده بین چارترها را برمی‌گرداند. این جدول معمولاً برای تعریف مسیرهای متصل (Connecting Flights) یا ارتباط بین یک چارتر اصلی و چارترهای وابسته استفاده می‌شود. خروجی شامل دو بخش اصلی src (چارتر مبدا) و dst (چارتر مقصد/متصل) است.

    Request Overview

    URL: /v2/charter/communications
    Method: GET
    Controller: CharterController@listCommunicationsCharter
    Middleware Stack: authWithJwt

    Access Control

    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 استخراج می‌کند:

    ۲. غنی‌سازی داده‌ها (Data Enrichment)

    پس از دریافت لیست خام ارتباطات، برای هر رکورد توابع زیر صدا زده می‌شوند تا جزئیات کامل برگردانده شود:

    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

    Start (GET /communications)
    ID Resolution
    If Origin set? → Find Charters starting at Origin → Get IDs
    If Dest set? → Find Charters starting at Dest → Get IDs
    Query `charter_communications` (Apply Filters)
    Paginate Results
    Loop through items (Map)
    Fetch SRC Charter Details
    Get Class/Train Info
    Fetch DST Charter Details
    Get Class/Train Info
    Format JSON Response

    GET /v2/charter/reservation/{type}

    Charter: List Reservations & Reports

    این اندپوینت لیست رزروها را بر اساس type (نوع گزارش) فیلتر می‌کند. این متد قلب تپنده گزارش‌گیری سیستم چارتر است و حالت‌های مختلفی از جمله رزروهای قطعی، موقت (لاگین شده)، استردادی و نمای گرافیکی "Plan" (مخصوص هتل) را پوشش می‌دهد.

    Request Overview

    URL: /v2/charter/reservation/{type}
    Method: GET
    Controller: CharterController@listCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    Request Parameters

    Path Parameters

    Parameter Type Description
    type string (enum) نوع لیست درخواستی. مقادیر مجاز:
    • definite: رزروهای قطعی و فعال
    • temporary: رزروهای موقت (در حال پرداخت/قفل شده)
    • refund: رزروهای استرداد شده
    • warranty: رکوردهای گارانتی
    • deleted: رزروهای حذف شده (Soft Delete)
    • plan: نمای جامع تقویم (مخصوص اقامتگاه)

    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:
    نحوه فیلتر تاریخ:
    • check_in: بر اساس تاریخ ورود
    • check_out: بر اساس تاریخ خروج
    • guests: مسافرین حاضر در هتل (Guest in House)
    • null: پیش‌فرض (ورود یا خروج در بازه باشد)

    Logic Details

    ۱. منطق فیلتر Definite (رزروهای قطعی)

    در این حالت، سیستم رزروهایی که refund_id ندارند و حذف نشده‌اند را برمی‌گرداند. منطق تاریخ بر اساس report_type متغیر است:

    ۲. منطق Temporary (رزروهای موقت)

    این بخش رکوردهایی را از جدول charter_temporary_reservation می‌خواند که هنوز منقضی نشده‌اند.
    شرط انقضا: created_at + duration > NOW()

    ۳. منطق Plan (نمای تقویم اقامتگاه)

    این حالت پیچیده‌ترین بخش است و داده‌ها را از ۴ منبع تجمیع می‌کند. اگر نوع چارتر accommodation نباشد، خطای 409 برمی‌گرداند.

    POST /v2/charter/reservation

    Charter: Insert Bulk Reservations

    این اندپوینت برای ایجاد یک یا چند رزرو به صورت همزمان طراحی شده است. ورودی اصلی آن آرایه‌ای از مسافران است. سیستم ابتدا تمام مسافران را اعتبارسنجی کرده، سپس ظرفیت را بررسی می‌کند و در نهایت رزروها را ایجاد می‌کند. این متد بین چارترهای مسیرمحور (مانند پرواز) و اقامتگاهی (هتل) تمایز قائل می‌شود.

    Request Overview

    URL: /v2/charter/reservation
    Method: POST
    Controller: CharterController@insertListCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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 (اختیاری) برای ثبت قیمت به صورت دستی. اگر ارسال نشود، قیمت به صورت خودکار محاسبه می‌شود.
    • financial.supplier: نام تأمین‌کننده
    • financial.sale_price: مبلغ قابل پرداخت
    • financial.commission: مبلغ کمیسیون

    Logic Details

    ۱. اعتبارسنجی گسترده

    پیش از هر اقدامی، سیستم تمام مسافران موجود در آرایه passengers را بررسی می‌کند. در صورت وجود اولین خطا، عملیات متوقف و کد خطای 422 Unprocessable Entity بازگردانده می‌شود.

    ۲. بررسی ظرفیت

    پس از اعتبارسنجی، سیستم تعداد مسافران بزرگسال و کودک را شمارش کرده و با ظرفیت باقی‌مانده آیتم مقایسه می‌کند.
    اگر ظرفیت کافی نباشد، عملیات متوقف و کد خطای 409 Conflict به همراه پیام "ظرفیت تکمیل است" (کد 1008) بازگردانده می‌شود.

    ۳. تفاوت منطق Route و Accommodation

    هسته اصلی این متد بر اساس نوع چارتر عمل می‌کند:

    ۴. محاسبه مالی و ثبت

    برای هر رزرو، اگر آبجکت 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

    Start (POST /reservation)
    Validation Loop (Passengers):
    Nationality → ID/Passport → Contact → Birth Date
    Fail → Halt & Return 422
    Pass ↓
    Capacity Check:
    Required (ADT+CHD) vs. Available Balance
    Fail → Halt & Return 409
    Pass ↓
    Check Charter Type
    Type: Route
    Loop Each Passenger
    Check for Duplicates
    Create Individual Reservation
    Add to `insertQuery` Array
    Type: Accommodation
    Check Room Availability
    Fail → Halt & Return 409
    Pass ↓
    Create One Reservation for All Guests
    Add to `insertQuery` Array
    Final Insertion Loop:
    Insert DB Record → Handle Refund Status → For Accommodation, Assign Room per Night
    Return 201 with Result Array

    PUT /v2/charter/reservation

    Charter: Update Reservation(s)

    این اندپوینت دو قابلیت مجزا اما مرتبط را فراهم می‌کند. بسته به پارامتر apply_all، می‌توان یک رزرو خاص را با تمام جزئیاتش به‌روزرسانی کرد، یا فقط اطلاعات مالی (مبلغ) را برای تمام رزروهای فعال یک چارتر به صورت یکجا تغییر داد. این متد برای اصلاح اطلاعات مسافر یا اعمال تغییرات قیمت کلی بسیار کاربردی است.

    Request Overview

    URL: /v2/charter/reservation
    Method: PUT
    Controller: CharterController@updateCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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 شماره موبایل جدید (اختیاری). تکی
    email string ایمیل جدید (اختیاری). تکی
    guarantor string شناسه ضامن (در صورت وجود). این فیلد اطلاعات warranty را تنظیم می‌کند. تکی

    Logic Details

    این متد بر اساس فیلد apply_all به دو شاخه منطقی اصلی تقسیم می‌شود:

    ۱. حالت به‌روزرسانی تکی (apply_all: false)

    این حالت پیش‌فرض است و برای ویرایش جزئیات یک رزرو مشخص به کار می‌رود.

    ۲. حالت به‌روزرسانی گروهی (apply_all: true)

    این حالت برای تغییر یکپارچه قیمت تمام رزروهای یک چارتر استفاده می‌شود.

    Response Structure

    پاسخ موفق

    در صورت موفقیت‌آمیز بودن هر یک از دو حالت، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز می‌گرداند که به معنای انجام موفقیت‌آمیز عملیات بدون نیاز به بازگرداندن محتوا است.

    پاسخ‌های خطا

    Flowchart

    Start (PUT /reservation)
    apply_all == true?
    ↓ No
    Mode: Single Update
    Find reservation by `reserve_id`
    Reservation Found?
    No → Return 422 (Not Found)
    Yes ↓
    Status == 1 OR 3?
    No → Return 422 (Invalid Status)
    Yes ↓
    Prepare data:
    passenger, amount, mobile, email, warranty...
    DB: UPDATE single record
    ↓ Yes
    Mode: Bulk Update
    Prepare data: `amount`, `updated_at`
    DB: UPDATE all records
    WHERE main_id = ? AND refund_id IS NULL

    Return 204 No Content

    DELETE /v2/charter/reservation

    Charter: Soft Delete Reservation(s)

    این اندپوینت برای حذف نرم (soft delete) یک یا چند رزرو به صورت همزمان طراحی شده است. عملیات حذف به صورت فیزیکی رکوردها را از پایگاه داده پاک نمی‌کند، بلکه وضعیت (status) آن‌ها را به 2 (حذف شده) تغییر داده و فیلد deleted_at را با زمان فعلی پر می‌کند. این متد همچنین رزرو اتاق‌های مرتبط با چارترهای اقامتی را نیز مدیریت می‌کند.

    Request Overview

    URL: /v2/charter/reservation
    Method: DELETE
    Controller: CharterController@deleteCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    Request Body (JSON)

    Field Type Description
    type string (الزامی) نوع چارتر (مثلاً 'route' یا 'accommodation'). این فیلد برای تعیین جدول صحیح رزروها در پایگاه داده استفاده می‌شود.
    reserves_id array[integer] (الزامی) آرایه‌ای از شناسه‌های رزروهایی که باید حذف شوند.

    Logic Details

    فرآیند حذف در دو مرحله کلیدی انجام می‌شود تا از هماهنگی داده‌ها اطمینان حاصل شود:

    ۱. به‌روزرسانی جدول اصلی رزروها

    ابتدا، سیستم با استفاده از پارامتر type، نام جدول مربوط به رزروها (مثلاً charter_reservation_route) را از طریق متد getTableCharter به دست می‌آورد. سپس یک دستور UPDATE گروهی اجرا می‌کند که تمام رکوردهای موجود در آرایه reserves_id را پیدا کرده و فیلدهای زیر را در آن‌ها به‌روزرسانی می‌کند:

    ۲. به‌روزرسانی جدول اتاق‌های اقامتی

    بلافاصله پس از مرحله اول، یک دستور 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

    Start (DELETE /reservation)
    Receive Request Body:
    type and reserves_id (array)
    Get Reservation Table Name using type
    Step 1: Update Main Reservation Table
    UPDATE charter_reservation_[type]
    SET status = 2, deleted_at = NOW()
    WHERE id IN (reserves_id)
    Step 2: Update Accommodation Rooms Table
    UPDATE charter_reservation_accommodation_rooms
    SET status = 2, deleted_at = NOW()
    WHERE reservation_id IN (reserves_id)
    Return 204 No Content
    → On Any Exception
    Return 400 Bad Request with Error Details

    PATCH /v2/charter/reservation/undo

    Charter: Undo Reservation Deletion

    این اندپوینت برای بازگردانی یک رزرو که قبلاً به صورت نرم (soft delete) حذف شده است، استفاده می‌شود. عملیات اصلی، تغییر وضعیت (status) رزرو از 2 (حذف شده) به 1 (قطعی) و پاک کردن مقدار فیلد deleted_at است. نکته بسیار مهم در این فرآیند، بررسی مجدد ظرفیت خالی قبل از بازگردانی است که دارای منطق متفاوتی برای مسافران نوزاد و غیرنوزاد می‌باشد.

    Request Overview

    URL: /v2/charter/reservation/undo
    Method: PATCH
    Controller: CharterController@undoDeleteCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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 را فراخوانی می‌کند تا ظرفیت باقی‌مانده واقعی را محاسبه کند. این متد با کسر کردن رزروهای قطعی، گارانتی و قفل‌های موقت از ظرفیت کل، عدد نهایی را به دست می‌آورد. سپس، یک شرط بر اساس نوع مسافر اعمال می‌شود:

    ۳. عملیات پایگاه داده

     

     

    Response Structure

    پاسخ موفق

    اگر بازگردانی با موفقیت انجام شود، سرور یک پاسخ خالی با کد وضعیت 204 No Content ارسال می‌کند.

    پاسخ‌های خطا

    Flowchart

    Start (PATCH /reservation/undo)
    Find reservation using `main_id` & `reserves_id`.
    Reservation Found?
    ↓ Yes
    Check remaining capacity via `capacityItemCharter`
    Is passenger an infant?
    ↓ No (Adult/Child)
    Proceed to update (Capacity check is bypassed)
    ↓ Yes (Infant)
    Capacity >= 1?
    No → Return 400 (Code 1008)
    Yes ↓

    DB UPDATE:
    SET status = 1
    SET updated_at = NOW()
    SET deleted_at = NULL
    Return 204 No Content
    ↓ No
    Exception Occurs
    (e.g., Cannot read property of null)
    Return 400 Bad Request
    with trace

    PUT /v2/charter/reservation/transfer

    Charter: Transfer Reservations

    این اندپوینت یک قابلیت مدیریتی قدرتمند برای انتقال یک یا چند رزرو از یک چارتر/آیتم به چارتر/آیتم دیگر فراهم می‌کند. فرآیند انتقال تنها در صورتی انجام می‌شود که چارتر مقصد ظرفیت کافی برای پذیرش تمام رزروهای درخواستی را داشته باشد. این عملیات برای جابجایی مسافران بین پروازها یا اتاق‌های مختلف بسیار کاربردی است.

    Request Overview

    URL: /v2/charter/reservation/transfer
    Method: PUT
    Controller: CharterController@transferCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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)) مقایسه می‌کند.

    Response Structure

    پاسخ موفق

    در صورتی که ظرفیت کافی باشد و تمام عملیات‌های به‌روزرسانی با موفقیت انجام شوند، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز می‌گرداند.

    پاسخ‌های خطا

    Flowchart

    Start (PUT /reservation/transfer)
    Receive Request Body:
    goal (main_id, item_id) and items (array of IDs)
    Calculate available capacity (`balance`) for destination item (`goal.item_id`)
    Is `balance` >= `count(items)`?
    ↓ Yes
    Loop through each `item` ID:
    UPDATE reservation_table
    SET main_id = goal.main_id,
    item_id = goal.item_id
    WHERE id = item_id
    Return 204 No Content
    ↓ No
    Return 422 Unprocessable Entity
    Message: "ظرفیت چارتر مورد نظر تکمیل می باشد."
    → On Any General Exception
    Return 400 Bad Request with Error Trace

    PATCH /character/reservation/refund

    Charter: Process Reservation Refund

    این اندپوینت برای پردازش بازپرداخت (Refund) برای یک یا چند رزرو قطعی (`status=1`) طراحی شده است. فرآیند شامل محاسبه جریمه (به صورت درصدی یا مبلغ ثابت)، به‌روزرسانی اطلاعات مالی رزرو، ثبت رکورد بازپرداخت در جدول مجزا، و در نهایت تغییر وضعیت رزرو به "مسترد شده" (`status=3`) است. این عملیات به صورت دسته‌ای (batch) برای لیستی از رزروها قابل اجراست.

    Request Overview

    URL: /v2/charter/reservation/refund
    Method: PATCH
    Controller: CharterController@storeRefundCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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 را پردازش می‌کند:

    1. دریافت اطلاعات رزرو: ابتدا رزرو مربوط به item فعلی از پایگاه داده خوانده می‌شود.
      • اگر رزرو یافت نشود: عملیات فوراً متوقف شده و خطای 422 با کد 1000 و پیام "آیتم [ID+10000] یافت نشد" بازگردانده می‌شود.
    2. بررسی وضعیت رزرو: وضعیت (status) رزرو بررسی می‌شود.
      • اگر وضعیت برابر 1 (قطعی) نباشد: عملیات متوقف شده و خطای 422 با کد 1000 و پیام "آیتم [ID+10000] در وضعیت خرید قطعی نمی باشد" بازگردانده می‌شود.
    3. محاسبه جریمه (Penalty):
      • اطلاعات مالی فعلی رزرو از فیلد financial (که به صورت JSON ذخیره شده) استخراج می‌شود.
      • بر اساس request->financial['value_type']:
        • اگر percent باشد: penalty = (original_payable * value) / 100
        • اگر currency باشد: penalty = value
    4. آماده‌سازی داده‌های جدید: یک آبجکت اطلاعاتی جدید برای این رزرو ساخته شده و به صورت موقت در یک آرایه به نام $queryInsert نگهداری می‌شود. این آبجکت شامل اطلاعاتی است که باید در جدول charter_refunds درج شود، از جمله آبجکت مالی جدید که در آن مبلغ قابل پرداخت (payable) به صورت original_payable - penalty محاسبه شده است.

    نکته مهم: اعتبارسنجی به صورت متوالی انجام می‌شود. در صورت بروز خطا برای هر یک از آیتم‌ها، کل فرآیند متوقف شده و هیچ تغییری در پایگاه داده اعمال نمی‌گردد.

    فاز ۲: اجرای تغییرات در پایگاه داده

    اگر فاز اول برای تمام آیتم‌ها بدون خطا به پایان برسد و حداقل یک آیتم معتبر برای استرداد وجود داشته باشد، سیستم وارد این فاز می‌شود:

    1. سیستم روی آرایه $queryInsert (که در فاز اول آماده شده) حلقه می‌زند.
    2. درج رکورد استرداد: برای هر آیتم، یک رکورد جدید در جدول charter_refunds با اطلاعات محاسبه شده درج می‌شود و شناسه (ID) رکورد جدید دریافت می‌گردد ($refundId).
    3. به‌روزرسانی رزرو اصلی: رزرو اصلی در جدول charter_reservations به‌روزرسانی می‌شود:
      • فیلد status به 3 (مسترد شده) تغییر می‌کند.
      • فیلد refund_id با شناسه رکورد استرداد ($refundId) پر می‌شود.
    4. عملیات ویژه برای اقامتگاه: اگر نوع چارتر accommodation باشد، یک عملیات اضافی انجام می‌شود:
      • رکورد(های) مرتبط با این رزرو در جدول charter_reservation_accommodation_rooms نیز حذف نرم (soft delete) می‌شوند (status به 2 تغییر کرده و deleted_at تنظیم می‌شود). این کار باعث آزاد شدن اتاق‌های اختصاص داده شده به این رزرو می‌شود.

    Response Structure

    پاسخ موفق

    در صورتی که تمام عملیات با موفقیت انجام شود (حتی اگر هیچ آیتم معتبری برای استرداد وجود نداشته باشد)، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز می‌گرداند.

    پاسخ‌های خطا

    Flowchart

    Start (PATCH /reservation/refund)
    Receive Request Body:
    type, items, financial
    Start Loop: For each `id` in `items`
    Reservation with `id` exists?
    ↓ Yes
    Reservation `status` == 1?
    ↓ Yes
    1. Calculate penalty
    2. Prepare new financial data
    3. Add to temporary `$queryInsert` array
    ↓ No
    Return 422: Invalid Status
    ↓ No
    Return 422: Not Found
    End Loop
    Is `$queryInsert` array not empty?
    ↓ Yes
    Start DB Update Loop:
    For each item in `$queryInsert`
    1. Insert into `charter_refunds` table
    2. Update `charter_reservations`: `status=3`, `refund_id=new_id`
    Is `type` == 'accommodation'?
    ↓ Yes
    Soft-delete from `..._rooms` table
    ↓ No
    End DB Update Loop
    ↓ No
    Return 204 No Content

    PATCH /v2/charter/reservation/refund/undo

    Charter: Undo Reservation Refund

    این اندپوینت برای لغو عملیات استرداد یک رزرو خاص و بازگرداندن آن به وضعیت "قطعی" (`status = 1`) استفاده می‌شود. قبل از بازگردانی، سیستم ظرفیت آیتم مربوطه (پرواز یا اتاق) را بررسی می‌کند. یک منطق خاص برای نوزادان (infant) وجود دارد که بازگردانی رزرو آن‌ها تنها در صورت وجود حداقل یک ظرفیت خالی امکان‌پذیر است، در حالی که برای بزرگسالان و کودکان این بررسی ظرفیت اعمال نمی‌شود.

    Request Overview

    URL: /v2/charter/reservation/refund/undo
    Method: PATCH
    Controller: CharterController@undoRefundCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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)

    این مهم‌ترین بخش منطق است.

    ۳. اجرای عملیات

    Response Structure

    پاسخ موفق

    در صورتی که عملیات با موفقیت انجام شود، سرور یک پاسخ خالی با کد وضعیت 204 No Content باز می‌گرداند.

    پاسخ‌های خطا

    Flowchart

    Start (PATCH /reservation/refund/undo)
    Receive Request Body:
    main_id, reserve_id
    Fetch the full reservation record using `reserve_id`
    Is capacity check required?
    (Only if `passenger_age_title` is 'infant')
    ↓ Yes (Infant)
    Is `capacity['total']` >= 1?
    ↓ Yes
    1. Update reservation: `status=1`, `refund_id=NULL`
    2. Update refund record: `status=2`
    Return 204 No Content
    ↓ No
    Return 400 - Code 1008 (Capacity Full)
    ↓ No (Adult/Child)
    1. Update reservation: `status=1`, `refund_id=NULL`
    2. Update refund record: `status=2`
    Return 204 No Content
    → On Any General Exception
    Return 400 Bad Request with Error Trace

    DELETE /v2/charter/reservation/temporary

    Charter: Soft Delete Temporary Reservation

    این اندپوینت برای حذف نرم (soft-delete) یک رزرو موقت از سیستم استفاده می‌شود. رزروهای موقت در جدول جداگانه‌ای به نام charter_temporary_reservation نگهداری می‌شوند. این عملیات رکورد را به طور کامل از پایگاه داده حذف نمی‌کند، بلکه فقط ستون status آن را به مقدار 2 (به معنای لغو شده یا حذف شده) تغییر می‌دهد تا در پردازش‌های بعدی نادیده گرفته شود.

    Request Overview

    URL: /v2/charter/reservation/temporary
    Method: DELETE
    Controller: CharterController@deleteTemporaryCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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)

    ۲. مدیریت خطا

    Response Structure

    برخلاف بسیاری از اندپوینت‌های RESTful که در پاسخ به متد DELETE موفق، کد وضعیت 204 No Content را برمی‌گردانند، این اندپوینت همیشه یک بدنه پاسخ (JSON) با کد وضعیت 200 OK باز می‌گرداند. وضعیت موفقیت یا شکست عملیات از طریق فیلد status در بدنه JSON مشخص می‌شود.

    پاسخ موفق

    در صورتی که کوئری UPDATE بدون خطا اجرا شود (حتی اگر هیچ رکوردی برای به‌روزرسانی پیدا نشود)، پاسخ زیر بازگردانده می‌شود.

    پاسخ خطا

    در صورت بروز هرگونه استثنا (Exception) در سطح پایگاه داده، پاسخ زیر بازگردانده می‌شود.

    Flowchart

    Start (DELETE /reservation/temporary)
    Receive Request
    Query Parameter: id
    Execute DB Query:
    UPDATE charter_temporary_reservation SET status = 2 WHERE id = ?
    Did a DB Exception Occur?
    ↓ No
    Return 200 OK
    {"status": true, "time": ...}
    ↓ Yes
    Return 200 OK
    {"status": false, "message": ..., "trace": ...}
    End

    POST /v2/charter/reservation/temporary

    Charter: Create Temporary Reservation (Lock)

    این اندپوینت برای ایجاد یک "رزرو موقت" یا "قفل" روی ظرفیت یک آیتم چارتر (مانند صندلی پرواز یا اتاق هتل) برای یک مدت زمان مشخص (به دقیقه) طراحی شده است. هدف اصلی آن جلوگیری از فروش همزمان یک ظرفیت توسط چند کاربر است. منطق این اندپوینت بر اساس نوع چارتر (`type`) به دو شاخه اصلی تقسیم می‌شود: اقامتگاهی (`accommodation`) و غیر اقامتگاهی (مانند مسیر `route`).

    Request Overview

    URL: /v2/charter/reservation/temporary
    Method: POST
    Controller: CharterController@storeTemporaryCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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)

    این حالت پیچیده‌تر است و نیازمند بررسی دقیق ظرفیت اتاق‌ها در بازه زمانی مشخص است.

    ۲. منطق برای چارتر غیر اقامتگاهی (Route)

    این حالت بسیار ساده‌تر است و مبتنی بر اعتماد به درخواست‌دهنده است.

    Response Structure

    مشابه اندپوینت قبلی، این API نیز در همه موارد (موفقیت یا شکست) کد وضعیت 200 OK را به همراه یک بدنه JSON برمی‌گرداند.

    پاسخ موفق (چارتر اقامتگاهی)

    پاسخ موفق (چارتر غیر اقامتگاهی)

    پاسخ خطا (ظرفیت ناکافی در چارتر اقامتگاهی)

    پاسخ خطا (استثنای عمومی)

    Flowchart

    Start (POST /reservation/temporary)
    Receive Request Body
    Fetch charter `type` from DB using `main_id`
    Is `type` == 'accommodation'?
    ↓ Yes
    Call `getAccommodationRooms` to find fully available rooms
    Available rooms >= Requested rooms?
    ↓ Yes
    Create & Bulk Insert one record per requested room
    Return Success (no data)
    ↓ No
    Return Error (Capacity Message)
    ↓ No
    1. Add seat counts to data
    2. Insert single record & Get ID
    Return Success (with `data = ID + 20000`)

    POST /v2/charter/reservation/plan/update

    Charter: Update Accommodation Reservation Room Plan

    این اندپوینت برای تغییر چینش اتاق‌های تخصیص داده شده به یک رزرو اقامتگاهی موجود استفاده می‌شود. کاربرد اصلی آن این است که به اپراتور اجازه می‌دهد اتاق فیزیکی (`room_id`) مربوط به یک یا چند تاریخ (`date`) خاص از یک رزرو (`reservation_id`) را تغییر دهد. برای مثال، اگر یک رزرو برای سه شب در اتاق ۱۰۱ ثبت شده، می‌توان با استفاده از این اندپوینت، شب دوم اقامت را به اتاق ۱۰۵ منتقل کرد.
    نکته مهم تجاری: شناسه‌های ورودی (id و room_id) شناسه‌های عمومی هستند و سیستم به صورت داخلی عدد 10,000 را از آن‌ها کم می‌کند تا به شناسه‌های واقعی در پایگاه داده دسترسی پیدا کند.

    Request Overview

    URL: /v2/charter/reservation/plan/update
    Method: POST
    Controller: CharterController@updatePlanCharterReservation
    Middleware Stack: authWithJwt

    Access Control

    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 برای مدیریت خطاها اجرا می‌شود.

    ۱. اعتبارسنجی ورودی

    ۲. پردازش حلقه به‌روزرسانی

    ۳. مدیریت خطا

    Response Structure

    پاسخ موفق

    در صورتی که تمام دستورات به‌روزرسانی با موفقیت اجرا شوند، سرور یک پاسخ 200 OK با ساختار زیر بازمی‌گرداند.

    پاسخ خطا (ورودی نامعتبر)

    اگر فیلد data در درخواست ارسال نشود یا خالی باشد.

    پاسخ خطا (استثنای عمومی)

    در صورت بروز هرگونه خطای پایگاه داده یا خطای دیگر در حین اجرا.

    Flowchart

    Start (POST /reservation/plan/update)
    Receive Request Body
    Is `data` field present & not empty?
    ↓ Yes
    Loop through each item in `data` array:
    - Execute `UPDATE charter_reservation_accommodation_rooms`
    - `SET room_id = (item.room_id - 10000)`
    - `WHERE reservation_id = (item.id - 10000) AND date = item.date`
    Return 200 OK with `{"payload": true, ...}`
    ↓ No
    Return 400 with `{"error": {"message": "The data field is required."}, ...}`
    → On Any DB Exception
    Return 400 with Full Exception Trace

    GET /v2/charter/financial

    Charter: Get Financial Report

    این اندپوینت یک گزارش مالی جامع و تجمیع‌شده برای یک چارتر خاص (با شناسه main_id) تولید می‌کند. هدف اصلی آن، ارائه یک دید کلی از وضعیت فروش، درآمد، هزینه‌ها و بدهی‌ها با سه دسته‌بندی مجزا است:

    1. بر اساس کلاس/آیتم (Classes): تفکیک مالی بر اساس هر آیتم (مانند کلاس پروازی Y یا اتاق دو تخته).
    2. بر اساس متعهدین (Pledgers): تفکیک مالی برای رزروهایی که توسط یک همکار خاص تعهد شده‌اند.
    3. بر اساس گارانتی‌کنندگان (Warranties): تفکیک مالی برای رزروهایی که تحت گارانتی یک شخص یا شرکت خاص (ثبت شده در سیستم) هستند.
    این گزارش برای تحلیل فروش، حسابداری و مدیریت درآمد چارترها کاربرد حیاتی دارد.

     

    Request Overview

    URL: /v2/charter/financial
    Method: GET
    Controller: CharterController@financialCharter
    Middleware Stack: authWithJwt

    Access Control

    Query Parameters

    Field Type Description
    id integer (الزامی) شناسه اصلی چارتر (main_id) که گزارش مالی برای آن درخواست شده است.

    Example Request

    GET /v2/charter/financial?id=451

    Logic Details

    فرآیند تولید گزارش در چندین مرحله و با تجمیع داده‌ها از جداول مختلف انجام می‌شود.

    ۱. شناسایی جداول و واکشی داده‌های اولیه

    ۲. ساخت نقشه قیمت خرید (Buy Price Map)

    ۳. حلقه اصلی و تجمیع داده‌ها

    سیستم یک حلقه بر روی تمام رزروهای واکشی شده (`reserves`) اجرا می‌کند و در هر تکرار، عملیات زیر را انجام می‌دهد:

    1. تجمیع بر اساس کلاس/آیتم (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 نگهداری می‌شود.

    1. تجمیع بر اساس گارانتی/تعهد (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'] با کلید شناسه گارانتی‌کننده تجمیع می‌شود.

    ۴. خروجی نهایی

    Response Structure

    پاسخ موفق

    در صورت موفقیت، سرور یک پاسخ 200 OK با ساختار پیچیده زیر بازمی‌گرداند. این ساختار شامل سه بخش اصلی `classes`, `pledgers`, و `warranties` است.

    پاسخ خطا

    در صورت بروز هرگونه خطا (مانند عدم یافتن چارتر یا خطای دیتابیس) سرور یک پاسخ 400 Bad Request بازمی‌گرداند.

    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

    Start (GET /charter/financial)
    Get `id` from query
    1. `getTableCharter(id)` to find table names
    2. Fetch all reservations (`reserves`)
    3. Fetch all calculation items (`calculations`)
    Loop `calculations` to build `$buyPrice` map.
    Use `getClassName` for titles.
    Loop through each reservation in `reserves`
    Aggregate financial data into `$return['classes']` based on `item_id` and `age_title`.
    Has `warranty_type`?
    ↓ Yes
    Type is 'colleague'?
    ↓ Yes
    Aggregate into `$return['pledgers']`
    ↓ No ('system')
    Aggregate into `$return['warranties']`
    ↓ No
    Do nothing for this group.
    End Loop
    Return 200 OK with `payload` containing aggregated data
    → On Any Exception
    Return 400 with Exception Details

    GET /v2/charter/financial/completion

    Charter: Get Completion Financial Report

    این اندپوینت برای ارائه یک گزارش تکمیل مالی و تحلیلی از یک چارتر طراحی شده است. این گزارش فراتر از تجمیع ساده داده‌های فروش رفته و با محاسبه شاخص‌های کلیدی عملکرد (KPIs) مانند هزینه کل خرید ظرفیت (Paid)، سود یا زیان (Profit)، و هزینه ظرفیت فروخته‌نشده (Burned)، یک دید ۳۶۰ درجه از وضعیت مالی چارتر ارائه می‌دهد.
    خروجی این اندپوینت در چهار بخش اصلی سازماندهی شده است:

    1. Classes: گزارش دقیق مالی به تفکیک هر آیتم (کلاس پروازی/نوع اتاق) به همراه محاسبات سود و زیان.
    2. Pledgers: گزارش تجمیعی فروش برای رزروهای تعهد شده توسط همکاران.
    3. Warranties: گزارش تجمیعی فروش برای رزروهای گارانتی شده.
    4. Total: یک شیء جامع که کل شاخص‌های مالی چارتر را در خود جمع‌بندی می‌کند.

     

    Request Overview

    URL: /v2/charter/financial/completion
    Method: GET
    Controller: CharterController@getCompletionFinancialCharter
    Middleware Stack: authWithJwt

    Access Control

    Query Parameters

    Field Type Description
    id integer (الزامی) شناسه اصلی چارتر (main_id) که گزارش برای آن درخواست شده است.

    Example Request

    GET /v2/charter/financial/completion?id=451

    Logic Details

    منطق این اندپوینت در دو فاز اصلی اجرا می‌شود: فاز اول تجمیع داده‌های اولیه و فاز دوم محاسبه شاخص‌های مالی پیشرفته.

    فاز اول: واکشی و تجمیع داده‌های اولیه

    فاز دوم: محاسبه شاخص‌های سود و زیان ( حلقه دوم)

    پس از اتمام حلقه اول و تجمیع داده‌های پایه، یک حلقه دوم روی آرایه تجمیع‌شده return['classes'] اجرا می‌شود تا محاسبات پیشرفته برای هر آیتم انجام شود:

    1. محاسبه هزینه کل آیتم (Paid):
      • فرمول: (Total Buy Price / Items Sold) * Total Capacity
      • توضیح: این مقدار، هزینه واقعی است که شرکت برای کل ظرفیت یک آیتم (چه فروخته شده و چه نشده) پرداخت کرده است. این محاسبه بر اساس میانگین قیمت خرید صندلی/اتاق‌های فروخته شده انجام می‌شود.
    2. محاسبه هزینه سوخت‌شده (Burned):
      • فرمول: Average Buy Price * (Total Capacity - Items Sold)
      • توضیح: این مقدار نشان‌دهنده هزینه ظرفیت فروخته‌نشده یا "سوخت‌شده" است. این یکی از مهم‌ترین شاخص‌ها برای ارزیابی عملکرد فروش است.
    3. محاسبه سود/زیان (Profit):
      • فرمول: Total Payable - Total Item Cost (Paid)
      • توضیح: این مقدار، تفاوت بین کل درآمد حاصل از فروش یک آیتم و کل هزینه پرداخت شده برای آن آیتم است. اگر مثبت باشد سود و اگر منفی باشد زیان را نشان می‌دهد.
    4. تشخیص وضعیت مالی (Diagnosis):
      • بر اساس مقدار `profit`، یک وضعیت متنی تعیین می‌شود:
        • creditor: بستانکار (سودده)
        • debtor: بدهکار (زیان‌ده)
        • neutral: سربه‌سر
    5. تجمیع نهایی در `total`: مقادیر محاسبه شده (`paid`, `burned`, `profit`) برای هر آیتم، به مقادیر متناظر در شیء `return['total']` اضافه می‌شوند تا جمع‌بندی نهایی برای کل چارتر به دست آید.

    محاسبه نهایی

    Response Structure

    پاسخ موفق

    پاسخ موفق 200 OK حاوی یک شیء `payload` با ساختاری بسیار غنی است که شامل بخش `total` در کنار سه بخش دیگر می‌باشد.

    پاسخ خطا

    در صورت بروز خطا، پاسخ 400 Bad Request با جزئیات استثنا بازگردانده می‌شود.

    Helper Functions

    این اندپوینت از همان توابع کمکی اندپوینت قبلی برای ترجمه نام کلاس و محاسبه مقادیر درصدی/ثابت استفاده می‌کند.

    Functions::getClassName($class, $lang)

    ReservationController::priceHandle($price, $values)

    Flowchart

    Start (GET /financial/completion)
    Phase 1: Aggregation
    1. Fetch `reserves` & `calculations`.
    2. Build `$buyPrice` map.
    3. Loop `reserves` to populate initial `classes`, `pledgers`, `warranties`, and `total` (buy, payable, counts).
    Phase 2: Profit Calculation
    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.
    Final Step
    Calculate final `diagnosis` for the `total` object.
    Return 200 OK with full `payload` (including `total` and calculated fields)
    → On Any Exception
    Return 400 with Exception Details

    PATCH /v2/charter/financial/completion

    Charter: Finalize Completion Financial Report

    این اندپوینت برای نهایی‌سازی (Finalization) گزارش مالی تکمیلی یک چارتر طراحی شده است. پس از اینکه یک چارتر به پایان می‌رسد و تمام داده‌های فروش و هزینه‌ها مشخص می‌شود، این اندپوینت فراخوانی می‌شود تا یک "اسنپ‌شات" (Snapshot) از وضعیت مالی نهایی (شامل سود، زیان، هزینه سوخت‌شده و...) تهیه و آن را در پایگاه داده ثبت کند. این عمل معادل "بستن گزارش مالی" برای آن چارتر است و معمولاً پس از آن، امکان تغییر در رزروها یا داده‌های مالی مرتبط با آن چارتر محدود یا مسدود می‌شود.

    Request Overview

    URL: /v2/charter/financial/completion
    Method: PATCH
    Controller: CharterController@setCompletionFinancialCharter
    Middleware Stack: authWithJwt

    Access Control

    Request Body

    برای مشخص کردن اینکه کدام چارتر باید نهایی شود، شناسه اصلی آن باید در بدنه درخواست ارسال گردد.

    Field Type Description
    main_id integer (الزامی) شناسه اصلی چارتر (main_id) که گزارش مالی آن باید نهایی و ثبت شود.

    Example Request

    {
      "main_id": 451
    }

    Logic Details

    نکته مهم: کد ارائه شده صرفاً یک ساختار اولیه است. منطق واقعی این اندپوینت بسیار پیچیده‌تر بوده و شامل مراحل زیر خواهد بود:

    1. اعتبارسنجی ورودی: سیستم بررسی می‌کند که main_id در بدنه درخواست وجود داشته و معتبر است.
    2. بررسی وضعیت چارتر:
      • ابتدا چارتر با main_id مشخص شده از پایگاه داده واکشی می‌شود.
      • سیستم بررسی می‌کند که آیا این چارتر قبلاً نهایی شده است یا خیر (مثلاً با چک کردن یک فیلد finalized_at در جدول اصلی چارتر). اگر قبلاً نهایی شده باشد، خطای 409 Conflict بازگردانده می‌شود تا از ثبت مجدد جلوگیری شود.
    3. تولید داده‌های گزارش:
      • سیستم به صورت داخلی منطق اندپوینت GET /v2/charter/financial/completion را فراخوانی می‌کند تا گزارش مالی کامل و تحلیلی (شامل `profit`, `burned`, `paid` و ...) را برای main_id مورد نظر تولید کند.
    4. ذخیره‌سازی اسنپ‌شات (Snapshot):
      • کل شیء گزارش تولید شده (که خروجی اندپوینت GET است) به فرمت JSON تبدیل می‌شود.
      • این رشته JSON در یک جدول جدید و اختصاصی به نام charter_financial_snapshots یا مشابه آن، به همراه main_id، شناسه کاربری که عملیات را انجام داده (finalized_by)، و تاریخ/زمان فعلی (finalized_at) ذخیره می‌شود. این کار تضمین می‌کند که یک نسخه بایگانی‌شده و غیرقابل تغییر از گزارش نهایی همیشه در دسترس است.
    5. به‌روزرسانی وضعیت چارتر اصلی:
      • پس از ذخیره موفقیت‌آمیز اسنپ‌شات، رکورد مربوط به چارتر در جدول اصلی (مثلاً charter_main) به‌روزرسانی شده و فیلد finalized_at آن با تاریخ و زمان فعلی پر می‌شود. این کار به عنوان یک پرچم (flag) عمل کرده و از نهایی‌سازی مجدد جلوگیری می‌کند.
    6. پاسخ نهایی: در صورت موفقیت تمام مراحل، یک پاسخ 204 No Content بازگردانده می‌شود که به کلاینت اعلام می‌کند عملیات با موفقیت انجام شده است.

    Response Structure

    پاسخ موفق

    یک پاسخ موفق به این معنی است که گزارش مالی با موفقیت تولید و بایگانی شده است. طبق استاندارد REST، برای عملیات PATCH یا PUT که منجر به به‌روزرسانی موفقی شده اما بدنه پاسخی برای برگرداندن ندارد، از کد وضعیت 204 استفاده می‌شود.

    پاسخ‌های خطا

    خطاهای مختلفی ممکن است در این فرآیند رخ دهد:

    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

    URL: /v2/charter/warranty
    Method: POST
    Controller: CharterController@operationWarrantyCharter
    Middleware Stack: authWithJwt

    Access Control

    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 عددی، تصمیم به ایجاد یا به‌روزرسانی می‌گیرد.

    2. پردازش آرایه `deleted` (حذف)

    Response Structure

    پاسخ موفق

    اگر تمام عملیات‌ها (ایجاد، به‌روزرسانی، حذف) بدون خطا انجام شوند، پاسخ 204 No Content بازگردانده می‌شود.

    پاسخ‌های خطا (Conflict)

    GET /v2/charter/warranty

    Charter: List Warranties

    این اندپوینت برای بازیابی لیستی کامل از تمام گارانتی‌های مرتبط با یک چارتر مشخص (main_id) استفاده می‌شود. به ازای هر گارانتی، اطلاعات تکمیلی شامل جزئیات گارانتی‌کننده (همکار) و لیست اتاق‌های اختصاص‌داده‌شده (برای چارترهای اقامتگاهی) نیز بازگردانده می‌شود. این اندپوینت از یک مکانیزم کش (Caching) با استفاده از Redis برای بهینه‌سازی و افزایش سرعت دریافت اطلاعات همکاران بهره می‌برد.

    Request Overview

    URL: /v2/charter/warranty
    Method: GET
    Controller: CharterController@listWarrantyCharter
    Middleware Stack: authWithJwt

    Access Control

    Request Parameters (Query String)

    این اندپوینت اطلاعات مورد نیاز خود را از طریق Query String دریافت می‌کند.

    Parameter Type Description
    id integer (الزامی) شناسه اصلی چارتر (charter_main.id) که گارانتی‌های آن باید لیست شوند.

    Example Request

    GET /v2/charter/warranty?id=451

    Logic Details

    فرآیند پردازش درخواست به شرح زیر است:

    1. واکشی گارانتی‌های اصلی:
      • ابتدا تمام رکوردها از جدول charter_warranties که main_id آن‌ها با id ارسال شده در درخواست برابر است، واکشی می‌شوند.
      • نتایج بر اساس id به صورت نزولی (DESC) مرتب می‌شوند تا جدیدترین گارانتی‌ها در ابتدای لیست قرار گیرند.
    2. پردازش و غنی‌سازی هر گارانتی (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 خواهد گرفت.

    Response Structure

    پاسخ موفق (Success)

    در صورت موفقیت، پاسخ 200 OK به همراه یک ساختار JSON استاندارد حاوی لیست گارانتی‌ها بازگردانده می‌شود.

    {
      "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

    URL: /v2/charter/services/list
    Method: GET
    Controller: CharterController@listServicesCharter
    Middleware Stack: authWithJwt

    Access Control

    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();
    }
      

    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

    Validate JWT
    Load Active Categories
    For Each Category → Load Services
    Assemble Response Array
    Return JSON (status=true)

    GET /v2/charter/flight-rate

    Charter: Get Approved Flight Rate

    این اندپوینت نرخ مصوب (Approved Flight Rate) میان دو فرودگاه را بر اساس جدول approved_flight_rate بازیابی می‌کند. سیستم ابتدا دقیقاً مسیر origin → destination را جستجو می‌کند و در صورت نبود، مسیر destination → origin را نیز بررسی می‌کند. در صورت نیافتن رکورد، خروجی با مقدارهای پیش‌فرض (0) برگردانده می‌شود.

    Request Overview

    URL: /v2/charter/flight-rate
    Method: GET
    Controller: CharterController@getApprovedFlightRate
    Middleware: authWithJwt

    Access Control

    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();
      

    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

    Validate JWT
    Read Query Params: origin, destination
    DB Query: origin → destination
    If not found → DB Query: destination → origin
    Record Found?
    Yes → Return real least/most
    No → Return least=0, most=0, now()
    Return JSON (status=true)

    GET /v2/charter/pledger

    Charter: List Branch Pledgers

    این اندپوینت لیست ضامنین (Pledgers) یک شعبه را از جدول charter_pledgers به همراه اطلاعات همکار (colleague) بازیابی می‌کند. داده‌ها با join روی جدول colleagues enrich شده و خروجی نهایی یک آرایه items با ساختار title.fa و title.en بازمی‌گرداند.

    Request Overview

    URL: /v2/charter/pledger
    Method: GET
    Controller: CharterController@listPledgerCharter
    Middleware: authWithJwt

    Query 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();
      

    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

    Validate JWT
    Read Query Param: branch
    DB Query charter_pledgers WHERE branch, status=1
    LEFT JOIN colleagues ON colleague_id
    Filter colleagues.status=1
    ORDER BY id DESC
    Map → Build title.fa / title.en
    Return JSON: items[], meta.timestamp

    POST /v2/charter/pledger

    pledger: storePledgerCharter

    این اندپوینت یک ضامن جدید (Pledger) را برای یک شعبه در جدول charter_pledgers ثبت می‌کند. در صورت موفقیت مقدار خاصی برنمی‌گرداند و تنها وضعیت 201 Created داده می‌شود.

    URL: /v2/charter/pledger
    Method: POST
    Controller: CharterController@storePledgerCharter
    Middleware: authWithJwt
    Auth: JWT Required

    Request 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

    Validate JWT
    Read: branch, colleague_id
    DB Insert → charter_pledgers
    Insert OK?
    Yes → return 201
    No → return 400 with message + trace

    POST /v2/charter/accommodation/rooms

    rooms: getCharterAccommodationRooms

    این اندپوینت برای دریافت لیست اتاق‌های اقامتگاه (Accommodation Rooms) مرتبط با یک محاسبه calculation_id استفاده می‌شود. مقدار calculation_id ابتدا اصلاح شده و از جدول مرتبط واکشی می‌گردد. فقط زمانی پاسخ معتبر برگردانده می‌شود که چارتر از نوع accommodation باشد.

    URL: /v2/charter/accommodation/rooms
    Method: POST
    Controller: CharterController@getCharterAccommodationRooms
    Middleware: authWithJwt
    Auth: JWT Required

    Request 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

    Validate JWT
    Check calculation_id exists?
    calculation_id - 10000
    Find calculation in charter_calculations_accommodation
    Find charter(type)
    type == accommodation?
    Yes → fetch rooms from charter_accommodation_rooms
    Return items + timestamp

    POST /v2/charter/accommodation/rooms/access

    Charter: Access Accommodation Rooms

    این اندپوینت برای «از دسترس خارج کردن» یا «بازگرداندن به دسترس» یک اتاق اقامتگاهی استفاده می‌شود. رفتار اندپوینت بر اساس ارسال یا عدم ارسال start_date و end_date دو حالت مختلف دارد: در حالت بازه‌دار روی تاریخ‌ها عمل می‌کند و در حالت بدون بازه روی وضعیت عمومی اتاق.

    URL: /v2/charter/accommodation/rooms/access
    Method: POST
    Controller: CharterController@accessCharterAccommodationRooms
    Middleware: authWithJwt
    Auth: JWT Required

    Request 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

    Validate JWT
    room_id - 10000
    delete flag?
    start_date & end_date provided?
    Yes → Build date period
    Check reservations in range
    If none → insert disable rows
    Else → return error
    No (no dates)
    Check future reservations
    If none → update status
    Else → return error

    GET /v2/academy/categories/view

    Academy: View Categories

    این اندپوینت برای دریافت لیست دسته‌بندی‌های فعال آکادمی استفاده می‌شود. تنها رکوردهایی که status = 1 دارند واکشی می‌شوند و داده‌ها در ساختار استاندارد شامل فیلدهای id، slug، flag، title، description و status بازگردانده می‌شوند.

    URL: /v2/academy/categories/view
    Method: GET
    Controller: AcademyController@viewCategories
    Middleware: authWithJwt
    Auth: JWT Required

    Request

    این اندپوینت هیچ پارامتر اجباری یا اختیاری دریافت نمی‌کند.

    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

    Validate JWT
    Fetch categories where status = 1
    Transform DB rows → array
    Return status=true + data + time()

    GET /v2/academy/courses/view

    Academy: View Courses

    این اندپوینت لیست دوره‌های آموزشی آکادمی را با امکان فیلتر بر اساس دسته‌بندی بازمی‌گرداند. داده‌ها شامل اطلاعات کامل دوره، اطلاعات دسته‌بندی مرتبط، وضعیت کاربر در دوره (progress)، تعداد کل مراحل و وضعیت نمایش (featured) است. تنها دوره‌هایی که وضعیت آن‌ها 1 یا 3 باشد در خروجی قرار می‌گیرند.

    URL: /v2/academy/courses/view
    Method: GET
    Controller: AcademyController@viewCourses
    Middleware: authWithJwt
    Auth: JWT Required
    Filter: ?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

    Validate JWT (operator loaded)
    Check optional category_id
    Query academy_courses WHERE status IN (1,3)
    LEFT JOIN academy_categories
    Loop → Fetch course progress (completed/current_step)
    Count total_steps from academy_course_steps
    Build final structured course object
    Return status=true + time + data[]

    GET /v2/academy/course/steps/view

    Academy: View Course Steps

    این اندپوینت اطلاعات کامل یک دوره آموزشی به‌همراه تمام مراحل (steps) آن را برمی‌گرداند. همچنین وضعیت پیشرفت کاربر (current_step و completed) را به‌روزرسانی و بازگردانی می‌کند. منطق این API بر اساس آخرین مرحله (آخرین رکورد step) داده دریافت شده بنا شده است و اطلاعات دوره نیز عمداً از همان آخرین step استخراج می‌شود (طبق رفتار فعلی سیستم).

    URL: /v2/academy/course/steps/view
    Method: GET
    Controller: AcademyController@viewStepsCourse
    Middleware: authWithJwt
    Auth: JWT Required

    Query 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

    Validate JWT (operator)
    Fetch steps (JOIN course + category)
    Load or create progress record
    Update current_step / completed
    Build steps[]
    Extract course info from LAST step
    Return final structured course object

    POST /v2/academy/course/steps/update

    Academy: Update / Store / Delete Course Steps

    این اندپوینت سه عملیات روی مراحل دوره (Course Steps) انجام می‌دهد: ایجاد (store)، ویرایش (update) و حذف (delete). نوع عملیات از طریق پارامتر action مشخص می‌شود. همه عملیات‌ها روی جدول academy_course_steps انجام می‌شود و در صورت خطا، پاسخ استاندارد شامل پیام خطا بازگردانده می‌شود.

    URL: /v2/academy/course/steps/update
    Method: POST
    Controller: AcademyController@updateStepsCourse
    Middleware: authWithJwt
    Auth: JWT Required

    Body 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

    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

    Validate JWT
    Read action (update / store / delete)
    Run respective DB query
    Return status=true | handle exception

    GET /v2/mail/servers/list

    Mail: Get Server List

    این اندپوینت لیستی از سرورهای ایمیل فعال (Status = 1) را برای یک شعبه (Branch) خاص بازیابی می‌کند.
    خروجی شامل اطلاعات پیکربندی سرویس و محدودیت‌های ماهانه هر سرور است که برای مدیریت ارسال ایمیل‌ها استفاده می‌شود.

    Request Overview

    URL: /v2/mail/servers/list
    Method: GET
    Controller: MailController@mailServer
    Middleware Stack: authWithJwt

    Access Control

    Query Parameters

    Field Type Description
    branch mixed (فیلتر) شناسه شعبه مورد نظر برای فیلتر کردن سرورهای ایمیل.

    Example Request

    GET /v2/mail/servers/list?branch=10

    Logic Details

    فرآیند پردازش در متد mailServer به صورت زیر است:

    1. دریافت ورودی: پارامتر branch از درخواست دریافت می‌شود.
    2. کوئری دیتابیس: به جدول mail_servers کوئری زده می‌شود با دو شرط:
      • branch برابر با پارامتر ورودی باشد.
      • status برابر با 1 باشد (فقط سرورهای فعال).
    3. تغییر ساختار (Transformation): روی نتایج حلقه زده می‌شود تا ساختار خروجی استاندارد شود:
      • یک شیء تو در تو به نام service ایجاد می‌شود که شامل نام سرویس (از فیلد service) و شناسه سرویس (از فیلد service_id) است.
      • سایر فیلدها مثل monthly_limit و تاریخ‌ها مستقیماً منتقل می‌شوند.
    4. ارسال پاسخ: لیست نهایی در قالب استاندارد با وضعیت true بازگردانده می‌شود.

    Response Structure

    پاسخ موفق

    در صورت موفقیت، لیست سرورها در آرایه data قرار می‌گیرد.

    پاسخ خطا

    در صورت بروز هرگونه خطا (Exception) در طول پردازش، کد وضعیت 400 بازگردانده می‌شود.

    Flowchart

    Start (GET /mail/servers/list)
    Database Query
    Table: `mail_servers`
    Where `branch` == Request input
    AND `status` == 1
    Data Transformation Loop
    For each server:
    Format `service` object {title, id}
    Return 200 OK (JSON List)
    → On Exception
    Return 400 Bad Request (Error Message)

    GET /v2/mail/address/list

    Mail: Get Address List

    این اندپوینت لیستی از آدرس‌های ایمیل تعریف شده روی یک سرور خاص را برمی‌گرداند.
    نکته کلیدی در این اندپوینت، سیستم دسترسی است: تنها آدرس‌هایی بازگردانده می‌شوند که شناسه اپراتور درخواست‌دهنده در لیست کاربران مجاز آن آدرس (`users` JSON column) وجود داشته باشد. همچنین آدرس نهایی با ترکیب نام کاربری و دامنه سرور ساخته می‌شود.

    Request Overview

    URL: /v2/mail/address/list
    Method: GET
    Controller: MailController@mailAddress
    Middleware Stack: authWithJwt

    Access Control

    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 به شرح زیر است:

    1. دریافت اطلاعات پایه:
      • شناسه اپراتور (`operator->id`) از درخواست استخراج می‌شود.
      • اطلاعات سرور ایمیل بر اساس پارامتر `server` از جدول `mail_servers` واکشی می‌شود (جهت دسترسی به دامنه سرور).
    2. جستجو در دیتابیس (فیلترینگ امنیتی):
      کوئری به جدول `mail_address` زده می‌شود با شروط زیر:
      • server: منطبق با شناسه سرور درخواستی باشد.
      • status: برابر با 1 (فعال) باشد.
      • users: این فیلد یک آرایه JSON است. سیستم با استفاده از دستور JSON_CONTAINS بررسی می‌کند که آیا شناسه اپراتور جاری در لیست کاربران مجاز این ایمیل وجود دارد یا خیر.
    3. آماده‌سازی خروجی (Data Mutation):
      • فیلدهای داخلی و حساس (`users` و `status`) از شیء نتیجه حذف می‌شوند.
      • آدرس کامل ایمیل ساخته می‌شود: مقدار فیلد `address` (نام کاربری) با کاراکتر `@` و `domain` (از جدول سرور) ترکیب می‌شود.
        مثال: `info` + `@` + `example.com` = `info@example.com`

    Response Structure

    پاسخ موفق

    لیست آدرس‌های مجاز به همراه آدرس کامل ساخته شده بازگردانده می‌شود.

    پاسخ خطا

    در صورت بروز خطا (مثلاً سرور یافت نشود یا خطای دیتابیس)، پاسخ 400 بازگردانده می‌شود.

    Flowchart

    Start (GET /mail/address/list)
    Fetch Server Info
    Get `mail_servers` row by ID.
    (Retrieve Domain)
    Query Addresses (Security Check)
    From `mail_address` WHERE:
    1. `server` matches
    2. `status` = 1
    3. `JSON_CONTAINS(users, operator_id)`
    Format Loop
    1. Remove `users`, `status`.
    2. `address` = `address` + '@' + `server.domain`.
    Return 200 OK (List)
    → On Exception
    Return 400 Bad Request

    POST /v2/mail/address/store

    Mail: Create Address

    این اندپوینت برای ایجاد یک حساب ایمیل جدید استفاده می‌شود.
    این متد یک فرآیند دو مرحله‌ای دارد: ابتدا درخواست ساخت اکانت را به سرویس‌دهنده خارجی (External API) ارسال می‌کند و در صورت موفقیت، اطلاعات آن را در دیتابیس محلی ذخیره کرده و به اپراتور درخواست‌دهنده تخصیص می‌دهد.

    Request Overview

    URL: /v2/mail/address/store
    Method: POST
    Controller: MailController@mailAddressStore
    Middleware Stack: authWithJwt

    Access Control

    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 به شرح زیر است:

    1. تعیین نام کاربری (Address Generation):
      • اگر پارامتر address ارسال شده باشد، همان استفاده می‌شود.
      • اگر ارسال نشده باشد، سیستم حرف اول first_name_en و کل last_name_en اپراتور را با نقطه ترکیب کرده و کوچک (lowercase) می‌کند. (مثال: `a.rezayi`).
    2. فراخوانی سرویس خارجی:
      • یک درخواست POST به آدرس baseUrl/{service_id}/accounts ارسال می‌شود.
      • پارامتر ارسالی: { "name": mailAddress }.
      • هدر: توکن Bearer داخلی سیستم (`$this->token`).
    3. بررسی پاسخ و ذخیره‌سازی:
      • اگر پاسخ سرویس خارجی موفق (`status == 'success'`) باشد:
        یک رکورد در جدول mail_address درج می‌شود شامل:
        • server: شناسه سرور داخلی.
        • address: نام کاربری ایمیل.
        • title: نام کامل فارسی اپراتور.
        • users: آرایه JSON حاوی شناسه اپراتور سازنده (به عنوان مالک).
        • avatar و service_id.
      • اگر پاسخ ناموفق باشد، پیام خطای سرویس خارجی بازگردانده می‌شود.

    Response Structure

    پاسخ موفق (Created)

    حساب کاربری هم در سرویس خارجی و هم در دیتابیس داخلی ایجاد شده است.

    پاسخ ناموفق (API Reject)

    سرویس خارجی درخواست ساخت اکانت را رد کرده است (مثلاً نام کاربری تکراری است).

    پاسخ خطا (Exception)

    Flowchart

    Start (POST /mail/address/store)
    Has `address` Input?
    No
    Yes
    Generate: lower(f.lastname)
    Use Input Address
    External API Call
    POST /accounts
    (Payload: name)
    API Result Success?
    ↓ (Yes)
    DB Insert (mail_address)
    Set `users` = [operator_id]
    Set `title`, `avatar` from operator
    Return Status True + Address
    → (No)
    Return Status False + API Message
    → On Exception
    Return 400 Bad Request

    GET /v2/mail/{type}/list

    Mail: Get Mailbox List

    این اندپوینت چندمنظوره برای دریافت لیست ایمیل‌ها بر اساس دسته‌بندی‌های مختلف استفاده می‌شود.
    بسته به پارامتر type در آدرس، سیستم می‌تواند صندوق ورودی، صندوق ارسال، پیش‌نویس‌ها، یا ایمیل‌های فیلتر شده بر اساس تگ و لیبل را بازگرداند.

    Request Overview

    URL: /v2/mail/{type}/list
    Method: GET
    Controller: MailController@mailBox
    Middleware Stack: authWithJwt

    Access Control

    Path Parameters

    Field Description
    type نوع صندوق یا فیلتر مورد نظر. مقادیر مجاز:
    • inbox (صندوق ورودی)
    • sent (صندوق ارسال)
    • Types: draft, trash, spam, outbox
    • Tags: starred, important
    • Labels: tickets, personal, work, finance, ...

    Query Parameters

    Field Type Description
    address_id integer (الزامی) شناسه آدرس ایمیل در دیتابیس (`mail_address.id`) که قصد دریافت ایمیل‌های آن را دارید.
    service_id integer (فقط برای Inbox) جهت سینک کردن ایمیل‌ها و ارسال به جاب صف (SnailJob).

    Logic Details

    منطق این متد بر اساس type به سه شاخه اصلی تقسیم می‌شود:

    1. حالت Inbox (صندوق ورودی):
      • ابتدا یک جاب (Job) برای همگام‌سازی ایمیل‌ها با سرویس‌دهنده خارجی (Liara Mail) در صف snailJob دیسپچ می‌شود (با تاخیر ۱۰ دقیقه).
      • از جدول mail_inbox کوئری گرفته می‌شود (شرط: recipient و status=1).
      • تعداد پیوست‌ها (Attachments) محاسبه شده و خروجی فرمت‌دهی می‌شود.
    2. حالت Sent (صندوق ارسال):
      • از جدول mail_sent کوئری گرفته می‌شود (شرط: from و status=1).
      • لیست گیرندگان (`recipients`) در خروجی قرار می‌گیرد.
    3. حالت‌های فیلتر (Type/Tag/Label):
      • سیستم ابتدا تشخیص می‌دهد که type درخواستی جزو کدام دسته است (Type, Tag یا Label).
      • سپس هر دو جدول mail_inbox و mail_sent را جستجو می‌کند.
      • این یعنی اگر شما دنبال تگ important باشید، هم ایمیل‌های دریافتی و هم ایمیل‌های ارسالی که این تگ را دارند بازگردانده می‌شوند.

    Response Structure

    پاسخ موفق

    خروجی آرایه‌ای از اشیاء ایمیل است. بسته به نوع (دریافتی/ارسالی) فیلد sender یا recipients وجود خواهد داشت.

    Flowchart

    Start (GET /mail/{type}/list)
    Check {type}
    Inbox
    Dispatch Job
    Sync Mail (10m delay)
    Query `mail_inbox`
    Where recipient = ID
    Sent
    Query `mail_sent`
    Where from = ID
    Other (Tag/Label)
    Dual Query
    Query `mail_inbox` AND `mail_sent`
    Where key = {type}
    Format & Merge
    Calculate Attachments Count
    Standardize Fields
    Return JSON Data

    POST /v2/mail/{type}/update

    Mail: Update Attributes

    این اندپوینت یک متد عمومی و داینامیک برای ویرایش ویژگی‌های یک ایمیل است.
    با استفاده از این متد می‌توان وضعیت‌هایی مانند "خوانده شده" (`read_at`)، برچسب‌ها (`label`)، تگ‌ها (`tag`) یا هر ستون دیگری از ایمیل را در صندوق ورودی یا ارسالی تغییر داد.

    Request Overview

    URL: /v2/mail/{type}/update
    Method: POST
    Controller: MailController@mailUpdate
    Middleware Stack: authWithJwt

    Access Control

    Path Parameters

    Field Description
    type نوع صندوق پستی که ایمیل در آن قرار دارد (نام جدول را تعیین می‌کند).
    • inbox: عملیات روی جدول mail_inbox
    • sent: عملیات روی جدول mail_sent

    Body Parameters

    Field Type Description
    mail_id integer (الزامی) شناسه منحصر‌به‌فرد ایمیل (`id`) که قصد ویرایش آن را دارید.
    key string (الزامی) نام ستون (Column) در دیتابیس که باید تغییر کند.
    مثال: `read_at`, `tag`, `label`, `type`.
    value mixed (الزامی) مقدار جدیدی که باید در ستون انتخاب شده قرار گیرد.

    Logic Details

    منطق این متد بسیار مستقیم است:

    1. تشخیص جدول هدف: سیستم نام جدول را با الحاق پیشوند mail_ به پارامتر type آدرس می‌سازد (مثلاً `mail_inbox`).
    2. اجرای آپدیت: یک کوئری Update روی جدول انتخاب شده اجرا می‌شود که در آن رکوردی با شناسه mail_id پیدا شده و مقدار ستون key با value جایگزین می‌شود.
    3. موارد استفاده معمول:
      • تغییر وضعیت خواندن: ارسال `key: read_at` و `value: (timestamp)`.
      • ستاره‌دار کردن: ارسال `key: tag` و `value: starred`.
      • تغییر لیبل: ارسال `key: label` و `value: work`.

    Response Structure

    پاسخ موفق

    پاسخ خطا

    در صورت بروز خطا (مثلاً نام ستون اشتباه باشد):

    Flowchart

    Start (POST /mail/{type}/update)
    Determine Table
    Table = "mail_" + {type}
    Construct Query
    UPDATE [Table]
    SET [key] = [value]
    WHERE id = [mail_id]
    Update Successful?
    No (Exception)
    Yes
    Return 400 + Error Trace
    Return Status True

    POST /v2/mail/attachments/download

    Mail: Download Attachment

    این اندپوینت وظیفه دریافت لینک دانلود فایل‌های پیوست ایمیل را بر عهده دارد.
    برای ایمیل‌های ورودی (Inbox)، از استراتژی Lazy Download استفاده می‌شود: فایل ابتدا روی سرور ایمیل خارجی است؛ اگر اولین بار باشد که درخواست می‌شود، فایل دانلود شده، در S3 ذخیره می‌شود و لینک آن در دیتابیس ثبت می‌گردد. در درخواست‌های بعدی، لینک مستقیم S3 بازگردانده می‌شود.

    Request Overview

    URL: /v2/mail/attachments/download
    Method: POST
    Controller: MailController@mailAttachmentsDownload
    Middleware Stack: authWithJwt

    Access Control

    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

    منطق پردازش به شرح زیر است:

    1. بازیابی رکورد: ابتدا رکورد پیوست از جدول mail_attachment بر اساس id خوانده می‌شود.
    2. بررسی جهت (Direction):
      • اگر Sent (ارسالی) باشد: فایل قبلاً هنگام ارسال آپلود شده است. لینک موجود در ستون storage مستقیماً بازگردانده می‌شود.
      • اگر Inbox (دریافتی) باشد:
        • بررسی کش (Cache Check): سیستم چک می‌کند آیا فیلد storage مقدار دارد (Not Null)؟
          • بله: فایل قبلاً دانلود شده است. لینک S3 بازگردانده می‌شود.
          • خیر (دانلود اولیه):
            1. یک درخواست به API سرویس‌دهنده ایمیل ارسال می‌شود (با استفاده از `service_id`, `mail_id`, `attach_id`).
            2. محتوای فایل (Base64) دریافت می‌شود.
            3. فایل در S3 (فضای ابری) آپلود می‌شود.
            4. لینک فایل جدید در ستون storage رکورد دیتابیس آپدیت می‌شود تا در دفعات بعدی دانلود نشود.
            5. لینک نهایی به کاربر ارسال می‌شود.

    Response Structure

    پاسخ موفق

    Flowchart

    Start (POST /attachments/download)
    Fetch `mail_attachment` by ID
    Direction?
    Sent
    Return `storage`
    (Existing Link)
    Inbox
    Storage Exists?
    Yes
    Return Link
    No (Null)
    Fetch External API
    Get Base64 Content
    Upload to S3
    Update DB `storage`
    Return New Link

    GET /v2/mail/sent/get

    Mail: Get Sent Details

    این اندپوینت برای دریافت جزئیات کامل یک ایمیل ارسال شده استفاده می‌شود.
    برخلاف لیست‌ها که اطلاعات خلاصه‌ای ارائه می‌دهند، این متد متن کامل بدنه ایمیل (`content`)، لیست کامل گیرندگان (CC/BCC) و آرایه کامل فایل‌های پیوست را باز می‌گرداند.

    Request Overview

    URL: /v2/mail/sent/get
    Method: GET
    Controller: MailController@mailSentGet
    Middleware Stack: authWithJwt

    Access Control

    Query Parameters

    Field Type Description
    mail_id integer (الزامی) شناسه منحصر‌به‌فرد ایمیل در جدول `mail_sent`.

    Logic Details

    فرآیند واکشی اطلاعات به صورت زیر است:

    1. واکشی ایمیل اصلی: سیستم در جدول mail_sent به دنبال رکوردی می‌گردد که id آن برابر ورودی باشد و status=1 (فعال) باشد.
    2. واکشی پیوست‌ها: همزمان، تمام رکوردهای جدول mail_attachment که mail_id آن‌ها منطبق است و جهت (`direction`) آن‌ها برابر با sent است، استخراج می‌شوند.
    3. فرمت‌دهی داده‌ها:
      • فیلد tag به صورت بولین برگردانده می‌شود (اگر تگ داشته باشد true، در غیر این صورت false).
      • فیلد label اگر موجود باشد مقدار رشته‌ای آن (مثلا "work") و اگر نباشد false برگردانده می‌شود.
      • آرایه attachment حاوی آبجکت‌های کامل فایل‌های پیوست است.

    Response Structure

    پاسخ موفق

    Flowchart

    Start (GET /mail/sent/get)
    Query `mail_sent`
    Where ID = request.id
    AND Status = 1
    Query `mail_attachment`
    Where mail_id = request.id
    AND direction = 'sent'
    Format Response
    Convert Tag to Bool
    Handle Label Nulls
    Attach Attachments Array
    Return JSON Data

    POST /v2/mail/sent/store

    Mail: Store & Send Email

    این اندپوینت وظیفه ذخیره‌سازی و ارسال ایمیل را بر عهده دارد.
    عملکرد این متد وابسته به پارامتر type است: اگر outbox باشد، ایمیل علاوه بر ذخیره شدن در دیتابیس، با استفاده از تنظیمات SMTP پویا (Dynamic SMTP) بلافاصله ارسال می‌شود. در غیر این صورت (مثلاً پیش‌نویس)، صرفاً ذخیره می‌شود.

    Request Overview

    URL: /v2/mail/sent/store
    Method: POST
    Controller: MailController@mailSentStore
    Middleware Stack: authWithJwt

    Access Control

    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

    منطق پردازش به شرح زیر است:

    1. ذخیره اولیه (Persist): اطلاعات اولیه ایمیل (گیرندگان، موضوع، متن) در جدول mail_sent درج شده و mail_id دریافت می‌شود.
    2. پردازش پیوست‌ها (DB):
      • اگر آرایه attachments ارسال شده باشد، سیستم روی آن‌ها حلقه می‌زند.
      • متادیتای هر فایل (mime, size, url) از دیسک ابری (Liara) استخراج می‌شود.
      • اطلاعات در جدول mail_attachment با جهت sent ذخیره می‌شود.
    3. منطق ارسال (SMTP): اگر type == 'outbox' باشد:
      1. واکشی تنظیمات: بر اساس from (شناسه آدرس)، اطلاعات سرور (Host, Port, User, Pass) و برند (Brand/Title, Signature) از جداول mail_address, mail_servers, offices استخراج می‌شود.
      2. کانفیگ پویا (Dynamic Config): تنظیمات mail.mailers.smtp در لاراول به صورت Runtime با اطلاعات واکشی شده جایگزین می‌شود.
      3. ارسال:
        • امضا (Signature) در صورت وجود به انتهای متن اضافه می‌شود.
        • هدر From با ترکیب آدرس+دامنه و عنوان+برند تنظیم می‌شود.
        • فایل‌های پیوست مجدداً از دیسک خوانده شده و به ایمیل ضمیمه می‌شوند (`$message->attach`).
        • ایمیل ارسال می‌گردد.

    Response Structure

    پاسخ موفق

    Flowchart

    Start (POST /sent/store)
    Insert into `mail_sent`
    Has Attachments?
    Yes
    Loop Attachments
    Get Meta from Disk
    Insert `mail_attachment`
    No
    Type == 'outbox'?
    No
    End (Saved Only)
    Yes
    Fetch SMTP Credentials
    (Join address, servers, offices)
    Set Runtime Config
    Config::set('mail.smtp', ...)
    Send Mail
    Add Signature + Attachments
    End (Sent)

    DELETE /v2/mail/sent/trash

    Mail: Move Sent to Trash

    این اندپوینت برای انتقال یک ایمیل ارسال شده به "زباله‌دان" (Trash) استفاده می‌شود.
    این عملیات یک حذف نرم (Soft Delete) است؛ رکورد فیزیکی از دیتابیس پاک نمی‌شود، بلکه فیلد type آن به trash تغییر می‌کند تا در لیست‌های عادی نمایش داده نشود.

    Request Overview

    URL: /v2/mail/sent/trash
    Method: DELETE
    Controller: MailController@mailSentTrash
    Middleware Stack: authWithJwt

    Access Control

    Body/Query Parameters

    Field Type Description
    mail_id integer (الزامی) شناسه ایمیل مورد نظر در جدول `mail_sent`.

    Logic Details

    منطق پردازش بسیار ساده است:

    1. به‌روزرسانی وضعیت: کوئری آپدیت روی جدول mail_sent اجرا می‌شود.
    2. شرط: رکوردی که id آن برابر با mail_id ورودی باشد.
    3. تغییر: مقدار ستون type به رشته trash تغییر می‌یابد.

    Response Structure

    پاسخ موفق

    Flowchart

    Start (DELETE /mail/sent/trash)
    Update `mail_sent`
    SET type = 'trash'
    WHERE id = request.mail_id
    Return Success

    GET /v2/mail/inbox/get

    Mail: Get Inbox Details

    این اندپوینت برای دریافت جزئیات کامل یک ایمیل ورودی (Inbox) استفاده می‌شود.
    اطلاعات شامل متن کامل ایمیل، فایل‌های پیوست، امتیاز اسپم و متادیتای فرستنده است.

    Request Overview

    URL: /v2/mail/inbox/get
    Method: GET
    Controller: MailController@mailInboxGet
    Middleware Stack: authWithJwt

    Access Control

    Query Parameters

    Field Type Description
    mail_id integer (الزامی) شناسه منحصر‌به‌فرد ایمیل در جدول `mail_inbox`.

    Logic Details

    فرآیند واکشی اطلاعات به صورت زیر است:

    1. واکشی ایمیل: جستجو در جدول mail_inbox با شرط id = mail_id و status = 1 (فعال).
    2. واکشی پیوست‌ها: جستجو در جدول mail_attachment با شرط mail_id و جهت direction = 'inbox'.
    3. آماده‌سازی داده‌ها:
      • مقدار avatar همیشه false بازگردانده می‌شود.
      • مقادیر tag و label در صورت null بودن در دیتابیس، به مقدار false تبدیل می‌شوند.
      • شامل اطلاعات تحلیل اسپم (`spam_score`, `spam_reason`) است.

    Response Structure

    پاسخ موفق

    Flowchart

    Start (GET /inbox/get)
    Query Mail Inbox
    WHERE id = request.mail_id
    AND status = 1
    Found?
    No (Exception)
    Return 400 Error
    Yes
    Fetch Attachments
    FROM `mail_attachment`
    WHERE mail_id AND direction='inbox'
    Format Response
    Handle Nulls (tag/label)
    Set Avatar False
    Return JSON Data

    GET /v2/mail/inbox/get

    Mail: Get Inbox Details

    این اندپوینت برای دریافت جزئیات کامل یک ایمیل ورودی (Inbox) استفاده می‌شود.
    اطلاعات شامل متن کامل ایمیل، فایل‌های پیوست، امتیاز اسپم و متادیتای فرستنده است.

    Request Overview

    URL: /v2/mail/inbox/get
    Method: GET
    Controller: MailController@mailInboxGet
    Middleware Stack: authWithJwt

    Access Control

    Query Parameters

    Field Type Description
    mail_id integer (الزامی) شناسه منحصر‌به‌فرد ایمیل در جدول `mail_inbox`.

    Logic Details

    فرآیند واکشی اطلاعات به صورت زیر است:

    1. واکشی ایمیل: جستجو در جدول mail_inbox با شرط id = mail_id و status = 1 (فعال).
    2. واکشی پیوست‌ها: جستجو در جدول mail_attachment با شرط mail_id و جهت direction = 'inbox'.
    3. آماده‌سازی داده‌ها:
      • مقدار avatar همیشه false بازگردانده می‌شود.
      • مقادیر tag و label در صورت null بودن در دیتابیس، به مقدار false تبدیل می‌شوند.
      • شامل اطلاعات تحلیل اسپم (`spam_score`, `spam_reason`) است.

    Response Structure

    پاسخ موفق

    Flowchart

    Start (GET /inbox/get)
    Query Mail Inbox
    WHERE id = request.mail_id
    AND status = 1
    Found?
    No (Exception)
    Return 400 Error
    Yes
    Fetch Attachments
    FROM `mail_attachment`
    WHERE mail_id AND direction='inbox'
    Format Response
    Handle Nulls (tag/label)
    Set Avatar False
    Return JSON Data

    DELETE /v2/mail/inbox/trash

    Mail: Move Inbox to Trash

    این اندپوینت برای انتقال یک ایمیل ورودی (Inbox) به "زباله‌دان" (Trash) استفاده می‌شود.
    مشابه اندپوینت بخش ارسالی‌ها، این عملیات نیز یک حذف نرم (Soft Delete) است و رکورد فیزیکی از دیتابیس پاک نمی‌شود؛ تنها فیلد type آن به trash تغییر می‌کند.

    Request Overview

    URL: /v2/mail/inbox/trash
    Method: DELETE
    Controller: MailController@mailInboxTrash
    Middleware Stack: authWithJwt

    Access Control

    Body/Query Parameters

    Field Type Description
    mail_id integer (الزامی) شناسه ایمیل مورد نظر در جدول `mail_inbox`.

    Logic Details

    مراحل پردازش:

    1. به‌روزرسانی دیتابیس: کوئری آپدیت روی جدول mail_inbox اجرا می‌شود.
    2. شرط: رکوردی که id آن برابر با mail_id ورودی باشد.
    3. تغییر وضعیت: مقدار ستون type به رشته trash تغییر می‌یابد تا از صندوق ورودی اصلی مخفی شود.

    Response Structure

    پاسخ موفق

    Flowchart

    Start (DELETE /mail/inbox/trash)
    Update `mail_inbox`
    SET type = 'trash'
    WHERE id = request.mail_id
    Return Success

    DELETE /v2/mail/trash/delete

    Mail: Permanent Delete (Trash)

    این اندپوینت برای حذف فیزیکی و دائمی یک ایمیل استفاده می‌شود.
    معمولاً زمانی فراخوانی می‌شود که کاربر بخواهد آیتمی را از پوشه "زباله‌دان" (Trash) به طور کامل پاک کند. برخلاف اندپوینت‌های قبلی که فقط برچسب (Type) را تغییر می‌دادند، این متد رکورد را از دیتابیس حذف می‌کند (Hard Delete).

    Request Overview

    URL: /v2/mail/trash/delete
    Method: DELETE
    Controller: MailController@mailTrashDelete
    Middleware Stack: authWithJwt

    Access Control

    Body/Query Parameters

    Field Type Description
    mail_id integer (الزامی) شناسه ایمیل مورد نظر برای حذف.
    type string (الزامی) مشخص‌کننده جدول مبدا.
    مقادیر مورد انتظار:
    • inbox: حذف از جدول mail_inbox
    • sent: حذف از جدول mail_sent

    Logic Details

    مراحل پردازش به شرح زیر است:

    1. انتخاب جدول پویا: نام جدول هدف با ترکیب پیشوند mail_ و پارامتر type ساخته می‌شود (مثلاً mail_inbox یا mail_sent).
    2. حذف رکورد: دستور DELETE روی جدول انتخاب شده با شرط id = mail_id اجرا می‌شود.
    3. نکته امنیتی/فنی: از آنجا که نام جدول به صورت پویا ساخته می‌شود، فرانت‌اند باید دقیقاً مقدار inbox یا sent را ارسال کند. ارسال مقادیر اشتباه ممکن است منجر به خطای SQL یا تلاش برای حذف از جداول نادرست شود.

    Response Structure

    پاسخ موفق

    پاسخ خطا

    Flowchart

    Start (DELETE /trash/delete)
    Determine Table Name
    Table = "mail_" + request.type
    (e.g., mail_inbox / mail_sent)
    Hard Delete Query
    DELETE FROM {Table}
    WHERE id = request.mail_id
    Return Success

    GET /v2/modules/lottery/registrations

    Module: Lottery Registrations List

    این اندپوینت برای دریافت لیست ثبت‌نام‌کنندگان در ماژول قرعه‌کشی (Lottery) استفاده می‌شود.
    این سرویس لیست فاکتورهایی را برمی‌گرداند که شامل آیتم خدمات با شناسه خاص (۱۷) هستند و محاسبات مالی سنگینی (سود، زیان، تراز دریافتی و پرداختی) برای هر ردیف انجام می‌دهد.

    Request Overview

    URL: /v2/modules/lottery/registrations
    Method: GET
    Controller: ModuleController@lotteryRegistrations
    Middleware Stack: authWithJwt

    Access Control

    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

    فرآیند تولید گزارش به شرح زیر است:

    1. تنظیم صفحه‌بندی: منطق صفحه‌بندی لاراول (`paginate`) با محاسبه `currentPage` بر اساس `start` و `length` تنظیم می‌شود.
    2. فیلترینگ سخت‌گیرانه (Hardcoded):
      • factors.operator = 1: فقط فاکتورهای مربوط به اپراتور با شناسه ۱ (احتمالاً سیستم یا ادمین اصلی).
      • factor_items.details->id = 17: شناسه ۱۷ در JSON جزئیات، نشان‌دهنده سرویس "قرعه‌کشی" است.
      • product = 'service': نوع آیتم باید خدمت باشد.
      • وضعیت فاکتور نباید ۲ (ابطال) یا ۵ (احتمالاً پیش‌نویس حذف شده) باشد.
    3. محاسبات مالی (Financial Analysis): برای هر رکورد یافت شده، تابع استاتیک TradeController::financial فراخوانی می‌شود. این تابع موارد زیر را محاسبه می‌کند:
      • مبالغ کل: جمع خرید، فروش، مرجوعی و جریمه‌ها.
      • اسناد مالی: بررسی جدول `pays` برای دریافت‌ها، پرداخت‌ها و اسناد تجمیعی (Aggregation).
      • تراز (Balance): محاسبه مانده بدهکاری/بستانکاری مسافر (`BalanceReceived`) و تامین‌کننده (`BalancePaid`).
      • سود (Profit): محاسبه دقیق سود با کسر تخفیفات و کارمزدها.

    Response Structure

    پاسخ موفق

    {
      "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

    Start
    Prepare Query
    Join `factors` & `factor_items`
    Filter: Branch, Operator=1, ItemID=17
    Search Param (r)?
    Yes
    No
    Filter Serial = (r - 10,000)
    No Filter
    Execute Pagination
    Loop through Results
    Calculate Financials
    (TradeController::financial)
    Calc Sums, Balances, Profit
    Fetch Discount Info
    Return JSON Response

    POST /v2/trade/tickets

    Trade: Tickets & Vouchers Data

    این اندپوینت وظیفه استخراج اطلاعات کامل بلیت‌ها، واچرها و فاکتورها را برای نمایش یا چاپ بر عهده دارد.
    این سرویس منطق پیچیده‌ای برای گردآوری اطلاعات مسافران، تامین‌کنندگان، وضعیت آیتم‌ها (عادی یا استردادی) و تنظیمات نمایش قیمت دارد.

    Request Overview

    URL: /v2/trade/tickets
    Method: POST
    Controller: V2TradeController@ticketsTrade

    Access Control

    Body Parameters

    Field Type Description
    branch integer (الزامی) شناسه شعبه برای فیلتر کردن فاکتورها.
    id mixed (الزامی) شناسه سند مورد نظر. رفتار این فیلد دوگانه است:
    • Array/Object: اگر آرایه باشد، به عنوان لیست `id`های جدول `factors` در نظر گرفته می‌شود.
    • Single Value: اگر تک مقدار باشد، به عنوان شماره سریال نمایشی در نظر گرفته می‌شود و سیستم به طور خودکار `ReferenceExtension` را از آن کم می‌کند تا سریال دیتابیس (`serial`) را پیدا کند.
    lang[id] string (اختیاری) کد زبان (پیش‌فرض: `fa`).

    Logic Details

    منطق پردازش این سرویس شامل مراحل زیر است:

    1. واکشی رفرنس (Factor): اطلاعات پایه فاکتور با Join به جداول `operators` و `customers` دریافت می‌شود.
    2. بررسی قابلیت چاپ:
      • اگر print == 0 باشد، خطای "عدم قابلیت چاپ" برمی‌گرداند.
      • اگر status == 5 باشد، خطای وضعیت همراه با توضیحات فاکتور برمی‌گرداند.
    3. تعیین حالت نمایش (Print Mode):
      • 1: مشاهده بدون قیمت.
      • 2: مشاهده با قیمت.
      • 3: عدم مشاهده بلیت و واچر (مسدود).
    4. پردازش آیتم‌ها (Factor Items):
      • بررسی وضعیت آیتم: آیتم‌های `refund` همیشه بررسی می‌شوند، اما آیتم‌های `online` بر اساس فیلد JSON `Status` بررسی می‌شوند.
      • استخراج مسافران:
        • اگر سرویس هتل (`accommodation`) باشد، لیست `roommate` از JSON استخراج می‌شود.
        • اطلاعات کامل مسافر از `customers` واکشی می‌شود.
        • کشینگ (Redis): اطلاعات کشور (`countries`) برای هر مسافر در Redis کش می‌شود تا فشار بر دیتابیس کاهش یابد.
      • استخراج تامین‌کننده: اطلاعات `colleagues` با استفاده از Redis کش و بازیابی می‌شود.
      • ساختاردهی خروجی: آیتم‌ها بر اساس نوع (`product`/`byproduct`) و شناسه مسافر گروه‌بندی می‌شوند. آیتم‌های استردادی (`refund`) به صورت تو در تو داخل آیتم اصلی قرار می‌گیرند.
    5. اطلاعات تماس (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

    Start
    Input ID Type?
    Array
    WhereIn('id', list)
    Single
    Where('serial', input - Extension)
    Fetch Factors with Joins
    Loop Factors
    Printable?
    No
    Return Error 5006/5001
    ↓ (Yes)
    Determine Print Mode (1, 2, 3)
    Process Items
    - Check Item Status
    - Extract Passengers (Redis Cache)
    - Extract Suppliers (Redis Cache)
    - Handle Refunds
    Format Output & Contact Info
    Return JSON (Code 5008)

    POST /v2/trade/contract

    Trade: Contract Generation

    این اندپوینت وظیفه تولید متن کامل قرارداد را بر عهده دارد.
    سیستم با استفاده از قالب‌های HTML ذخیره شده در دیتابیس و ترکیب آن‌ها با اطلاعات پویای فاکتور (مسافران، خدمات، قیمت‌ها)، خروجی نهایی قرارداد را برای نمایش یا چاپ آماده می‌کند.

    Request Overview

    URL: /v2/trade/contract
    Method: POST
    Controller: V2TradeController@contractTradeApi

    Access Control

    Body Parameters

    Field Type Description
    id integer (الزامی) شماره سریال نمایشی فاکتور.
    سیستم به صورت داخلی `ReferenceExtension` را از این عدد کم می‌کند تا سریال واقعی را در دیتابیس بیابد.
    branch integer (الزامی) شناسه شعبه.
    lang[id] string (الزامی) کد زبان برای انتخاب متن و قالب قرارداد (مثلاً `fa`).

    Logic Details

    فرآیند تولید قرارداد شامل مراحل زیر است:

    1. واکشی اطلاعات پایه: جستجوی فاکتور با اتصال (Join) به جداول operators و customers بر اساس سریال و شعبه.
    2. اعتبارسنجی‌ها:
      • اگر print == 0 باشد، خطای "عدم قابلیت چاپ" (5006) برمی‌گرداند.
      • اگر status == 5 باشد، خطای وضعیت (5001) همراه با توضیحات فاکتور برمی‌گرداند.
    3. انتخاب قالب (Template Engine):
      • سیستم قالبی را از جدول pages انتخاب می‌کند که type آن برابر با contract_{route} باشد (مثلاً contract_internal).
    4. پردازش هوشمند آیتم‌ها:
      • محاسبه مجموع ارقام با استفاده از ApiTradeController::financial.
      • مدیریت استرداد (Refund Logic): اگر آیتمی از نوع refund باشد، آیتم اصلی متناظر با آن از لیست خدمات قرارداد حذف می‌شود تا سرویس کنسل شده نمایش داده نشود.
    5. تولید جداول HTML: دو جدول به صورت پویا در کد ساخته می‌شوند:
      • %passengers-table%: لیست نام، کدملی و تاریخ تولد مسافران.
      • %services-table%: لیست شرح خدمات باقی‌مانده.
    6. جایگذاری متغیرها: متغیرهایی مانند %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

    Start
    Query Factor (Join Ops/Cus)
    Factor Exists?
    No
    Error 5001
    ↓ (Yes)
    Printable?
    No
    Error 5006
    ↓ (Yes)
    Fetch Template
    From `pages` table
    Data Processing
    1. Calculate Financials
    2. Filter Refunds (Hide Cancelled Items)
    3. Build HTML Tables (Pass/Service)
    4. Replace %Placeholders%
    Return JSON + HTML

    POST /v2/trade/confirmation

    Trade: Contract Confirmation

    این اندپوینت جهت تایید نهایی قرارداد (فاکتور) توسط اپراتور استفاده می‌شود.
    با فراخوانی این سرویس، فیلد تاییدیه در دیتابیس با زمان جاری مقداردهی شده و در صورت درخواست، پیامک اطلاع‌رسانی حاوی لینک قرارداد دیجیتال برای مسافر (سرگروه) ارسال می‌شود.

    Request Overview

    URL: /v2/trade/confirmation
    Method: POST
    Controller: V2TradeController@confirmationTrade

    Access Control

    Body Parameters

    Field Type Description
    serial_id integer (الزامی) شناسه فاکتور (Contract ID) که باید تایید شود.
    branch integer (الزامی) شناسه شعبه جهت فیلترینگ امنیتی.
    notices boolean (اختیاری) اگر `true` ارسال شود، سیستم اقدام به ارسال پیامک اطلاع‌رسانی به مسافر می‌کند.

    Logic Details

    فرآیند تایید شامل مراحل زیر است:

    1. یافتن فاکتور: جستجو در جدول factors بر اساس serial_id و branch.
    2. ثبت تاییدیه: به‌روزرسانی ستون confirmation با زمان جاری سرور (`Carbon::now`).
    3. ارسال اعلان (Notification Logic): اگر پارامتر notices ارسال شده باشد:
      • شماره موبایل مسافر (Customer) از جدول `customers` استخراج می‌شود.
      • اطلاعات شعبه (نام فارسی و تلفن) از جدول `offices` استخراج می‌شود.
      • ساخت پیام: متنی حاوی تایید قرارداد، لینک کوتاه (`mmah.ir/c/{slug}`)، تاریخ شمسی و تلفن پشتیبانی ایجاد می‌شود.
      • صف‌بندی: جاب SendNotification به صف اولویت بالا (`fastJob`) ارسال می‌شود.

    Response Structure

    پاسخ موفق

    {
      "status": true,
      "time": 1715001200
    }

    پاسخ خطا

    {
      "status": false,
      "code": 5007,
      "message": "خطا در ارسال اطلاعات.",
      "trace": [ ... ]
    }

    Flowchart

    Start
    Fetch Factor Details
    (ID, Slug, CustomerID)
    Update Database
    SET confirmation = NOW()
    Notice Req?
    No
    Yes
    Prepare SMS
    1. Get Customer Mobile
    2. Get Branch Info
    3. Build Msg (Link+Date)
    Dispatch to `fastJob`
    Return Success JSON

    POST /v2/credit-card

    Credit Card: Issue Request

    این اندپوینت وظیفه احراز هویت پیشرفته و صدور کارت اعتباری برای مسافر را بر عهده دارد.
    در این فرآیند، اطلاعات هویتی و مالکیتی سیم‌کارت از طریق سرویس‌های شاهکار و ثبت‌احوال (Jibit) استعلام شده و در صورت تأیید، یک کارت بانکی مجازی با الگوریتم استاندارد بانکی تولید و ۵۰,۰۰۰ ریال از کیف پول اپراتور کسر می‌گردد.

    Request Overview

    URL: /v2/credit-card
    Method: POST
    Controller: CreditCards@createCreditCard
    Middleware: authWithJwt

    Access Control

    Body Parameters

    Field Type Description
    passenger integer (الزامی) شناسه مسافر (Customer ID) که باید در سیستم فعال باشد و نام/نام خانوادگی فارسی داشته باشد.
    branch integer (الزامی) شناسه شعبه صادرکننده (جهت تولید پیش‌شماره کارت).
    mobile string (اختیاری) شماره موبایل جهت استعلام مالکیت. اگر ارسال نشود، از شماره موبایل پروفایل مسافر استفاده می‌شود.
    identity_code string (اختیاری) کدملی جهت استعلام. اگر ارسال نشود، از کدملی پروفایل مسافر استفاده می‌شود.
    operator object (تزریق سیستمی) آبجکت اپراتور که معمولاً توسط میدل‌ور به ریکوئست اضافه می‌شود (جهت ثبت در تراکنش کیف پول).

    Logic Details

    فرآیند صدور کارت دارای منطق پیچیده اعتبارسنجی به شرح زیر است:

    1. اعتبارسنجی اولیه:
      • مسافر باید وجود داشته باشد، وضعیت آن فعال (`status=1`) باشد و نام/نام‌خانوادگی فارسی پر شده باشد.
      • فرمت شماره موبایل و کد ملی (الگوریتم چک‌دیجیت) بررسی می‌شود.
      • بررسی تکراری نبودن: اگر مسافر از قبل کارت فعال (وضعیت ۱ یا ۳) داشته باشد، خطا بازگردانده می‌شود.
    2. استعلام تطابق هویتی (Jibit Identity Check):
      این مرحله تنها در صورتی اجرا می‌شود که مسافر قبلاً احراز نشده باشد (`identity_check IS NULL`).
      • سرویس getIdentitySimilarity فراخوانی می‌شود.
      • اگر درصد تشابه نام و نام خانوادگی کمتر از 90% باشد، خطا بازگردانده می‌شود.
      • نتیجه استعلام در دیتابیس مشتری (`identity_check` و `identity_data`) کش می‌شود.
    3. استعلام مالکیت سیم‌کارت (Shahkar):
      • سرویس getMatchingService برای تطابق کدملی و موبایل فراخوانی می‌شود.
      • در صورت عدم تطابق مالکیت، فرآیند متوقف شده و خطا برمی‌گردد.
    4. تولید شماره کارت (Luhn Algorithm):
      • پیش‌شماره (BIN): بر اساس کد شعبه ساخته می‌شود.
        اگر شعبه >= 90 باشد: 990 + branch+10
        در غیر این صورت: 9900 + branch+10
      • ساختار: [BIN] + [AccountType:1] + [PassengerID:9digits]
      • ارقام باقی‌مانده تصادفی تولید شده و رقم آخر (Check Digit) با فرمول Luhn محاسبه می‌شود.
      • CVV2 تصادفی و انقضا ۳ ساله تنظیم می‌شود.
    5. عملیات مالی و ذخیره‌سازی:
      • ثبت کارت در جدول credit_cards.
      • کسر مبلغ 50,000 ریال از کیف پول اپراتور با عنوان "بابت استعلام هویت مسافر".
      • ارسال پیامک اطلاع‌رسانی به مسافر از طریق صف fastJob.

    Response Structure

    پاسخ موفق

    پاسخ‌های خطا (نمونه)

    {
      "error": {
        "code": 1000, // یا کدهای خطای جیبیت
        "message": "اطلاعات هویتی مسافر با اطلاعات هویتی موجود در سیستم ثبت احوال مطابقت ندارد."
      },
      "meta": {
        "timestamp": 1715001200
      }
    }

    کدهای وضعیت HTTP:

    Flowchart

    Start Request
    Validate Inputs & Passenger Profile
    Card Exists?
    Yes
    Error: Active Card
    ↓ (No)
    Identity Check is Null?
    Yes
    Jibit Identity API
    Check Name Similarity
    Similiarity > 90%?
    (No) -> Error 422
    No (Already Checked)
    Jibit Shahkar API
    Match Mobile & NationalCode
    Match OK?
    No
    Error: Ownership
    ↓ (Yes)
    Generate Card
    Calc BIN + Luhn Check Digit
    Finalize
    1. Insert Card
    2. Deduct Wallet (50k)
    3. Queue SMS
    Return 201 Created

    GET /v2/credit-card/index

    Credit Card: List & Index

    این اندپوینت وظیفه واکشی و نمایش لیست کارت‌های اعتباری صادر شده را بر عهده دارد.
    مهم‌ترین ویژگی این بخش، اعمال لایه‌ی امنیتی روی شماره کارت‌ها (Masking) جهت حفاظت از داده‌های حساس و همچنین فیلترینگ هوشمند بر اساس دسترسی شعبه (Branch Access) می‌باشد.

    Request Overview

    URL: /v2/credit-card/index
    Method: GET
    Controller: CreditCards@indexCreditCard
    Middleware: authWithJwt

    Access Control

    Query Parameters

    Parameter Type Description
    branch integer (الزامی) شناسه شعبه درخواست‌کننده. اگر کاربر ادمین مرکزی نباشد، لیست فقط محدود به رکوردهای این شعبه می‌شود.
    paginate[length] integer (الزامی) تعداد رکورد مورد نظر در هر صفحه (Limit).
    paginate[start] integer (الزامی) آفست (Offset) شروع رکوردها. سیستم از این عدد برای محاسبه شماره صفحه فعلی استفاده می‌کند.

    Logic Details

    منطق پردازش لیست شامل مراحل امنیتی و محاسباتی زیر است:

    1. کنترل سطح دسترسی شعبه (Branch Filtering):
      • سیستم بررسی می‌کند که آیا branch ارسال شده برابر با 1 (دفتر مرکزی) است یا خیر.
      • اگر branch != 1 باشد، شرط whereJsonContains('branch', $branch) به کوئری اضافه می‌شود. این یعنی یک کارت ممکن است به چند شعبه دسترسی داشته باشد (ساختار JSON).
    2. غنی‌سازی داده‌ها (Data Enrichment):
      • جدول credit_cards با جدول customers به صورت Left Join ترکیب می‌شود.
      • هدف: استخراج نام و نام خانوادگی فارسی مسافر (`first_name_fa`, `last_name_fa`) جهت نمایش در لیست.
    3. محاسبه صفحه‌بندی (Pagination Logic):
      • لاراول به صورت پیش‌فرض با page کار می‌کند، اما ورودی دیتاتیبل معمولاً start و length است.
      • فرمول تبدیل: $page = ($start == 0 ? $length : $start + $length) / $length.
    4. لایه امنیت و تغییر فرمت (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

    Start Request
    Is Branch == 1?
    No (Specific Branch)
    Add Filter
    whereJsonContains('branch', id)
    Yes (HQ)
    No Filter Applied
    Execute Query
    Left Join Customers table
    Apply Pagination
    Transform Data
    1. Mask Card Number (****)
    2. Format Response JSON
    Return 200 OK

    GET /v2/credit-card

    Credit Card: Show Details

    این اندپوینت برای مشاهده جزئیات یک کارت اعتباری خاص استفاده می‌شود.
    ویژگی منحصر‌به‌فرد این متد، حساسیت به مالکیت (Ownership Awareness) است. سیستم بررسی می‌کند که آیا درخواست‌کننده (Operator) همان صاحب کارت است یا خیر. اگر مالک باشد، اطلاعات محرمانه (CVV2، تاریخ انقضا و شماره کامل) نمایش داده می‌شود؛ در غیر این صورت، داده‌ها ماسک می‌شوند.

    Request Overview

    URL: /v2/credit-card
    Method: GET
    Controller: CreditCards@showCreditCard
    Middleware: authWithJwt

    Access Control

    Query Parameters

    Parameter Type Description
    id integer (اختیاری*) شناسه منحصر‌به‌فرد کارت اعتباری. (معمولاً یا این فیلد یا passenger_id باید ارسال شود).
    passenger_id integer (اختیاری*) شناسه مسافر. برای یافتن کارت فعال یک مسافر خاص استفاده می‌شود.

    * توجه: اگرچه پارامترها اختیاری (Optional) تعریف شده‌اند، اما برای دریافت خروجی معتبر باید حداقل یکی از آن‌ها ارسال شود تا کوئری نتیجه‌ای برگرداند.

    Logic Details & Security

    منطق پردازش شامل مراحل زیر است:

    1. جستجو و اتصال (Query & Join):
      • جستجو در جدول credit_cards بر اساس id یا passenger_id.
      • اتصال (Left Join) به جدول customers برای دریافت نام مسافر.
      • دریافت اولین رکورد منطبق (first()).
    2. بررسی مالکیت (Ownership Check):
      • سیستم شناسه کاربری که لاگین کرده ($request->operator->id) را با شناسه صاحب کارت (passenger_id) مقایسه می‌کند.
    3. سطح‌بندی نمایش داده‌ها (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

    Start Request
    Find Card (ID or PassengerID)
    Found?
    No
    Return 422 Error
    ↓ Yes
    Is Operator == Passenger?
    Yes (Owner)
    Full Access
    Show Full Number
    Add CVV2 & Expire
    No (Admin)
    Restricted Access
    Mask Number (****)
    Hide Sensitive Data
    Return JSON Payload

    POST /v2/articles/{id}/views

    Article: Increment Views

    این اندپوینت وظیفه افزایش شمارنده بازدید یک مقاله خاص را بر عهده دارد.
    این متد معمولاً زمانی فراخوانی می‌شود که کاربر وارد صفحه جزئیات مقاله (Single Page) می‌شود. عملیات افزایش بازدید به صورت مستقیم روی دیتابیس (Atomic) انجام شده و تعداد جدید بازدیدها بلافاصله برگردانده می‌شود.

    Request Overview

    URL: /v2/articles/{id}/views
    Method: POST
    Controller: ArticleController@incrementViews
    Middleware: authWithJwt

    Access Control

    Path Parameters

    Parameter Type Description
    id integer (الزامی) شناسه منحصر‌به‌فرد مقاله (Article ID) که در آدرس URL قرار می‌گیرد.

    Logic Details

    روند پردازش به شرح زیر است:

    1. جستجوی رکورد (Find or Fail):
      • از متد Article::findOrFail($id) استفاده می‌شود.
      • اگر مقاله‌ای با این شناسه وجود نداشته باشد، لاراول به صورت خودکار یک خطای 404 Not Found استاندارد پرتاب می‌کند و ادامه کد اجرا نمی‌شود.
    2. افزایش اتمیک (Atomic Increment):
      • از متد increment('views') استفاده می‌شود.
      • این دستور مستقیماً یک کوئری UPDATE articles SET views = views + 1 WHERE id = ... به دیتابیس ارسال می‌کند. این روش از Race Condition جلوگیری کرده و پرفورمنس بالایی دارد (نیازی به خواندن، ویرایش در PHP و ذخیره مجدد نیست).
    3. بازگشت پاسخ:
      • تعداد بازدیدهای جدید (آپدیت شده) همراه با برچسب زمان بازگردانده می‌شود.

    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

    Start Request
    Find Article (findOrFail)
    Exists?
    No
    Return 404
    ↓ Yes
    Increment DB Column
    UPDATE `views` = `views` + 1
    Return JSON
    (New View Count)

    RESOURCE /v2/articles

    Article Resource Management

    این بخش شامل ۵ اندپوینت استاندارد برای مدیریت کامل مقالات (Articles) می‌باشد.
    ویژگی‌های کلیدی این کنترلر شامل فیلترینگ پیشرفته در لیست‌گیری (بر اساس دسته‌بندی، تگ و مکان) و مدیریت روابط متا تگ‌ها (PageMetatags) هنگام ساخت و ویرایش است.


    1. List Articles (Index)

    URL: /v2/articles
    Method: GET
    Controller: ArticleController@index

    پارامترهای فیلترینگ (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)

    URL: /v2/articles
    Method: POST
    Controller: ArticleController@store

    یک مقاله جدید ایجاد می‌کند. همزمان اگر آرایه 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)

    URL: /v2/articles/{id}
    Method: GET
    Controller: ArticleController@show

    جزئیات کامل یک مقاله را برمی‌گرداند. از Route Model Binding لاراول استفاده می‌کند (اگر پیدا نشود ۴۰۴ می‌دهد).


    4. Update Article

    URL: /v2/articles/{id}
    Method: PUT/PATCH
    Controller: ArticleController@update

    اطلاعات مقاله را ویرایش می‌کند.
    منطق هوشمند متاتگ‌ها: در آرایه metatags، اگر آیتمی دارای id باشد، آن رکورد در دیتابیس آپدیت می‌شود. اگر فاقد ID باشد، به عنوان یک متاتگ جدید برای این مقاله ایجاد می‌شود.

    Start Update
    Update Main Article Fields
    Has Metatags?
    ↓ Yes
    Loop through items:

    Item has "id"?
    YESPageMetatag::find($id)->update()
    NOPageMetatag::create()
    Return { status: true }

    5. Delete Article

    URL: /v2/articles/{id}
    Method: DELETE
    Controller: ArticleController@destroy

    مقاله را حذف می‌کند. متاتگ‌های وابسته (اگر Cascade در دیتابیس تنظیم شده باشد) حذف می‌شوند، در غیر این صورت فقط رکورد مقاله حذف می‌شود.

    {
        "status": true,
        "time": 1715009000
    }

    RESOURCE /v2/categories

    Category Resource Management

    این بخش شامل مدیریت کامل دسته‌بندی‌ها (Categories) است.
    این کنترلر به طور پیش‌فرض در متد لیست‌گیری، فقط دسته‌بندی‌های اصلی (بدون والد) را برمی‌گرداند. همچنین از فیلترهای مکان و نوع پشتیبانی می‌کند.


    1. List Categories (Index)

    URL: /v2/categories
    Method: GET
    Controller: CategoryController@index

    نکته مهم: این اندپوینت دارای فیلتر سخت‌گیرانه 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)

    URL: /v2/categories
    Method: POST
    Controller: CategoryController@store

    یک دسته‌بندی جدید ایجاد می‌کند.

    بدنه درخواست (Body Parameters)

    Field Type Description
    title string (الزامی) عنوان دسته‌بندی
    slug string (الزامی) آدرس یکتا
    main integer|null شناسه دسته‌بندی والد (اگر زیرمجموعه است). اگر خالی باشد، دسته اصلی محسوب می‌شود.
    image string آدرس تصویر
    description text توضیحات
    places array لیستی از مکان‌های مرتبط. (در دیتابیس به صورت JSON ذخیره می‌شود).
    branch integer (از طریق توکن/ریکوئست) شناسه شعبه.

    3. Show Category

    URL: /v2/categories/{id}
    Method: GET
    Controller: CategoryController@show

    مشاهده جزئیات یک دسته‌بندی خاص.


    4. Update Category

    URL: /v2/categories/{id}
    Method: PUT/PATCH
    Controller: CategoryController@update

    ویرایش اطلاعات دسته‌بندی. ورودی‌ها دقیقاً مشابه متد Store هستند.

    {
        "status": true,
        "time": 1715012000
    }

    5. Delete Category

    URL: /v2/categories/{id}
    Method: DELETE
    Controller: CategoryController@destroy

    حذف کامل دسته‌بندی.

    Index Logic Flowchart

    Start Request
    Apply Filters:
    1. Branch = Request->branch
    2. Main IS NULL (Root Categories)
    Has "place" param?
    Yes
    Find ID from `articles_places`
    (by ID or Slug)

    Filter: whereIn('places', [ID])
    No
    Skip Place Filter
    Pagination (15 items)
    Return Resource Collection

    RESOURCE /v2/tags

    Tag Resource Management

    این بخش شامل مدیریت کامل تگ‌ها (Tags) است که برای برچسب‌گذاری روی مقالات و سایر محتواها استفاده می‌شود.
    این کنترلر از ساختار استاندارد Resource در لاراول پیروی می‌کند.


    1. List Tags (Index)

    URL: /v2/tags
    Method: GET
    Controller: TagController@index

    لیست تگ‌ها را به صورت صفحه‌بندی شده (۱۵ آیتم در هر صفحه) برمی‌گرداند.

    مثال پاسخ

    {
      "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)

    URL: /v2/tags
    Method: POST
    Controller: TagController@store

    یک تگ جدید ایجاد می‌کند. پارامتر 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

    URL: /v2/tags/{id}
    Method: GET
    Controller: TagController@show

    مشاهده جزئیات یک تگ خاص بر اساس ID.


    4. Update Tag

    URL: /v2/tags/{id}
    Method: PUT/PATCH
    Controller: TagController@update

    ویرایش اطلاعات تگ. فیلدها مشابه متد Store هستند.

    {
        "status": true,
        "time": 1715015200
    }

    5. Delete Tag

    URL: /v2/tags/{id}
    Method: DELETE
    Controller: TagController@destroy

    حذف تگ از سیستم.

    Delete Request
    Find Tag by ID
    Delete Record
    Return {status: true}

    RESOURCE /v2/landing_pages

    Landing Page Management

    این بخش شامل مدیریت کامل صفحات فرود (Landing Pages) است.
    این صفحات معمولاً برای کمپین‌های خاص یا صفحات ایستا استفاده می‌شوند و شامل مدیریت سئو (Meta Tags) به صورت تودرتو هستند.


    1. List Landing Pages (Index)

    URL: /v2/landing_pages
    Method: GET
    Controller: LandingPageController@index

    توجه: در این متد از صفحه‌بندی (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)

    URL: /v2/landing_pages
    Method: POST
    Controller: LandingPageController@store

    ایجاد صفحه فرود جدید به همراه متاتگ‌های سئو.

    بدنه درخواست (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

    URL: /v2/landing_pages/{id}
    Method: GET
    Controller: LandingPageController@show

    دریافت جزئیات کامل یک صفحه فرود.


    4. Update Landing Page

    URL: /v2/landing_pages/{id}
    Method: PUT/PATCH
    Controller: LandingPageController@update

    ویرایش اطلاعات صفحه فرود و مدیریت هوشمند متاتگ‌ها.

    منطق آپدیت متاتگ‌ها (Metatags Logic)

    هنگام ارسال آرایه metatags، سیستم روی هر آیتم پیمایش می‌کند:

    Update Request
    Update LandingPage Basic Info
    Loop Metatags Array
    Has "id"?
    Find PageMetatag(id)
    Update key & value
    No "id"
    Create NEW PageMetatag
    (type='article')
    Return {status: true}

    5. Delete Landing Page

    URL: /v2/landing_pages/{id}
    Method: DELETE
    Controller: LandingPageController@destroy

    حذف صفحه فرود (و احتمالاً متاتگ‌های وابسته اگر Cascade در دیتابیس تنظیم شده باشد).

    RESOURCE /v2/page_metatags

    Page Metatag Management (Direct)

    این بخش شامل مدیریت مستقیم و مستقل متاتگ‌های سئو (Meta Tags) است.
    معمولاً متاتگ‌ها همراه با مقاله یا صفحه فرود ویرایش می‌شوند، اما این اندپوینت‌ها برای اصلاحات سریع یا مدیریت سیستمی کاربرد دارند.


    1. List All Metatags (Index)

    URL: /v2/page_metatags
    Method: GET
    Controller: PageMetatagController@index

    هشدار عملکردی: این متد از 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)

    URL: /v2/page_metatags
    Method: POST
    Controller: PageMetatagController@store

    ایجاد یک متاتگ جدید و انتساب آن به یک صفحه یا مقاله.

    بدنه درخواست (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

    URL: /v2/page_metatags/{id}
    Method: GET
    Controller: PageMetatagController@show

    مشاهده جزئیات یک متاتگ خاص.


    4. Update Metatag

    URL: /v2/page_metatags/{id}
    Method: PUT/PATCH
    Controller: PageMetatagController@update

    ویرایش کامل یک متاتگ. ورودی‌ها دقیقاً مشابه متد Store هستند.

    {
        "status": true,
        "time": 1715018200
    }

    5. Delete Metatag

    URL: /v2/page_metatags/{id}
    Method: DELETE
    Controller: PageMetatagController@destroy

    حذف متاتگ از دیتابیس.

    RESOURCE /v2/places

    Article Places Management

    این بخش مربوط به مدیریت مکان‌های نمایش (Places) مقالات است.
    مکان‌ها احتمالاً نواحی خاصی در سایت هستند (مثل "اسلایدر اصلی"، "اخبار فوری") که مقالات یا دسته‌بندی‌ها به آن‌ها منتسب می‌شوند.


    1. List Places (Index)

    URL: /v2/places
    Method: GET
    Controller: PlaceController@index

    لیست مکان‌ها را برمی‌گرداند. برای هر مکان، دسته‌بندی‌های متصل به آن نیز (که در جدول articles_categories تعریف شده‌اند) واکشی و ضمیمه می‌شوند.

    فیلترها و منطق کوئری

    ساختار پاسخ (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)

    URL: /v2/places
    Method: POST
    Controller: PlaceController@store

    ایجاد یک مکان جدید در جدول articles_places.

    بدنه درخواست (Body Parameters)

    Field Type Description
    title string (الزامی) عنوان مکان
    type string نوع مکان (مثلاً slider, sidebar)
    status integer وضعیت (1 فعال، 0 غیرفعال)
    branch integer شناسه شعبه (معمولاً از توکن یا ورودی گرفته می‌شود)

    3. Show Place

    URL: /v2/places/{id}
    Method: GET
    Controller: PlaceController@show

    نمایش جزئیات یک مکان خاص.
    در اینجا نیز کوئری دوم اجرا می‌شود تا تمام دسته‌بندی‌هایی که این مکان در فیلد places (JSON) آن‌ها وجود دارد، پیدا شده و به خروجی اضافه شوند.


    4. Update Place

    URL: /v2/places/{id}
    Method: PUT/PATCH
    Controller: PlaceController@update

    ویرایش اطلاعات مکان. پارامترها مشابه متد Store است.

    {
        "status": true,
        "time": 1715020500
    }

    5. Delete Place

    URL: /v2/places/{id}
    Method: DELETE
    Controller: PlaceController@destroy

    حذف رکورد از جدول articles_places.

    RESOURCE /v2/media

    Media Management (File Storage)

    این ماژول وظیفه مدیریت فایل‌ها و رسانه‌های سیستم را بر عهده دارد.
    برخلاف ذخیره‌سازی محلی، این کنترلر مستقیماً با فضای ذخیره‌سازی ابری (Liara Object Storage) در ارتباط است. فایل‌ها بر اساس نوع، شعبه، سال و ماه در پوشه‌بندی‌های منظم ذخیره شده و متادیتای آن‌ها (شامل سایز، مسیر و ارتباط با سایر موجودیت‌ها) در دیتابیس نگهداری می‌شود.


    1. Upload File (Store)

    URL: /v2/media
    Method: POST
    Controller: MediaController@store
    Content-Type: multipart/form-data

    Body 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

    1. دریافت و آماده‌سازی فایل:
      • استخراج پسوند (Extension) و نام اصلی فایل.
      • نام فایل در دیتابیس بدون پسوند ذخیره می‌شود.
    2. ساخت مسیر ذخیره‌سازی (Path Generation):
      مسیر فایل به صورت دینامیک بر اساس تاریخ جاری و ورودی‌ها ساخته می‌شود:
      {base_path}/{branch}/{type}/{Year}/{Month}/{ext}/
      • اگر پارامتر path ارسال شود، به عنوان base_path استفاده می‌شود.
      • در غیر این صورت، uploads به عنوان پیش‌فرض قرار می‌گیرد.
    3. ذخیره‌سازی فیزیکی (Liara Disk):
      • استفاده از Storage::disk('liara')->putFileAs(...).
      • فایل با نام اصلی + پسوند در مسیر ساخته شده آپلود می‌شود.
      • سپس سایز واقعی فایل آپلود شده از دیسک ابری استعلام می‌شود (size()).
    4. ثبت در دیتابیس:
      • رکورد جدید با اطلاعات کامل (شامل مسیر کامل path، سایز، نوع و ارتباطات) در جدول media ایجاد می‌شود.

    Flowchart (Upload Process)

    Start Request
    Extract File info
    (Ext, Name)
    Has Custom Path?
    Yes
    Base = $request->path
    No
    Base = 'uploads'
    Generate Path Structure
    base/branch/type/Y/m/ext/
    Liara Storage Upload
    putFileAs() & get Size()
    Create DB Record & Return

    2. List Media (Index)

    URL: /v2/media
    Method: GET
    Controller: MediaController@index

    لیست فایل‌های آپلود شده به صورت صفحه‌بندی شده (۱۵ تایی).

    فیلترهای جستجو (Query Params)


    3. Update Media Status

    URL: /v2/media/{id}
    Method: PUT/PATCH
    Controller: MediaController@update

    توجه: در متد آپدیت، فایل فیزیکی یا مسیر آن تغییر نمی‌کند. تنها می‌توان وضعیت (Status) فایل را تغییر داد.

    {
      "status": 0 // غیرفعال کردن فایل
    }

    4. Delete Media

    URL: /v2/media/{id}
    Method: DELETE
    Controller: MediaController@destroy

    عملیات حذف دومرحله‌ای:

    1. ابتدا با استفاده از مسیر ذخیره شده در دیتابیس (`path`)، فایل فیزیکی از دیسک Liara حذف می‌شود (`Storage::disk('liara')->delete`).
    2. سپس رکورد از دیتابیس پاک می‌شود.

    GET /v2/travel_requests

    Travel Requests List

    این اندپوینت لیست درخواست‌های سفر (مانند پرواز، هتل، قطار و ...) را با قابلیت‌های پیشرفته فیلترینگ و غنی‌سازی (Data Enrichment) بازمی‌گرداند.
    نکته متمایز این کنترلر، منطق Scoping (تعیین سطح دسترسی بر اساس گروه کاربری) و همچنین اتصال به چندین جدول (Operators, Customers, Colleagues, Cities, Factors) و دیتابیس Redis برای ساخت یک آبجکت پاسخ کامل است.


    List Requests (Index)

    URL: /v2/travel_requests
    Method: GET
    Controller: TravelRequestsController@index

    Query Parameters

    Parameter Type Description
    branch integer (الزامی) شناسه شعبه.
    group string تعیین کننده سطح دسترسی (`requester_id`):
    • base / b2e: دسترسی ادمین به درخواست‌های یک کاربر خاص (نیاز به ارسال `requester_id`).
    • colleague / b2b: لیست درخواست‌های خود اپراتور B2B.
    • agent / b2c: لیست درخواست‌های خود اپراتور B2C.
    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

    1. منطق صفحه‌بندی (Pagination Calculation):
      سیستم از متد استاندارد `page` لاراول استفاده نمی‌کند، بلکه شماره صفحه را بر اساس `start` و `length` محاسبه می‌کند:
      Current Page = (start + length) / length
      اگر `start=0` باشد، صفحه ۱ در نظر گرفته می‌شود.
    2. منطق Scoping (تعیین Requester):
      • اگر group برابر base/b2e باشد: کوئری روی `requester_id` ورودی فیلتر می‌شود.
      • اگر colleague/b2b یا agent/b2c باشد: کوئری روی `operator->id` (کاربر لاگین شده) قفل می‌شود.
      • در غیر این صورت: همه درخواست‌ها نمایش داده می‌شوند.
    3. غنی‌سازی داده‌ها (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)

    Start Request
    Calc Pagination
    Page = (Start + Length) / Length
    Check 'group' param
    base/b2e
    Filter by Input
    Requester_ID
    b2b/b2c
    Filter by Auth
    Operator ID
    Others
    No Filter
    (Admin View)
    DB Query & Filters
    Apply branch, method, status...
    Data Mapping (Loop)
    Fetch Cities, Operator, Factor (Redis), Hotel info
    Generate Title
    Origin + Dest + Hotel + Dates
    Return JSON (Items + Meta)

    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

    URL: /v2/travel_requests/status/{id}
    Method: POST
    Controller: TravelRequestsController@changeStatus

    Body Parameters

    Parameter Type Description
    status integer (الزامی) کد وضعیت جدید درخواست.
    operator_id integer (اختیاری) شناسه اپراتور مسئول رسیدگی. اگر مقدار خالی یا `0` ارسال شود، در دیتابیس `NULL` ثبت می‌شود.
    reference_id integer (شرطی) شناسه فاکتور صادر شده. این فیلد تنها زمانی در دیتابیس آپدیت می‌شود که status برابر با 4 باشد.

    Logic Details

    1. آماده‌سازی داده‌ها:
      • مقدار operator_id بررسی می‌شود؛ در صورت وجود مقدار معتبر ثبت می‌شود، در غیر این صورت (استفاده از Elvis Operator `?:`) مقدار `NULL` لحاظ می‌شود.
      • مقدار status مستقیماً از ورودی خوانده می‌شود.
    2. شرط ثبت فاکتور (Reference Logic):
      سیستم تنها در صورتی فیلد reference_id را آپدیت می‌کند که دو شرط زیر همزمان برقرار باشند:
      • وضعیت جدید (`status`) برابر با 4 باشد.
      • مقدار `reference_id` در ورودی ارسال شده و دارای مقدار باشد.
      در غیر این صورت، حتی اگر `reference_id` ارسال شود، نادیده گرفته می‌شود.
    3. به‌روزرسانی دیتابیس: عملیات با استفاده از `Query Builder` روی جدول `travel_requests` انجام می‌شود.

    Flowchart (Status Change Logic)

    Start Request
    Set Operator
    (Val or NULL)
    Status == 4 ?
    Yes
    Has Ref ID?
    Yes
    Add reference_id
    to update list
    No
    Ignore reference_id
    DB Update
    Update travel_requests WHERE id = $id
    Return Success JSON

    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

    URL: /v2/accommodation/facilities
    Method: GET
    Controller: FacilityController@index

    دریافت لیست کامل امکانات بدون هیچ‌گونه فیلترینگ یا صفحه‌بندی.

    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

    URL: /v2/accommodation/facilities
    Method: POST
    Controller: FacilityController@store

    Body 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

    URL: /v2/accommodation/facilities/{id}
    Method: GET
    Controller: FacilityController@show

    نمایش جزئیات یک رکورد خاص بر اساس ID.


    Update Facility

    URL: /v2/accommodation/facilities/{id}
    Method: PUT/PATCH
    Controller: FacilityController@update

    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

    URL: /v2/accommodation/facilities/{id}
    Method: DELETE
    Controller: FacilityController@destroy

    حذف فیزیکی رکورد از دیتابیس (Hard Delete) با استفاده از دستور delete() کوئری بیلدر.

    RESOURCE /v2/accommodation/facilities_category

    Accommodation Facility Categories

    این کنترلر وظیفه مدیریت دسته‌بندی‌های امکانات (Facility Categories) را بر عهده دارد (مانند: تفریحی، بهداشتی، عمومی و ...).
    مشابه کنترلر Facilities، این بخش نیز برای افزایش سرعت و کارایی از Query Builder لاراول (`DB::table`) استفاده می‌کند.


    List Categories

    URL: /v2/accommodation/facilities_category
    Method: GET
    Controller: FacilityCategoryController@index

    دریافت لیست کامل دسته‌بندی‌ها بدون فیلترینگ و صفحه‌بندی.

    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

    URL: /v2/accommodation/facilities_category
    Method: POST
    Controller: FacilityCategoryController@store

    Body Parameters

    Parameter Type Description
    title_en string عنوان انگلیسی دسته‌بندی.
    title_fa string عنوان فارسی دسته‌بندی.
    branch integer شناسه شعبه.
    icon string نام یا مسیر فایل آیکون.
    description string توضیحات.
    status integer وضعیت (1: فعال، 0: غیرفعال).

    Show Category

    URL: /v2/accommodation/facilities_category/{id}
    Method: GET
    Controller: FacilityCategoryController@show

    نمایش تکی یک دسته‌بندی بر اساس ID.


    Update Category

    URL: /v2/accommodation/facilities_category/{id}
    Method: PUT/PATCH
    Controller: FacilityCategoryController@update

    Body Parameters

    Parameter Type Description
    title_en string عنوان انگلیسی.
    title_fa string عنوان فارسی.
    branch integer شناسه شعبه. (قابل ویرایش)
    icon string آیکون.
    description string توضیحات.
    status integer وضعیت.
    نکته: برخلاف کنترلر Facilities، در این متد فیلد branch نیز در آرایه آپدیت وجود دارد و قابل ویرایش است.

    Delete Category

    URL: /v2/accommodation/facilities_category/{id}
    Method: DELETE
    Controller: FacilityCategoryController@destroy

    حذف فیزیکی رکورد از جدول facilities_category.

    RESOURCE /v2/accommodation/rules

    Accommodation Rules CRUD

    این کنترلر وظیفه مدیریت قوانین اقامتگاه (Accommodation Rules) را بر عهده دارد (مانند: قوانین کنسلی، ساعت ورود/خروج، ممنوعیت حیوانات خانگی و ...).
    داده‌ها مستقیماً در جدول accommodation_rules ذخیره می‌شوند و برای تمام عملیات دیتابیس از Query Builder استفاده شده است.


    List All Rules

    URL: /v2/accommodation/rules
    Method: GET
    Controller: RuleController@index

    دریافت لیست کامل قوانین ثبت شده بدون فیلترینگ یا صفحه‌بندی.

    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

    URL: /v2/accommodation/rules
    Method: POST
    Controller: RuleController@store

    Body Parameters

    Parameter Type Description
    accommodation integer شناسه اقامتگاه مرتبط (Relation Key).
    title_en string عنوان انگلیسی قانون.
    title_fa string عنوان فارسی قانون.
    description string توضیحات کامل قانون.
    status integer وضعیت (1: فعال، 0: غیرفعال).

    Show Rule Details

    URL: /v2/accommodation/rules/{id}
    Method: GET
    Controller: RuleController@show

    نمایش جزئیات یک قانون خاص بر اساس شناسه.


    Update Rule

    URL: /v2/accommodation/rules/{id}
    Method: PUT/PATCH
    Controller: RuleController@update

    Body Parameters

    Parameter Type Description
    accommodation integer شناسه اقامتگاه. (قابل ویرایش)
    title_en string عنوان انگلیسی.
    title_fa string عنوان فارسی.
    description string توضیحات.
    status integer وضعیت.
    تفاوت با سایر کنترلرها: در این کنترلر، فیلد رابطه (accommodation) نیز در متد Update وجود دارد و قابل تغییر است.

    Delete Rule

    URL: /v2/accommodation/rules/{id}
    Method: DELETE
    Controller: RuleController@destroy

    حذف فیزیکی رکورد از جدول accommodation_rules.

    GET /v2/comments

    Comments: List & Filter

    این اندپوینت برای دریافت لیستی از نظرات (Comments) با قابلیت‌های فیلترینگ و مرتب‌سازی پیشرفته طراحی شده است.
    این پیاده‌سازی از Eloquent ORM و API Resources لاراول بهره می‌برد و نتایج را به صورت صفحه‌بندی شده (15 آیتم در هر صفحه) بازمی‌گرداند.


    List All Comments

    URL: /v2/comments
    Method: GET
    Controller: CommentController@index

    Query 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

    URL: /v2/comments/{id}
    Method: GET
    Controller: CommentController@show

    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

    URL: /v2/comments/{id}
    Method: DELETE
    Controller: CommentController@destroy

    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

    1. ساخت آبجکت JSON: ابتدا اطلاعات نظر را در قالب یک آبجکت JSON استاندارد آماده کنید (ساختار آن در جدول زیر آمده است).
    2. رمزنگاری (Encryption): رشته JSON حاصل را با استفاده از کلید عمومی RSA که مختص branch مورد نظر است، رمزنگاری کنید.
    3. کدگذاری Base64: خروجی باینری حاصل از مرحله رمزنگاری را به فرمت رشته Base64 تبدیل کنید.
    4. ارسال درخواست: یک درخواست POST به این اندپوینت ارسال کنید که بدنه آن فقط شامل یک فیلد data حاوی رشته Base64 نهایی است.
    5. پردازش در سرور: میدلور decryptData در سمت سرور با استفاده از کلید خصوصی (Private Key) متناظر، داده‌ها را رمزگشایی کرده و در اختیار کنترلر قرار می‌دهد.

    Create a New Comment

    URL: /v2/comments
    Method: POST
    Controller: CommentController@store
    Middleware Chain: `authWithJwt`, `decryptData`

    Request Body Structure (Before Encryption)

    آبجکت JSON که باید رمزنگاری و ارسال شود، باید شامل فیلدهای زیر باشد:

    Parameter Type Description Validation
    content_id integer شناسه محتوایی که نظر برای آن است (مثلاً شناسه مقاله). Required
    category string نوع محتوا (مثلاً 'article', 'accommodation'). Required
    content string متن اصلی نظر. Required
    name string نام نویسنده نظر. Required
    email 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

    پس از رمزگشایی موفق، کنترلر به صورت خودکار اطلاعات زیر را به داده‌های دریافتی اضافه کرده و در دیتابیس ذخیره می‌کند:

     

    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) است. خلاصه‌ی مراحل به شرح زیر است:

    1. ساخت آبجکت JSON: داده‌های مورد نظر برای آپدیت را در قالب یک آبجکت JSON آماده کنید.
    2. رمزنگاری و Base64: آبجکت JSON را با کلید عمومی (Public Key) شعبه رمزنگاری کرده و به Base64 تبدیل کنید.
    3. ارسال درخواست: درخواست PUT را به همراه بدنه حاوی فیلد data (شامل رشته نهایی) ارسال نمایید.

     

    توجه: میدلور decryptData در سمت سرور، با استفاده از کلید خصوصی (Private Key)، داده‌ها را رمزگشایی کرده و برای پردازش به کنترلر تحویل می‌دهد.

    Update an Existing Comment

    URL: /v2/comments/{id}
    Method: PUT
    Controller: CommentController@update
    Middleware Chain: `authWithJwt`, `decryptData`

    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

    POST /v2/comments/{commentId}/{action}

    Like or Dislike a Comment

    این اندپوینت برای ثبت لایک یا دیسلایک برای یک نظر مشخص استفاده می‌شود. عملیات مورد نظر (لایک یا دیسلایک) از طریق پارامتر action در URL تعیین می‌گردد.
    این متد به صورت اتمیک عمل نمی‌کند، اما شمارنده‌های likes و dislikes را در مدل Comment یک واحد افزایش می‌دهد.


    Increment Like/Dislike Counter

    URL: /v2/comments/{commentId}/{action}
    Method: POST
    Controller: CommentController@likeOrDislike
    Middleware: `authWithJwt`

    Path Parameters

    Parameter Type Description
    commentId integer شناسه منحصر به فرد نظری که لایک یا دیسلایک می‌شود.
    action string عملیات مورد نظر. این پارامتر باید یکی از دو مقدار زیر باشد:
    • like: برای افزایش شمارنده لایک.
    • dislike: برای افزایش شمارنده دیسلایک.

    Request Body

    این اندپوینت به هیچ داده‌ای در بدنه درخواست (Request Body) نیاز ندارد.

    Server-Side Logic

     

     

    رفتار در صورت ارسال action نامعتبر: اگر مقداری غیر از 'like' یا 'dislike' برای پارامتر action ارسال شود، هیچ خطایی رخ نمی‌دهد و هیچ شمارنده‌ای تغییر نمی‌کند. درخواست با موفقیت پردازش شده و مقادیر فعلی لایک و دیسلایک بازگردانده می‌شود.

    Success Response

    در صورت موفقیت‌آمیز بودن عملیات، سرور یک پاسخ با کد 200 به همراه مقادیر به‌روز شده‌ی شمارنده‌های likes و dislikes برمی‌گرداند.

    {
        "status": true,
        "time": 1715000000,
        "data": {
            "likes": 25,
            "dislikes": 3
        }
    }

    Error Response

    DELETE /v2/comments/{commentId}/{action}

    Remove a Like or Dislike from a Comment

    این اندپوینت برای کاهش (حذف) یک لایک یا دیسلایک از یک نظر مشخص استفاده می‌شود. این عملیات، معکوس اندپوینت POST برای افزودن لایک/دیسلایک است و به کاربر اجازه می‌دهد تا رأی خود را پس بگیرد.


    Decrement Like/Dislike Counter

    URL: /v2/comments/{commentId}/{action}
    Method: DELETE
    Controller: CommentController@removeLikeOrDislike
    Middleware: `authWithJwt`

    Path Parameters

    Parameter Type Description
    commentId integer شناسه منحصر به فرد نظری که رأی آن باید حذف شود.
    action string نوع رأیی که باید حذف شود. این پارامتر باید یکی از دو مقدار زیر باشد:
    • like: برای کاهش شمارنده لایک.
    • dislike: برای کاهش شمارنده دیسلایک.

    Request Body

    این اندپوینت به هیچ داده‌ای در بدنه درخواست (Request Body) نیاز ندارد.

    فلوچارت منطق سرور (Server Logic Flow)

    روند پردازش درخواست در سمت سرور به شرح زیر است:

    1. دریافت نظر (Find Comment): سیستم با استفاده از commentId ارسالی در URL، نظر مربوطه را با متد findOrFail از دیتابیس جستجو می‌کند.
      • اگر نظر پیدا نشود، به صورت خودکار پاسخ 404 Not Found بازگردانده می‌شود.
    2. بررسی نوع عملیات (Check Action): مقدار پارامتر action بررسی می‌شود.
    3. منطق کاهش شمارنده (Conditional Decrement):
      • اگر `action` برابر با `like` باشد: سیستم ابتدا بررسی می‌کند که آیا شمارنده likes بزرگتر از صفر است ($comment->likes > 0). اگر شرط برقرار باشد، یک واحد از آن کم می‌شود. در غیر این صورت (اگر شمارنده صفر باشد)، هیچ تغییری اعمال نمی‌شود.
      • اگر `action` برابر با `dislike` باشد: سیستم بررسی می‌کند که آیا شمارنده dislikes بزرگتر از صفر است ($comment->dislikes > 0). اگر شرط برقرار باشد، یک واحد از آن کم می‌شود. در غیر این صورت، تغییری اعمال نمی‌شود.
    4. ذخیره تغییرات (Save Changes): تغییرات روی مدل Comment در دیتابیس ذخیره می‌شود ($comment->save()).
    5. ارسال پاسخ نهایی (Return Response): یک پاسخ موفقیت‌آمیز با کد 200 حاوی شمارنده‌های به‌روز شده بازگردانده می‌شود.
    رفتار محافظتی: این منطق به طور هوشمند از منفی شدن شمارنده‌ها جلوگیری می‌کند. اگر شمارنده یک نظر از قبل صفر باشد و درخواستی برای کاهش آن ارسال شود، مقدار آن تغییری نکرده و همان صفر باقی می‌ماند.

    Success Response

    در صورت موفقیت‌آمیز بودن عملیات، سرور یک پاسخ با کد 200 به همراه مقادیر به‌روز شده‌ی شمارنده‌های likes و dislikes برمی‌گرداند.

    {
        "status": true,
        "time": 1715000000,
        "data": {
            "likes": 24,
            "dislikes": 2
        }
    }

    Error Response

    POST /v2/invoice/process

    Hub: Create Payment Invoice

    این اندپوینت برای ایجاد یک صورت‌حساب قابل پرداخت طراحی شده است.
    فرآیند شامل ثبت یک "قبض" (Bill)، یافتن یک درگاه پرداخت فعال، تولید یک فاکتور منحصر به فرد با اسلاگ (Slug) یکتا، و در نهایت بازگرداندن یک لینک پرداخت برای کاربر است.

    Request Overview

    URL: /v2/invoice/process
    Method: POST
    Controller: HubController@createInvoice
    Middleware: authWithJwt

    Access Control

    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

    فرآیند ایجاد لینک پرداخت دارای چندین مرحله کلیدی است:

    1. اعتبارسنجی اولیه:
      • بررسی می‌شود که فیلدهای الزامی price, type, و id در درخواست وجود داشته باشند.
      • مبلغ price باید بزرگتر یا مساوی 10,000 ریال باشد.
      • نوع پرداخت (type) باید دقیقاً برابر با 'credit' باشد. در غیر این صورت خطا بازگردانده می‌شود.
    2. ثبت رکورد قبض (Bill):
      • یک رکورد جدید در جدول airplus_bills ایجاد می‌شود.
      • این رکورد حاوی اطلاعات پایه پرداخت مانند مبلغ، شناسه اپراتور، شعبه و شناسه آبجکت مرتبط است. وضعیت اولیه آن 1 (فعال) ثبت می‌شود.
    3. انتخاب درگاه پرداخت (Gateway Selection):
      • تابع کمکی Functions::getGatewayConfig فراخوانی می‌شود.
      • اولویت اول: اگر فیلد driver در درخواست ارسال شده باشد، سیستم به دنبال یک درگاه فعال با همان نام درایور برای آن شعبه می‌گردد.
      • اولویت دوم (Fallback): اگر driver ارسال نشده باشد، سیستم از جدول office_config به دنبال درگاه پیش‌فرض (`DEFAULT_BANKING_GATEWAY`) برای آن شعبه می‌گردد.
      • اگر هیچ درگاه فعالی (نه با درایور مشخص و نه به عنوان پیش‌فرض) یافت نشود، خطا بازگردانده می‌شود.
    4. ایجاد فاکتور نهایی (Invoice):
      • با استفاده از تابع Functions::generateSlugUnique، یک رشته تصادفی و منحصر به فرد به طول ۸ کاراکتر به عنوان `slug` فاکتور تولید می‌شود. این تابع تضمین می‌کند که اسلاگ در جدول airplus_invoices تکراری نباشد.
      • یک رکورد جدید در جدول airplus_invoices ثبت می‌شود که حاوی اسلاگ، اطلاعات درگاه، مبلغ، و شناسه قبض (Bill) ایجاد شده در مرحله ۲ است.
    5. تولید لینک و ارسال پاسخ:
      • یک URL پرداخت با استفاده از دامنه ثابت https://ipg.airplus.app/invoice/payment/ و اسلاگ منحصر به فرد تولید می‌شود.
      • این URL به همراه مبلغ در قالب یک پاسخ موفقیت‌آمیز به کلاینت بازگردانده می‌شود.

    Response Structure

    پاسخ موفق

    {
        "payload": {
            "status": "payment_link",
            "amount": 50000,
            "url": "https://ipg.airplus.app/invoice/payment/aB3xZ7cR"
        },
        "meta": {
            "timestamp": 1715001200
        }
    }

    پاسخ‌های خطا (نمونه)

    {
      "error": {
        "code": 1000,
        "message": "درگاه پرداخت فعال یافت نشد"
      }
    }

    Flowchart

    Start Request
    Validate Inputs (price, type, id)
    Inputs Valid & price >= 10k?
    No
    Error: 422
    ↓ (Yes)
    type == 'credit'?
    No
    Error: 400 Invalid Type
    ↓ (Yes)
    Create record in `airplus_bills` table
    Get Gateway Config
    1. Check for `driver` input
    2. Fallback to branch default
    Gateway Found?
    No
    Error: 400 No Gateway
    ↓ (Yes)
    Generate Unique Slug
    (Loop until unique in `airplus_invoices`)
    Finalize Invoice
    1. Insert into `airplus_invoices`
    2. Link to Bill & Gateway
    Construct Payment URL
    Return 201 Created with URL

    POST /v2/invoice/payment/details

    Hub: Get Invoice Payment Details

    این اندپوینت برای استعلام وضعیت و دریافت جزئیات تراکنش یک فاکتور خاص استفاده می‌شود.
    معمولاً پس از بازگشت کاربر از درگاه بانک، کلاینت با ارسال slug فاکتور به این اندپوینت، وضعیت نهایی پرداخت (موفق یا ناموفق)، شماره پیگیری، شماره کارت و علت خطا (در صورت شکست) را دریافت می‌کند.

    Request Overview

    URL: /v2/invoice/payment/details
    Method: POST
    Controller: HubController@paymentSlugData
    Middleware: authWithJwt

    Access Control

    Body Parameters

    Field Type Description
    slug string (الزامی) شناسه منحصر به فرد (۸ کاراکتری) فاکتور که در مرحله ایجاد فاکتور تولید شده است.

    Logic Details

    فرآیند استعلام شامل مراحل دقیق زیر است:

    1. جستجوی فاکتور:
      • سیستم در جدول airplus_invoices به دنبال رکوردی با slug ارسال شده می‌گردد.
      • اگر رکوردی یافت نشود، پاسخ خطای 409 با پیام "سند ارسالی به بانک یافت نشد" بازگردانده می‌شود.
    2. پردازش داده‌های درگاه (Gateway Parsing):
      • اگر فاکتور پیدا شد، ستون result (که حاوی پاسخ JSON از بانک است) پردازش می‌شود.
      • سیستم بر اساس نوع درگاه (drive)، اطلاعات شماره پیگیری و شماره کارت را استخراج می‌کند:
        • Behpardakht: از فیلدهای SaleReferenceId و CardHolderPan استفاده می‌شود.
        • Sep (Saman): از فیلدهای TraceNo و SecurePan استفاده می‌شود.
      • همچنین رکورد قبض (Bill) مرتبط از جدول airplus_bills بازیابی می‌شود تا نوع سرویس (`object_type`) مشخص گردد.
    3. بررسی وضعیت نهایی (Status Check):
      • حالت موفق (Status == 3): اگر وضعیت فاکتور برابر با 3 باشد، پرداخت موفقیت‌آمیز بوده است. جزئیات کامل تراکنش با کد 200 ارسال می‌شود.
      • حالت ناموفق (Status != 3): اگر وضعیت هر چیزی غیر از 3 باشد:
        1. پیام خطای دقیق از پاسخ بانک استخراج می‌شود (مثلاً errorDesc برای سامان یا پیام عمومی برای به‌پرداخت).
        2. پاسخ با کد وضعیت 409 ارسال می‌شود که حاوی جزئیات تراکنش + آبجکت error است.

    Response Structure

    پاسخ موفق (پرداخت تایید شده)

    {
        "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
        }
    }

    پاسخ ناموفق (خطای پرداخت یا یافت نشدن)

    {
        "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

    Start Request
    Find Invoice by `slug`
    Invoice Found?
    No
    Return 409 (Not Found)
    ↓ (Yes)
    Parse Gateway Data
    Extract Tracking & Card No based on driver (Sep/Behpardakht)
    Status == 3 ?
    Yes
    Return 200 OK
    (Payment Details)
    No

    ↓ (No)
    Extract Error Msg
    Get specific error from gateway response
    Return 409 Conflict
    (Details + Error Obj)

    POST /v2/invoice/payment/wallet

    Hub: Pay using Wallet

    این اندپوینت یک فرآیند پرداخت ترکیبی را مدیریت می‌کند که به کاربر اجازه می‌دهد از کیف پول داخلی شعبه برای تسویه حساب استفاده کند.
    این مسیر دو سناریوی اصلی دارد:
    ۱. موجودی کافی: مبلغ کامل از کیف پول کسر شده و تراکنش با موفقیت ثبت می‌شود.
    ۲. موجودی ناکافی: موجودی کیف پول به طور کامل مصرف شده و برای باقیمانده مبلغ، یک لینک پرداخت آنلاین (از طریق اندپوینت createInvoice) ایجاد می‌شود.

    Request Overview

    URL: /v2/invoice/payment/wallet
    Method: POST
    Controller: HubController@payByWallet
    Middleware: authWithJwt

    Access Control

    Body Parameters

    Field Type Description
    type string (الزامی) نوع موجودیتی که پرداخت برای آن انجام می‌شود. اگر مقدار آن 'bill' باشد، مبلغ از روی رکورد قبض محاسبه می‌شود.
    id integer (الزامی) شناسه موجودیت (مثلاً شناسه قبض یا سفارش).
    amount integer (شرطی) مبلغ کل به ریال. ارسال این فیلد تنها زمانی الزامی است که type مقداری غیر از 'bill' داشته باشد.
    operator object (تزریق سیستمی) آبجکت اپراتور لاگین شده.
    branch integer (تزریق سیستمی) شناسه شعبه‌ای که اپراتور به آن تعلق دارد.

    Logic Details

    این اندپوینت فرآیند پیچیده‌ای را دنبال می‌کند که به دو شاخه اصلی تقسیم می‌شود:

    1. اعتبارسنجی و محاسبه مبلغ:
      • بررسی می‌شود که فیلدهای type و id ارسال شده باشند.
      • اگر type برابر با 'bill' باشد:
        • رکورد مربوطه از جدول airplus_bills با وضعیت 1 (فعال) جستجو می‌شود.
        • مبلغ نهایی از فرمول ($bill->amount + $bill->tax) - $bill->discount محاسبه می‌شود.
        • اگر صورتحساب یافت نشود، خطای 422 بازگردانده می‌شود.
      • در غیر این صورت: مبلغ از فیلد amount در درخواست خوانده می‌شود.
      • مبلغ محاسبه شده باید حداقل 10,000 ریال باشد.
    2. بررسی موجودی کیف پول:
      • تابع getBalanceWallet فراخوانی شده تا موجودی فعلی کیف پول شعبه (`branch`) را دریافت کند.
      • شرایط بررسی می‌شود: اگر مبلغ مورد نیاز از موجودی کیف پول بیشتر باشد، به سناریوی "موجودی ناکافی" می‌رویم.
    3. سناریو ۱: موجودی ناکافی
      • مبلغ باقیمانده ($amount - $balance) محاسبه می‌شود.
      • اگر مبلغ باقیمانده کمتر از 10,000 ریال باشد، برای ایجاد لینک پرداخت، حداقل مبلغ 10,000 ریال در نظر گرفته می‌شود.
      • یک درخواست جدید به صورت داخلی ساخته شده و به متد createInvoice فوروارد می‌شود تا برای مبلغ باقیمانده، یک لینک پرداخت آنلاین تولید کند.
      • خروجی این اندپوینت در این حالت، دقیقاً مشابه خروجی اندپوینت createInvoice خواهد بود (یک لینک پرداخت).
    4. سناریو ۲: موجودی کافی
      • وضعیت صورتحساب (در جدول airplus_bills) به 5 (پرداخت شده از کیف پول) تغییر می‌کند.
      • یک رکورد بدهی (Debit) جدید در جدول wallet برای شعبه ثبت می‌شود. این رکورد شامل شناسه اپراتور، مبلغ کسر شده و توضیحات تراکنش است.
      • پاسخ موفقیت‌آمیز با کد 201 بازگردانده می‌شود که حاوی شناسه تراکنش کیف پول است.

    Response Structure

    پاسخ موفق (پرداخت کامل از کیف پول)

    {
        "payload": {
            "status": "succeed",
            "id": 542,
            "datetime": "2024-05-18 15:00:00"
        },
        "meta": {
            "timestamp": 1716027000
        }
    }

    پاسخ موفق (پرداخت ترکیبی - موجودی ناکافی)

    {
        "payload": {
            "status": "payment_link",
            "amount": 450000,
            "url": "https://ipg.airplus.app/invoice/payment/kL9sW1aP"
        },
        "meta": {
            "timestamp": 1716027100
        }
    }

    پاسخ‌های خطا

    {
      "error": {
        "code": 1000,
        "message": "صورتحساب یافت نشد."
      },
      "meta": {
        "timestamp": 1716027200
      }
    }

    Flowchart

    Start Request
    Validate Inputs & Calculate Amount
    Inputs Valid & Amount >= 10k?
    No
    Return 422 (Validation Error)
    ↓ (Yes)
    Check Branch Wallet Balance
    Sufficient Balance?
    No
    Calculate Remainder
    Forward to `createInvoice`
    Return 201 (Payment Link)
    ↓ (Yes)
    Update Bill Status to 5
    Create Debit Record in `wallet` table
    Return 201 (Succeed Status)

    GET /v2/core/bill/{type}

    Hub: Get Branch Bills

    این اندپوینت برای دریافت لیست صورتحساب‌های پرداخت نشده (`status = 1`) یک شعبه خاص طراحی شده است.
    کلاینت می‌تواند با ارسال نوع سرویس (مانند `hotel`, `flight` و...)، فقط صورتحساب‌های مربوط به آن سرویس را فیلتر و دریافت کند. خروجی شامل جزئیات کامل هر صورتحساب به همراه مبلغ نهایی قابل پرداخت است.

    Request Overview

    URL: /v2/core/bill/{type}
    Method: GET
    Controller: HubController@indexBranchBill
    Middleware: authWithJwt

    Access Control

    URL Parameters

    Field Type Description
    type string (الزامی) نوع سرویسی که صورتحساب برای آن صادر شده است. مقادیر ممکن: 'hotel', 'flight', 'tour' و غیره.

    Logic Details

    فرآیند دریافت صورتحساب‌ها به شرح زیر است:

    1. استخراج اطلاعات: شناسه شعبه (`branch`) از توکن JWT و نوع سرویس (`type`) از URL خوانده می‌شود.
    2. کوئری از دیتابیس:
      • یک کوئری به جدول airplus_bills ارسال می‌شود.
      • این کوئری نتایج را بر اساس سه شرط اصلی فیلتر می‌کند:
        1. branch: باید با شناسه شعبه کاربر لاگین کرده مطابقت داشته باشد.
        2. status: باید دقیقاً برابر با 1 باشد (فقط صورتحساب‌های در انتظار پرداخت).
        3. object_type: باید با پارامتر type ارسال شده در URL مطابقت داشته باشد.
    3. پردازش و تبدیل داده‌ها (`map`):
      • اگر یک یا چند صورتحساب پیدا شود، سیستم روی هرکدام از آن‌ها یک فرآیند تبدیل اجرا می‌کند:
      • یک فیلد جدید به نام total با استفاده از فرمول `(((amount + tax) - discount) + markup)` محاسبه و به خروجی اضافه می‌شود.
      • یک آبجکت details ساخته می‌شود که حاوی month_title است. این فیلد با استفاده از تابع CalendarUtils::strftime نام فارسی ماه (مانند "آذر ماه") را از روی تاریخ ایجاد صورتحساب استخراج می‌کند.
      • ساختار نهایی هر آیتم برای خوانایی بیشتر در پاسخ API بازآرایی می‌شود.
    4. ساختار پاسخ نهایی:
      • اگر هیچ صورتحسابی یافت نشود، پاسخ با کد 200 OK بازگردانده شده و مقدار فیلد items برابر با false خواهد بود.
      • نکته مهم: بلوک کد مربوط به خطای 404 در کنترلر به دلیل نحوه عملکرد تابع get()، عملاً اجرا نمی‌شود و پاسخ همیشه 200 است.
      • اگر صورتحساب‌ها پیدا شوند، آرایه‌ای از آبجکت‌های تبدیل شده در فیلد items قرار می‌گیرد.

    Response Structure

    پاسخ موفق (در صورت یافتن صورتحساب)

    {
        "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
        }
    }

    پاسخ موفق (در صورت عدم وجود صورتحساب)

    {
        "items": false,
        "meta": {
            "timestamp": 1733735400
        }
    }

    Flowchart

    Start Request (GET /v2/core/bill/{type})
    Extract `branch` from JWT & `type` from URL
    Query `airplus_bills` WHERE:
    - `branch` = user_branch
    - `status` = 1
    - `object_type` = {type}
    Bills Collection is Empty?
    Yes
    Set `items` field to `false`
    Return 200 OK
    ↓ (No)
    Map over results:
    - Calculate `total`
    - Generate `month_title`
    - Format structure
    Return 200 OK with `items` array

    GET /v2/core/hub/information

    Hub: Get Basic Information

    این اندپوینت به عنوان یک مسیر عمومی برای دریافت اطلاعات پایه‌ای و مرکزی (Hub Information) عمل می‌کند. رفتار این اندپوینت بر اساس پارامتر action که در کوئری استرینگ ارسال می‌شود، تغییر می‌کند.
    در حال حاضر، این مسیر تنها برای دریافت لیست تمام دفاتر (Offices) پیاده‌سازی شده است.

    Request Overview

    URL: /v2/core/hub/information
    Method: GET
    Controller: HubController@getBasicInformation
    Middleware: authWithJwt

    Access Control

    Query Parameters

    Field Type Description
    action string (اختیاری) عملکرد مورد نظر را مشخص می‌کند. اگر مقدار آن برابر با 'offices' باشد، لیست دفاتر برگردانده می‌شود. در غیر این صورت، خروجی متفاوت خواهد بود.

    Logic Details

    منطق این اندپوینت کاملاً به پارامتر action وابسته است:

    1. بررسی شرط: سیستم ابتدا بررسی می‌کند که آیا پارامتر action در درخواست وجود دارد و مقدار آن برابر با 'offices' است یا خیر.
    2. سناریو ۱: اگر action = 'offices' باشد:
      • یک کوئری به جدول offices در دیتابیس زده می‌شود و فیلدهای id, brand_fa و brand_en برای تمام رکوردها استخراج می‌شود.
      • نتایج با استفاده از متد map پردازش می‌شوند تا ساختار خروجی تغییر کند:
        • فیلد id بدون تغییر باقی می‌ماند.
        • یک آبجکت جدید به نام title ایجاد می‌شود که دارای دو کلید fa و en است.
        • مقدار هر عنوان با فرمول زیر ساخته می‌شود:
          (شناسه دفتر + 10,000) - نام برند
          برای مثال، اگر id=25 و brand_fa='آژانس تابان' باشد، خروجی '10025 - آژانس تابان' خواهد بود.
      • آرایه تبدیل شده در فیلد items پاسخ نهایی قرار می‌گیرد.
    3. سناریو ۲: اگر action ارسال نشود یا مقداری غیر از 'offices' داشته باشد:
      • شرط اصلی برقرار نمی‌شود و بلوک کد مربوط به کوئری دفاتر اجرا نمی‌گردد.
      • در این حالت، متغیر $items تعریف نمی‌شود.
      • نکته کلیدی: به دلیل تعریف نشدن متغیر $items، در پاسخ JSON نهایی، مقدار فیلد items برابر با null خواهد بود.

    Response Structure

    پاسخ موفق (برای action=offices)

    {
        "items": [
            {
                "id": 25,
                "title": {
                    "fa": "10025 - آژانس تابان",
                    "en": "10025 - Taban Agency"
                }
            },
            {
                "id": 26,
                "title": {
                    "fa": "10026 - آژانس سپهر",
                    "en": "10026 - Sepehr Agency"
                }
            }
        ],
        "meta": {
            "timestamp": 1733736000
        }
    }

    پاسخ برای سایر مقادیر action (یا عدم ارسال آن)

    {
        "items": null,
        "meta": {
            "timestamp": 1733736100
        }
    }

    Flowchart

    Start Request (GET /v2/core/hub/information)
    Query Param `action` == 'offices'?
    No
    `$items` is not initialized
    Return 200 OK with `items: null`
    ↓ (Yes)
    Query `offices` table (select id, brand_fa, brand_en)
    Map results to new structure:
    `title.fa = (id + 10k) + ' - ' + brand_fa`
    `title.en = (id + 10k) + ' - ' + brand_en`
    Return 200 OK with formatted `items` array

    GET /v2/core/hub/analyze

    Hub: Flight Reservation Analysis

    این اندپوینت یک گزارش تحلیلی جامع از تمام رزروهای پرواز آنلاین (`product='online'`, `byproduct='aircraft'`) ارائه می‌دهد. داده‌ها بر اساس سال شمسی، تأمین‌کننده (Supplier)، و تأمین‌کننده سیستمی (System Supplier) دسته‌بندی و agregare می‌شوند. خروجی شامل تعداد کل رزروها و مجموع مبالغ خرید در هر دسته‌بندی است.

    Request Overview

    URL: /v2/core/hub/analyze
    Method: GET
    Controller: HubController@hubAnalysis
    Middleware: authWithJwt

    Access Control

    Logic Details

    فرآیند تحلیل داده‌ها در این اندپوینت طی چندین مرحله پیچیده انجام می‌شود:

    1. استخراج داده‌های اولیه:
      • یک کوئری به جدول factor_items ارسال می‌شود تا تمام رکوردهایی که دارای شرایط زیر هستند، استخراج شوند:
        • product برابر با 'online'
        • byproduct برابر با 'aircraft'
      • نتایج بر اساس id به صورت نزولی (DESC) مرتب می‌شوند تا ابتدا جدیدترین رزروها پردازش شوند.
    2. پردازش تک‌تک رزروها:
      • سیستم در یک حلقه (loop) تمام رزروهای استخراج شده را پیمایش می‌کند.
      • برای هر رزرو، فیلد details که یک رشته JSON است، به آرایه PHP تبدیل می‌شود.
      • اعتبارسنجی حیاتی: سیستم بررسی می‌کند که آیا مسیر $details['Book']['DepartureSegment'] در آبجکت JSON وجود دارد یا نه. اگر وجود نداشته باشد، آن رزرو از فرآیند تحلیل حذف شده و حلقه به تکرار بعدی می‌رود.
    3. گروه‌بندی و تجمیع داده‌ها (Aggregation):
      • استخراج تاریخ: سال و ماه شمسی از فیلد created_at رزرو با استفاده از CalendarUtils::strftime استخراج می‌شود (مثلاً `1404-09`).
      • استخراج تأمین‌کنندگان: شناسه‌های Supplier و SystemSupplier از آبجکت DepartureSegment خوانده می‌شوند.
      • کش کردن نام تأمین‌کننده:
        • برای جلوگیری از کوئری‌های تکراری به دیتابیس، یک آرایه به نام $colleagues به عنوان کش داخلی عمل می‌کند.
        • هر بار که یک شناسه تأمین‌کننده جدید یافت می‌شود، سیستم یک بار به جدول colleagues کوئری می‌زند تا نام دفتر (office) را پیدا کند.
        • اگر نام دفتر پیدا شود، در کش ذخیره می‌شود. در غیر این صورت، خود شناسه تأمین‌کننده به عنوان نام پیش‌فرض استفاده می‌شود.
      • افزایش شمارنده‌ها: برای هر رزرو معتبر، شمارنده‌های count (تعداد) و paid (مجموع مبلغ خرید از فیلد buy) در سطوح مختلف ساختار داده افزایش می‌یابند:
        1. مجموع کل (Grand Total)
        2. مجموع کل سال مربوطه
        3. مجموع کل تأمین‌کننده (Supplier) در آن سال
        4. مجموع ماهانه تأمین‌کننده (Supplier) در آن سال
        5. همین فرآیند به طور موازی برای تأمین‌کننده سیستمی (System Supplier) نیز تکرار می‌شود.
    4. ساختار نهایی خروجی:
      • نتیجه نهایی یک آبجکت بسیار تودرتو است که داده‌ها را به تفکیک سال، تأمین‌کننده، و ماه نمایش می‌دهد.
      • این آبجکت در فیلد payload پاسخ نهایی قرار می‌گیرد.

    Response Structure

    پاسخ موفق

    {
        "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

    Start Request (GET /v2/core/hub/analyze)
    Query `factor_items` WHERE `product`='online' AND `byproduct`='aircraft'
    Initialize `analyze` & `colleagues` arrays
    For each reservation in results:
    Decode `details` JSON
    `details['Book']['DepartureSegment']` exists?
    No
    Continue to next reservation
    ↓ (Yes)
    Extract Year, Month, Supplier IDs
    Is Supplier ID new?
    ↓ (Yes)
    Query `colleagues` table & cache the title
    Aggregate Data:
    Increment `count` & `paid` at all levels (Grand, Year, Supplier, Month) for both Supplier & SystemSupplier
    Return 200 OK with `payload`

    GET /v2/core/hub/reservation

    Hub: Get Master Reservation List

    این اندپوینت یک لیست جامع و صفحه‌بندی شده از تمام رزروهای ثبت شده در سیستم مرکزی (Hub) را برمی‌گرداند. برای هر رزرو، اطلاعات از جداول مختلفی مانند offices، factor_items، colleagues، و wallet استخراج و با داده‌های اصلی ادغام می‌شود تا یک خروجی کامل و غنی‌شده ارائه دهد.

    Request Overview

    URL: /v2/core/hub/reservation
    Method: GET
    Controller: HubController@hubReservation
    Middleware: authWithJwt

    Access Control

    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

    فرآیند دریافت و پردازش رزروها بسیار گسترده است و شامل مراحل زیر می‌شود:

    1. کوئری اولیه و صفحه‌بندی:
      • یک کوئری اصلی به جدول hub_reservation زده می‌شود.
      • این کوئری با جدول offices بر اساس hub_reservation.branch جوین می‌شود تا نام فارسی شعبه (brand_fa) استخراج شود.
      • نتایج بر اساس id به صورت نزولی مرتب می‌شوند.
      • با استفاده از پارامترهای length و start، صفحه‌بندی سفارشی روی نتایج اعمال می‌شود.
    2. حلقه غنی‌سازی داده‌ها (Data Enrichment Loop):
      • سیستم روی هر یک از رزروهای برگشتی از کوئری اصلی، یک حلقه اجرا کرده و برای هرکدام، مجموعه‌ای از عملیات و کوئری‌های اضافی را انجام می‌دهد:
      • نکته عملکردی (Performance Consideration): این رویکرد (کوئری در حلقه) به مشکل N+1 Query منجر می‌شود و ممکن است با افزایش تعداد آیتم‌ها در هر صفحه، عملکرد را تحت تأثیر قرار دهد.
    3. مراحل داخل حلقه برای هر رزرو:
      1. واکشی آیتم فاکتور: از جدول factor_items، آیتم مربوط به رزرو (item_id) واکشی می‌شود. اگر آیتم یافت نشود، کل آن رزرو از خروجی نهایی حذف می‌شود.
      2. واکشی نام سرویس‌ها: شناسه‌های عددی service و sub_service با اجرای کوئری روی جدول colleagues به نام متنی (فیلد office) تبدیل می‌شوند.
      3. تولید عنوان آیتم (title_item): با فراخوانی متد پیچیده TradeController::getReferenceItemTitle، یک عنوان توصیفی و قابل فهم برای آیتم (مانند "هواپیما تهران به مشهد | ۱۴۰۴/۰۹/۱۸ ۱۲:۳۰") تولید می‌شود. این متد به شدت از کش Redis برای بهینه‌سازی عملکرد استفاده می‌کند.
      4. واکشی اطلاعات اپراتور: با فراخوانی متد StaticController::getOperators، شناسه اپراتور به یک آبجکت کامل شامل نام، نام خانوادگی و سایر مشخصات تبدیل می‌شود. در صورت عدم وجود اپراتور، مقدار false بازگردانده می‌شود.
      5. پردازش فیلدهای JSON: رشته‌های JSON موجود در فیلدهای supplier_details و details به آبجکت/آرایه تبدیل می‌شوند.
      6. ضمیمه کردن داده‌های جدید: اطلاعات پردازش شده (نام سرویس، عنوان، اطلاعات اپراتور و...) به آبجکت اصلی رزرو اضافه می‌شوند.
      7. بررسی استرداد (Refund): سیستم در جدول wallet جستجو می‌کند تا ببیند آیا تراکنش استردادی (target_type='refund') برای این رزرو ثبت شده است یا خیر. در صورت وجود، جزئیات آن به فیلد refund اضافه می‌شود؛ در غیر این صورت، این فیلد مقدار false می‌گیرد.

    Response Structure

    پاسخ موفق

    {
        "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

    Start Request (GET /v2/core/hub/reservation)
    Execute Paginated Query on `hub_reservation` with JOIN on `offices`.
    (Using custom `start`/`length` params)
    For each reservation in results:
    Query `factor_items` using `item_id`
    Item Found?
    No
    Skip to next reservation
    ↓ (Yes)
    Query `colleagues` for Service & Sub-Service names
    Call `getReferenceItemTitle` (Cached Title Generation)
    Call `getOperators` to format operator data
    Query `wallet` to find refund details
    Assemble all new data into the final reservation object
    Return 200 OK with `items` array & `meta.table`

    POST /v2/core/hub/reservation

    Hub: Create Manual Wallet Transaction

    این اندپوینت برای ثبت دستی یک تراکنش مالی (سند) در کیف پول (`wallet`) استفاده می‌شود. اگرچه URL به "رزرو" اشاره دارد، اما عملکرد اصلی آن ایجاد یک رکورد بدهکار (`debit`) یا بستانکار (`credit`) برای یک شعبه خاص است. این عملیات معمولاً توسط مدیران هاب برای اصلاح حساب یا ثبت سند دستی انجام می‌شود.

    Request Overview

    URL: /v2/core/hub/reservation
    Method: POST
    Controller: HubController@hubStoreReservation
    Middleware: authWithJwt

    Access Control

    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 (الزامی) نوع ماهیت تراکنش. مقادیر مجاز:
    • 'credit': افزایش اعتبار (بستانکار)
    • 'debit': کاهش اعتبار (بدهکار)
    value numeric (الزامی) مبلغ تراکنش. بسته به diagnosis در ستون credit یا debit قرار می‌گیرد.
    description string توضیحات تراکنش (بابت چه چیزی).

    Logic Details

    فرآیند ثبت در کنترلر به صورت زیر است:

    1. آماده‌سازی داده‌ها:
      • فیلد object_type به صورت سخت‌کد شده برابر با 'branch' قرار می‌گیرد.
      • فیلد object برابر با مقدار ارسالی branch تنظیم می‌شود.
      • شناسه اپراتور انجام‌دهنده عملیات از $request->get('operator')->id خوانده می‌شود.
    2. تعیین ماهیت مالی (Diagnosis Logic):
      • اگر diagnosis == 'credit' باشد: مقدار value در ستون credit و ستون debit برابر 0 می‌شود.
      • اگر diagnosis == 'debit' باشد: مقدار value در ستون debit و ستون credit برابر 0 می‌شود.
    3. درج در دیتابیس: آرایه ساخته شده مستقیماً با استفاده از DB::table('wallet')->insert() در جدول ذخیره می‌شود.

    Response Structure

    پاسخ موفق

    پاسخ خطا

    {
        "error": {
            "code": 1000,
            "message": "SQLSTATE[23000]: Integrity constraint violation..." // پیام خطا
        },
        "meta": {
            "timestamp": 1733737200
        }
    }

    Flowchart

    Start Request (POST)
    Extract Operator from Request Attributes
    Check Diagnosis
    ↓ (credit)
    Set Credit = Value
    Set Debit = 0
    ↓ (debit)
    Set Debit = Value
    Set Credit = 0
    Prepare Insert Array
    (Set object_type='branch')
    DB::table('wallet')->insert(...)
    Success?
    No
    Return 400 Bad Request
    ↓ (Yes)
    Return 201 Created

    PATCH /v2/core/hub/reservation/refund

    Hub: Process Reservation Refund

    این اندپوینت برای انجام عملیات استرداد (Refund) یک رزرو ثبت‌شده در سیستم هاب استفاده می‌شود. فرآیند شامل محاسبه مبلغ جریمه (به صورت درصدی یا مبلغ ثابت)، ثبت مبلغ قابل بازگشت به عنوان یک تراکنش بستانکار در کیف پول شعبه، و در نهایت به‌روزرسانی وضعیت رزرو به "استرداد شده" است.

    Request Overview

    URL: /v2/core/hub/reservation/refund
    Method: PATCH
    Controller: HubController@hubRefund
    Middleware: authWithJwt

    Access Control

    Request Body Parameters

    Field Type Description
    id integer (الزامی) شناسه (ID) رزروی که باید استرداد شود.
    value_type string (الزامی) نوع محاسبه جریمه. مقادیر مجاز:
    • 'percent': جریمه به صورت درصدی از مبلغ خرید (buy) رزرو محاسبه می‌شود.
    • مقادیر دیگر (مثلاً 'fixed'): جریمه به عنوان یک مبلغ ثابت در نظر گرفته می‌شود.
    value numeric (الزامی) مقدار جریمه. اگر value_type برابر 'percent' باشد، این مقدار درصد است (مثلاً 10 برای ۱۰٪). در غیر این صورت، این مقدار مبلغ ریالی جریمه است.
    description string (اختیاری) توضیحات اضافی که به انتهای توضیحات پیش‌فرض تراکنش کیف پول اضافه می‌شود.

    Logic Details

    فرآیند استرداد در کنترلر به صورت گام‌به‌گام زیر انجام می‌شود:

    1. یافتن رزرو:
      • سیستم با استفاده از id ارسال شده، در جدول hub_reservation جستجو می‌کند تا رکورد رزرو مورد نظر را پیدا کند.
    2. محاسبه جریمه (Penalty):
      • اگر value_type در درخواست برابر با 'percent' باشد، مبلغ جریمه از فرمول زیر محاسبه می‌شود:
        $penalty = ($reservation->buy * $request->value) / 100
      • در غیر این صورت، مقدار value ارسالی مستقیماً به عنوان مبلغ ثابت جریمه در نظر گرفته می‌شود.
    3. ایجاد تراکنش کیف پول (Wallet Transaction):
      • یک رکورد جدید برای درج در جدول wallet آماده می‌شود:
      • Mبلغ بستانکاری (credit): برابر است با مبلغ خرید اولیه رزرو منهای جریمه محاسبه‌شده ($reservation->buy - $penalty).
      • توضیحات (description): یک رشته توصیفی به صورت خودکار ساخته می‌شود که شامل شناسه رزرو، مبلغ جریمه، و توضیحات اختیاری ارسال شده در درخواست است.
      • این رکورد در جدول wallet درج می‌شود و اعتبار به حساب شعبه بازمی‌گردد.
    4. به‌روزرسانی وضعیت رزرو:
      • رکورد اصلی رزرو در جدول hub_reservation به‌روزرسانی می‌شود:
      • فیلد refund_penalty با مبلغ جریمه محاسبه‌شده پر می‌شود.
      • فیلد status به مقدار ثابت ۴ (به معنای استرداد شده) تغییر می‌کند.
      • فیلد updated_at با زمان فعلی به‌روز می‌شود.

    Response Structure

    پاسخ موفق

    پاسخ خطا

    {
        "error": {
            "code": 1000,
            "message": "رزرو یافت نشد."
        }
    }

    Flowchart

    Start Request (PATCH /.../refund)
    Query `hub_reservation` by `id`
    Reservation Found?
    No
    Return 400 - "رزرو یافت نشد."
    ↓ (Yes)
    Is `value_type` == 'percent'?
    ↓ (Yes)
    Calculate Penalty:
    `buy_price * value / 100`
    ↓ (No)
    Set Penalty = `value` (fixed amount)
    Create `wallet` transaction (credit = buy_price - penalty)
    Update `hub_reservation` (status=4, refund_penalty=penalty)
    Return 201 Created (Empty Body)

    PATCH /v2/core/hub/reservation/refund/undo

    Hub: Undo Reservation Refund

    این اندپوینت برای لغو یک عملیات استرداد (Refund) که قبلاً انجام شده، استفاده می‌شود. فرآیند شامل پیدا کردن تراکنش بستانکاری مربوط به استرداد، بررسی کافی بودن موجودی کیف پول شعبه برای بازگرداندن آن مبلغ، ایجاد یک تراکنش بدهکاری جدید برای خنثی کردن تراکنش قبلی، و در نهایت بازگرداندن وضعیت رزرو به حالت اولیه (فعال) است.

    Request Overview

    URL: /v2/core/hub/reservation/refund/undo
    Method: PATCH
    Controller: HubController@hubUndoRefund
    Middleware: authWithJwt

    Access Control

    Request Body Parameters

    Field Type Description
    id integer (الزامی) شناسه (ID) رزروی که عملیات استرداد آن باید لغو شود. سیستم از این شناسه برای یافتن تراکنش استرداد مرتبط در جدول wallet استفاده می‌کند.

    Logic Details

    فرآیند لغو استرداد به صورت گام‌به‌گام زیر است:

    1. یافتن تراکنش استرداد:
      • سیستم در جدول wallet به دنبال تراکنشی می‌گردد که شرایط زیر را داشته باشد:
        • target_type == 'refund'
        • target == request('id') (شناسه رزرو)
        • credit > 0 (تراکنش بستانکاری که پول را به شعبه بازگردانده است)
      • اگر چنین تراکنشی یافت نشود، خطای 400 با پیام "استرداد یافت نشد" بازگردانده می‌شود.
    2. بررسی موجودی کیف پول (Critical Step):
      • قبل از برداشت وجه، سیستم با فراخوانی متد AccountingController::getCheckWallet موجودی شعبه را بررسی می‌کند.
      • این متد چک می‌کند که آیا موجودی فعلی شعبه (با در نظر گرفتن حد اعتبار یا credit_limit) برای پوشش مبلغ استرداد شده ($transaction->credit) کافی است یا خیر.
      • اگر موجودی کافی نباشد، خطای 400 با پیام "موجودی کیف پول جهت برگشت این آیتم کافی نیست" بازگردانده می‌شود.
    3. ایجاد تراکنش بدهکاری (Debit Transaction):
      • یک رکورد جدید در جدول wallet برای برداشت مبلغ استرداد شده از حساب شعبه ایجاد می‌شود:
      • Mبلغ بدهکاری (debit): برابر است با مبلغ credit تراکنش استرداد اولیه.
      • توضیحات (description): متنی مانند "برگشت استرداد [شناسه تراکنش]" ثبت می‌شود.
      • این رکورد بدهکاری، تراکنش بستانکاری قبلی را خنثی می‌کند.
    4. بازگردانی وضعیت رزرو:
      • رکورد اصلی رزرو در جدول hub_reservation به‌روزرسانی می‌شود:
      • فیلد refund_penalty به null تغییر می‌کند.
      • فیلد status به مقدار ۱ (به معنای فعال/صادر شده) بازمی‌گردد.
      • فیلد updated_at به‌روز می‌شود.

    Response Structure

    پاسخ موفق

    پاسخ‌های خطا

    ۱. تراکنش استرداد یافت نشد:

    {
        "error": {
            "code": 1000,
            "message": "استرداد یافت نشد"
        }
    }

    ۲. موجودی کیف پول ناکافی است:

    {
        "error": {
            "code": 1000,
            "message": "موجودی کیف پول جهت برگشت این آیتم کافی نیست."
        },
        "meta": {
            "timestamp": 1733737200
        }
    }

    ۳. بروز خطای عمومی (Exception):

    {
        "error": {
            "code": 1000,
            "message": "General exception message..."
        },
        "meta": {
            "timestamp": 1733737200
        }
    }

    Flowchart

    Start Request (PATCH /.../refund/undo)
    Find Refund Transaction in `wallet` table
    Transaction Found?
    No
    Return 400 - "استرداد یافت نشد"
    ↓ (Yes)
    Check Wallet Balance (getCheckWallet)
    No (Insufficient)
    Return 400 - "موجودی ناکافی"
    ↓ (Yes)
    Create Reversal `Debit` Transaction in `wallet`
    Update `hub_reservation` (status=1, refund_penalty=null)
    Return 201 Created

    POST /v2/flights/ticket/information/{type}

    L. Flight Ticket Information (Nira)

    این اندپوینت برای استعلام اطلاعات دقیق بلیط‌های صادر شده از طریق سیستم تامین‌کننده نیرا (Nira) استفاده می‌شود. این سرویس قابلیت جستجو بر اساس "شماره بلیط" یا "PNR" را دارد و به صورت هوشمند وضعیت استرداد (Refund) بلیط را بررسی کرده و در صورت نیاز، جزئیات مالی استرداد (جریمه و مبلغ پرداختی) را از سیستم نیرا استخراج می‌کند.

    Request Overview

    URL: /v2/flights/ticket/information/{type}
    Method: POST
    Controller: V2BaseController@ticketInformation
    Middleware: authWithJwt

    Access Control

    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

    منطق پردازش در کنترلر به شرح زیر است:

    1. اعتبارسنجی اولیه:
      • اگر پارامتر type برابر با nira نباشد، خطای 409 بازگردانده می‌شود.
      • تنظیمات اتصال (Api Url, Username, Password) بر اساس شناسه شعبه کاربر یا شعبه پیش‌فرض (1) بارگذاری می‌شود.
    2. سناریوی ۱: جستجو با شماره بلیط (Ticket Number):
      • متد ticket_information نیرا فراخوانی می‌شود.
      • اگر StatusCode == 'R' (استرداد شده) باشد:
        • یک درخواست کامند (command) با مقدار DMB + ticket_number ارسال می‌شود.
        • پاسخ متنی با استفاده از Regex (متد getRefundedTicketData) پارس شده و فیلدهای RefundedAmount و Penalty استخراج می‌شوند.
    3. سناریوی ۲: جستجو با 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
        }
    }

    پاسخ خطا

    Flowchart

    Start Request (POST)
    Check Type == 'nira'
    No
    Return 409 Conflict
    ↓ (Yes)
    Has Ticket Number?
    ↓ (No - Use PNR)
    Call 'reserve_information'
    Loop through Tickets
    Extract Ticket Data
    ↓ (Yes)
    Call 'ticket_information'
    Extract Single Ticket
    Is Ticket Refunded? ('R')
    No
    → Skip
    ↓ (Yes)
    Send Command 'DMB'
    Parse Regex for Penalty
    Enrich Response Item
    Return 200 OK (JSON)

    POST /v2/flights/routes/update

    M. Update Airline Active Routes (Manual Trigger)

    این اندپوینت برای بروزرسانی دستی جدول مسیرهای فعال (Airline Active Routes) استفاده می‌شود. سیستم با فراخوانی این سرویس، موجودی پروازها را برای ۷ روز آینده (از فردا) به تفکیک روزهای هفته بررسی کرده و در دیتابیس ذخیره می‌کند. این فرآیند با ارسال درخواست‌های متوالی به وب‌سرویس تامین‌کننده (Nira) انجام می‌شود.

    Request Overview

    URL: /v2/flights/routes/update
    Method: POST
    Controller: V2BaseController@updateAirlineActiveRoute
    Logic Handler: CronController::updateAirlineActiveRoute
    Middleware: authWithJwt

    Access Control

    Request Body Parameters

    Field Type Description
    airline array/string (اختیاری) کد IATA ایرلاین‌ها. در صورت ارسال، بروزرسانی فقط برای این ایرلاین(ها) انجام می‌شود (مثلاً ['W5', 'NV']).
    route_id integer (اختیاری) شناسه مسیر (ID) در جدول approved_flight_rate. اگر ارسال شود، پردازش فقط برای این مسیر خاص انجام می‌گیرد.

    Logic Details

    منطق پردازش در CronController بسیار پیچیده است و شامل مراحل زیر می‌باشد:

    1. پاکسازی وضعیت قبلی: کلید ردیس airline_active_route:cron:route_id در ابتدای کار حذف می‌شود تا پردازش از نو آغاز شود.
    2. واکشی مسیرها (Routes):
      • از جدول approved_flight_rate مسیرهایی که مبدا و مقصد غیرفارسی دارند انتخاب می‌شوند.
      • فیلتر بر اساس route_id (در صورت وجود در ورودی) اعمال می‌شود.
    3. واکشی ایرلاین‌ها:
      • از جدول application_interface رکوردهایی با تایپ api و سرویس nira که فعال هستند (status=1) دریافت می‌شوند.
      • اگر پارامتر airline ارسال شده باشد، لیست ایرلاین‌ها محدود می‌شود.
    4. حلقه پردازش (هفتگی):
      • برای ۷ روز آینده (شروع از فردا) یک بازه زمانی ایجاد می‌شود.
      • به ازای هر مسیر و هر روز، متد NiraApi->sendRequestFlight فراخوانی می‌شود.
      • اگر flight['Data']['Information'] حاوی داده باشد، وضعیت آن روز (مثلاً monday) برابر با 1 و در غیر این صورت 0 در نظر گرفته می‌شود.
    5. بروزرسانی دیتابیس (Upsert):
      • داده‌ها در جدول airline_active_route ذخیره می‌شوند.
      • سیستم بررسی می‌کند که آیا رکوردی با ترکیب (Airline + Origin + Destination) وجود دارد یا خیر (بدون توجه به جهت مسیر).
      • اگر موجود باشد Update و اگر نباشد Insert انجام می‌شود.
    6. محدودیت پردازش: یک شرط break وجود دارد: اگر تعداد ایرلاین‌های یافت شده بیش از ۱ مورد باشد، حلقه مسیرها (Routes) پس از اولین اجرا متوقف می‌شود (جهت جلوگیری از Timeout در پردازش‌های انبوه).

    Response Structure

    پاسخ موفق (200 OK)

    {
        "payload": {
            "Status": true,
            "Time": 1733738500
        },
        "meta": {
            "timestamp": 1733738500
        }
    }

    Flowchart

    Start Request (POST)
    Redis::del(cron_key)
    Fetch Routes (approved_flight_rate)
    Fetch Airlines (Nira API configs)
    Loop Routes
    Loop Period (Next 7 Days)
    1. Call NiraApi->sendRequestFlight
    2. Check Availability (1 or 0)
    3. Store in Memory ($checkedRoutes)
    DB Check (airline_active_route)
    ↙ (Exists)
    Update Row
    ↘ (New)
    Insert Row
    Count(Airlines) > 1 ?
    Yes
    Break Loop (Prevent Timeout)
    ↓ (No / Continue)
    Return JSON Payload

    POST /v2/flights/routes/min_price

    N. Flight Route Min Price (Cache)

    این اندپوینت برای دریافت حداقل قیمت پرواز در یک مسیر خاص (مبدا و مقصد) استفاده می‌شود. داده‌های این سرویس مستقیماً از Redis Cache خوانده می‌شوند و دو حالت عملکرد دارد: دریافت قیمت برای یک تاریخ خاص، یا دریافت لیست تمام قیمت‌های موجود (Calendar View) برای آن مسیر.

    Request Overview

    URL: /v2/flights/routes/min_price
    Method: POST
    Controller: V2BaseController@getRouteFlightsMinPrice
    Storage: Redis (Read-Only)
    Middleware: authWithJwt

    Access Control

    Request Body Parameters

    Field Type Description
    origin string (الزامی) کد IATA فرودگاه مبدا (مثلاً MHD).
    destination string (الزامی) کد IATA فرودگاه مقصد (مثلاً THR).
    date string (اختیاری) تاریخ پرواز (فرمت YYYY-MM-DD). اگر ارسال شود، فقط قیمت همان روز برگردانده می‌شود. اگر ارسال نشود، لیست تمام تاریخ‌های موجود بازگردانده می‌شود.

    Logic Details

    منطق کنترلر بر اساس وجود یا عدم وجود پارامتر date به دو شاخه تقسیم می‌شود:

    1. حالت تک تاریخ (Specific Date):
      • کلید ردیس به صورت مستقیم ساخته می‌شود: min_price:flights:{origin}:{destination}:{date}.
      • اگر کلید موجود باشد، مقدار آن (قیمت) در قالب آبجکت payload بازگردانده می‌شود.
      • اگر موجود نباشد، پاسخ JSON با خطای داخلی code: 404 بازگردانده می‌شود.
    2. حالت کلی (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

    Start Request (POST)
    Is 'date' provided?
    ↙ (Yes)
    Redis::get(Specific Key)
    Found?
    No
    Return 404 Error
    Yes
    Return Single Payload
    ↘ (No)
    Redis::keys(Pattern *)
    Loop Keys:
    1. Remove Prefix
    2. Extract Date
    3. Get Price
    Return Items List

    GET /v2/core/application_interface

    O. List Application Interfaces (Core)

    این اندپوینت برای دریافت لیست رابط‌های نرم‌افزاری (Application Interfaces) استفاده می‌شود. خروجی این سرویس لیستی از شعب (Offices) است که تنظیمات و دسترسی‌های مربوطه (مانند APIهای ایرلاین‌ها، دسترسی همکاران و...) به عنوان زیرمجموعه آن‌ها گروه‌بندی شده‌اند. همچنین امکان دریافت مانده حساب کیف پول هر شعبه نیز به صورت اختیاری وجود دارد.

    Request Overview

    URL: /v2/core/application_interface
    Method: GET
    Controller: CoreController@listApplicationInterface
    Middleware: authWithJwt

    Access Control

    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

    فرآیند پردازش داده‌ها شامل مراحل زیر است:

    1. دریافت و فیلتر رابط‌ها: ابتدا داده‌ها از جدول application_interface بر اساس فیلترهای ورودی (branch, service, type, status) دریافت می‌شوند.
    2. غنی‌سازی داده‌ها (Data Mapping):
      • اگر object_type برابر با colleague باشد، اطلاعات همکار (نام، سریال، آفیس) از مدل Colleague استخراج و جایگزین فیلد object می‌شود.
      • فیلدهای رشته‌ای ساده (service, type, object_type) به ساختار استاندارد آرایه‌ای {id, title} تبدیل می‌شوند.
    3. گروه‌بندی: داده‌های پردازش شده بر اساس شناسه شعبه (branch) گروه‌بندی می‌شوند.
    4. تجمیع با شعب (Offices):
      • لیست شعب از جدول offices دریافت می‌شود (اگر فیلتر branch باشد، فقط همان شعبه).
      • برای هر شعبه، رابط‌های مربوطه از مرحله قبل در فیلد items قرار می‌گیرند.
    5. محاسبه مانده (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

    Start Request (GET)
    Fetch application_interface (Apply Filters)
    Map Data Loop:
    1. Expand 'colleague' object
    2. Format fields to {id, title}
    Group Data by 'branch'
    Fetch Offices (DB)
    Need Balance?
    ↙ (Yes)
    Calc Wallet (Credit - Debit)
    ↘ (No)
    Skip
    Return Merged JSON

    GET /v2/core/application_interface/{id}

    P. Show Application Interface (Single Item)

    این اندپوینت برای دریافت جزئیات کامل یک رابط نرم‌افزاری خاص استفاده می‌شود. با ارسال شناسه (ID) رکورد، سیستم اطلاعات آن را از دیتابیس استخراج کرده و پس از استانداردسازی فیلدها و غنی‌سازی اطلاعات (مانند اطلاعات همکار)، خروجی را بازمی‌گرداند.

    Request Overview

    URL: /v2/core/application_interface/{id}
    Method: GET
    Controller: CoreController@showApplicationInterface
    Middleware: authWithJwt

    Access Control

    Path Parameters

    Field Type Description
    id integer (الزامی) شناسه منحصر به فرد (Primary Key) رکورد در جدول رابط‌های نرم‌افزاری.

    Logic Details

    منطق پردازش این متد به شرح زیر است:

    1. جستجو در دیتابیس: ابتدا با استفاده از DB::table('application_interface')->find($id) رکورد مورد نظر بازیابی می‌شود.
    2. بررسی نوع آبجکت (Object Expansion):
      • اگر object_type برابر با colleague باشد، سیستم شناسه موجود در فیلد object را برداشته و اطلاعات همکار (نام، نام خانوادگی، سریال، دفتر و وضعیت) را از مدل Colleague جستجو می‌کند.
      • نتیجه جایگزین مقدار عددی فیلد object می‌شود.
    3. استانداردسازی فرمت (Data Formatting):
      • فیلدهای object_type، service و type که به صورت رشته ساده هستند، به آرایه‌ای شامل id و title تبدیل می‌شوند تا ساختار پاسخ یکپارچه باشد (Front-end friendly).
    4. خروجی: داده نهایی درون آبجکت 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

    Start Request (GET {id})
    DB::find($id)
    Is object_type == 'colleague'?
    ↙ (Yes)
    Fetch Colleague Data
    & Replace Object
    ↘ (No)
    Keep Original Object ID
    Format Fields:
    Convert Strings to {id, title} Structure
    Return JSON Payload

    DELETE /v2/core/application_interface/{id}

    Q. Delete Application Interface (Core)

    این اندپوینت برای حذف یک رابط نرم‌افزاری از سیستم استفاده می‌شود. عملیات حذف به صورت مستقیم بر روی دیتابیس انجام شده و غیرقابل بازگشت است (Hard Delete).

    Request Overview

    URL: /v2/core/application_interface/{id}
    Method: DELETE
    Controller: CoreController@deleteApplicationInterface
    Middleware: authWithJwt

    Access Control

    Path Parameters

    Field Type Description
    id integer (الزامی) شناسه منحصر به فرد (Primary Key) رکورد که باید حذف شود.

    Logic Details

    منطق پردازش این متد بسیار ساده و مستقیم است:

    1. اجرای دستور حذف: متد به صورت مستقیم کوئری DELETE را بر روی جدول application_interface با شرط id اجرا می‌کند.
    2. نتیجه عملیات: خروجی تابع دیتابیس، تعداد ردیف‌های حذف شده است (معمولاً 1 در صورت وجود رکورد و 0 در صورت عدم وجود).
    3. پاسخ‌دهی: تعداد ردیف‌های حذف شده به عنوان payload بازگردانده می‌شود.

    Response Structure

    نمونه پاسخ موفق (JSON)

    در این نمونه، عدد 1 نشان‌دهنده حذف موفقیت‌آمیز یک رکورد است.

    {
        "payload": 1,
        "meta": {
            "timestamp": 1733751000
        }
    }

    Flowchart

    Start Request (DELETE {id})
    DB::table('application_interface')->delete($id)
    Return Deleted Count (int)

    POST /v2/core/application_interface

    Store Application Interface (Create New)

    این اندپوینت برای ایجاد یک رابط نرم‌افزاری جدید در سیستم استفاده می‌شود. نکته حائز اهمیت در این متد، نحوه ارسال پارامترهای دسته‌بندی است؛ فیلدهایی مانند نوع و سرویس باید به صورت آبجکت ارسال شوند تا سیستم بتواند عنوان (Title) آن‌ها را استخراج و ذخیره کند.

    Request Overview

    URL: /v2/core/application_interface
    Method: POST
    Controller: CoreController@storeApplicationInterface
    Middleware: authWithJwt

    Access Control

    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

    فرآیند ذخیره‌سازی به شرح زیر است:

    1. استخراج داده‌ها: مقادیر از آبجکت‌های ورودی استخراج می‌شوند:
      • type['title'] → ذخیره در ستون type
      • service['title'] → ذخیره در ستون service
      • object_type['title'] → ذخیره در ستون object_type
      • object['id'] → ذخیره در ستون object (اگر موجود نباشد Null).
    2. درج در دیتابیس: رکورد جدید با دستور insertGetId ایجاد شده و شناسه (ID) تولید شده دریافت می‌شود.
    3. بازیابی: بلافاصله پس از درج، رکورد کامل با استفاده از ID جدید از دیتابیس خوانده می‌شود (find($id)).
    4. خروجی: رکورد ذخیره شده به عنوان پاسخ بازگردانده می‌شود.

    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

    Start Request (POST Data)
    Extract Properties:
    type -> title
    service -> title
    object -> id
    DB::insertGetId(...)
    DB::find($new_id)
    Return New Record

    PUT /v2/core/application_interface/{id}

    Update Application Interface

    این اندپوینت برای ویرایش اطلاعات یک رابط نرم‌افزاری موجود استفاده می‌شود. با ارسال شناسه رکورد و داده‌های جدید، سیستم رکورد را بروزرسانی می‌کند. همانند متد ثبت، پارامترهای دسته‌بندی باید به صورت آبجکت ارسال شوند تا سیستم بتواند مقادیر مورد نیاز (Title یا ID) را از آن‌ها استخراج کند.

    Request Overview

    URL: /v2/core/application_interface/{id}
    Method: PUT
    Controller: CoreController@updateApplicationInterface
    Middleware: authWithJwt

    Access Control

    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

    فرآیند بروزرسانی به شرح زیر است:

    1. دریافت و استخراج داده‌ها: مقادیر ورودی پردازش می‌شوند:
      • برای فیلدهای type، service و object_type مقدار ['title'] برداشته می‌شود.
      • برای فیلد object مقدار ['id'] برداشته می‌شود (با استفاده از عملگر Null Coalescing).
    2. بروزرسانی دیتابیس: دستور UPDATE بر روی جدول application_interface برای رکوردی که id آن برابر با پارامتر مسیر است اجرا می‌شود.
    3. بازیابی مجدد: پس از اعمال تغییرات، رکورد بروزرسانی شده با 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

    Start Request (PUT {id})
    Extract Properties:
    type['title'], service['title']
    object['id'] ?? null
    DB::table(...)->where('id', $id)->update(...)
    DB::find($id)
    Return Updated Record

    GET /v2/core/application_interface_types

    List Application Interface Types

    این اندپوینت لیست ثابت و از پیش تعریف‌شده‌ای از انواع (Types) قابل قبول برای رابط‌های نرم‌افزاری را بازمی‌گرداند. این لیست معمولاً برای پر کردن Dropdown‌ها در فرم‌های ایجاد یا ویرایش (اندپوینت‌های R و S) استفاده می‌شود تا کاربر یکی از مقادیر مجاز را انتخاب کند.

    Request Overview

    URL: /v2/core/application_interface_types
    Method: GET
    Controller: CoreController@listApplicationInterfacTypes
    Middleware: authWithJwt

    Access Control

    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

    Start Request
    Load Static Data Array
    (recaptcha, sms, api, ...)
    Return JSON Response

    GET /v2/core/application_interface_services

    List Application Interface Services

    این اندپوینت لیست ثابت و از پیش تعریف‌شده‌ای از سرویس‌ها (Services) قابل انتخاب برای رابط‌های نرم‌افزاری را بازمی‌گرداند. این مقادیر مشخص می‌کنند که یک رابط خاص (مثلاً یک API Key) مربوط به کدام سرویس‌دهنده یا پلتفرم خارجی است.

    Request Overview

    URL: /v2/core/application_interface_services
    Method: GET
    Controller: CoreController@listApplicationInterfacServices
    Middleware: authWithJwt

    Access Control

    Logic Details

    این متد هیچ پردازش دیتابیسی انجام نمی‌دهد و یک آرایه استاتیک شامل لیست سرویس‌دهندگان پشتیبانی شده را برمی‌گرداند:

    ID / Title Description
    airplus وب‌سرویس خدمات پرواز ایرپلاس.
    google سرویس‌های گوگل (مانند 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

    Start Request
    Load Static Service List
    (airplus, google, nira, ...)
    Return JSON Response

    GET /v2/core/application_interface_object_types

    List Application Interface Object Types

    این اندپوینت لیست ثابت انواع موجودیت‌های (Object Types) قابل اتصال به رابط‌های نرم‌افزاری را بازمی‌گرداند. این فیلد تعیین می‌کند که شناسه موجود در فیلد object به کدام جدول دیتابیس (مثلاً جدول همکاران) اشاره دارد.

    Request Overview

    URL: /v2/core/application_interface_object_types
    Method: GET
    Controller: CoreController@listApplicationInterfacObjectType
    Middleware: authWithJwt

    Access Control

    Logic Details

    این متد در حال حاضر تنها یک مقدار ثابت را بازمی‌گرداند که نشان‌دهنده اتصال رابط به موجودیت «همکار» است:

    ID / Title Description
    colleague نشان می‌دهد که این رابط نرم‌افزاری متعلق به یک همکار (Colleague) خاص است و فیلد object باید حاوی شناسه آن همکار باشد.

    Response Structure

    نمونه پاسخ موفق (200 OK)

    {
        "items": [
            {
                "id": "colleague",
                "title": "colleague"
            }
        ],
        "meta": {
            "timestamp": 1733754000
        }
    }

    Flowchart

    Start Request
    Load Static Object Types
    (Currently only 'colleague')
    Return JSON Response

    PUT /v2/core/application_interface/status/{id}

    Update Application Interface Status

    این اندپوینت به منظور تغییر سریع وضعیت (Status) یک رابط نرم‌افزاری استفاده می‌شود. برخلاف متد ویرایش کلی، این متد تنها فیلد status را در دیتابیس بروزرسانی می‌کند.

    Request Overview

    URL: /v2/core/application_interface/status/{id}
    Method: PUT
    Controller: CoreController@updateStatusApplicationInterface
    Middleware: authWithJwt

    Access Control

    Parameters

    Parameter Type Location Description
    id Integer Path شناسه یکتای رکورد در جدول application_interface.
    status String Body وضعیت جدید برای اعمال (مثلاً active یا inactive).

    Logic Details

    عملیات به صورت مستقیم روی دیتابیس انجام می‌شود:

    1. ابتدا با استفاده از id دریافتی، کوئری آپدیت روی جدول application_interface اجرا شده و فیلد status با مقدار ورودی جایگزین می‌شود.
    2. بلافاصله پس از آپدیت، رکورد مورد نظر مجدداً از دیتابیس (با 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

    Start Request (ID, Status)
    Update 'status' in DB
    (Where ID = $id)
    Fetch Updated Record
    (DB::find($id))
    Return JSON (Payload)

    RESOURCE /v2/scrumboard/boards

    List Scrum Boards

    این اندپوینت لیست بوردهای اسکرام را بازیابی می‌کند. نتایج شامل بوردهایی است که کاربر جاری یا سازنده (Owner) آن‌هاست و یا به عنوان عضو (Member) به آن‌ها دعوت شده است.

    URL: /v2/scrumboard/boards
    Method: GET
    Controller: BoardController@index
    Middleware: authWithJwt

    Parameters

    Parameter Type Location Description
    branch Integer Query شناسه شعبه (الزامی برای فیلتر اولیه).
    status Integer Query (اختیاری) فیلتر بر اساس وضعیت فعال/غیرفعال.

    Logic Details

    سیستم پردازش‌های زیر را هنگام دریافت لیست انجام می‌دهد:

    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

    این اندپوینت یک بورد اسکرام جدید ایجاد می‌کند. نکته مهم در این متد، ایجاد خودکار لیست‌ها و لیبل‌های پیش‌فرض همزمان با ساخت بورد است تا بورد بلافاصله قابل استفاده باشد.

    URL: /v2/scrumboard/boards
    Method: POST
    Controller: BoardController@store
    Middleware: authWithJwt

    Parameters

    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

    دریافت اطلاعات کامل یک بورد خاص شامل اعضا، لیست‌ها و لیبل‌ها. دسترسی به داده‌های خروجی این متد محدود به سازنده یا اعضای آن بورد است.

    URL: /v2/scrumboard/boards/{id}
    Method: GET
    Controller: BoardController@show

    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 منطبق) اجازه ویرایش را دارد.

    URL: /v2/scrumboard/boards/{id}
    Method: PUT / PATCH
    Controller: BoardController@update

    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

    حذف کامل بورد از دیتابیس. این عملیات نیز مانند ویرایش، تنها توسط سازنده بورد قابل انجام است.

    URL: /v2/scrumboard/boards/{id}
    Method: DELETE
    Controller: BoardController@destroy

    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) اجرا می‌شود تا ساختار درختی کامل بورد را بسازد:

    Start Request (Index/Store/Show)
    Fetch List(s) Record from DB
    Loop: For Each List
    Fetch `scrumboard_cards`
    Has Cards?
    No
    Skip Hydration
    Return Empty Cards []
    ↓ (Yes)
    Start Nested Loop: For Each Card
    1. Decode Members
    Fetch Operators (Avatar, Name)
    2. Decode Labels
    Fetch Label Details
    3. Sprints
    Filter Sprints by JSON
    4. Comments
    Fetch & Hydrate Operator
    5. Checklists
    Decode Items & Hydrate Operator
    6. Media
    Fetch Attachments
    Merge All Data into Card Object
    Add Card to List Object
    Return Nested JSON Response


    Get Board Lists

    دریافت تمام لیست‌های (ستون‌های) مربوط به یک بورد خاص به همراه تمام کارت‌ها و جزئیات آن‌ها.

    URL: /v2/scrumboard/lists
    Method: GET
    Controller: ListController@index

    Parameters

    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 روی آن اجرا می‌شود.

    URL: /v2/scrumboard/lists
    Method: POST
    Controller: ListController@store

    Parameters

    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. این متد نیز تمامی کارت‌ها و وابستگی‌های آن‌ها را بارگذاری می‌کند.

    URL: /v2/scrumboard/lists/{id}
    Method: GET
    Controller: ListController@show

    Path Parameters

    Parameter Type Description
    id Integer شناسه لیست (List ID).

    Update List

    ویرایش اطلاعات یک لیست.

    URL: /v2/scrumboard/lists/{id}
    Method: PUT / PATCH
    Controller: ListController@update

    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

    حذف کامل یک لیست از دیتابیس.

    URL: /v2/scrumboard/lists/{id}
    Method: DELETE
    Controller: ListController@destroy

    Success Response

    {
        "payload": 1, // تعداد ردیف‌های حذف شده
        "meta": {
            "timestamp": 1733754000
        }
    }

    RESOURCE /v2/scrumboard/labels


    Label Logic Flow

    منطق پردازش ساده برای مدیریت لیبل‌ها (برچسب‌ها) که مستقیماً با دیتابیس در تعامل است:

    Start Request
    Request Type?
    GET (Index/Show)
    Fetch from `scrumboard_labels`
    Return Data or False
    POST/PUT
    Insert/Update DB
    Set Timestamps
    Fetch Updated Record
    Return Payload
    DELETE
    Delete by ID
    Return Count (0/1)


    Get Board Labels

    دریافت لیست تمامی لیبل‌های تعریف شده برای یک بورد خاص.

    URL: /v2/scrumboard/labels
    Method: GET
    Controller: LabelController@index

    Parameters

    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

    ایجاد یک لیبل جدید برای بورد.

    URL: /v2/scrumboard/labels
    Method: POST
    Controller: LabelController@store

    Parameters

    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

    دریافت جزئیات یک لیبل خاص.

    URL: /v2/scrumboard/labels/{id}
    Method: GET
    Controller: LabelController@show

    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

    ویرایش نام، وضعیت یا انتقال لیبل به بورد دیگر.

    URL: /v2/scrumboard/labels/{id}
    Method: PUT / PATCH
    Controller: LabelController@update

    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

    حذف لیبل از دیتابیس.

    URL: /v2/scrumboard/labels/{id}
    Method: DELETE
    Controller: LabelController@destroy

    Success Response

    {
        "payload": 1, // تعداد ردیف‌های حذف شده
        "meta": {
            "timestamp": 1733754000
        }
    }

    RESOURCE /v2/scrumboard/checklists


    Checklist Hydration Logic

    در تمامی متدها (لیست، ایجاد، نمایش، ویرایش)، داده‌های خام دیتابیس قبل از ارسال به کلاینت پردازش می‌شوند. فیلد `checkitems` از رشته JSON به آرایه تبدیل شده و شناسه `operator` به آبجکت کامل اپراتور تبدیل می‌گردد.

    Fetch Record(s) from DB
    Step 1: Decode Checkitems
    Input: JSON String (e.g., "[{...}]")
    Output: Array (or [] if null)
    Step 2: Hydrate Operator
    Input: Operator ID (Int)
    Action: Fetch from `operators` table
    Output: Object {id, name, avatar...}
    Return Hydrated JSON


    Get Card Checklists

    دریافت لیست تمامی چک‌لیست‌های متصل به یک کارت (Card) مشخص. آیتم‌های داخلی چک‌لیست و اطلاعات اپراتور مسئول به صورت خودکار بارگذاری می‌شوند.

    URL: /v2/scrumboard/checklists
    Method: GET
    Controller: ChecklistController@index

    Parameters

    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 تبدیل و ذخیره می‌شود.

    URL: /v2/scrumboard/checklists
    Method: POST
    Controller: ChecklistController@store

    Parameters

    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.

    URL: /v2/scrumboard/checklists/{id}
    Method: GET
    Controller: ChecklistController@show

    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`) استفاده می‌شود.

    URL: /v2/scrumboard/checklists/{id}
    Method: PUT / PATCH
    Controller: ChecklistController@update

    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

    حذف چک‌لیست از دیتابیس.

    URL: /v2/scrumboard/checklists/{id}
    Method: DELETE
    Controller: ChecklistController@destroy

    Success Response

    {
        "payload": 1, // تعداد ردیف‌های حذف شده
        "meta": {
            "timestamp": 1733754000
        }
    }

    RESOURCE /v2/scrumboard/comments


    Comment Operator Hydration

    در تمامی متدها (لیست، ثبت، نمایش و ویرایش)، سیستم پس از دریافت اطلاعات کامنت از جدول `scrumboard_comments`، به صورت دستی اطلاعات اپراتور (نویسنده کامنت) را از جدول `operators` استخراج کرده و به پاسخ اضافه می‌کند.

    Fetch Comment(s)
    Enrich with Operator
    Input: operator_id
    Query: SELECT id, first_name, last_name, avatar FROM operators WHERE id = ?
    Result: Attach object to `operator` field
    Return JSON Response


    Get Card Comments

    دریافت لیست نظرات ثبت شده برای یک کارت (Card). این متد امکان فیلتر کردن بر اساس اپراتور و وضعیت را نیز دارد.

    URL: /v2/scrumboard/comments
    Method: GET
    Controller: CommentController@index

    Parameters

    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()`) ذخیره می‌شود.

    URL: /v2/scrumboard/comments
    Method: POST
    Controller: CommentController@store

    Parameters

    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

    مشاهده جزئیات یک نظر خاص با استفاده از شناسه آن.

    URL: /v2/scrumboard/comments/{id}
    Method: GET
    Controller: CommentController@show

    Path Parameters

    Parameter Type Description
    id Integer شناسه نظر.

    Response

    در صورت عدم یافتن رکورد، مقدار payload: false بازگردانده می‌شود.

    {
        "payload": {
            "id": 10,
            "message": "Specific comment details...",
            "operator": { ... },
            ...
        },
        "meta": { "timestamp": 1733754000 }
    }

    Update Comment

    ویرایش متن، وضعیت و یا حتی انتقال نظر به کارت/اپراتور دیگر.

    URL: /v2/scrumboard/comments/{id}
    Method: PUT / PATCH
    Controller: CommentController@update

    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

    حذف دائمی یک نظر از دیتابیس.

    URL: /v2/scrumboard/comments/{id}
    Method: DELETE
    Controller: CommentController@destroy

    Success Response

    {
        "payload": 1, // تعداد ردیف‌های حذف شده
        "meta": {
            "timestamp": 1733754000
        }
    }

    RESOURCE /v2/scrumboard/cards


    Card Deep Hydration Logic

    کارت‌ها قلب سیستم اسکرام هستند. در تمامی متدها (Index, Store, Show, Update)، پس از دریافت اطلاعات خام کارت، یک فرآیند سنگین برای بارگذاری تمام وابستگی‌ها اجرا می‌شود. این فرآیند شامل دیکد کردن JSONها و کوئری‌های متعدد به جداول مختلف است.

    Fetch Card(s) Data
    Step 1: JSON Fields
    Decode `members` & `labels`
    Fetch Operators & Label Objects via IDs
    Step 2: Sub-Entities
    1. Get Comments (+ Hydrate Operators)
    2. Get Checklists (+ Hydrate Operators + Decode Items)
    3. Get Attachments (Media table)
    4. Get Sprints (JSON Search)
    Return Fully Hydrated Card


    Get Cards List

    دریافت لیست کارت‌های موجود در یک لیست (ستون) خاص. تمامی اطلاعات وابسته (اعضا، لیبل‌ها، پیوست‌ها و...) به همراه کارت برگردانده می‌شوند.

    URL: /v2/scrumboard/cards
    Method: GET
    Controller: CardController@index

    Parameters

    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

    ایجاد یک کارت جدید ساده. پس از ایجاد، کارت بلافاصله بازیابی شده و ساختار کامل (با آرایه‌های خالی برای وابستگی‌ها) برگردانده می‌شود.

    URL: /v2/scrumboard/cards
    Method: POST
    Controller: CardController@store

    Parameters

    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

    دریافت جزئیات کامل یک کارت با شناسه مشخص.

    URL: /v2/scrumboard/cards/{id}
    Method: GET
    Controller: CardController@show

    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 را نیز دارد. شما می‌توانید همزمان با ویرایش کارت، یک کامنت یا چک‌لیست را ایجاد، ویرایش یا حذف کنید.

    URL: /v2/scrumboard/cards/{id}
    Method: PUT / PATCH
    Controller: CardController@update

    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 حذف نمی‌شوند مگر اینکه در سطح دیتابیس تنظیم شده باشد.

    URL: /v2/scrumboard/cards/{id}
    Method: DELETE
    Controller: CardController@destroy

    Success Response

    {
        "payload": 1, // 1: موفق، 0: ناموفق
        "meta": { "timestamp": 1733754000 }
    }

    RESOURCE /v2/scrumboard/sprints


    Sprint Hydration Logic

    اسپرینت‌ها ظروف نگهداری کارت‌ها در بازه‌های زمانی مشخص هستند. منطق Hydration در اینجا بسیار سنگین است زیرا با دریافت اسپرینت، سیستم به صورت خودکار تمام کارت‌های داخل آن را واکشی کرده و فرآیند "Card Deep Hydration" را برای تک تک آن‌ها اجرا می‌کند.

    Fetch Sprint Data
    Step 1: Reports
    Fetch `scrumboard_sprint_reports`
    + Hydrate Operator for each report
    Step 2: Cards Hydration (Heavy)
    Decode `cards` JSON (Array of IDs)
    Loop through Cards & Apply Card Deep Hydration:
    (Members, Labels, Comments, Checklists, Attachments)
    Return Sprint with Full Nested Data


    Get Sprints List

    دریافت لیست اسپرینت‌های یک بورد. هر آیتم در لیست شامل آرایه‌ای کامل از کارت‌های داخل آن اسپرینت است.

    URL: /v2/scrumboard/sprints
    Method: GET
    Controller: SprintController@index

    Parameters

    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

    ایجاد یک اسپرینت جدید. می‌توان در لحظه ایجاد، لیست کارت‌های مرتبط را نیز تعیین کرد.

    URL: /v2/scrumboard/sprints
    Method: POST
    Controller: SprintController@store

    Body 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

    مشاهده جزئیات یک اسپرینت خاص به همراه تمامی وابستگی‌ها (کارت‌ها و گزارش‌ها).

    URL: /v2/scrumboard/sprints/{id}
    Method: GET
    Controller: SprintController@show

    Update Sprint

    به‌روزرسانی اطلاعات اسپرینت. مهم: اگر پارامتر cards ارسال شود، لیست کارت‌های اسپرینت با لیست جدید جایگزین می‌شود (Sync).

    URL: /v2/scrumboard/sprints/{id}
    Method: PUT / PATCH
    Controller: SprintController@update

    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

    حذف اسپرینت از دیتابیس. این عملیات کارت‌ها را حذف نمی‌کند، بلکه فقط ارتباط اسپرینت را از بین می‌برد (مگر اینکه در دیتابیس تنظیمات دیگری اعمال شده باشد).

    URL: /v2/scrumboard/sprints/{id}
    Method: DELETE
    Controller: SprintController@destroy

    Response

    {
        "payload": 1, // تعداد رکوردهای حذف شده
        "meta": { "timestamp": 1733754000 }
    }

    RESOURCE /v2/scrumboard/sprint_reports


    Sprint Report Hydration Logic

    گزارش‌های اسپرینت، زیرمجموعه‌ای از اسپرینت‌ها هستند و منطق Hydration ساده‌ای دارند. در تمام متدها، پس از دریافت داده‌های گزارش، سیستم فقط اطلاعات اپراتور (نویسنده) را از جدول `operators` بارگذاری می‌کند.

    Fetch Sprint Report(s)
    Hydrate Operator
    If `operator` field exists, fetch the full operator object from the `operators` table.
    Return Report with Operator Details


    Get Sprint Reports List

    دریافت لیست گزارش‌های ثبت شده برای یک اسپرینت مشخص.

    URL: /v2/scrumboard/sprint_reports
    Method: GET
    Controller: SprintReportController@index

    Parameters

    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`) استخراج و ثبت می‌شود.

    URL: /v2/scrumboard/sprint_reports
    Method: POST
    Controller: SprintReportController@store

    Body 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

    مشاهده جزئیات یک گزارش خاص با شناسه آن.

    URL: /v2/scrumboard/sprint_reports/{id}
    Method: GET
    Controller: SprintReportController@show

    Response

    در صورت پیدا نشدن گزارش، payload: false برمی‌گردد.

    {
        "payload": {
            "id": 1,
            "title": "Daily Stand-up - Day 3",
            "operator": { ... },
            ...
        },
        "meta": { "timestamp": 1733754000 }
    }

    Update Sprint Report

    به‌روزرسانی اطلاعات یک گزارش. توجه داشته باشید که نویسنده (operator) قابل تغییر نیست.

    URL: /v2/scrumboard/sprint_reports/{id}
    Method: PUT / PATCH
    Controller: SprintReportController@update

    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

    حذف یک گزارش از سیستم.

    URL: /v2/scrumboard/sprint_reports/{id}
    Method: DELETE
    Controller: SprintReportController@destroy

    Response

    {
        "payload": 1, // 1 برای موفقیت، 0 برای عدم موفقیت
        "meta": { "timestamp": 1733754000 }
    }

    RESOURCE /v2/cartable/letters


    Letter & Workflow Logic

    ماژول کارتابل دارای یک منطق گردش کار (Workflow) است. زمانی که یک نامه ایجاد می‌شود، سیستم به صورت خودکار اولین مرحله (Step) آن فرآیند را ایجاد می‌کند. همچنین زمانی که وضعیت یک مرحله به "انجام شده" تغییر می‌کند، سیستم به صورت هوشمند مرحله بعدی را تشخیص داده و ایجاد می‌کند.

    Create Letter Request
    Step 1: Initialization
    Generate Serial ID
    Insert Letter Record
    Step 2: Workflow Trigger
    Fetch `cartable_subjects` (Process Config)
    Decode `steps` JSON
    Insert First Step into `cartable_letter_steps`
    Return Letter Resource


    Get Letters List

    دریافت لیست نامه‌های موجود در کارتابل. این متد از یک سیستم صفحه‌بندی (Pagination) خاص استفاده می‌کند که پارامترهای `length` و `start` را به شماره صفحه لاراول تبدیل می‌کند.

    URL: /v2/cartable/letters
    Method: GET
    Controller: LetterController@index

    Query 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` برای این نامه ثبت می‌کند.

    URL: /v2/cartable/letters
    Method: POST
    Controller: LetterController@store

    Body 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 برمی‌گرداند.

    URL: /v2/cartable/letters/{id}
    Method: GET
    Controller: LetterController@show

    Update Letter

    ویرایش اطلاعات عمومی نامه (عنوان، گیرنده، توضیحات و...). این متد مراحل گردش کار را تغییر نمی‌دهد.

    URL: /v2/cartable/letters/{id}
    Method: PUT / PATCH
    Controller: LetterController@update

    Body Parameters

    Parameter Type Description
    title String عنوان نامه.
    subject Integer تغییر موضوع نامه.
    operators Array[Int] لیست اپراتورها.
    description String توضیحات.
    recipient String گیرنده.

    Delete Letter

    حذف نرم (Soft Delete) نامه. فیلد deleted_at با زمان فعلی مقداردهی می‌شود.

    URL: /v2/cartable/letters/{id}
    Method: DELETE
    Controller: LetterController@destroy

    Update Step & Advance Workflow

    توجه: این متد در کنترلر وجود دارد اما روت آن در اسنیپت ارائه شده نیست (احتمالاً روت سفارشی دارد).
    این متد برای تغییر وضعیت یک مرحله (Step) استفاده می‌شود.
    منطق هوشمند: اگر وضعیت ارسالی برابر با 3 (انجام شده) باشد، سیستم به صورت خودکار مرحله بعدی فرآیند را از تنظیمات موضوع (`subject steps`) پیدا کرده و آن را ایجاد می‌کند.

    Method URL: (Custom Route) /v2/cartable/steps/{id}
    Target ID: شناسه مرحله (cartable_letter_steps.id)
    Controller: LetterController@updateLetterStep

    Body Parameters

    Parameter Type Description
    status Integer (الزامی) وضعیت جدید مرحله (مثلاً 3 برای اتمام).
    description String (اختیاری) توضیحات یا دستور انجام کار روی مرحله.
    Update Step Request
    Update `status` & `description`
    Status == 3 ?
    YES →
    Find Current Step Index
    Check for Next Step in Config
    Insert Next Step

    RESOURCE /v2/cartable/subjects


    Subject & Workflow Configuration Logic

    این کنترلر وظیفه مدیریت "موضوعات نامه" را بر عهده دارد. اهمیت اصلی این بخش در فیلد steps است.
    فیلد steps یک آرایه JSON است که "نقشه راه" یا "گردش کار" (Workflow) یک نامه را تعریف می‌کند. وقتی نامه‌ای با موضوع خاصی ایجاد می‌شود، سیستم از این تنظیمات برای تولید مراحل (`cartable_letter_steps`) استفاده می‌کند.

    Admin Define Subject
    Define Steps (JSON)
    Example: [Step1: Secretary, Step2: Manager, Step3: Archive]
    Saved in `cartable_subjects`

    Used by LetterController


    List Subjects

    دریافت لیست تمام موضوعات تعریف شده. برخلاف نامه‌ها، در اینجا فعلاً صفحه‌بندی (Pagination) وجود ندارد و همه رکوردها بر اساس فیلترها بازگردانده می‌شوند.

    URL: /v2/cartable/subjects
    Method: GET
    Controller: SubjectController@index

    Query 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` شده در دیتابیس ذخیره می‌شود.

    URL: /v2/cartable/subjects
    Method: POST
    Controller: SubjectController@store

    Body 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

    مشاهده جزئیات یک موضوع.

    URL: /v2/cartable/subjects/{id}
    Method: GET
    Controller: SubjectController@show

    Update Subject

    ویرایش تنظیمات موضوع.
    نکته: طبق کد، فیلد `branch` در متد Update قابل ویرایش نیست (فقط department, title, steps).

    URL: /v2/cartable/subjects/{id}
    Method: PUT / PATCH
    Controller: SubjectController@update

    Body Parameters

    Parameter Type Description
    department Integer دپارتمان.
    title String عنوان موضوع.
    steps Array بازتعریف مراحل گردش کار (این تغییر روی نامه‌های قدیمی تاثیر نمی‌گذارد، فقط نامه‌های جدید).

    Delete Subject

    حذف فیزیکی (Hard Delete) یک موضوع از دیتابیس.
    هشدار: این عملیات Soft Delete نیست و رکورد کاملاً پاک می‌شود.

    URL: /v2/cartable/subjects/{id}
    Method: DELETE
    Controller: SubjectController@destroy

    RESOURCE /v2/cartable/subject_steps


    Subject Steps Management Logic

    این کنترلر وظیفه مدیریت مراحل پیش‌فرض (Step Templates) را بر عهده دارد.
    این جدول (`cartable_subject_steps`) به عنوان یک بانک اطلاعاتی از مراحل عمل می‌کند که شامل یک عنوان (`title`) و یک اپراتور مسئول (`operator`) است. این مراحل مستقل هستند اما می‌توانند هنگام تعریف گردش کار (Workflow) در بخش Subjectها مورد استفاده قرار گیرند.

    Admin Define Step Template
    Assign Operator & Title
    (e.g. Operator: 105, Title: "Financial Review")
    Saved in `cartable_subject_steps`

    Selectable in Subject Workflow Config


    List Step Definitions

    دریافت لیست مراحل تعریف شده. این لیست بر اساس تاریخ ایجاد (نزولی) مرتب شده است.

    URL: /v2/cartable/subject_steps
    Method: GET
    Controller: SubjectStepsController@index

    Query 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).

    URL: /v2/cartable/subject_steps
    Method: POST
    Controller: SubjectStepsController@store

    Body 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

    مشاهده جزئیات یک مرحله خاص.

    URL: /v2/cartable/subject_steps/{id}
    Method: GET
    Controller: SubjectStepsController@show

    Response Structure

    {
        "payload": {
            "id": 15,
            "operator": 105,
            "title": "تایید نهایی",
            "created_at": "...",
            "updated_at": "..."
        },
        "meta": { "timestamp": 1733760000 }
    }

    Update Step Definition

    ویرایش اطلاعات اپراتور یا عنوان مرحله.

    URL: /v2/cartable/subject_steps/{id}
    Method: PUT / PATCH
    Controller: SubjectStepsController@update

    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ها استفاده شده باشد، ممکن است باعث ناهماهنگی در تنظیمات شود (بسته به منطق فرانت‌اند). در اینجا حذف کامل انجام می‌شود.

    URL: /v2/cartable/subject_steps/{id}
    Method: DELETE
    Controller: SubjectStepsController@destroy

    Success Response

    {
        "payload": 1, // تعداد رکوردهای حذف شده
        "meta": { "timestamp": 1733760000 }
    }

    RESOURCE /v2/cartable/recipients


    Department Recipients Management Logic

    این کنترلر وظیفه مدیریت لیست "گیرندگان" (`recipients`) را در سطح دپارتمان‌ها بر عهده دارد.
    این جدول (`cartable_department_recipients`) عناوین شغلی یا واحدهایی را نگهداری می‌کند که در یک شعبه و دپارتمان خاص مجاز به دریافت نامه هستند (مانند: "دبیرخانه"، "مدیریت مالی" و ...).

    Define Recipient Title
    Link to Branch & Department
    (e.g. Branch: 100, Dept: 5, Title: "Secretariat")
    Saved in `cartable_department_recipients`


    List Recipients

    دریافت لیست تمام گیرندگان تعریف شده. این لیست بر اساس تاریخ ایجاد (نزولی) مرتب شده است.

    URL: /v2/cartable/recipients
    Method: GET
    Controller: RecipientController@index

    Query 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

    تعریف یک گیرنده جدید برای یک شعبه و دپارتمان خاص.

    URL: /v2/cartable/recipients
    Method: POST
    Controller: RecipientController@store

    Body 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

    مشاهده جزئیات یک گیرنده خاص.

    URL: /v2/cartable/recipients/{id}
    Method: GET
    Controller: RecipientController@show

    Response Structure

    {
        "payload": {
            "id": 12,
            "department": 5,
            "branch": 101,
            "title": "حسابداری",
            "created_at": "...",
            "updated_at": "..."
        },
        "meta": { "timestamp": 1733762000 }
    }

    Update Recipient

    ویرایش اطلاعات گیرنده.
    نکته مهم: طبق منطق کد، فیلد `branch` در متد Update قابل تغییر نیست و فقط `department` و `title` بروزرسانی می‌شوند.

    URL: /v2/cartable/recipients/{id}
    Method: PUT / PATCH
    Controller: RecipientController@update

    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) یک گیرنده از سیستم.

    URL: /v2/cartable/recipients/{id}
    Method: DELETE
    Controller: RecipientController@destroy

    Success Response

    {
        "payload": 1, // تعداد رکوردهای حذف شده
        "meta": { "timestamp": 1733762000 }
    }

    RESOURCE /v2/cartable/operator_role


    Operator Role & Signature Logic

    این کنترلر برای انتساب یک "عنوان" (`title`) به یک "اپراتور" (`operator`) و مهم‌تر از آن، ذخیره **تصویر امضای دیجیتال** (`signature_file`) استفاده می‌شود.
    فایل‌های آپلود شده در فضای ذخیره‌سازی ابری (دیسک `liara`) با ساختار پوشه‌بندی بر اساس شناسه شعبه (`branch`) و تاریخ ذخیره می‌شوند.

    Select Operator
    Define Title & Upload Signature
    (Image validation: max 2MB, jpeg/png...)
    File Uploaded to Cloud
    Record Saved in DB


    List Operator Roles

    دریافت لیست نقش‌ها و امضاهای تعریف شده.

    URL: /v2/cartable/operator_role
    Method: GET
    Controller: OperatorRoleController@index

    Query 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 ارسال شود.

    URL: /v2/cartable/operator_role
    Method: POST
    Controller: OperatorRoleController@store

    Body 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

    مشاهده جزئیات یک نقش خاص.

    URL: /v2/cartable/operator_role/{id}
    Method: GET
    Controller: OperatorRoleController@show

    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 استفاده کنید.

    URL: /v2/cartable/operator_role/{id}
    Method: PUT / PATCH
    Controller: OperatorRoleController@update

    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) پاک می‌کند.

    URL: /v2/cartable/operator_role/{id}
    Method: DELETE
    Controller: OperatorRoleController@destroy

    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

    تغییر وضعیت یک مرحله، ثبت توضیحات و (در صورت اتمام) ارجاع خودکار به مرحله بعد.

    URL: /v2/cartable/letters/step/{id}
    Method: PUT
    Controller: LetterController@updateLetterStep

    URL Parameters

    Parameter Type Description
    id Integer (الزامی) شناسه رکورد مرحله در جدول cartable_letter_steps.

    Body Parameters

    Parameter Type Description
    status Integer (الزامی) وضعیت جدید.
    3 = انجام شده (Done) -> محرک مرحله بعدی.
    سایر اعداد = صرفاً بروزرسانی وضعیت.
    description String (اختیاری) توضیحات یا هامش روی این مرحله.

    Logic Details (Backend)

    Success Response

    {
        "payload": 1, // نشان دهنده موفقیت آمیز بودن عملیات (تعداد ردیف تغییر کرده)
        "meta": {
            "timestamp": 1733765000
        }
    }

    Visual Logic Flow

    Update Step Status
    Check: Is Status == 3?
    ↓ (Yes)
    Fetch Workflow Config
    (Find current step index in Subject's JSON)
    Next Step Exists?
    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`) از دیتابیس خوانده شده و در متن گزارش استفاده می‌شود تا تحلیل شخصی‌سازی شده باشد.

    URL: /v2/ai/chat/completions
    Method: POST
    Controller: MainController@getReport

    Body 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 می‌دهد:

    Success Response Example

    {
        // خروجی بسته به کلاینت AI متفاوت است اما محتوا HTML است
        "content": "

    گزارش عملکرد آژانس تعطیلات رویایی

    
    

    بر اساس داده‌های دریافتی...

    ..."
    }

    Visual Logic Flow

    Receive Financial JSON
    Fetch Branch Info
    (Get 'title_fa' from DB)
    Build Contextual Prompt
    (Inject Branch Name + JSON Data + HTML formatting rules)
    Send to LLM (Gemini/GPT)
    Return HTML Report

    POST /v2/trade/reference/type


    Global Reference Search

    این متد به عنوان یک جستجوگر چندمنظوره (Poly-morphic Search) عمل می‌کند. فرانت‌اند با ارسال پارامتر goal مشخص می‌کند که به دنبال چه نوع داده‌ای است (مثلاً مسافر، حساب بانکی، یا ایرلاین) و سیستم با جستجو در جداول مربوطه، لیست استاندارد شده‌ای شامل id و text برمی‌گرداند.




    Search Entities (Autocomplete)

    جستجو و واکشی اطلاعات برای ورودی‌های Select و Autocomplete در سراسر سیستم.

    URL: /v2/trade/reference/type
    Method: POST
    Controller: BaseController@tradeReferenceType
    Middleware: authWithJwt

    Request 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.

    URL: /v2/base/reference
    Method: POST
    Controller: BaseController@tradeReferenceType
    Middleware: authWithJwt

    Request 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

    Request: Goal + Query
    Switch Case (Goal)
    Select Table (User, Account, Office...)
    Apply Filters
    (Branch check, Search 'q', Extra params)
    Format Output
    { id: ..., text: ... }

    POST /v2/config


    System Configuration & Tenant Init

    این متد حیاتی‌ترین اندپوینت برای راه‌اندازی اولیه اپلیکیشن (Bootstrapping) است.
    سیستم بر اساس هدر Domain تشخیص می‌دهد که کاربر مربوط به کدام شعبه (Office) است و تنظیمات ظاهری، مالی، حقوقی و ماژول‌های فعال آن شعبه را برمی‌گرداند.




    Get Office Configuration

    URL: /v2/config
    Method: POST
    Controller: V2BaseController@config
    Auth: Public (No Auth Middleware Required usually, relies on Domain Header)

    Headers (Required)

    Header Name Description
    Domain آدرس دامنه‌ای که اپلیکیشن روی آن اجرا می‌شود (مثلاً crm.example.com).
    سیستم www. و پورت (:8080) را به صورت خودکار حذف می‌کند تا دامنه پایه را پیدا کند.

    Response Structure (JSON)

    خروجی شامل چندین بخش اصلی است:

    Key Block Description
    office_id شناسه شعبه + 1000 (جهت Obfuscation).
    title / brand عنوان‌ها و نام برند به فارسی و انگلیسی.
    communicational اطلاعات تماس (تلفن، موبایل، ایمیل، آدرس، کد پستی و لوکیشن).
    design تنظیمات ظاهری شامل:
    • logo/paper_logo/favicon: لینک تصاویر.
    • base_color/theme/style: تنظیمات CSS و رنگ‌بندی.
    • login_text: متن نمایش داده شده در صفحه ورود.
    support_online کدهای مربوط به ابزارهای چت آنلاین (مثل Raychat یا Crisp).
    details.financial_debt Boolean
    آیا شعبه بدهی پرداخت نشده بابت پشتیبانی دارد؟ (بررسی جدول airplus_bills).
    details.official قوانین هاردکد شده سیستم برای مرخصی‌ها (Calendar Limit):
    • leave: قوانین مرخصی روزانه/ساعتی.
    • license: قوانین مجوزها.
    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

    Request Header: Domain
    Clean Domain
    Remove 'www.' and ':port'
    DB Query (offices)
    WHERE json_contains(domain, $baseDomain)
    Found?
    ↙ No
    Yes ↘
    Return 400
    "The office is not defined"
    Fetch Details
    1. VAT Info
    2. Office Configs
    3. Check Expired Bills
    Return Complete JSON

    GET /v2/config


    Initial System Configuration

    این متد نقطه شروع (Entry Point) اپلیکیشن است. فرانت‌اند قبل از هر کاری باید این اندپوینت را صدا بزند.
    سیستم با بررسی هدر Domain، شعبه (Office) مورد نظر را پیدا کرده و تمام تنظیمات حیاتی شامل رنگ‌بندی، لوگو، اطلاعات تماس، قوانین مالی و وضعیت بدهی را برمی‌گرداند.




    Get Tenant Config

    URL: /v2/config
    Method: GET
    Controller: V2BaseController@config
    Auth: Public (نیاز به توکن ندارد)

    Headers (الزامی)

    Header Name Description
    Domain آدرس دامنه‌ای که کاربر با آن سایت را باز کرده است.
    مثال: crm.travel-agency.com یا localhost:3000
    سیستم به صورت خودکار www. و پورت را حذف می‌کند.

    Response Structure

    Field Block Description
    office_id شناسه واقعی شعبه + 1000 (جهت امنیت و عدم نمایش ID اصلی).
    design تنظیمات UI/UX:
    • theme/style/base_color: برای تنظیم متغیرهای CSS.
    • logo/favicon: لینک فایل‌ها.
    • login_text: متن خوش‌آمدگویی صفحه لاگین.
    details.financial_debt مهم: (Boolean)
    اگر true باشد، یعنی شعبه فاکتور پرداخت نشده "پشتیبانی" (Support Bill) دارد و دسترسی باید محدود شود.
    details.official قوانین هاردکد شده (Hardcoded) مربوط به سیستم حضور و غیاب:
    • leave: قوانین کسر اضافه کار و جایگزین برای مرخصی‌ها.
    • license: قوانین مربوط به مرخصی ساعتی/مجوزها.
    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

    Request (Header: Domain)
    Domain Parsing
    Remove 'www.', Remove Port
    Extract Base Domain
    Exists in DB?
    (table: offices)
    No ↙
    400 Bad Request
    "The office is not defined"
    Yes ↘
    Load Data
    1. Configs (office_config)
    2. VAT (accounting_titles)
    3. Debt Check (airplus_bills)
    4. Redis Cache Update
    Return JSON Config

    GET /b2c/v1/config


    B2C Frontend Configuration

    این اندپوینت مخصوص وب‌سایت‌های فروش آنلاین (White-label B2C) است.
    سیستم با بررسی هدر Domain، آدرس سایت مشتری را در ستون b2c_domains جستجو می‌کند و تنظیمات ظاهری، بنرها، مجوزهای نماد اعتماد و پیکربندی ماژول‌های فروش را برمی‌گرداند.




    Get B2C Config

    URL: /b2c/v1/config
    Method: GET
    Controller: V1BaseController@config
    Auth: Public (Public Access)

    Headers (الزامی)

    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):
    • theme/palette: چیدمان و رنگ‌بندی قالب سایت.
    • modules: ماژول‌های فعال (پرواز، هتل، بیمه و ...).
    • features: ویژگی‌های خاص فعال شده برای این مشتری.
    • authentication: تنظیمات نحوه لاگین/ثبت‌نام کاربران (OTP، ایمیل و ...).

    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

    Request Header: Domain
    Parse Domain
    Normalize URL (Remove www)
    Search DB
    WHERE json_contains(b2c_domains, $domain)
    ↓ (Found)
    Fetch Aggregated Data
    1. advertisement (Active banners)
    2. certificates (Trust logos)
    3. hub_config (Site settings)
    Return B2C Config JSON

    POST /b2c/v1/auth/otp


    B2C Customer Authentication (OTP)

    این اندپوینت وظیفه مدیریت ورود و ثبت‌نام کاربران در وب‌سایت‌های فروش (B2C) را بر عهده دارد.
    سیستم از مکانیزم رمز یکبار مصرف (OTP) استفاده می‌کند. نکته کلیدی این است که اگر شماره موبایل در سیستم وجود نداشته باشد، کاربر به صورت خودکار (Auto-Register) ثبت‌نام شده و شناسه مسافر (`passenger_id`) ایجاد می‌گردد.




    Request OTP / Login

    URL: /b2c/v1/auth/otp
    Method: POST
    Controller: V1UserController@authOtp
    Auth: Public (Guest Access)

    Body 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

    Request (Mobile + Branch)
    Normalize Mobile
    Ensure starts with '0'
    Convert Persian nums to English
    Exist in `customers`?
    ↓ No
    Auto Register
    INSERT INTO customers
    Branch: [$branch]
    ↓ Yes
    Select ID
    Generate OTP
    StaticController::generateOtp
    Send SMS
    StaticController::sendNotification
    Log to SnailJob Queue
    Return JSON (Passenger ID)

    POST /b2c/v1/auth/submit


    B2C Verify OTP & Login

    این اندپوینت مرحله نهایی احراز هویت است.
    کلاینت کد دریافتی (OTP) را به همراه شناسه مسافر ارسال می‌کند. سیستم کد را در جدول otp_requests اعتبارسنجی کرده (بررسی انقضا و عدم استفاده قبلی) و در صورت صحت، وضعیت کد را به "استفاده شده" تغییر می‌دهد و یک JWT Token با اعتبار ۷ روز برای کاربر صادر می‌کند.




    Submit OTP

    URL: /b2c/v1/auth/submit
    Method: POST
    Controller: V1UserController@authSubmit
    Auth: Public (Guest Access)

    Headers (الزامی)

    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

    Request (ID + OTP)
    Validate OTP
    Check table `otp_requests`
    Match ID & Code AND
    Not Expired AND Not Used
    ↓ Invalid
    Error 1209
    Return 422 JSON
    ↓ Valid
    Update OTP
    Set is_used = 1
    Fetch User Info
    SELECT from `customers`
    Detect User Agent (Device)
    Generate JWT
    Claims: uuid, branch, ip, domain
    Exp: +7 Days
    Return Token + User Data

    POST /b2c/v1/auth/basic


    B2C Colleague Authentication (Basic)

    این اندپوینت جهت ورود همکاران و آژانس‌های طرف قرارداد (Colleagues) طراحی شده است.
    سیستم با دریافت نام کاربری و رمز عبور، اعتبار حساب را در جدول colleague_auth بررسی کرده و در صورت فعال بودن حساب و عدم انقضای قرارداد، یک توکن JWT صادر می‌کند. همچنین جزئیات سقف اعتبار و اطلاعات تماس همکار نیز بازگردانده می‌شود.




    Colleague Login

    URL: /b2c/v1/auth/basic
    Method: POST
    Controller: V1UserController@authBasic
    Auth: Public (Guest Access)

    Headers (الزامی)

    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 نقش همکار:
    • pledger (اگر type=1 باشد - متعهد)
    • partner (سایر موارد - همکار)
    user.ceiling اطلاعات سقف اعتباری:
    • amount: مبلغ اعتبار
    • deadline: مهلت تسویه (ماهانه)
    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

    Request (User + Pass + Branch)
    Query DB (`colleague_auth`)
    Match Username & Branch
    Check expired_at > Now (or Null)
    Record Found?
    ↓ Yes
    Verify Password
    Hash::check(input, db_hash)
    ↓ No
    Error: Invalid Info
    Status == 1?
    ↓ Yes (Active)
    Logging
    Dispatch `SystemLog` to snailJob queue (10min delay)
    ↓ No (Inactive)
    Error: Inactive Status
    Generate JWT
    Type: colleague
    Claims: uuid, brn, uip, brw
    Return User Data + Token

    POST /b2c/v1/get_country


    Get Nationalities List

    این اندپوینت لیست کشورهای فعال که دارای عنوان «ملیت» (Nationality) تعریف شده هستند را باز می‌گرداند.
    از این داده‌ها معمولاً در فرم‌های ورود اطلاعات مسافر جهت انتخاب ملیت/تابعیت (مثلاً: ایرانی، آلمانی و...) استفاده می‌شود. لیست خروجی تنها شامل رکوردهایی است که وضعیت آن‌ها فعال (`status = 1`) باشد.




    Fetch Country/Nationality Data

    URL: /b2c/v1/get_country
    Method: POST
    Controller: V1UserController@getCitizen
    Auth: Public (Guest Access)

    Parameters

    این درخواست نیاز به پارامتر ورودی (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

    Request Received
    Query Database (Countries)
    WHERE status = 1
    AND fa_nationality IS NOT NULL
    Select Columns
    id, iso, fa_nationality, en_nationality
    Return JSON Response

    GET /b2c/v1/user/passengers


    Get User Passengers List

    این اندپوینت لیست تمامی مسافران مرتبط با حساب کاربری فعلی را باز می‌گرداند.
    سیستم لیست مسافران را از دو منبع استخراج می‌کند:

    1. سابقه خرید (History): افرادی که کاربر لاگین شده قبلاً در فاکتورهای خود (`factors`) برای آن‌ها بلیط (`factor_items`) تهیه کرده است (به جز خود کاربر).
    2. زیرمجموعه‌ها (Relationship): افرادی که در پروفایل کاربر به عنوان همراه یا خانواده تعریف شده‌اند (`relationship = user_id`).

     




    Fetch Passengers

    URL: /b2c/v1/user/passengers
    Method: GET
    Controller: V1UserController@passengersList
    Auth: Required (Bearer Token)

    Headers (الزامی)

    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

    Request (GET)
    Identify User
    Get operator->id from JWT
    Query 1: Purchase History
    Find `customer_id` in `factor_items` where:
    1. Invoice owner is User (`factors.customer == id`)
    2. Passenger is NOT User (`customer_id != id`)
    Query 2: Fetch Customers
    SELECT * FROM customers WHERE:
    Condition A: relationship == user_id
    OR
    Condition B: id IN [History IDs]
    Return List

    GET /b2c/v1/office/users


    Get Office/Company Users

    این اندپوینت جهت دریافت لیست تمامی کاربران (Users) تعریف شده در زیرمجموعه یک شرکت یا آژانس (Colleague) استفاده می‌شود.
    نکته مهم: دسترسی به این متد محدود است و تنها کاربرانی که سطح دسترسی آن‌ها مدیریت (Management) باشد مجاز به فراخوانی آن هستند. در غیر این صورت خطای 406 بازگردانده می‌شود.




    Fetch Sub-Users

    URL: /b2c/v1/office/users
    Method: GET
    Controller: V1UserController@usersOffice
    Auth: Required (Bearer Token - Management Only)

    Headers (الزامی)

    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

    Request Received
    User Category == 'management'?
    ↓ Yes
    Fetch Users
    Query `colleague_auth`
    Where `colleague` == Operator's Colleague ID
    ↓ No
    Error 406
    "Access Denied"
    Map & Format Data
    Format Dates, Check Ceilings, Enrich Colleague Info (if applicable)
    Return JSON

    GET /b2c/v1/cost-center/list/form


    Get Cost Centers List (Form)

    این اندپوینت جهت دریافت لیست مراکز هزینه (Cost Centers) برای فرم‌ها استفاده می‌شود.
    محدودیت دسترسی: این سرویس صرفاً مخصوص کاربران B2B (همکاران) است که شرکت آن‌ها در سطح هلدینگ (Category 7) تعریف شده باشد. هلدینگ‌ها از این طریق لیست زیرمجموعه‌های خود (که فیلد relationship آن‌ها برابر با شناسه هلدینگ است) را دریافت می‌کنند.




    Fetch Cost Centers

    URL: /b2c/v1/cost-center/list/form
    Method: GET
    Controller: V1UserController@getCostCenterForRequest
    Auth: Required (Bearer Token)

    Headers (الزامی)

    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

    Request Received
    Group check:
    is 'colleague' OR 'b2b'?
    ↓ Yes
    Check Company Type
    Get operator's colleague info.
    Condition: category == 7 (Holding)
    ↓ No
    Error 406
    "Access Denied"
    Is Holding (Cat 7)?
    ↓ Yes
    Fetch Children
    SELECT * FROM colleagues
    WHERE relationship == holding_id
    ↓ No
    Error 406
    "Not a Holding Company"
    Format Output
    Generate FA Title: Office + (First Name + Last Name)
    Return Items

    GET /b2c/v1/credit-card


    Get Credit Card Detail

    این اندپوینت برای دریافت اطلاعات یک کارت اعتباری خاص استفاده می‌شود.
    ویژگی امنیتی (Security Masking): سیستم به طور خودکار بررسی می‌کند که آیا کاربر درخواست‌دهنده (`operator`) همان مالک کارت (`passenger_id`) است یا خیر:

    • اگر مالک باشد: اطلاعات کامل شامل `cvv2`، تاریخ انقضا و شماره کامل کارت نمایش داده می‌شود.
    • اگر مالک نباشد: شماره کارت به صورت ماسک شده (مثلاً `6037****1234`) برمی‌گردد و فیلدهای حساس (`cvv2`, `expire_date`) حذف می‌شوند.

     




    Fetch Credit Card

    URL: /b2c/v1/credit-card
    Method: GET
    Controller: CreditCards@showCreditCard
    Auth: Required (Bearer Token)

    Headers (الزامی)

    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

    Request (GET)
    Database Query
    Join `credit_cards` with `customers`
    Filter by `id` OR `passenger_id` from Request
    Record Found?
    ↓ Yes
    Check Ownership
    request->operator->id == card->passenger_id
    ↓ No
    Error 422
    "Card Not Found"
    Is Owner?
    ↓ Yes
    Return Full Data
    Include `cvv2`, `expire_date`
    Real Card Number
    ↓ No
    Return Safe Data
    Exclude Sensitive Fields
    Mask Number: 1234********5678

    POST /b2c/v1/media/upload/s3


    Upload Media to S3

    این اندپوینت جهت آپلود فایل‌ها (تصاویر، اسناد و ...) بر روی فضای ذخیره‌سازی ابری (S3 Compatible / Liara) استفاده می‌شود.
    انعطاف‌پذیری: کلاینت می‌تواند محدودیت‌های حجم، فرمت فایل و مسیر ذخیره‌سازی را در درخواست تعیین کند. در غیر این صورت، مقادیر پیش‌فرض سیستم اعمال می‌شوند.




    Upload File

    URL: /b2c/v1/media/upload/s3
    Method: POST
    Controller: V1S3Controller@uploadFile
    Content-Type: multipart/form-data

    Body 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

    Request (Multipart)
    Config Initialization
    Set Size: input OR 5120KB
    Set Mimes: input OR defaults
    Set Path: input OR b2c/uploads/...
    Validation Passed?
    ↓ Yes
    Generate Filename
    If `name` exists → Use it
    Else → Y-m-d-His-microtime
    ↓ No
    Error 422
    Validation Exception
    Storage Put
    Disk: 'liara'
    put(path + filename, contents)
    Upload Success?
    ↓ Yes
    Return 201
    {"status": true, "file": "path..."}
    ↓ No
    Return 400
    {"status": false}

    GET /b2c/v1/base/data


    Get Base Data (Cities, Trains, Configs)

    این اندپوینت مرکزی برای دریافت داده‌های پایه (Base Data) و لیست‌های انتخابی (Select Options) در سیستم استفاده می‌شود.
    عملکرد داینامیک: خروجی این سرویس کاملاً وابسته به پارامتر ورودی subject است. این متد برای پر کردن لیست شهرها، ایستگاه‌های قطار، فرودگاه‌ها و تنظیمات هتل کاربرد دارد.




    Fetch Base Data

    URL: /b2c/v1/base/data
    Method: GET
    Controller: V1BaseController@baseData
    Auth: Public / Optional

    Query Parameters (پارامترهای ورودی)

    Parameter Type Required Description
    subject String Yes* نوع داده درخواستی. مقادیر مجاز:
    • cities (لیست شهرها)
    • train_companies (شرکت‌های ریلی)
    • train_types (انواع قطار: ۴ ستاره و...)
    • train_stations (ایستگاه‌های قطار)
    • room_rate (انواع نرخ اتاق هتل)
    • room_view (انواع چشم‌انداز اتاق)
    • airports (لیست فرودگاه‌ها)
    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

    Request (GET)
    Check `subject`?
    cities
    If type='train' → Filter IDs from `train_stations`
    Search/State Filter
    train_*
    Query `train_companies`, `types`, or `stations`
    room_*
    Decode JSON from `hotel_titles` table
    airports
    Join Countries & States
    Is `action` == 'airports'?
    ↓ Yes
    Custom Format
    {"status": true, "data": ...}
    ↓ No
    Standard Format
    {"items": [...], "meta": ...}

    GET /b2c/v1/base/accommodations/list


    Search Accommodations & Cities

    این اندپوینت برای "Autocomplete" یا جستجوی اولیه در بخش هتل استفاده می‌شود.
    ورودی کاربر را گرفته و به صورت همزمان در لیست شهرها و اقامتگاه‌ها (هتل، آپارتمان و...) جستجو می‌کند.




    Get Accommodation List

    URL: /b2c/v1/base/accommodations/list
    Method: GET
    Controller: V1BaseController@accommodationList
    Auth: Optional (Public)

    Query Parameters (پارامترهای ورودی)

    Parameter Type Required Description
    search String No عبارت مورد نظر برای جستجو.
    در نام شهر، نام هتل، آدرس، نام استان و نام کشور (فارسی و انگلیسی) جستجو می‌شود.

    Business Logic (منطق تجاری)

    1. جستجوی شهرها (Cities Search):
      • جدول cities با جداول states و countries جوین می‌شود.
      • اگر search ارسال شده باشد، در نام‌های فارسی و انگلیسی (شهر، استان، کشور) جستجو می‌کند.
      • نتایج بر اساس priority مرتب شده و حداکثر ۳۰ مورد بازگردانده می‌شود.
    2. جستجوی اقامتگاه‌ها (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

    URL: /b2c/v1/base/suggestions/items/{type}
    Method: GET
    Controller: V1BaseController@suggestionsItemsIndex
    Auth: Optional (Public)

    Parameters (پارامترها)

    Type Parameter Required Description
    Path {type} Yes نوع پیشنهاد مورد نظر:
    • tour (پکیج‌های تور)
    • train (بلیط قطار)
    • aircraft (بلیط هواپیما)
    • accommodation (هتل و اقامتگاه)
    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

    URL: /b2c/v1/trade/list
    Method: GET
    Controller: V1TradeController@listTrade
    Middleware: authWithJwt (Required)

    Business Logic (منطق تجاری)

    1. شناسایی کاربر (User Identification):
      • بر اساس توکن JWT، نوع کاربر (group) و اطلاعات کاربر (operator) استخراج می‌شود.
      • اگر گروه کاربر b2b یا colleague باشد، فیلتر بر اساس colleague_auth انجام می‌شود.
      • در غیر این صورت (مسافر عادی)، فیلتر بر اساس ستون customer انجام می‌شود.
    2. فیلتر سفارشات (Query Factors):
      • سفارشاتی که وضعیت (status) آن‌ها 2 یا 5 باشد، از لیست حذف می‌شوند (whereNotIn).
      • لیست بر اساس شناسه (ID) به صورت نزولی (جدیدترین به قدیمی‌ترین) مرتب می‌شود.
    3. غنی‌سازی داده‌ها (Data Enrichment Loop):
      برای هر فاکتور یافت شده عملیات زیر انجام می‌شود:
      • عنوان (Redis): تلاش برای دریافت عنوان فارسی سفارش از کلید Redis: reference:{id}:information:title:fa.
      • نوع محصول: استخراج product و byproduct از جدول factor_items.
      • محاسبات مالی: اگر عنوان در Redis موجود باشد، تابع ApiTradeController::financial فراخوانی شده تا وضعیت مالی دقیق (فروش، جریمه، پرداختی و مانده) محاسبه شود.

    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

    URL: /b2c/v1/trade/store
    Method: POST
    Controller: V1TradeController@storeTradeBeforePay
    Middleware: authWithJwt (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 روش پرداخت:
    • wallet : پرداخت از اعتبار حساب.
    • online (یا هر مقدار دیگر): پرداخت از درگاه بانکی.
    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)

    Status: 201 Created
    {
        "payload": {
            "payment_link": "https://airplus.app/p/a1b2c3d4",
            "temporary_id": 12345, // شناسه رزرو موقت
            "status_data": {
                "changed": false // یعنی قیمت تغییر نکرده است
            }
        },
        "meta": {
            "timestamp": 1702123456
        }
    }

    حالت دوم: خرید موفق با کیف پول (Wallet)

    Status: 201 Created
    {
        "payload": {
            "payment_id": 9876,
            "reference_id": 500120 // شماره فاکتور نهایی (رزرو قطعی)
        },
        "meta": {
            "timestamp": 1702123456
        }
    }

    حالت سوم: تغییر قیمت (نیاز به تایید کاربر)

    Status: 201 Created
    {
        "payload": {
            "payment_link": null,
            "temporary_id": false,
            "status_data": {
                "changed": true,
                "message": "قیمت پرواز تغییر کرده است...",
                "new_price": 15500000
            }
        },
        "meta": {
            "timestamp": 1702123456
        }
    }

    حالت چهارم: خطای موجودی ناکافی

    Status: 402 Payment Required
    {
        "error": {
            "code": 1001,
            "message": "اعتبار شما برای این خرید کافی نمی باشد. لطفا قبل از انجام این عملیات موجودی حساب خود را افزایش دهید."
        },
        "meta": {
            "timestamp": 1702123456
        }
    }

    POST /b2c/v1/trade/completion


    Trade Completion & Issuance

    این اندپوینت برای تکمیل فرآیند خرید استفاده می‌شود.
    زمانی که پرداخت (آنلاین یا کیف پول) تایید شد، این متد برای صدور فاکتور (Factor)، بروزرسانی اطلاعات مسافران، ثبت تراکنش‌های مالی (Pledgers/Pays) و ارسال SMS نهایی به کاربر فراخوانی می‌گردد.




    Finalize Order

    URL: /b2c/v1/trade/completion
    Method: POST
    Controller: V1TradeController@storeTradeAfterPay
    Middleware: authWithJwt (Required)
    Transaction: Atomic (DB::transaction)

    Request Body Parameters

    این متد یک آبجکت JSON پیچیده شامل اطلاعات پرداخت و جزئیات درخواست را دریافت می‌کند.

    Parameter Type Required Description
    branch Integer Yes شناسه شعبه (Branch ID) برای تعیین تنظیمات مالی و اعتباری.
    payment Object Yes اطلاعات مربوط به پرداخت انجام شده (مبلغ، درگاه و...).
    request Object Yes شامل جزئیات اصلی سفارش:
    • passengers: لیست کامل مسافران برای آپدیت در DB.
    • data: لیست آیتم‌های خریداری شده (پرواز، هتل، قطار).
    • pledgers: (اختیاری) لیست متعهدان مالی (برای B2B).
    • notices: (بولین) آیا SMS ارسال شود؟
    • income_id: شناسه درآمد.

    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

    URL: /b2c/v1/online/search/{type}
    Method: POST
    Controller: V1OnlineController@routeSearch
    Auth: Optional (Handled manually via Pipeline)

    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)

    Status: 409 Conflict
    {
        "error": {
            "code": 1000,
            "message": "تاریخ شروع و پایان جستجو معتبر نمی باشد"
        }
    }

    POST /b2c/v1/online/{type}/lock


    Lock Item (Pre-Booking)

    این متد برای "قفل کردن" یا "اعتبارسنجی نهایی" یک آیتم انتخابی (مانند یک پرواز یا بلیط قطار) قبل از ورود اطلاعات مسافران استفاده می‌شود.
    در این مرحله، سیستم با تامین‌کننده ارتباط برقرار کرده، تغییرات احتمالی قیمت را بررسی می‌کند و یک رکورد در جدول رزروهای موقت ایجاد می‌کند.




    Lock Selection

    URL: /b2c/v1/online/{type}/lock
    Method: POST
    Controller: V1OnlineController@lockItemProgress
    Auth: Required (JWT)
    Middleware: authWithJwt

    Path 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

    URL: /b2c/v1/online/{type}/penalty
    Method: POST
    Controller: V1OnlineController@penaltyItemProgress
    Auth: Public (No Authentication Required)

    Path 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

    Request Body: data + branch
    Initialize BaseService
    ساخت نمونه: $BaseService = new \App\Lib\BaseService()
    Call getRefundPenalty()
    $BaseService->getRefundPenalty($request->data, $request->get('branch'))
    Return JSON Result
    شامل اطلاعات جریمه و مبلغ قابل بازگشت برای هر آیتم
    در صورت بروز Exception → ساخت پاسخ خطا با اطلاعات تماس پشتیبانی

    POST /b2c/v1/online/{type}/refund


    Refund Request Operation

    این اندپوینت جهت ثبت و پردازش عملیات درخواست استرداد (Refund) برای بلیط یا رزرو آنلاین ایجاد شده است. پس از ارسال اطلاعات رزرو، توضیحات کاربر و مشخصات شعبه، فرآیند استرداد از طریق سرویس اصلی BaseService::refundItemProgress اجرا می‌شود. پاسخ شامل اطلاعات تراکنش استرداد و وضعیت نهایی عملیات خواهد بود.



    Refund Item Progress

    URL: /b2c/v1/online/{type}/refund
    Method: POST
    Controller: V1OnlineController@refundItemProgress
    Auth: Public (No Authentication Required)

    Path 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

    Request: includes data, description, and branch
    Initialize BaseService
    $BaseService = new \App\Lib\BaseService()
    Call refundItemProgress()
    $BaseService->refundItemProgress($request->data, $request->description, $request->get('branch'))
    Return JSON Result
    شامل داده‌های استرداد، مبلغ بازگشتی و وضعیت نهایی.
    در صورت خطا → ارسال پاسخ خطایی حاوی کد 1002 و اطلاعات تماس پشتیبانی.

    GET /b2c/v1/online/accommodation/list


    List Accommodation Items

    این اندپوینت فهرستی از اقامتگاه‌ها (هتل‌ها) را بر اساس شهر، IATA، یا شناسه‌ی مستقیم هتل بازیابی می‌کند. امکان اعمال فیلترهای متنوع شامل ستاره، نوع، نام، سرویس‌دهنده‌ها و محدوده‌ی تاریخی رزرو وجود دارد. در صورت انتخاب only_charters=true، نتایج محدود به هتل‌های دارای چارتر فعال در بازه‌ی داده‌شده خواهند بود.



    Endpoint Info

    URL: /b2c/v1/online/accommodation/list
    Method: GET
    Controller: V1OnlineController@listAccommodation
    Service Layer: LibBaseService::listAccommodation()
    Auth: Public Access (No JWT Required)

    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)

    ورودی درخواست شامل type، value، filters و paginate
    Validate Pagination
    اگر پارامتر paginate وجود نداشته یا کامل نیست، مقدار پیش‌فرض (length=30, start=0) ست می‌شود.
    Determine Search Mode
    بسته به type، داده از جدول‌های cities، states، countries یا hotels واکشی می‌شود.
    Build Filter Query
    روی جدول hotels با join بر روی mapping_accommodations انجام می‌شود.
    Filter Logic
    - ستاره‌ها (rate)
    - نوع اقامتگاه (type)
    - نام جزئی (fa_title LIKE '%name%')
    - وجود چارتر فعال بین تاریخ‌های واردشده.
    Paginate & Return
    پاسخ شامل 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

    URL: /b2c/v1/online/accommodation/get_min_prices
    Method: GET
    Controller: V1OnlineController@getAccommodationMinPrices
    Service: LibBaseService::getAccommodationMinPrices
    Auth: Public (No JWT Required)

    Query 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

    درخواست chứa query شامل checkin_date، checkout_date، branch و IDs
    Normalize Dates
    محاسبه تعداد روز اقامت (diff)، همچنین تولید تاریخ‌های next Tuesday → next Wednesday برای تست قیمت‌ها.
    Determine Active Providers
    بررسی وضعیت APIهای فعال در جدول application_interface برای سرویس‌های tport، sepehr_hotel، snapptrip_hotel.
    Iterate accommodations
    برای هر شناسه اقامتگاه → بررسی cache Redis. در نبود cache، شروع واکشی از همه providerها.
    External API Calls
    • TportApi::get_hotel_room_price: بر اساس mapped id و بازه تاریخی.
    • SepehrApi::search_by_city_and_date: بر اساس IATA شهر یا استان.
    • AirPlusApi::search: منبع داخلی hub.
    • SnappTripApi::get_hotel_availability_calendar.
    Compute Min Prices
    محاسبه کمترین مقدار نهایی و اصل بین همه نتایج موجود minNetPrice = min(Tport, Sepehr, AirPlus, SnappTrip)
    Cache to Redis
    کلید accommodations:min_price:{id} با TTL وابسته به درجه هتل (۵ستاره تا ۷روزه)
    Return JSON Result
    لیست اقامتگاه‌ها با فیلدهای 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

    URL: /b2c/v1/online/accommodation/get_room_type_prices
    Method: GET
    Controller: V1OnlineController@getRoomTypePrices
    Service: LibBaseService::getRoomTypePrices
    Auth: Public (No JWT Required)

    Query 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 خوانده می‌شود.
    RoomType Join
    واکشی تمام رکوردهای accommodation_roomtypes با join روی mapping_roomtypes برای شناسه‌های تپورت، سپهر، ایرپلاس و اسنپ‌تریپ.
    Activate Providers
    بررسی فعال بودن اینترفیس‌های سرویس‌دهنده در جدول application_interface.
    فعال‌های معمول: tport، sepehr_hotel، snapptrip_hotel، و AirPlus (همیشه فعال).
    Tport Price Fetch
    🔹 فراخوانی TportApi::get_hotel_room_price واکشی مالیات و قیمت هر روز → محاسبه‌ی تجمعی و markups از BaseService::getRoomTypeMarkups.
    ذخیره در $room->board_type_list[].
    Sepehr Prices
    🔹 فراخوانی SepehrApi::search_by_city_and_date بر اساس IATA مقصد. واکشی RoomTypeList و RateDetailList. فیلتر + مارکاپ → ایجاد Financial array.
    AirPlus Prices
    🔹 فراخوانی AirPlusApi::search با نوع accommodation. محاسبه قیمت روزانه بزرگسال/کودک/تخت اضافه → ذخیره در فیلد financial_total.
    بررسی ظرفیت در جدول‌های رزرو موقت از ReservationController::getAccommodationRooms().
    SnappTrip Prices
    🔹 فراخوانی SnappTripApi::get_hotel_availability. قیمت‌های نهایی بر اساس pricing.original_sell_price و تعداد روز اقامت محاسبه می‌شوند. ضرب در 10 برای تبدیل به ریال.
    Sort + Cache
    مرتب‌سازی بر اساس کمترین 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

    URL: /b2c/v1/online/accommodation/get_details
    Method: GET
    Controller: V1OnlineController@getAccommodationDetails
    Service: LibBaseService::getAccommodationDetails
    Auth: Public (No JWT Required)

    Query Parameters

    پارامتر نوع الزامی توضیح
    accommodation_id integer شناسه اقامتگاه مورد نظر در جدول hotels.

    Logic Flow

    دریافت رکورد هتل از جدول hotels توسط accommodation_id.
    Media Aggregation واکشی مدیاهای مرتبط از جدول media:
    • اگر type = cover → در اولویت نمایش.
    • مدیاها بر اساس نوع گروه‌بندی می‌شوند: تصاویر (image)، ویدیوها (video).
    • اگر لوگوی خاص موجود نباشد، لوگوی پیش‌فرض (default) جایگزین می‌شود.
    Facilities & Categories
    • اتصال جدول facilities با accommodation_facilities_mapping.
    • سپس گروه‌بندی امکانات بر اساس facilities_categories.
    • اگر هیچ موردی موجود نباشد → مقدار false برگردانده می‌شود.
    Policies & Rules
    • واکشی accommodation_policies، accommodation_cancellation_rules، و accommodation_rules.
    • در صورت وجود چارتر فعال (جدول charters)، قوانین Transfer (Welcome/Return) از charter_items.details افزوده می‌شود.
    Return JSON Response
    داده‌ها در قالب 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

    URL: /b2c/v1/online/flight/class/{calculation_id}
    Method: GET
    Controller: V1OnlineController@getFlightClassData
    Service: AirPlusApi & LibBaseService::checkSearchFlightItem
    Auth: Public (Without JWT)

    Path 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.
    ۲. فراخوانی AirPlusApi:
    متد 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 بازگردانده می‌شود.
    ❌ در صورت عدم پیدا شدن رکورد محاسبه، پاسخ خطا با کد 404 و message "آیتم مورد نظر یافت نشد." برگردانده می‌شود.

    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

    GET /b2c/v1/online/payment/flight/tracking

    Tracking Payment Flight

    اندپوینت زیر برای بررسی وضعیت پرداخت و رزرو پرواز از روی کد ملی و شماره رفرنس طراحی شده است. این متد با اتصال داخلی به V2TradeController::operationTrade() وضعیت فاکتور، اطلاعات پرواز و مسافران را بازیابی کرده و داده‌های حساس مالی را از خروجی حذف می‌کند.



    Endpoint Info

    URL: /b2c/v1/online/payment/flight/tracking
    Method: GET
    Controller: V1OnlineController@trackingPaymentFlight
    Auth: Public (بدون نیاز به JWT)

    Query 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 - 10000
    • branch = branch_id
    • customer = customer.id
    اگر رکوردی یافت شود، ادامه می‌دهد؛ در غیر این صورت پاسخ خطا با پیام "رفرنس مورد نظر یافت نشد" برمی‌گردد.
    ۳. آماده‌سازی درخواست برای TradeController:
    افزودن پارامتر id = reference به درخواست.
    تنظیم اپراتور فرضی: { id: 12, access: ... } برای اجرای تابع operationTrade.
    ۴. اجرای Trade Operation:
    ساخت شیء V2TradeController و اجرای متد operationTrade($request).
    دادهٔ بازگشتی (JSON) شامل فاکتور و اقلام تراکنش واکشی می‌شود.
    ۵. پاک‌سازی داده‌ها:
    در آرایهٔ خروجی data، فیلدهای محرمانه حذف می‌شوند:
    [buy, value_added, serial, provider, currency, deadline, failure_bill]
    و فیلدها به شکل زیر اصلاح می‌شوند:
    serial_id = serial_id + 10000
    ✅ خروجی نهایی شامل اطلاعات کامل فاکتور و مسافران است و در قالب کلید payload برمی‌گردد.

    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

    GET /b2c/v1/financial/list

    List Financial History

    این اندپوینت برای نمایش لیست تراکنش‌های مالی کاربران طراحی شده است و بسته به نوع گروه کاربری (b2c یا b2b/ colleague) داده‌ها از جداول متفاوتی واکشی می‌گردند. برای کاربران B2C، از جداول factor_items، factors و pays اطلاعات استخراج می‌شود و برای کاربران B2B/Colleague از جدول wallet.



    Endpoint Info

    URL: /b2c/v1/financial/list
    Method: GET
    Controller: CreditDebitController@listFinancial
    Auth: JWT Required (middleware: authWithJwt)

    Query Parameters

    پارامتر نوع اجباری توضیح
    group string نوع گروه کاربری فعال. مقادیر مجاز: b2c, b2b, colleague.
    branch integer شناسه شعبه کاربر (مورد استفاده فقط برای B2B/Colleague).
    operator.id integer شناسهٔ کاربر یا اپراتور فعلی که از JWT تزریق می‌شود.

    Logic Flow

    📥 دریافت پارامترهای JWT شامل operator.id و group
    ۱. مسیر B2C:
    • جستجوی فاکتورها در 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
    داده‌ها با وضعیت غیر از [۱،۲،۵] فیلتر می‌گردند.
    ۲. ترکیب داده با Redis Cache:
    برای هر رکورد pays، مقدار توضیحات حسابداری از کلید accounting:pays:{id} در Redis واکشی می‌شود و در فیلد description درج می‌گردد.
    ۳. مسیر B2B / Colleague:
    واکشی تراکنش‌های کیف پول (wallet) با شرط‌های زیر:
    • branch = request.branch
    • status = 1
    • operator_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

    GET /b2c/v1/passengers/previous

    List Previous Passengers

    این اندپوینت جهت واکشی لیست مسافرانی که پیش‌تر در رزروهای کاربر (یا همکار) شرکت داشته‌اند طراحی شده است. اطلاعات از جدول customers واکشی شده و شامل جزئیات هویتی، ملیتی، گذرنامه و تماس فرد می‌باشد.



    Endpoint Info

    URL: /b2c/v1/passengers/previous
    Method: GET
    Controller: PassengersController@indexPassengersPrevious
    Auth: JWT Required (middleware: authWithJwt)

    Query Parameters (from JWT + Request)

    پارامتر نوع اجباری توضیح
    operator.id integer شناسه اپراتور / مسافر اصلی (از JWT)
    group string نوع گروه کاربری فعال. مقادیر مجاز: b2c، b2b، colleague.

    Logic Flow

    📥 دریافت شناسه اپراتور از JWT → $passengerId = $request->get('operator')->id
    بررسی وجود passengerId. اگر نباشد، پاسخ خطای 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

    GET /b2c/v1/gateway/details

    Payment Gateway Details

    این اندپوینت جزئیات تراکنش از درگاه بانکی (Payment Gateway) را بر اساس serial_id برمی‌گرداند. در صورتی‌که وضعیت تراکنش موفق (status=3) باشد، اطلاعات کارت و کد رهگیری نمایش داده می‌شود؛ در غیر این صورت، پیام خطا و جزئیات تراکنش ناموفق بازگردانده می‌شود.



    Endpoint Info

    URL: /b2c/v1/gateway/details
    Method: GET
    Controller: CreditDebitController@paymentGatewayDetails
    Auth: ندارد (مشاهده جزئیات بر اساس serial)
    Source Tables: payment_gateway, gateways

    Query Parameters

    پارامتر نوع اجباری توضیح
    serial_id string | integer شناسه‌ٔ تراکنش در جدول payment_gateway

    Logic Flow

    📥 ۱. دریافت پارامتر serial_id از QueryString
    ۲. اجرای Query:
    SELECT 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 بازگردانده می‌شود.
    ✅ پاسخ نهایی JSON با وضعیت 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

    GET /b2c/v1/discount/submit

    Submit Discount Code

    این اندپوینت برای بررسی و اعمال کد تخفیف‌های فعال هر branch (دفتر عامل) طراحی شده است. کد تخفیف فقط در صورتی قابل استفاده است که هنوز منقضی نشده باشد، محدودیت استفاده آن تمام نشده باشد و در صورت تعریف کاربر خاص، توسط همان کاربر ارسال شود.


    Endpoint Info

    URL: /b2c/v1/discount/submit
    Method: GET
    Controller: CreditDebitController@discountSubmit
    Authorization: اختیاری (استفاده از JWT برای استخراج operator)
    Related Table: payment_discount

    Query Parameters

    پارامتر نوع الزامی توضیح
    branch integer شناسه دفتر (Branch ID)
    type string نوع استفاده از تخفیف (مثلاً flight، hotel، train)
    code string کد تخفیف واردشده توسط کاربر

    Logic Flow

    ۱️⃣ کوئری روی جدول payment_discount
    SELECT * FROM payment_discount
    WHERE branch = :branch
      AND status = 1
      AND type = :type
      AND code = LOWER(:code)
    LIMIT 1;
    آیا رکورد یافت شد؟
    • ✅ بله → ادامه
    • ❌ خیر → پیام خطای «کد تخفیف معتبر نمی‌باشد»
    آیا محدودیت استفاده (`limit`) دارد؟
    • اگر null → بدون محدودیت
    • اگر >0 → قابل استفاده
    • اگر 0 → پیام خطای «کد تخفیف تمام شده است»
    آیا تخفیف برای کاربر خاص تعریف شده؟
    • اگر user=null → برای همه مجاز
    • اگر user=id اپراتور → مجاز
    • در غیر این صورت → «کد تخفیف برای کاربر دیگری می‌باشد»
    بررسی انقضا (expiration)
    • اگر null → همیشه معتبر
    • اگر زمان فعلی < expiration → معتبر
    • در غیر این صورت → «کد تخفیف منقضی شده است»
    🔄 اگر limit برابر 1 → بروزرسانی رکورد:
    status = 3, limit = 0
    در غیر این صورت فقط عدد limit یک واحد کاهش می‌یابد.
    ✅ بازگرداندن پاسخ موفق با داده‌های تخفیف:
    • id
    • title
    • type (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

    GET /b2c/v1/discount/unsubmit

    Unsubmit Discount Code

    این اندپوینت برای بازگرداندن ظرفیت یا اعتبار مصرف‌شده‌ی یک کد تخفیف در سیستم B2C طراحی شده است. در واقع، اگر کد تخفیف اشتباهی استفاده یا تراکنش مربوط به آن لغو شود، با این درخواست می‌توان مقدار limit آن را افزایش داد و status را در حالت فعال (1) قرار داد.


    Endpoint Info

    URL: /b2c/v1/discount/unsubmit
    Method: GET
    Controller: CreditDebitController@discountUnSubmit
    Authorization: ندارد (در نسخه فعلی عمومی)
    Related Table: payment_discount
    کد بازگرداننده اعتبار: با id منحصر‌به‌فرد تخفیف فراخوانی می‌شود.

    Query Parameters

    پارامتر نوع اجباری توضیح
    id integer شناسه رکورد تخفیف در جدول payment_discount

    Logic Flow

    📥 ۱. دریافت پارامتر id از Query string.
    ۲. جستجوی رکورد تخفیف در جدول payment_discount با شرط id.
    آیا رکورد یافت شد؟
    • ✅ بله → ادامه پردازش
    • ❌ خیر → بازگرداندن پیام «کد تخفیف معتبر نمی‌باشد»
    بررسی مقدار limit:
    • اگر limit == 0 → یعنی تخفیف کاملاً مصرف شده بوده؛ حالا برگردان به حالت اولیه:
      • limit = 1
      • status = 1 (فعال)
    • اگر limit > 0 → فقط یک واحد افزایش پیدا می‌کند.
    ✏️ بروزرسانی جدول payment_discount با مقادیر جدید.
    ✅ بازگشت پاسخ موفق با:
    • status = true
    • time = timestamp()

    Response Samples

    ✅ بازگردانی موفق اعتبار

    {
      "status": true,
      "time": 1733758800
    }

    ❌ کد تخفیف یافت نشد

    {
      "status": false,
      "time": 1733758810,
      "message": "کد تخفیف معتبر نمی باشد."
    }

    Technical Notes

    GET /b2c/v1/wallet/ballance

    Wallet Balance

    این اندپوینت برای واکشی موجودی کیف‌پول کاربر در سیستم B2C طراحی شده است. بر اساس group (مقدار JWT احراز هویت)، نوع کاربر بین B2C و B2B (colleague) تشخیص داده می‌شود و سپس موجودی کیف پول از جدول wallet با استفاده از کنترلر مالی اصلی (AccountingController) فراخوانی می‌گردد.


    Endpoint Info

    URL: /b2c/v1/wallet/ballance
    Method: GET
    Controller: CreditDebitController@walletBalance
    Middleware: authWithJwt
    Related Method: AccountingController::getBalanceWallet()
    Database Table: wallet

    Query Parameters (JWT‑required)

    پارامتر نوع الزامی توضیح
    group string گروه کاربر: b2c یا colleague. هر مقدار دیگر منجر به خطای 1000 می‌شود.
    operator object (JWT) شیء اپراتور احراز هویت‌شده شامل فیلدهای id، ceiling و deadline_month

    Logic Flow

    📥 ۱. بررسی ورودی JWT و تعیین نوع کاربر ($request->group)
    اگر group مقدار b2c داشت → $userType='b2c'
    اگر group مقدار colleague داشت → $userType='b2b'
    در غیر این صورت → بازگشت خطا:
    {
      "error": {
        "code": 1000,
        "message": "گروه کاربری یافت نشد"
      },
      "meta": { "timestamp": 1733763100 }
    }
    📊 استخراج مقادیر زیر از JWT اپراتور:
    • 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

    POST /b2c/v1/wallet/credit

    POST /b2c/v1/wallet/credit

    این اندپوینت به کاربران B2C و همکاران (B2B/Colleague) اجازه می‌دهد تا از طریق درگاه پرداخت فعال دفتر خود، مبلغی را به کیف‌پولشان واریز کنند. نتیجه نهایی تولید لینک پرداخت منحصر‌به‌فرد با ساختار /p/{slug} است.


    Endpoint Information

    URL: /b2c/v1/wallet/credit
    Method: POST
    Controller: CreditDebitController@walletCredit
    Middleware: authWithJwt
    Related Tables: pays, payment_gateway, gateways, office_config, offices

    Request 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

    📥 ۱. دریافت پارامترها از بدنه درخواست
    بررسی فیلد price:
    • ❌ در صورت خالی بودن → پاسخ با HTTP 422 و پیام «لطفا تمامی فیلدها را پر کنید»
    • ❌ اگر مبلغ < 10000 → پاسخ با HTTP 422 و پیام «حداقل مبلغ قابل پرداخت 10000 ریال است»
    • ✅ در غیر این‌صورت → ادامه
    ⚙️ ۲. فراخوانی Functions::getGatewayConfig(branch, driver) برای یافتن درگاه فعال:
    • اگر درگاه به‌صورت مستقیم انتخاب شود (driver)، از جدول gateways بررسی می‌گردد.
    • در غیر این‌صورت مقدار پیش‌فرض از جدول office_config با کلید DEFAULT_BANKING_GATEWAY استخراج می‌شود.
    • ❌ در صورت نبودن درگاه فعال → پاسخ 400 با پیام «درگاه پرداخت فعال یافت نشد»
    تعیین نوع حساب بر اساس group:
    • 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) و شناسه درایو درگاه پرداخت
    ✅ ۵. پاسخ موفق (HTTP 201): شامل اطلاعات پرداخت و لینک پرداخت آماده برای کاربر.

    🎯 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 تعیین درگاه پیش‌فرض شعبه

    توضیح توابع وابسته


    نکات فنی

    GET /b2c/v1/ledger-account

    ledger-account

    این اندپوینت برای واکشی حساب کل دفتر همکار (colleague) در سیستم مالی B2C به‌کار می‌رود و از داده‌های تجمیع‌شده در Redis و خروجی متد ColleaguesController::colleagueLedgerAccounts استفاده می‌کند تا هم جزئیات صورت‌حساب‌ها (ledger items) و هم مانده‌ی کل حساب را برگرداند.


    Endpoint Information

    URL: /b2c/v1/ledger-account
    Method: GET
    Controller: CreditDebitController@ledgerAccount
    Middleware: authWithJwt
    Dependencies: ColleaguesController@colleagueLedgerAccounts, Redis, Morilog\Jalali\Jalalian

    پارامترهای ورودی

    نام نوع الزامی توضیح
    operator (JWT) object حاوی اطلاعات کاربر احراز شده، خصوصاً colleague_id
    branch integer شناسه دفتر یا شعبه کاربر

    منطق پردازشی (Flowchart)

    📥 ۱. دریافت درخواست و استخراج شناسه همکار از JWT (operator->colleague_id)
    ⚙️ ۲. ساخت پیش‌فرض‌های JSON جستجو (فیلتر زمانی و پارامترهای گزارش):
    • from = یک ماه قبل به تاریخ شمسی (با Jalalian::now()->subMonths(1))
    • to = تاریخ امروز
    • status = "0"
    • lbalance = false
    🧩 ۳. تزریق داده‌ی JSON تولیدشده در $request['json'] و $request['id'] سپس ایجاد نمونه از ColleaguesController و فراخوانی متد colleagueLedgerAccounts() برای واکشی جزئیات تراکنش‌ها.
    🗄️ ۴. واکشی مانده کلی حساب از Redis با کلید: colleagues:general_billing:all{colleague_id} در صورت موجود بودن داده، پارس آن با json_decode().
    بررسی وجود داده در Redis:
    • ✅ اگر موجود بود → ساخت آرایه شامل debit, credit, balance, diagnosis, documents از داده Redis
    • ❌ اگر نبود → مقادیر صفر پیش‌فرض تنظیم می‌شود
    ✅ ۵. ساخت پاسخ JSON شامل لیست آیتم‌های دفتر کل و جزئیات موجودی: { 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 }
    }

    نکات فنی و باگ‌های شناسایی‌شده

    GET /b2c/v1/articles

    GET /b2c/v1/articles

    این اندپوینت برای دریافت فهرست مقالات در سیستم B2C استفاده می‌شود. امکان فیلتر بر اساس دسته‌بندی‌ها، تگ‌ها و موقعیت‌ها (places) و همچنین مرتب‌سازی بر اساس امتیاز (score) یا تعداد بازدید (views) فراهم شده است. در حالت عادی خروجی صفحه‌بندی‌شده برمی‌گردد، اما در حالت مرتب‌سازی خاص، داده‌ها به صورت محدود و غیر صفحه‌بندی بازگردانده می‌شوند.


    Endpoint Information

    URL: /b2c/v1/articles
    Method: GET
    Controller: V1ArticleController@index
    Middleware: web (بدون JWT)
    Model: Article
    Resource: ArticleResource

    پارامترهای Query قابل استفاده

    نام پارامتر نوع الزامی توضیح
    branch integer شناسه دفتر یا شعبه‌ای که مقاله در آن تعریف شده است
    categories array (JSON) لیست شناسه‌های دسته‌بندی برای فیلتر (مثلاً [1,5,7])
    tags array (JSON) لیست شناسه‌های تگ‌ها برای فیلتر
    places array (JSON) لیست شناسه‌ نواحی مکانی مرتبط با مقاله
    sortByScore boolean مرتب‌سازی بر اساس بیشترین امتیاز (برترین ۱۰ مقاله)
    sortByViews boolean مرتب‌سازی بر اساس بیشترین بازدید (۶ مقاله برتر)

    منطق پردازشی و جریان داده (Flowchart)

    📥 ۱. دریافت پارامترها از Query (categories, tags, places, sortBy…)
    ⚙️ ۲. ایجاد Query Builder روی مدل 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 شامل وضعیت، زمان، داده‌ها و لینک‌های صفحه‌بندی.

    📦 ساختار پاسخ 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
    }

    نکات فنی


    🚀 پیشنهادات بهبود برای توسعه آینده

    GET /b2c/v1/articles/{article}

    GET /b2c/v1/articles/{article}

    این اندپوینت برای مشاهده جزئیات یک مقاله خاص در سامانه‌ی B2C طراحی شده است. ورودی شامل شناسه یا slug مقاله است که به‌صورت Model Binding لاراول به Article تبدیل می‌شود. نتیجه در قالب ArticleResource بازگردانده می‌شود تا داده‌ها به شکل استاندارد JSON ارائه شود.


    Endpoint Information

    URL: /b2c/v1/articles/{article}
    Method: GET
    Controller: V1ArticleController@show
    Middleware: web (بدون JWT)
    Model: Article
    Resource: ArticleResource

    پارامترهای مسیر (Path Parameters)

    نام نوع الزامی توضیح
    article integer|string شناسه عددی یا slug مقاله؛ به‌صورت خودکار در لاراول از طریق Route Model Binding واکشی می‌شود.

    منطق اجرای متد (Flow Logic)

    📥 ۱. دریافت پارامتر مسیر {article} و Resolve به مدل Article توسط لاراول.
    🧩 ۲. بسته‌بندی داده مقاله در قالب ArticleResource برای تبدیل فیلدها به JSON ساخت‌یافته.
    ✅ ۳. بازگرداندن پاسخ 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)

    GET /b2c/v1/categories

    GET /b2c/v1/categories

    این اندپوینت برای دریافت فهرست دسته‌بندی‌ها (Categories) طراحی شده است. داده‌ها از جدول categories گرفته می‌شوند و امکان فیلتر بر اساس نوع (type) و شعبه (branch) وجود دارد. فقط دسته‌بندی‌های سطح اول (یعنی مواردی که main آن‌ها NULL است) بازگردانده می‌شوند.


    Endpoint Information

    URL: /b2c/v1/categories
    Method: GET
    Controller: V1CategoryController@index
    Middleware: web (بدون JWT)
    Model: Category
    Resource: CategoryResource

    پارامترهای ورودی (Query Parameters)

    نام نوع الزامی توضیح
    branch integer شناسه دفتر یا شعبه‌ای که دسته‌بندی‌ها به آن تعلق دارند
    type string نوع دسته‌بندی (مثلاً article، product، destination) برای فیلتر اختیاری
    page integer شماره صفحه برای صفحه‌بندی (به‌صورت پیش‌فرض ۱)

    منطق پردازشی (Process Flow)

    📥 ۱. دریافت پارامترهای branch، type و page از Query
    ⚙️ ۲. اجرای کوئری روی مدل Category:
    • اعمال شرط where('branch', branch)
    • در صورت وجود typewhere('type', type)
    • نمایش فقط دسته‌های سطح اول (whereNull('main'))
    📑 ۳. صفحه‌بندی خروجی با paginate(15).
    ✅ ۴. بازگرداندن JSON شامل وضعیت، زمان، داده‌ها و لینک‌های صفحه‌بندی.

    📦 ساختار پاسخ 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 ارسال نشده است."
    }

    نکات فنی و اجرایی


    🚀 پیشنهاد بهبود آینده

    POST /b2c/v1/articles/{id}/views

    POST /b2c/v1/articles/{id}/views

    این اندپوینت برای افزایش شمارش بازدید مقاله استفاده می‌شود. هنگام نمایش جزئیات مقاله در سمت کاربر (مانند صفحه‌ی article detail)، فرانت‌اند می‌تواند پس از بارگذاری موفق مقاله، این متد را برای ثبت بازدید فراخوانی کند. افزایش بازدید مستقیماً در دیتابیس انجام می‌شود و مقدار جدید بازگردانده می‌شود.


    Endpoint Information

    URL: /b2c/v1/articles/{id}/views
    Method: POST
    Controller: V1ArticleController@incrementViews
    Middleware: web (بدون JWT)
    Model: Article
    Action: افزایش مقدار views در ستون پایگاه داده

    پارامترهای مسیر (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 در پایگاه داده یک واحد افزایش می‌یابد.
    ✅ ۴. پاسخ JSON شامل وضعیت، زمان (timestamp) و مقدار جدید views برگردانده می‌شود.

    نکات فنی برای توسعه‌دهنده (Developer Notes)

    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

    پاسخ نمونه

    {
      "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 }
    }

    خروجی موفق (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 و غیره) از جداول مجزا خوانده می‌شوند.


    ۴. ویرایش — PUT /b2c/v1/travel_requests/{id}

    پاسخ نمونه

    {
      "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

    {
      "operator_id": 5,
      "status": 4,
      "reference_id": 999
    }

    خروجی موفق

    {
      "payload": { "status": true },
      "meta": { "timestamp": 1733821930 }
    }

    نکات فنی و عملکردی