مبانی حسابداری برای توسعهدهندگان امیر
این راهنما برای توسعهدهندگانی نوشته شده که میخواهند در پروژه امیر مشارکت کنند و نیاز به درک مفاهیم حسابداری و ساختار کد دارند.
چرا توسعهدهنده باید حسابداری بداند؟
عدم درک مفاهیم حسابداری منجر به:
- ایجاد باگهای منطق کسبوکار
- طراحی نادرست پایگاه داده
- عدم رعایت اصول موازنه مالی
- ایجاد گزارشات نادرست
- نقض قوانین حسابداری
نکته مهم: در سیستمهای مالی، یک خطای کوچک برنامهنویسی میتواند منجر به عدم تطبیق حسابها و مشکلات جدی مالی شود.
مبانی حسابداری
بدهکار و بستانکار چیست؟
- بدهکار (Debit): تراکنشهایی که مقدار منفی دارند - افزایش دارایی یا هزینه، کاهش بدهی
- بستانکار (Credit): تراکنشهایی که مقدار مثبت دارند - کاهش دارایی یا هزینه، افزایش بدهی یا درآمد
در امیر، تراکنشها با یک فیلد value ذخیره میشوند:
- مقدار منفی = بدهکار
- مقدار مثبت = بستانکار
// example: خرید ۱۰۰,۰۰۰ تومان کالا نقداً
// تراکنش 1: افزایش موجودی کالا (بدهکار)
$transaction1 = Transaction::create([
'subject_id' => $inventorySubject->id,
'value' => -100000, // منفی = بدهکار
'desc' => 'خرید کالا'
]);
// تراکنش 2: کاهش صندوق (بستانکار)
$transaction2 = Transaction::create([
'subject_id' => $cashSubject->id,
'value' => 100000, // مثبت = بستانکار
'desc' => 'پرداخت وجه نقد'
]);
قانون طلایی: موازنه
در هر سند، مجموع بدهکار = مجموع بستانکار یعنی مجموع همه تراکنشها باید برابر صفر باشد.
نکته مهم: همیشه اسناد موازنه نیستند! امیر برای راحتی کار اجازه ثبت اسناد غیرموازنه میدهد ولی:
- اسناد دستی: میتوانند غیرموازنه ثبت شوند (برای سهولت کار)
- اسناد خودکار: همیشه باید موازنه باشند (فاکتور، چک و…)
- تأیید نهایی: تنها اسناد موازنه قابل تأیید نهایی هستند
$totalValue = $document->transactions->sum('value');
if ($totalValue != 0) {
throw ValidationException::withMessages([
'transactions' => ['سند متوازن نیست'],
]);
}
ساختار سرفصلهای حسابداری
سرفصلهای اصلی
010 - بانکها
011 - موجودیهای نقدی
├── 011001 - صندوق
└── 011002 - تنخواه گردانها
012 - بدهکاران/بستانکاران
└── 012001 - اشخاص متفرقه
040 - هزینه ها
├── 040001 - حقوق پرسنل
├── 040002 - آب
├── 040003 - برق
041 - قیمت تمام شده کالای فروش رفته
└── 041001 - قیمت تمام شده کالای فروش رفته
050 - درآمدها
└── 050001 - درآمد متفرقه
060 - فروش
└── 060001 - فروش
کدینگ سرفصلها
نکته مهم: کدینگ سرفصلها در امیر سه تا سه تا انجام میشود:
- سرفصل کل:
010 - سرفصل معین:
011001 - سرفصل تفضیلی:
011001001 - سرفصل تفضیلی سطح بعد:
011001001001 - و به همین ترتیب بینهایت ادامه دارد
// 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)
تعریف: گزارش عملکرد مالی در یک دوره زمانی (نه یک تاریخ مشخص)
محتوای گزارش:
- درآمدها: فروش، درآمدهای غیرعملیاتی
- هزینهها: بهای کالای فروخته شده، هزینههای عملیاتی، هزینههای مالی
- سود/زیان خالص: درآمدها منهای هزینهها
ساختار کلاسیک:
درآمد فروش
- بهای کالای فروخته شده
= سود ناخالص
- هزینههای عملیاتی
= سود عملیاتی
+ درآمدهای غیرعملیاتی
- هزینههای غیرعملیاتی
= سود خالص
تفاوتهای کلیدی:
| ویژگی | ترازنامه | سود و زیان |
|---|---|---|
| زمان | یک تاریخ مشخص | یک دوره زمانی |
| حسابها | دائمی (دارایی، بدهی، سرمایه) | موقت (درآمد، هزینه) |
| هدف | وضعیت مالی | عملکرد مالی |
| تجدید | سالانه باقی میماند | سالانه صفر میشود |
نکات مهم در امیر:
- حسابهای درآمد و هزینه در پایان سال به حساب “سود انباشته” منتقل میشوند
- امکان تولید گزارش برای دورههای مختلف (ماهانه، فصلی، سالانه)
- در صورت زیان، مقدار منفی در سرمایه منظور میشود
ارتباط گزارشها:
- دفتر روزنامه → دادههای خام همه تراکنشها
- دفتر کل → تجمیع تراکنشها بر اساس سرفصل
- ترازنامه → مانده حسابهای دائمی در یک تاریخ
- سود و زیان → عملکرد حسابهای موقت در یک دوره
مهم: در امیر همه این گزارشها از یک پایگاه داده یکپارچه تولید میشوند و باید با یکدیگر تطبیق داشته باشند.
تنظیمات و پیکربندی
فلسفه طراحی Config در امیر
برای جلوگیری از hard-code کردن روابط بین بخشهای مختلف سیستم، تمام اتصالات و تنظیمات در جدول configs ذخیره میشوند. این طراحی به کاربران اجازه میدهد بدون تغییر کد، تنظیمات را شخصیسازی کنند.
ConfigLoader Middleware
کدهای ConfigLoader تمام تنظیمات را از پایگاه داده خوانده و در config() Laravel بارگذاری میکند تا در سراسر برنامه قابل دسترسی باشند.
ویژگیها:
- خوانش خودکار تمام configs از پایگاه داده
- بارگذاری در
config('amir.key')برای دسترسی آسان - اجرا در هر درخواست HTTP
برای اطلاعات بیشتر ConfigController را مشاهده کنید
نحوه دسترسی به تنظیمات
در کد Laravel:
$cashSubjectId = config('amir.cash_book');
// با مقدار پیشفرض
$bankSubjectId = config('amir.bank', null);
برای تنظیمات خاص شرکت:
$config = Config::where('company_id', session('active-company-id'))
->where('key', 'cash_book')
->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();
نکته نهایی: در سیستمهای مالی، دقت و پایبندی به استانداردهای حسابداری بیش از سرعت اولویت دارد. همیشه کدهای خود را چندین بار تست کنید و از سرویسهای ارائه شده در امیر استفاده کنید.