Add Phase 21 stock item master mapping intelligence
This commit is contained in:
@@ -7,6 +7,7 @@ from sqlalchemy import select
|
||||
|
||||
from app.modules.accounting.chart_models import AccountingChartLedger
|
||||
from app.modules.accounting.chart_service import effective_role
|
||||
from app.modules.accounting.stock_service import resolve_stock_item, resolve_unit
|
||||
from app.modules.accounting.voucher_engine_models import (
|
||||
AccountingVoucherDraft,
|
||||
AccountingVoucherItemLine,
|
||||
@@ -299,6 +300,58 @@ def create_eway_invoice_draft(
|
||||
cess = round(float(invoice.get("cess") or 0), 2)
|
||||
gross = round(taxable + cgst + sgst + igst + cess, 2)
|
||||
|
||||
# Phase 21 resolves every source item against the synchronized ERP mirror
|
||||
# before a Purchase/Sales draft is created. Loose fuzzy-only candidates do
|
||||
# not silently pass through to Tally.
|
||||
resolved_items = []
|
||||
unresolved = []
|
||||
for source_item in invoice.get("items") or []:
|
||||
source_description = _s(source_item.get("item_name"))
|
||||
source_hsn = _s(source_item.get("hsn"))
|
||||
stock = resolve_stock_item(
|
||||
db,
|
||||
tenant_id=tenant_id,
|
||||
client_id=client_id,
|
||||
tally_guid=tally_guid,
|
||||
source_type="ewaybill",
|
||||
party_key=party_name,
|
||||
source_description=source_description,
|
||||
hsn_sac=source_hsn,
|
||||
)
|
||||
unit = resolve_unit(
|
||||
db,
|
||||
tenant_id=tenant_id,
|
||||
client_id=client_id,
|
||||
tally_guid=tally_guid,
|
||||
source_unit=_s(source_item.get("unit")),
|
||||
)
|
||||
|
||||
if not stock.get("matched"):
|
||||
unresolved.append(
|
||||
f"{source_description or '(blank item)'}: {stock.get('reason') or 'stock item not mapped'}"
|
||||
)
|
||||
continue
|
||||
if not unit.get("matched"):
|
||||
unresolved.append(
|
||||
f"{source_description or '(blank item)'}: {unit.get('reason') or 'unit not mapped'}"
|
||||
)
|
||||
continue
|
||||
|
||||
resolved_items.append(
|
||||
{
|
||||
"source": source_item,
|
||||
"stock": stock,
|
||||
"unit": unit,
|
||||
}
|
||||
)
|
||||
|
||||
if unresolved:
|
||||
raise ValueError(
|
||||
"Stock Item Intelligence review is required before this item-wise "
|
||||
f"{voucher_type} draft can be created. "
|
||||
+ " | ".join(unresolved[:8])
|
||||
)
|
||||
|
||||
draft = AccountingVoucherDraft(
|
||||
tenant_id=int(tenant_id),
|
||||
client_id=int(client_id),
|
||||
@@ -340,26 +393,48 @@ def create_eway_invoice_draft(
|
||||
line_no = _append_line(draft, line_no, input_igst, "CR", igst, "Output IGST")
|
||||
_append_line(draft, line_no, cess_ledger, "CR", cess, "Output Cess")
|
||||
|
||||
for index, item in enumerate(invoice.get("items") or [], 1):
|
||||
for index, resolved in enumerate(resolved_items, 1):
|
||||
item = resolved["source"]
|
||||
mapped_stock = resolved["stock"]["stock_item"]
|
||||
unit_result = resolved["unit"]
|
||||
|
||||
item_amount = round(float(item.get("taxable_value") or 0), 2)
|
||||
qty = float(item.get("quantity") or 0)
|
||||
rate = float(item.get("rate") or 0)
|
||||
source_qty = float(item.get("quantity") or 0)
|
||||
source_rate = float(item.get("rate") or 0)
|
||||
|
||||
factor = float(unit_result.get("quantity_factor") or 1.0)
|
||||
qty = source_qty * factor
|
||||
rate = source_rate / factor if source_rate and factor else source_rate
|
||||
if not rate and qty:
|
||||
rate = item_amount / qty
|
||||
|
||||
evidence = {
|
||||
**item,
|
||||
"source_type": "ewaybill",
|
||||
"party_name": party_name,
|
||||
"source_stock_item_name": _s(item.get("item_name")),
|
||||
"mapped_stock_item_id": mapped_stock.id,
|
||||
"mapped_stock_item_name": mapped_stock.name,
|
||||
"stock_mapping_confidence": int(resolved["stock"].get("confidence") or 0),
|
||||
"stock_mapping_reason": resolved["stock"].get("reason") or "",
|
||||
"source_unit": _s(item.get("unit")),
|
||||
"mapped_unit": unit_result.get("target_unit") or "",
|
||||
"quantity_factor": factor,
|
||||
}
|
||||
|
||||
draft.items.append(
|
||||
AccountingVoucherItemLine(
|
||||
line_no=index,
|
||||
stock_item_name=_s(item.get("item_name")),
|
||||
hsn_sac=_s(item.get("hsn")),
|
||||
stock_item_name=mapped_stock.name,
|
||||
hsn_sac=_s(item.get("hsn") or mapped_stock.hsn_code),
|
||||
quantity=qty,
|
||||
unit=_s(item.get("unit")),
|
||||
unit=unit_result.get("target_unit") or mapped_stock.base_units,
|
||||
rate=round(rate, 4),
|
||||
amount=item_amount,
|
||||
dr_cr=item_side,
|
||||
accounting_ledger_name=main.name,
|
||||
godown_name="",
|
||||
source_item_json=json.dumps(item, ensure_ascii=False, default=str),
|
||||
source_item_json=json.dumps(evidence, ensure_ascii=False, default=str),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user