You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

57 lines
3.2 KiB

"""Read-only domain validation for review; commit also uses finance posting rules."""
from datetime import date
from finance.services.base import DomainError, money
from finance.models import Transaction
def validate(candidate, decision, account, refs, profile):
issues = list(candidate.get("errors", []))
if account is None:
return issues + ["ACCOUNT_NOT_FOUND"]
if account.archived:
issues.append("ACCOUNT_ARCHIVED")
if account.currency != "BRL":
issues.append("UNSUPPORTED_CURRENCY")
expected = "BANK" if profile == "BANK_CSV_V1" else "CREDIT_CARD"
if account.kind != expected:
issues.append("ACCOUNT_KIND_MISMATCH")
if issues:
return issues
try:
amount = money(candidate["amount"])
date.fromisoformat(candidate["posting_date"])
except (DomainError, ValueError, TypeError, KeyError):
return ["INVALID_MOVEMENT"]
kind = decision.get("kind", candidate["kind"])
if kind not in {"INCOME", "EXPENSE", "REFUND", "TRANSFER"} or (kind in {"INCOME", "REFUND"} and amount <= 0) or (kind == "EXPENSE" and amount >= 0):
issues.append("INVALID_KIND_OR_SIGN")
for field, table in (("payee_id", "payees"), ("category_id", "categories")):
if decision.get(field):
ref = refs[table].get(decision[field])
if ref is None or ref.archived:
issues.append("INVALID_" + field.upper())
category = refs["categories"].get(decision.get("category_id"))
if category and category.kind != {"INCOME": "INCOME", "EXPENSE": "EXPENSE", "REFUND": "EXPENSE"}.get(kind):
issues.append("CATEGORY_KIND_MISMATCH")
statement = refs["statements"].get(decision.get("statement_id"))
if decision.get("statement_id") and (statement is None or statement.account_id != account.pk or account.kind != "CREDIT_CARD"):
issues.append("STATEMENT_ACCOUNT_MISMATCH")
original = refs["transactions"].get(decision.get("refund_of_id"))
if decision.get("refund_of_id") and (original is None or kind != "REFUND" or original.kind != "EXPENSE" or original.account.currency != account.currency):
issues.append("INVALID_REFUND_REFERENCE")
number, total = candidate.get("installment_number"), candidate.get("installment_total")
if number is not None or total is not None:
if not isinstance(number, int) or not isinstance(total, int) or not 1 <= number <= total or kind != "EXPENSE":
issues.append("INVALID_INSTALLMENT")
plan_id, new_plan = decision.get("installment_plan_id"), decision.get("new_plan")
if bool(plan_id) == bool(new_plan):
issues.append("INSTALLMENT_PLAN_REQUIRED")
if plan_id:
plan = refs["plans"].get(plan_id)
if plan is None or plan.account_id != account.pk or plan.total_installments != total:
issues.append("INSTALLMENT_PLAN_MISMATCH")
elif decision.get("action") == "CREATE" and Transaction.objects.filter(installment_plan=plan, installment_number=number).exists():
issues.append("INSTALLMENT_ALREADY_POSTED")
elif decision.get("installment_plan_id") or decision.get("new_plan"):
issues.append("UNEXPECTED_INSTALLMENT_PLAN")
return issues