20 KiB
Executable file
سناریو نهایی: فاکتور تولید با استفاده از فرمول تولید (BOM)
📋 خلاصه اجرایی
این سند شامل سناریو کامل، مشکلات شناسایی شده، و پیشنهادات نهایی برای پیادهسازی صحیح فاکتور تولید با استفاده از فرمول تولید (BOM) است.
🎯 اصل طراحی
انفجار فرمول تولید صرفاً از طریق فاکتور تولید انجام میشود و این کار از نظر حسابداری اصولیتر است.
مزایای این رویکرد:
- ✅ یکپارچگی حسابداری: تمام عملیات تولید در یک سند (فاکتور تولید) ثبت میشود
- ✅ ردیابی کامل: هر فاکتور تولید به فرمول(های) استفاده شده لینک میشود
- ✅ کنترل بهتر: جلوگیری از خطاهای انسانی در وارد کردن دستی مواد اولیه
- ✅ سازگاری با اصول حسابداری: ثبتهای حسابداری به درستی انجام میشود
🔍 مشکلات شناسایی شده
1. مشکلات فوری (Blocker)
مشکل 1.1: فایل BomExplosionWidget وجود ندارد
- وضعیت: فایل
lib/widgets/invoice/bom_explosion_widget.dartوجود ندارد - تأثیر: کامپایل انجام نمیشود
- اولویت: فوری
- راهحل: ایجاد فایل با عملکرد کامل
مشکل 1.2: خطای کامپایل
Error: Error when reading 'lib/widgets/invoice/bom_explosion_widget.dart':
No such file or directory
- محل: خط 27 و 2440 در
new_invoice_page.dart - اولویت: فوری
2. مشکلات مهم (Critical)
مشکل 2.1: عدم وجود فیلد production_operations_total در UI
- وضعیت: بکاند از این فیلد استفاده میکند (خط 1694 و 2492)
- مشکل: در UI فیلدی برای وارد کردن هزینه عملیات/سربار تولید وجود ندارد
- تأثیر حسابداری: هزینه عملیات ثبت نمیشود
- اولویت: مهم
- راهحل: اضافه کردن فیلد در تب "تنظیمات" یا تب "کالاها"
مشکل 2.2: عدم تنظیم cost_price برای محصولات نهایی
- وضعیت: بکاند از
cost_priceدرextra_infoاستفاده میکند (خط 2507) - مشکل: در UI امکان تنظیم
cost_priceبرای ردیفهایmovement: "in"وجود ندارد - تأثیر: محاسبه هزینه محصول نهایی نادرست میشود
- اولویت: مهم
- راهحل:
- محاسبه خودکار:
cost_price = (مواد اولیه + هزینه عملیات) / تعداد محصول - یا امکان ویرایش دستی در جدول ردیفها
- محاسبه خودکار:
مشکل 2.3: API explode_bom اطلاعات کامل برنمیگرداند
- مشکلات:
movementدر پاسخ API نیست (باید در Frontend تنظیم شود)bom_idدر پاسخ API نیست (باید در Frontend اضافه شود)cost_priceدر پاسخ API نیست (باید در Frontend محاسبه شود)
- اولویت: مهم
3. مشکلات متوسط (Medium)
مشکل 3.1: مدیریت ناقص _bomIds
- مشکلات:
- اگر کاربر ردیفهای مربوط به یک BOM را حذف کند،
_bomIdمربوطه از_bomIdsحذف نمیشود - همگامسازی با حذف ردیفها انجام نمیشود
- اگر کاربر ردیفهای مربوط به یک BOM را حذف کند،
- اولویت: متوسط
مشکل 3.2: عدم پشتیبانی کامل در صفحه ویرایش
- مشکلات:
BomExplosionWidgetوجود نداردbom_idsازextra_infoبارگذاری نمیشود- اعتبارسنجی
movementانجام نمیشود
- اولویت: متوسط
مشکل 3.3: اعتبارسنجی ناقص
- Frontend: فقط خروجیهای BOM بررسی میشود
- Backend: فقط
movementبررسی میشود،bom_idsبررسی نمیشود - اولویت: متوسط
📐 سناریو کامل پیادهسازی
مرحله 1: تعریف فرمول تولید (بخش کالاها)
مکان: product_bom_section.dart
عملکرد:
- ✅ کاربر فرمولهای تولید را تعریف میکند
- ✅ مواد اولیه (items) را اضافه میکند
- ✅ خروجیها (outputs) را اضافه میکند
- ✅ عملیات (operations) را اضافه میکند
- ✅ فرمول را ذخیره میکند
- ✅ (اختیاری) فرمولی را به عنوان پیشفرض تنظیم میکند
- ✅ کارت راهنما: "برای استفاده از این فرمول، به بخش فاکتور تولید بروید"
نکته: در این بخش هیچ دکمه "انفجار فرمول" وجود ندارد ✅
مرحله 2: صدور فاکتور تولید
مکان: new_invoice_page.dart
2.1: انتخاب نوع فاکتور
- کاربر نوع فاکتور را "تولید" انتخاب میکند
- سیستم بررسی میکند که آیا فرمولی منفجر شده است؟
2.2: ویجت انفجار فرمول (BomExplosionWidget)
عملکرد:
- نمایش: فقط برای
InvoiceType.production - انتخاب کالا: کاربر کالای تولیدی را انتخاب میکند
- انتخاب فرمول:
- اگر فرمول پیشفرض وجود دارد، به صورت پیشفرض انتخاب میشود
- در غیر این صورت، کاربر فرمول را انتخاب میکند
- ورود مقدار: کاربر مقدار تولید را وارد میکند
- انفجار: فراخوانی API
explode_bom - تبدیل نتایج:
items→ ردیفهای فاکتور با:movement: "out"درextra_infobom_idدرextra_infocost_priceاز COGS کالاwarehouse_idازsuggested_warehouse_id(اگر وجود دارد)
outputs→ ردیفهای فاکتور با:movement: "in"درextra_infobom_idدرextra_infocost_priceمحاسبه شده (مواد اولیه + هزینه عملیات) / تعدادwarehouse_id(قابل ویرایش)
- افزودن به فاکتور: ردیفها به
_lineItemsاضافه میشوند - ذخیره
bom_id:bomIdبه_bomIdsاضافه میشود
2.3: ویرایش ردیفها (پس از انفجار)
قابل ویرایش:
- ✅ مقدار (quantity)
- ✅ قیمت (unit_price)
- ✅ انبار (warehouse_id)
- ✅
cost_price(برای محصولات نهایی)
غیرقابل ویرایش:
- ❌
movement(برای جلوگیری از خطا) - ❌
bom_id(برای ردیابی)
2.4: فیلد هزینه عملیات
مکان: تب "تنظیمات" یا تب "کالاها"
نوع: TextField با اعتبارسنجی عددی
ذخیره: در extra_info فاکتور به عنوان production_operations_total
محاسبه هزینه محصول نهایی:
cost_price = (مجموع هزینه مواد اولیه + هزینه عملیات) / تعداد محصول نهایی
2.5: اعتبارسنجی قبل از ذخیره
Frontend (_validateAndBuildPayload):
- ✅ بررسی وجود حداقل یک فرمول منفجر شده (
_bomIds.isEmpty) - ✅ بررسی وجود
movementدر تمام ردیفها - ✅ بررسی وجود حداقل یک ردیف با
movement: "out"(مواد اولیه) - ✅ بررسی وجود حداقل یک ردیف با
movement: "in"(محصول نهایی) - ✅ بررسی وجود خروجیهای BOM در فاکتور (
_validateBomOutputs)
Backend (create_invoice):
- ✅ بررسی وجود
movementدر تمام ردیفها - ✅ بررسی وجود حداقل یک ردیف با
movement: "out" - ✅ بررسی وجود حداقل یک ردیف با
movement: "in" - ✅ (پیشنهادی) بررسی وجود
bom_idsدرextra_infoفاکتور
2.6: ساخت Payload
در extra_info فاکتور:
{
"bom_ids": [1, 2], // لیست ID فرمولهای استفاده شده
"production_operations_total": 50000, // هزینه عملیات/سربار
"post_inventory": true,
"totals": {...}
}
در extra_info هر ردیف:
{
"movement": "out" | "in",
"bom_id": 1, // ID فرمول استفاده شده
"cost_price": 1000, // برای محصولات نهایی
"warehouse_id": 5,
"unit_price": 1000,
...
}
مرحله 3: ثبت حسابداری (Backend)
محاسبات:
-
مواد اولیه (movement: "out"):
- محاسبه COGS از
cogs_amountیاcost_priceیاunit_price - مجموع:
total_materials_cost
- محاسبه COGS از
-
هزینه عملیات:
- از
production_operations_totalدرheader_extra - مجموع:
operations_total
- از
-
محصولات نهایی (movement: "in"):
- اولویت:
cost_price>unit_price> (مواد اولیه + عملیات) / تعداد - مجموع:
total_finished_cost
- اولویت:
ثبتهای حسابداری:
بدهکار: WIP (مواد اولیه + هزینه عملیات)
بستانکار: موجودی مواد اولیه
بدهکار: موجودی محصولات نهایی
بستانکار: WIP
🛠️ پیشنهادات پیادهسازی
پیشنهاد 1: ایجاد BomExplosionWidget
فایل: hesabixUI/hesabix_ui/lib/widgets/invoice/bom_explosion_widget.dart
ساختار:
class BomExplosionWidget extends StatefulWidget {
final int businessId;
final Function(List<InvoiceLineItem>, int bomId) onExploded;
// ...
}
class _BomExplosionWidgetState extends State<BomExplosionWidget> {
// State variables
Product? _selectedProduct;
ProductBOM? _selectedBom;
double? _productionQuantity;
bool _isLoading = false;
// Methods
Future<void> _loadBomsForProduct() async { ... }
Future<void> _explodeAndAdd() async { ... }
List<InvoiceLineItem> _convertToLineItems(BomExplosionResult result, int bomId) { ... }
}
عملکرد:
- نمایش کارت برجسته در تب "کالاها"
- دیالوگ انتخاب کالا و فرمول
- ورود مقدار تولید
- فراخوانی API
explode_bom - تبدیل نتایج به
InvoiceLineItemبا تنظیم:movement: "out"برایitemsmovement: "in"برایoutputsbom_idدرextra_infocost_priceاز COGS (برای مواد اولیه)cost_priceمحاسبه شده (برای محصولات نهایی)
- فراخوانی
onExplodedبا لیست ردیفها وbomId
پیشنهاد 2: بهبود API explode_bom (اختیاری)
پیشنهاد: اضافه کردن bom_id به پاسخ API
return {
"items": explosion_items,
"outputs": out_scaled,
"bom_id": bom.id, # اضافه شود
}
نکته: اگر این تغییر انجام نشود، باید در Frontend bomId را از انتخاب کاربر بگیریم.
پیشنهاد 3: اضافه کردن فیلد production_operations_total
مکان: تب "تنظیمات" در new_invoice_page.dart
کد:
// در _buildSettingsTab()
if (_selectedInvoiceType == InvoiceType.production) ...[
TextFormField(
decoration: InputDecoration(
labelText: 'هزینه عملیات/سربار تولید',
helperText: 'هزینه عملیات و سربار تولید (ریال)',
),
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
_productionOperationsTotal = double.tryParse(value);
});
},
),
],
ذخیره در _validateAndBuildPayload:
if (_selectedInvoiceType == InvoiceType.production) {
if (_productionOperationsTotal != null && _productionOperationsTotal! > 0) {
extraInfo['production_operations_total'] = _productionOperationsTotal;
}
}
پیشنهاد 4: محاسبه cost_price برای محصولات نهایی
در BomExplosionWidget پس از انفجار:
List<InvoiceLineItem> _convertToLineItems(
BomExplosionResult result,
int bomId,
double operationsTotal,
) {
final lineItems = <InvoiceLineItem>[];
// محاسبه مجموع هزینه مواد اولیه
double totalMaterialsCost = 0;
for (final item in result.items) {
// دریافت COGS از محصول
final cogs = _getProductCogs(item.componentProductId);
totalMaterialsCost += item.requiredQty * cogs;
}
// محاسبه هزینه کل
final totalCost = totalMaterialsCost + operationsTotal;
// محاسبه تعداد کل محصولات نهایی
double totalOutputQty = 0;
for (final output in result.outputs) {
totalOutputQty += output.ratio;
}
// محاسبه cost_price برای هر محصول نهایی
final costPricePerUnit = totalOutputQty > 0
? totalCost / totalOutputQty
: 0;
// تبدیل items به ردیفها (movement: "out")
for (final item in result.items) {
final cogs = _getProductCogs(item.componentProductId);
lineItems.add(InvoiceLineItem(
productId: item.componentProductId,
quantity: item.requiredQty,
unitPrice: cogs,
extraInfo: {
'movement': 'out',
'bom_id': bomId,
'cost_price': cogs,
'warehouse_id': item.suggestedWarehouseId,
},
));
}
// تبدیل outputs به ردیفها (movement: "in")
for (final output in result.outputs) {
lineItems.add(InvoiceLineItem(
productId: output.outputProductId,
quantity: output.ratio,
unitPrice: costPricePerUnit,
extraInfo: {
'movement': 'in',
'bom_id': bomId,
'cost_price': costPricePerUnit,
},
));
}
return lineItems;
}
پیشنهاد 5: بهبود مدیریت _bomIds
در new_invoice_page.dart:
// هنگام حذف ردیف
void _onLineItemRemoved(int index) {
final removedItem = _lineItems[index];
final bomId = removedItem.extraInfo?['bom_id'];
setState(() {
_lineItems.removeAt(index);
// بررسی اینکه آیا ردیف دیگری با این bom_id وجود دارد
if (bomId != null) {
final hasOtherItems = _lineItems.any(
(item) => item.extraInfo?['bom_id'] == bomId
);
if (!hasOtherItems) {
_bomIds.remove(bomId);
}
}
// محاسبه مجدد جمعها
_recalculateTotals();
});
}
پیشنهاد 6: پشتیبانی در صفحه ویرایش
در edit_invoice_page.dart:
- بارگذاری
bom_ids:
final bomIds = _originalExtraInfo['bom_ids'] as List<dynamic>?;
_bomIds = bomIds?.map((e) => e as int).toSet() ?? <int>{};
- افزودن
BomExplosionWidget:
if (_selectedInvoiceType == InvoiceType.production) ...[
BomExplosionWidget(
businessId: widget.businessId,
onExploded: (newItems, bomId) {
setState(() {
_lineItems = [..._lineItems, ...newItems];
_bomIds.add(bomId);
_recalculateTotals();
});
},
),
],
- اعتبارسنجی مشابه صفحه ایجاد
📊 اولویتبندی کارها
فاز 1: رفع مشکلات فوری (اولویت بالا)
-
✅ ایجاد فایل
BomExplosionWidget- زمان تخمینی: 4-6 ساعت
- وابستگی: ندارد
-
✅ اضافه کردن فیلد
production_operations_total- زمان تخمینی: 1-2 ساعت
- وابستگی: ندارد
-
✅ تبدیل
BomExplosionResultبهInvoiceLineItem- زمان تخمینی: 2-3 ساعت
- وابستگی: نیاز به
BomExplosionWidget
فاز 2: بهبود عملکرد (اولویت متوسط)
-
✅ محاسبه
cost_priceبرای محصولات نهایی- زمان تخمینی: 2-3 ساعت
- وابستگی: نیاز به فیلد
production_operations_total
-
✅ بهبود مدیریت
_bomIds- زمان تخمینی: 1-2 ساعت
- وابستگی: ندارد
-
✅ بهبود اعتبارسنجی
- زمان تخمینی: 2-3 ساعت
- وابستگی: ندارد
فاز 3: تکمیل (اولویت پایین)
-
✅ پشتیبانی در صفحه ویرایش
- زمان تخمینی: 3-4 ساعت
- وابستگی: نیاز به
BomExplosionWidget
-
✅ بهبود API
explode_bom(اختیاری)- زمان تخمینی: 1-2 ساعت
- وابستگی: ندارد
📝 نکات حسابداری مهم
1. محاسبه هزینه تمامشده
فرمول:
هزینه تمامشده = هزینه مواد اولیه + هزینه عملیات/سربار
برای هر واحد محصول:
cost_price = هزینه تمامشده / تعداد محصولات نهایی
2. ثبتهای حسابداری
برای مواد اولیه (movement: "out"):
- بدهکار: WIP (Work In Process)
- بستانکار: موجودی مواد اولیه
- مبلغ: مجموع COGS مواد اولیه
برای هزینه عملیات:
- بدهکار: WIP
- بستانکار: حساب هزینه عملیات/سربار
- مبلغ:
production_operations_total
برای محصولات نهایی (movement: "in"):
- بدهکار: موجودی محصولات نهایی
- بستانکار: WIP
- مبلغ:
cost_price × quantityبرای هر محصول
3. اعتبارسنجی توازن
باید بررسی شود:
مجموع بدهکار WIP = مجموع بستانکار WIP
یعنی:
(مواد اولیه + هزینه عملیات) = مجموع هزینه محصولات نهایی
✅ چکلیست پیادهسازی
Frontend
- ایجاد فایل
BomExplosionWidget - تبدیل
BomExplosionResultبهInvoiceLineItem - تنظیم
movement: "out"برای مواد اولیه - تنظیم
movement: "in"برای محصولات نهایی - اضافه کردن
bom_idبهextra_infoهر ردیف - محاسبه
cost_priceبرای محصولات نهایی - اضافه کردن فیلد
production_operations_total - ذخیره
bom_idsدرextra_infoفاکتور - اعتبارسنجی قبل از ذخیره
- بهبود مدیریت
_bomIds - پشتیبانی در صفحه ویرایش
Backend
- (اختیاری) اضافه کردن
bom_idبه پاسخ APIexplode_bom - (اختیاری) بهبود اعتبارسنجی در
create_invoice - بررسی وجود
bom_idsدرextra_infoفاکتور
🎯 نتیجهگیری
این سناریو یک راهحل کامل و اصولی برای پیادهسازی فاکتور تولید با استفاده از فرمول تولید ارائه میدهد. با رعایت این سناریو:
- ✅ انفجار فرمول فقط از طریق فاکتور تولید انجام میشود
- ✅ تمام عملیات تولید در یک سند ثبت میشود
- ✅ ثبتهای حسابداری به درستی انجام میشود
- ✅ ردیابی کامل فرمولهای استفاده شده امکانپذیر است
- ✅ کنترل و اعتبارسنجی مناسب وجود دارد
تاریخ ایجاد: 2024 نسخه: 1.0 وضعیت: پیشنهاد نهایی