feat(billing): post + reconcile only PAID invoices, keeping original dates

_post_and_reconcile_paid: for invoices NexaCloud marks paid, set the ledger
entry's invoice_date AND accounting date to the original NexaCloud date,
post, then reconcile the Stripe payment dated to the actual paid_at. Unpaid
invoices stay draft. Per-invoice isolated. 76 tests green on odoo-trial.
This commit is contained in:
gsinghpal
2026-05-27 17:29:41 -04:00
parent 7a66d7849d
commit c8529b8a99
2 changed files with 53 additions and 0 deletions

View File

@@ -197,6 +197,41 @@ class FusionBillingInvoiceLedgerWizard(models.TransientModel):
_logger.exception("Ledger post: move %s failed", mv.id)
return posted
@api.model
def _post_and_reconcile_paid(self, data):
"""Post + reconcile ONLY the invoices NexaCloud marks paid, dating the ledger entry
to the ORIGINAL invoice date and the payment to the actual paid_at. Leaves unpaid
invoices as draft. Per-invoice isolated."""
Move = self.env["account.move"]
summary = {"posted": 0, "reconciled": 0, "skipped_unpaid": 0,
"skipped_missing": 0, "failed": []}
for inv in data:
nc_id = str(inv.get("id") or "")
paid = float(inv.get("amount_paid") or 0.0)
if inv.get("status") != "paid" and paid <= 0:
summary["skipped_unpaid"] += 1
continue
mv = Move.search([("x_fc_nexacloud_invoice_id", "=", nc_id),
("move_type", "=", "out_invoice")], limit=1)
if not mv:
summary["skipped_missing"] += 1
continue
try:
with self.env.cr.savepoint():
if mv.state == "draft":
inv_date = inv.get("invoice_date")
# keep the original invoice + accounting date (not today)
mv.write({"invoice_date": inv_date, "date": inv_date})
mv.action_post()
summary["posted"] += 1
if mv.payment_state not in ("paid", "in_payment", "reversed"):
if self._fc_reconcile_payment(mv, inv):
summary["reconciled"] += 1
except Exception as e: # noqa: BLE001 - per-invoice isolation
_logger.exception("Post+pay: invoice %s failed", nc_id)
summary["failed"].append({"id": nc_id, "error": "%s: %s" % (type(e).__name__, e)})
return summary
def _cron_ingest_recent(self):
since = fields.Datetime.to_string(fields.Datetime.now() - timedelta(days=2))
return self._ingest_invoices(self._read_nexacloud_invoices(since=since), post=True)