Add action_test_connection — a read-only connectivity/schema check that
reports source row counts and imports nothing, the safe first step before
a dry-run. Wire a "Test Connection" button on the wizard. Document the
end-to-end run in the README: least-privilege read-only DB role SQL, the
fusion_billing.nexacloud_dsn system parameter (libpq DSN = NexaCloud's
URL minus +asyncpg), and the Test → dry-run → real-run flow. Refresh the
stale SCAFFOLD status. 53/53 green on odoo-trial.
Resolves findings from the post-build review:
- C1: a partial import was indistinguishable from success. action_run_import
now logs failed rows at ERROR (survives nexa's log_level=warn) and the
wizard shows red/amber banners with failed/skipped counts.
- H3: an unrecognized billing_cycle silently fell back to monthly (wrong
plan AND price). Now raised per-row -> failed[], never silently mis-billed.
- M5: a NULL plan price silently became a $0 line. Prices now preserve
NULL-vs-0.0; a missing price for the subscription's cycle is failed[].
- H2: post-connect query/schema errors now become a clean UserError, not a
raw SQL traceback (matches the connection-error path).
- M4: per-row failures now record the exception type and log a traceback.
- MED#3: charge plan_id set explicitly False so re-runs re-assert the
shadow-safe NULL even if it was changed between runs.
- HIGH-edge: re-run only rewrites x_fc_* on existing subs; partner_id/plan_id/
line are set at creation only (never rewrite immutable fields).
- account_link: partner email match is now case-insensitive (=ilike) to avoid
duplicate partners against a differently-cased pre-existing partner.
Shadow-safety invariant unchanged and re-confirmed. 52/52 green on odoo-trial.
fusion.billing.import.wizard backfills NexaCloud into Odoo: read-only
psycopg2 reader (_read_nexacloud_rows, DSN from ir.config_parameter)
split from pure-Odoo writes (_import_rows/_do_import) so the logic is
unit-tested headless. Maps users→partners+links (reusing
_resolve_or_create_partner, stashing stripe_customer_id), plans→a
cpu_seconds charge catalog (included_quota=cpu_seconds_quota,
unit_batch=3600, $0.0075/core-hour, plan_id NULL), and deployments→one
DRAFT shadow sale.order per deployment with the flat price set
explicitly. Shadow-safe by construction: draft + no payment token +
charge plan_id NULL (rating cron is a no-op). Idempotent re-runs;
per-row savepoints isolate bad rows; dry-run rolls back. 11 tests,
50/50 green on odoo-trial.