dodil/erp-brick-expenses

DODIL ERP brick: expenses

src/brick_expenses/domain/rebill.py

"""Re-billing a cost to a customer (EXP-21, the rebill extension): a line of an approved claim becomes ONE draft invoice line in ar.

Plain Python. ar owns the invoice and its tax; expenses only says what the customer is billed for: the cost - the line's amount less the VAT tax says is
reclaimable (that VAT comes back to the company, it is not a cost to pass on) - or the cost plus a markup, to four places, and which sales tax category it
falls under. The invoice is keyed source_id = exp:<report_id>:<line_no>, so asking twice is ar's replay and the expense is never billed twice."""
from decimal import ROUND_HALF_UP, Decimal

from brick_expenses.domain.errors import RuleBroken
from brick_expenses.domain.money import ZERO

PRICE = Decimal("0.0001")
REBILLABLE_FROM = ("submitted", "approved", "post_failed", "posted", "paid")


def source_id(report_id: str, line_no: int) -> str:
    return f"exp:{report_id}:{line_no}"


def unit_price(amount: Decimal, vat_reclaimable: Decimal, markup_pct: Decimal) -> Decimal:
    """The cost (net of reclaimable VAT) plus the markup, rounded once, half up, to four places."""
    cost = amount - (vat_reclaimable or ZERO)
    if cost <= ZERO:
        raise RuleBroken("there is no cost to bill: the whole line is reclaimable VAT")
    return (cost * (Decimal(1) + markup_pct / Decimal(100))).quantize(PRICE, rounding=ROUND_HALF_UP)


def invoice_body(report_id: str, line, currency: str) -> dict:
    """The POST /invoices/raise body for one marked line (ar AR-20)."""
    return {"source_kind": "expense", "source_id": source_id(report_id, line.line_no), "bp_id": str(line.rebill_bp_id), "currency": currency, "kind": "regular",
            "lines": [{"description": f"{line.merchant} ({line.expense_date.isoformat()})"[:200], "quantity": "1", "unit_code": "C62",
                       "unit_price": f"{unit_price(line.amount, line.vat_reclaimable, line.rebill_markup_pct or ZERO):.4f}", "tax_category": line.rebill_tax_category or "standard"}]}