FreeAmir

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

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

چرا توسعه‌دهنده باید حسابداری بداند؟

عدم درک مفاهیم حسابداری منجر به:

نکته مهم: در سیستم‌های مالی، یک خطای کوچک برنامه‌نویسی می‌تواند منجر به عدم تطبیق حساب‌ها و مشکلات جدی مالی شود.

مبانی حسابداری

بدهکار و بستانکار چیست؟

در امیر، تراکنش‌ها با یک فیلد value ذخیره می‌شوند:

// example: خرید ۱۰۰,۰۰۰ تومان کالا نقداً
// تراکنش 1: افزایش موجودی کالا (بدهکار)
$transaction1 = Transaction::create([
    'subject_id' => $inventorySubject->id,
    'value' => -100000,  // منفی = بدهکار
    'desc' => 'خرید کالا'
]);

// تراکنش 2: کاهش صندوق (بستانکار)
$transaction2 = Transaction::create([
    'subject_id' => $cashSubject->id,
    'value' => 100000,  // مثبت = بستانکار
    'desc' => 'پرداخت وجه نقد'
]);

قانون طلایی: موازنه

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

نکته مهم: همیشه اسناد موازنه نیستند! امیر برای راحتی کار اجازه ثبت اسناد غیرموازنه می‌دهد ولی:

  1. اسناد دستی: می‌توانند غیرموازنه ثبت شوند (برای سهولت کار)
  2. اسناد خودکار: همیشه باید موازنه باشند (فاکتور، چک و…)
  3. تأیید نهایی: تنها اسناد موازنه قابل تأیید نهایی هستند
$totalValue = $document->transactions->sum('value');
if ($totalValue !== 0) {
    throw new DocumentServiceException('سند متوازن نیست');
}

ساختار سرفصل‌های حسابداری

سرفصل‌های اصلی

010 - بانکها
011 - موجودیهای نقدی
├── 011001 - صندوق
└── 011002 - تنخواه گردانها

012 - بدهکاران/بستانکاران
└── 012001 - اشخاص متفرقه

040 - هزینه ها
├── 040001 - حقوق پرسنل
├── 040002 - آب
├── 040003 - برق

041 - قیمت تمام شده کالای فروش رفته
└── 041001 - قیمت تمام شده کالای فروش رفته

050 - درآمدها
└── 050001 - درآمد متفرقه

060 - فروش
└── 060001 - فروش

کدینگ سرفصل‌ها

نکته مهم: کدینگ سرفصل‌ها در امیر سه تا سه تا انجام می‌شود:

// example: ساختار کدینگ در جدول subjects
'code'      => '011001',            // کد سرفصل صندوق
'name'      => 'صندوق',             // نام سرفصل
'parent_id' => 3,                   // شناسه والد (موجودیهای نقدی)
'type'      => 'both'               // نوع حساب

ارتباط گروه‌ها با سرفصل‌ها

$customerGroupConfig = Config::where('key', 'customer_default_subject')
                            ->where('company_id', session('active-company-id'))
                            ->value('value');

// گروه کالاها به کدام سرفصل‌ها متصل شود
$productInventoryConfig = Config::where('key', 'product_inventory_subject')
                               ->where('company_id', session('active-company-id'))
                               ->value('value');

استفاده از سرویس‌ها برای مدیریت کدها

هشدار مهم: برای تولید کد سرفصل‌ها حتماً از generateCode() در مدل Subject استفاده کنید:

$subject = new Subject();
$subject->name = 'صندوق شعبه مرکزی';
$subject->parent_id = $parentSubject->id;
$subject->type = 'both';
// کد به صورت خودکار تولید می‌شود
$subject->save();

// یا اگر می‌خواهید کد خاصی تعیین کنید:
$subject->code = $subject->generateCode(15); // کد 015

نمایش کدها

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

echo $subject->formattedCode();  // "011/001"

// نمایش کد با نام
echo $subject->formattedName();     // "011/001 صندوق"

گروه‌بندی مشتریان، کالاها و ارتباط با سرفصل‌ها

مفهوم گروه‌بندی

در امیر، هر گروه مشتری یا کالا به یک سرفصل معین متصل است. این ارتباط از طریق تنظیمات (configs) تعریف می‌شود.

مشتریان (Customers)

$customerGroup = CustomerGroup::create([  // هر گروه مشتری به یک سرفصل متصل است
    'name' => 'مشتریان عمومی',
    'subject_id' => 58  // سرفصل اشخاص متفرقه (012001)
]);

// هنگام ایجاد مشتری، سرفصل تفضیلی ایجاد می‌شود
$customer = Customer::create([
    'name' => 'آقای محمدی',
    'group_id' => $customerGroup->id
]);

// سرفصل جدید: اشخاص متفرقه/محمدی با کد مثلاً 012001001

کالاها و خدمات

$productGroup = ProductGroup::create([// هر گروه کالا نیز به سرفصل‌های مختلف متصل است
    'name' => 'کالاهای اساسی',
    'inventory_subject_id' => 70,             // حساب موجودی (015002)
    'income_subject_id' => 20,                // حساب درآمد فروش (060001)
    'expense_subject_id' => 89                // حساب بهای تمام شده (041001)
]);

// هنگام ایجاد کالا، سرفصل‌های تفضیلی ایجاد می‌شوند
$product = Product::create([
    'name' => 'برنج طارم',
    'group_id' => $productGroup->id
]);

تعداد سرفصل‌های تفضیلی نامحدود

امیر قابلیت ایجاد سرفصل‌های تفضیلی در سطوح نامحدود را دارد:

// سطح 1: 010
// سطح 2: 011001
// سطح 3: 011001001
// سطح 4: 011001001001
// سطح 5: 011001001001001
// و بی‌نهایت ادامه دارد

اسناد (Document) و تراکنش‌ها (Transaction): ساختار اسناد و تراکنش‌ها

ساختار کلی

class Document extends Model {
    protected $fillable = [
        'number',       // شماره سند (عددی که در سال مالی یکتا می‌شود)
        'date',         // تاریخ سند
        'title',        // شرح کلی سند
        'approved_at',  // تاریخ تأیید سند
        'creator_id',   // شناسه کاربر ایجادکننده
        'approver_id',  // شناسه کاربر تأییدکننده
        'company_id',   // شناسه شرکت/سال مالی
    ];

    protected $casts = [
        'date' => 'date',
        'approved_at' => 'date',
    ];

    public function transactions() {
        return $this->hasMany(Transaction::class);
    }

    public function creator() {
        return $this->belongsTo(User::class, 'creator_id');
    }

    public function approver() {
        return $this->belongsTo(User::class, 'approver_id');
    }
}

class Transaction extends Model {
    protected $fillable = [
        'subject_id',   // شناسه سرفصل
        'document_id',  // شناسه سند
        'user_id',      // شناسه کاربر
        'desc',         // شرح تراکنش
        'value',        // مقدار (منفی=بدهکار، مثبت=بستانکار)
    ];
    
    // محاسبه خودکار بدهکار/بستانکار
    public function getDebitAttribute() {
        return $this->value < 0 ? formatNumber(-1 * $this->value) : '';
    }

    public function getCreditAttribute() {
        return $this->value > 0 ? formatNumber($this->value) : '';
    }
}

نمونه سند در کد

// example: ثبت فروش ۵۰۰,۰۰۰ تومان نقدی
$documentData = [
    'date' => '2024-01-01',
    'title' => 'فروش کالا به مشتری'
];

// ورودی فرم (همان چیزی که DocumentController::store دریافت می‌کند)
$requestTransactions = [
    [
        'subject_id' => $cashSubject->id,
        'debit' => 500000,
        'credit' => 0,
        'desc' => 'دریافت وجه نقد',
    ],
    [
        'subject_id' => $salesSubject->id,
        'debit' => 0,
        'credit' => 500000,
        'desc' => 'درآمد حاصل از فروش',
    ],
];

$transactionsData = [];
foreach ($requestTransactions as $transactionData) {
    $transactionsData[] = [
        'subject_id' => $transactionData['subject_id'],
        'value' => $transactionData['credit'] - $transactionData['debit'], // منطق DocumentController::store
        'desc' => $transactionData['desc'],
    ];
}

// خروجی: مقدار منفی = بدهکار، مقدار مثبت = بستانکار
// [
//     ['subject_id' => $cashSubject->id, 'value' => -500000, 'desc' => 'دریافت وجه نقد'],   // بدهکار
//     ['subject_id' => $salesSubject->id, 'value' => 500000, 'desc' => 'درآمد حاصل از فروش'], // بستانکار
// ]

$document = DocumentService::createDocument(
    auth()->user(),
    $documentData,
    $transactionsData
);

سرویس‌های ضروری امیر

DocumentService

مهم: همیشه از این سرویس برای کار با اسناد استفاده کنید:

use App\Services\DocumentService;

// ایجاد سند جدید
$document = DocumentService::createDocument($user, $documentData, $transactionsData);

// به‌روزرسانی سند
$document = DocumentService::updateDocument($document, $newData);

// تأیید سند (بررسی موازنه)
DocumentService::approveDocument($document);

// ایجاد تراکنش
$transaction = DocumentService::createTransaction($document, $transactionData);

// به‌روزرسانی تراکنش‌های سند
DocumentService::updateDocumentTransactions($documentId, $transactionsData);

// حذف سند (همراه با تراکنش‌ها)
DocumentService::deleteDocument($documentId);

SubjectCreatorService

برای ایجاد سرفصل‌ها (برای تولید خودکار کد حسابداری حتما از این سرویس استفاده کنید):

use App\Models\Subject;
use App\Services\SubjectCreatorService;

$subjectService = new SubjectCreatorService();

// ابتدا سرفصل والد را پیدا کنید (مثلاً صندوق اصلی)
$parentSubject = Subject::where('code', '011001')->firstOrFail();

$subject = $subjectService->createSubject([
    'name' => 'صندوق جدید',
    'parent_id' => $parentSubject->id,
    'type' => 'debtor',   // اختیاری؛ مقدار پیش‌فرض 'both' است
]);

FiscalYearService

برای مدیریت سال‌های مالی و مهاجرت داده:

use App\Services\FiscalYearService;

// صادرات داده‌های سال مالی
$data = FiscalYearService::exportData($fiscalYearId, [
    'subjects',
    'customers', 
    'products',
    'documents'
]);

// وارد کردن به سال جدید
$newFiscalYear = FiscalYearService::importData($data, $newFiscalYearData);

// دریافت بخش‌های قابل صادرات
$availableSections = FiscalYearService::getAvailableSections();

سال مالی و چندشرکته بودن

مفهوم سال مالی در امیر

سال مالی در امیر به عنوان “شرکت” (Company) شناخته می‌شود. هر شرکت نمایانگر یک سال مالی مستقل است و مقدار عددی سال (مثلاً 1403) در ستون fiscal_year همان رکورد نگهداری می‌شود.

class Company extends Model {    // هر ردیف نمایانگر یک سال مالی است
    protected $fillable = [
        'name',            // عنوان سال/شرکت (مانند "سال مالی 1403")
        'logo',            // مسیر لوگو (اختیاری)
        'address',         // آدرس شرکت
        'economical_code', // کد اقتصادی
        'national_code',   // شناسه ملی
        'postal_code',     // کد پستی
        'phone_number',    // تلفن تماس
        'fiscal_year',     // سال مالی به صورت عدد صحیح (مثلاً 1403)
    ];
}

جداسازی داده‌ها با FiscalYearScope

همه مدل‌ها از FiscalYearScope استفاده می‌کنند:

protected static function booted()
{
    static::addGlobalScope(new FiscalYearScope);
}

// برای کار بدون scope در مواقع خاص
$allData = SomeModel::withoutGlobalScope(FiscalYearScope::class)->get();

مهاجرت و کپی سال مالی

برای اطلاعات بیشتر FiscalYearExportImport.md

php artisan fiscal-year:export 1 --sections=subjects,customers
php artisan fiscal-year:import exported_data.json --name="سال 1404" --year=1404

تنظیمات شرکت‌ها

تنظیمات در جدول configs برای هر شرکت جداگانه نگهداری می‌شود:

گزارش‌های مالی

دفتر روزنامه (Journal)

تعریف: لیست تمام اسناد و تراکنش‌ها به ترتیب تاریخ ثبت

خصوصیات:

کاربرد:

نکات مهم:

دفتر کل (General Ledger)

تعریف: گردآوری و خلاصه‌سازی تراکنش‌ها بر اساس هر سرفصل حسابداری

تفاوت با دفتر روزنامه:

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

1. دفتر کل (سرفصل‌های اصلی)

2. دفتر معین (سرفصل‌های معین)

3. دفتر تفضیلی (سرفصل‌های تفضیلی)

مثال عملی:

انتخاب سرفصل: "صندوق" (011/001)
نتیجه: همه تراکنش‌های مربوط به این صندوق خاص

نکات مهم:

ترازنامه (Balance Sheet)

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

ساختار کلاسیک:

دارایی‌ها = بدهی‌ها + سرمایه

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

نکات مهم:

تفاوت با سایر گزارش‌ها:

سود و زیان (Income Statement)

تعریف: گزارش عملکرد مالی در یک دوره زمانی (نه یک تاریخ مشخص)

محتوای گزارش:

ساختار کلاسیک:

درآمد فروش
- بهای کالای فروخته شده
= سود ناخالص

- هزینه‌های عملیاتی
= سود عملیاتی

+ درآمدهای غیرعملیاتی
- هزینه‌های غیرعملیاتی
= سود خالص

تفاوت‌های کلیدی:

ویژگی ترازنامه سود و زیان
زمان یک تاریخ مشخص یک دوره زمانی
حساب‌ها دائمی (دارایی، بدهی، سرمایه) موقت (درآمد، هزینه)
هدف وضعیت مالی عملکرد مالی
تجدید سالانه باقی می‌ماند سالانه صفر می‌شود

نکات مهم در امیر:

ارتباط گزارش‌ها:

  1. دفتر روزنامه → داده‌های خام همه تراکنش‌ها
  2. دفتر کل → تجمیع تراکنش‌ها بر اساس سرفصل
  3. ترازنامه → مانده حساب‌های دائمی در یک تاریخ
  4. سود و زیان → عملکرد حساب‌های موقت در یک دوره

مهم: در امیر همه این گزارش‌ها از یک پایگاه داده یکپارچه تولید می‌شوند و باید با یکدیگر تطبیق داشته باشند.

تنظیمات و پیکربندی

فلسفه طراحی Config در امیر

برای جلوگیری از hard-code کردن روابط بین بخش‌های مختلف سیستم، تمام اتصالات و تنظیمات در جدول configs ذخیره می‌شوند. این طراحی به کاربران اجازه می‌دهد بدون تغییر کد، تنظیمات را شخصی‌سازی کنند.

ConfigLoader Middleware

کدهای ConfigLoader تمام تنظیمات را از پایگاه داده خوانده و در config() Laravel بارگذاری می‌کند تا در سراسر برنامه قابل دسترسی باشند.

ویژگی‌ها:

برای اطلاعات بیشتر ConfigController را مشاهده کنید

نحوه دسترسی به تنظیمات

در کد Laravel:

$cashSubjectId = config('amir.cash');

// با مقدار پیش‌فرض
$bankSubjectId = config('amir.bank', null);

برای تنظیمات خاص شرکت:

$config = Config::where('company_id', session('active-company-id'))
               ->where('key', 'cash')
               ->value('value');

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

نقش‌ها در سیستم مالی

برای مدیریت دسترسی ها از spatie permission استفاده می کنیم

'accounting.documents.create'
'accounting.documents.edit'
'accounting.documents.delete'
'accounting.reports.view'
'accounting.settings.manage'

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

1. همیشه موازنه را کنترل کنید (فقط برای اسناد خودکار)

$totalValue = collect($transactions)->sum('value'); // برای اسناد خودکار (فاکتور، چک و...)
if ($totalValue != 0) {
    throw new ValidationException('سند خودکار باید متوازن باشد');
}

// برای اسناد دستی موازنه اجباری نیست

2. از Transaction در پایگاه داده استفاده کنید

DB::transaction(function() {
    // عملیات مالی
    $document = DocumentService::createDocument($user, $data, $transactions);
});

3. همیشه از سرویس‌های ارائه شده استفاده کنید

$document = Document::create($data); // اشتباه ❌

// درست ✅
$document = DocumentService::createDocument($user, $data, $transactions);

5. استفاده صحیح از کدهای سرفصل

$subject->code = $subject->generateCode();

// برای نمایش کد فرمت شده
echo $subject->formattedCode();  // "001/002/003"

7. همیشه scope های مربوط به شرکت را در نظر بگیرید

$subjects = Subject::all(); // درست ✅ - با scope خودکار

// برای کار بدون scope
$allSubjects = Subject::withoutGlobalScope(FiscalYearScope::class)->get();

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