این راهنما برای توسعهدهندگانی نوشته شده که میخواهند در پروژه امیر مشارکت کنند و نیاز به درک مفاهیم حسابداری و ساختار کد دارند.
عدم درک مفاهیم حسابداری منجر به:
نکته مهم: در سیستمهای مالی، یک خطای کوچک برنامهنویسی میتواند منجر به عدم تطبیق حسابها و مشکلات جدی مالی شود.
در امیر، تراکنشها با یک فیلد 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 new DocumentServiceException('سند متوازن نیست');
}
010 - بانکها
011 - موجودیهای نقدی
├── 011001 - صندوق
└── 011002 - تنخواه گردانها
012 - بدهکاران/بستانکاران
└── 012001 - اشخاص متفرقه
040 - هزینه ها
├── 040001 - حقوق پرسنل
├── 040002 - آب
├── 040003 - برق
041 - قیمت تمام شده کالای فروش رفته
└── 041001 - قیمت تمام شده کالای فروش رفته
050 - درآمدها
└── 050001 - درآمد متفرقه
060 - فروش
└── 060001 - فروش
نکته مهم: کدینگ سرفصلها در امیر سه تا سه تا انجام میشود:
010011001011001001011001001001// 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) تعریف میشود.
$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
// و بینهایت ادامه دارد
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
);
مهم: همیشه از این سرویس برای کار با اسناد استفاده کنید:
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);
برای ایجاد سرفصلها (برای تولید خودکار کد حسابداری حتما از این سرویس استفاده کنید):
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' است
]);
برای مدیریت سالهای مالی و مهاجرت داده:
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 استفاده میکنند:
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 برای هر شرکت جداگانه نگهداری میشود:
تعریف: لیست تمام اسناد و تراکنشها به ترتیب تاریخ ثبت
خصوصیات:
کاربرد:
نکات مهم:
تعریف: گردآوری و خلاصهسازی تراکنشها بر اساس هر سرفصل حسابداری
تفاوت با دفتر روزنامه:
انواع دفتر کل در امیر:
مثال عملی:
انتخاب سرفصل: "صندوق" (011/001)
نتیجه: همه تراکنشهای مربوط به این صندوق خاص
نکات مهم:
تعریف: گزارش وضعیت مالی در یک تاریخ مشخص (عکس فوری از داراییها، بدهیها و سرمایه)
ساختار کلاسیک:
داراییها = بدهیها + سرمایه
محتوای ترازنامه:
نکات مهم:
تفاوت با سایر گزارشها:
تعریف: گزارش عملکرد مالی در یک دوره زمانی (نه یک تاریخ مشخص)
محتوای گزارش:
ساختار کلاسیک:
درآمد فروش
- بهای کالای فروخته شده
= سود ناخالص
- هزینههای عملیاتی
= سود عملیاتی
+ درآمدهای غیرعملیاتی
- هزینههای غیرعملیاتی
= سود خالص
تفاوتهای کلیدی:
| ویژگی | ترازنامه | سود و زیان |
|---|---|---|
| زمان | یک تاریخ مشخص | یک دوره زمانی |
| حسابها | دائمی (دارایی، بدهی، سرمایه) | موقت (درآمد، هزینه) |
| هدف | وضعیت مالی | عملکرد مالی |
| تجدید | سالانه باقی میماند | سالانه صفر میشود |
نکات مهم در امیر:
ارتباط گزارشها:
مهم: در امیر همه این گزارشها از یک پایگاه داده یکپارچه تولید میشوند و باید با یکدیگر تطبیق داشته باشند.
برای جلوگیری از hard-code کردن روابط بین بخشهای مختلف سیستم، تمام اتصالات و تنظیمات در جدول configs ذخیره میشوند. این طراحی به کاربران اجازه میدهد بدون تغییر کد، تنظیمات را شخصیسازی کنند.
کدهای ConfigLoader تمام تنظیمات را از پایگاه داده خوانده و در config() Laravel بارگذاری میکند تا در سراسر برنامه قابل دسترسی باشند.
ویژگیها:
config('amir.key') برای دسترسی آسانبرای اطلاعات بیشتر 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'
$totalValue = collect($transactions)->sum('value'); // برای اسناد خودکار (فاکتور، چک و...)
if ($totalValue != 0) {
throw new ValidationException('سند خودکار باید متوازن باشد');
}
// برای اسناد دستی موازنه اجباری نیست
DB::transaction(function() {
// عملیات مالی
$document = DocumentService::createDocument($user, $data, $transactions);
});
$document = Document::create($data); // اشتباه ❌
// درست ✅
$document = DocumentService::createDocument($user, $data, $transactions);
$subject->code = $subject->generateCode();
// برای نمایش کد فرمت شده
echo $subject->formattedCode(); // "001/002/003"
$subjects = Subject::all(); // درست ✅ - با scope خودکار
// برای کار بدون scope
$allSubjects = Subject::withoutGlobalScope(FiscalYearScope::class)->get();
نکته نهایی: در سیستمهای مالی، دقت و پایبندی به استانداردهای حسابداری بیش از سرعت اولویت دارد. همیشه کدهای خود را چندین بار تست کنید و از سرویسهای ارائه شده در امیر استفاده کنید.