hesabixArc/hesabixAPI/docs/INVOICE_ISSUES_AND_SCENARIOS.md
2025-10-31 18:02:23 +00:00

12 KiB

مشکلات و سناریوهای ثبت فاکتور

🔴 مشکلات شناسایی شده

1. عدم تطابق نام فیلد نوع فاکتور

  • مشکل: در UI از type استفاده می‌شود اما API از invoice_type انتظار دارد
  • مکان: new_invoice_page.dart خط 801
  • وضعیت فعلی: 'type': _selectedInvoiceType!.value (مثل 'sales', 'purchase')
  • وضعیت مورد انتظار: 'invoice_type': 'invoice_sales', 'invoice_purchase', و غیره

2. عدم تطابق فرمت نوع فاکتور

  • مشکل: UI مقادیر ساده ارسال می‌کند ('sales') اما API فرمت کامل می‌خواهد ('invoice_sales')
  • مکان: تبدیل از InvoiceType.value به فرمت API
  • مثال: 'sales' باید به 'invoice_sales' تبدیل شود

3. عدم استخراج person_id از customer/seller

  • مشکل: API فقط extra_info.person_id را می‌خواند اما UI customer_id و seller_id را ارسال می‌کند
  • مکان: invoice_service.py خط 393-399 (_person_id_from_header)
  • وضعیت فعلی: customer_id و seller_id در payload هستند اما API آن‌ها را نمی‌خواند

4. عدم تطابق نام فیلد تاریخ

  • مشکل: UI از invoice_date استفاده می‌کند اما API از document_date انتظار دارد
  • مکان: new_invoice_page.dart خط 804

5. عدم تطابق ساختار خطوط

  • مشکل: UI از line_items استفاده می‌کند اما API از lines انتظار دارد
  • مکان: new_invoice_page.dart خط 823

6. عدم وجود فیلد تامین‌کننده برای فاکتور خرید

  • مشکل: برای فاکتورهای خرید و برگشت از خرید، باید تامین‌کننده (supplier) انتخاب شود نه مشتری
  • مکان: UI فقط CustomerPickerWidget دارد که برای خرید مناسب نیست

7. عدم ارسال person_id در extra_info

  • مشکل: person_id باید در extra_info ارسال شود نه به صورت مستقیم
  • مکان: new_invoice_page.dart - ساخت payload

📋 سناریوهای صحیح برای هر نوع فاکتور

فاکتور فروش (Sales Invoice)

فیلدهای مورد نیاز:

  • نوع فاکتور: invoice_sales
  • مشتری (Customer): الزامی - باید به person_id تبدیل شود
  • فروشنده/بازاریاب (Seller): اختیاری - فقط برای کارمزد
  • تراکنش‌ها: دریافت (Receipt) - اختیاری
  • تاریخ فاکتور: document_date
  • تاریخ سررسید: due_date (اختیاری)

ساختار payload صحیح:

{
  "invoice_type": "invoice_sales",
  "document_date": "2024-01-15",
  "due_date": "2024-02-15",
  "currency_id": 1,
  "is_proforma": false,
  "description": "فروش محصولات",
  "extra_info": {
    "person_id": 123,  // از customer_id استخراج شود
    "seller_id": 456,  // اختیاری
    "commission": {
      "type": "percentage",
      "value": 5.5
    },
    "totals": {
      "gross": 1000000,
      "discount": 50000,
      "tax": 95000,
      "net": 1045000
    }
  },
  "lines": [
    {
      "product_id": 1,
      "quantity": 10,
      "extra_info": {
        "unit_price": 100000,
        "line_discount": 5000,
        "tax_amount": 9500,
        "movement": "out"
      }
    }
  ],
  "payments": [
    {
      "transaction_type": "cash",
      "amount": 500000,
      "transaction_date": "2024-01-15"
    }
  ]
}

فاکتور برگشت از فروش (Sales Return)

فیلدهای مورد نیاز:

  • نوع فاکتور: invoice_sales_return
  • مشتری (Customer): الزامی - همان مشتری فاکتور اصلی
  • فروشنده/بازاریاب: اختیاری - همان فروشنده اصلی
  • تراکنش‌ها: پرداخت (Payment) - اختیاری
  • تاریخ فاکتور: document_date

ساختار payload صحیح:

{
  "invoice_type": "invoice_sales_return",
  "document_date": "2024-01-20",
  "currency_id": 1,
  "is_proforma": false,
  "extra_info": {
    "person_id": 123,  // از customer_id استخراج شود
    "totals": {
      "gross": 500000,
      "discount": 0,
      "tax": 47500,
      "net": 547500
    }
  },
  "lines": [
    {
      "product_id": 1,
      "quantity": 5,
      "extra_info": {
        "unit_price": 100000,
        "movement": "in"
      }
    }
  ]
}

فاکتور خرید (Purchase Invoice)

فیلدهای مورد نیاز:

  • نوع فاکتور: invoice_purchase
  • تامین‌کننده (Supplier): الزامی - باید PersonPickerWidget با فیلتر person_types: ['تامین‌کننده', 'فروشنده'] باشد
  • تراکنش‌ها: پرداخت (Payment) - اختیاری
  • تاریخ فاکتور: document_date

مشکل فعلی: UI فقط CustomerPickerWidget دارد که برای خرید مناسب نیست!

ساختار payload صحیح:

{
  "invoice_type": "invoice_purchase",
  "document_date": "2024-01-15",
  "currency_id": 1,
  "is_proforma": false,
  "extra_info": {
    "person_id": 789,  // از supplier_id استخراج شود
    "totals": {
      "gross": 2000000,
      "discount": 100000,
      "tax": 190000,
      "net": 2090000
    }
  },
  "lines": [
    {
      "product_id": 2,
      "quantity": 20,
      "extra_info": {
        "unit_price": 100000,
        "movement": "in"
      }
    }
  ],
  "payments": [
    {
      "transaction_type": "cash",
      "amount": 1000000,
      "transaction_date": "2024-01-15"
    }
  ]
}

فاکتور برگشت از خرید (Purchase Return)

فیلدهای مورد نیاز:

  • نوع فاکتور: invoice_purchase_return
  • تامین‌کننده (Supplier): الزامی - همان تامین‌کننده فاکتور خرید اصلی
  • تراکنش‌ها: دریافت (Receipt) - اختیاری
  • تاریخ فاکتور: document_date

ساختار payload صحیح:

{
  "invoice_type": "invoice_purchase_return",
  "document_date": "2024-01-20",
  "currency_id": 1,
  "is_proforma": false,
  "extra_info": {
    "person_id": 789,  // از supplier_id استخراج شود
    "totals": {
      "gross": 500000,
      "discount": 0,
      "tax": 47500,
      "net": 547500
    }
  },
  "lines": [
    {
      "product_id": 2,
      "quantity": 5,
      "extra_info": {
        "unit_price": 100000,
        "movement": "out"
      }
    }
  ]
}

فاکتور مصرف مستقیم (Direct Consumption)

فیلدهای مورد نیاز:

  • نوع فاکتور: invoice_direct_consumption
  • مشتری/تامین‌کننده: نیاز ندارد
  • تراکنش‌ها: نیاز ندارد
  • تاریخ فاکتور: document_date

ساختار payload صحیح:

{
  "invoice_type": "invoice_direct_consumption",
  "document_date": "2024-01-15",
  "currency_id": 1,
  "is_proforma": false,
  "extra_info": {
    "totals": {
      "gross": 0,
      "discount": 0,
      "tax": 0,
      "net": 0
    }
  },
  "lines": [
    {
      "product_id": 3,
      "quantity": 5,
      "extra_info": {
        "movement": "out"
      }
    }
  ]
}

فاکتور ضایعات (Waste)

فیلدهای مورد نیاز:

  • نوع فاکتور: invoice_waste
  • مشتری/تامین‌کننده: نیاز ندارد
  • تراکنش‌ها: نیاز ندارد
  • تاریخ فاکتور: document_date

ساختار payload صحیح:

{
  "invoice_type": "invoice_waste",
  "document_date": "2024-01-15",
  "currency_id": 1,
  "is_proforma": false,
  "extra_info": {
    "totals": {
      "gross": 0,
      "discount": 0,
      "tax": 0,
      "net": 0
    }
  },
  "lines": [
    {
      "product_id": 4,
      "quantity": 2,
      "extra_info": {
        "movement": "out"
      }
    }
  ]
}

فاکتور تولید (Production)

فیلدهای مورد نیاز:

  • نوع فاکتور: invoice_production
  • مشتری/تامین‌کننده: نیاز ندارد
  • تراکنش‌ها: نیاز ندارد
  • تاریخ فاکتور: document_date
  • خطوط خروجی (مواد اولیه): movement: "out"
  • خطوط ورودی (کالای ساخته شده): movement: "in"

ساختار payload صحیح:

{
  "invoice_type": "invoice_production",
  "document_date": "2024-01-15",
  "currency_id": 1,
  "is_proforma": false,
  "extra_info": {
    "totals": {
      "gross": 0,
      "discount": 0,
      "tax": 0,
      "net": 0
    }
  },
  "lines": [
    {
      "product_id": 5,  // مواد اولیه
      "quantity": 10,
      "extra_info": {
        "movement": "out"
      }
    },
    {
      "product_id": 6,  // کالای ساخته شده
      "quantity": 5,
      "extra_info": {
        "movement": "in"
      }
    }
  ]
}

🔧 راه‌حل‌های پیشنهادی

1. تبدیل صحیح نوع فاکتور

String _convertInvoiceTypeToApi(InvoiceType type) {
  return 'invoice_${type.value}';
}

2. استخراج person_id از customer/seller

// در ساخت payload:
if (_selectedInvoiceType == InvoiceType.sales || 
    _selectedInvoiceType == InvoiceType.salesReturn) {
  // برای فروش: person_id از customer
  if (_selectedCustomer != null) {
    extraInfo['person_id'] = _selectedCustomer!.id;
  }
}

if (_selectedInvoiceType == InvoiceType.purchase || 
    _selectedInvoiceType == InvoiceType.purchaseReturn) {
  // برای خرید: person_id از supplier
  if (_selectedSupplier != null) {
    extraInfo['person_id'] = _selectedSupplier!.id;
  }
}

3. افزودن PersonPickerWidget برای فاکتور خرید

// در _buildInvoiceInfoTab:
if (_selectedInvoiceType == InvoiceType.purchase || 
    _selectedInvoiceType == InvoiceType.purchaseReturn) {
  PersonPickerWidget(
    personTypes: ['تامین‌کننده', 'فروشنده'],
    selectedPerson: _selectedSupplier,
    onChanged: (person) {
      setState(() {
        _selectedSupplier = person;
      });
    },
  ),
}

4. تبدیل نام فیلدها

final payload = <String, dynamic>{
  'invoice_type': _convertInvoiceTypeToApi(_selectedInvoiceType!),
  'document_date': _invoiceDate!.toIso8601String().split('T')[0],
  // ...
  'lines': _lineItems.map((e) => _serializeLineItem(e)).toList(),
  'extra_info': {
    if (_selectedInvoiceType == InvoiceType.sales || 
        _selectedInvoiceType == InvoiceType.salesReturn)
      if (_selectedCustomer != null)
        'person_id': _selectedCustomer!.id,
    if (_selectedInvoiceType == InvoiceType.purchase || 
        _selectedInvoiceType == InvoiceType.purchaseReturn)
      if (_selectedSupplier != null)
        'person_id': _selectedSupplier!.id,
    'totals': {
      'gross': _sumSubtotal,
      'discount': _sumDiscount,
      'tax': _sumTax,
      'net': _sumTotal,
    },
  },
};

📝 خلاصه

فاکتورهای نیازمند مشتری (Customer):

  • فاکتور فروش
  • فاکتور برگشت از فروش

فاکتورهای نیازمند تامین‌کننده (Supplier):

  • فاکتور خرید
  • فاکتور برگشت از خرید

فاکتورهای بدون person:

  • فاکتور مصرف مستقیم
  • فاکتور ضایعات
  • فاکتور تولید

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

  • فاکتور فروش (Receipt)
  • فاکتور برگشت از فروش (Payment)
  • فاکتور خرید (Payment)
  • فاکتور برگشت از خرید (Receipt)