Fix DB setup: savepoints for seed, deduplicate AddField, field fixes

Critical fixes for fresh DB creation:
- AddField now skips duplicates (ExtendModel from multiple modules
  was adding same field twice → duplicate column error)
- SeedWithSetup wrapped in savepoints per seed block (one failing
  INSERT no longer aborts entire transaction)
- sale.order.cancel: display_name → cancel_reason (avoid magic field clash)
- purchase: removed duplicate supplier_rank (already on res.partner)
- safeExec helper: SAVEPOINT + ROLLBACK TO on error

Fresh DB creation now works:
- /web/database/create → creates all tables, seeds data, returns session
- Login works immediately after creation
- All 191 models, 51 menus, 34 actions seeded

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-04-04 01:37:10 +02:00
parent fad2a37d1c
commit 5973a445c0
4 changed files with 34 additions and 16 deletions

View File

@@ -316,26 +316,29 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
if err := seedJournalsAndSequences(ctx, tx); err != nil {
return err
}
seedChartOfAccounts(ctx, tx, cfg)
seedStockData(ctx, tx)
seedViews(ctx, tx)
seedAccountReports(ctx, tx)
seedActions(ctx, tx)
seedMenus(ctx, tx)
// Each seed function wrapped in savepoint to prevent TX abort on non-critical errors
safeExec(ctx, tx, "chart_of_accounts", func() { seedChartOfAccounts(ctx, tx, cfg) })
safeExec(ctx, tx, "stock_data", func() { seedStockData(ctx, tx) })
safeExec(ctx, tx, "views", func() { seedViews(ctx, tx) })
safeExec(ctx, tx, "account_reports", func() { seedAccountReports(ctx, tx) })
safeExec(ctx, tx, "actions", func() { seedActions(ctx, tx) })
safeExec(ctx, tx, "menus", func() { seedMenus(ctx, tx) })
// Settings record (res.config.settings needs at least one record to display)
tx.Exec(ctx, `INSERT INTO res_config_settings (id, company_id, show_effect, create_uid, write_uid)
VALUES (1, 1, true, 1, 1) ON CONFLICT (id) DO NOTHING`)
// Settings record
safeExec(ctx, tx, "settings", func() {
tx.Exec(ctx, `INSERT INTO res_config_settings (id, company_id, show_effect, create_uid, write_uid)
VALUES (1, 1, true, 1, 1) ON CONFLICT (id) DO NOTHING`)
})
seedSystemParams(ctx, tx)
seedLanguages(ctx, tx)
seedTranslations(ctx, tx)
safeExec(ctx, tx, "system_params", func() { seedSystemParams(ctx, tx) })
safeExec(ctx, tx, "languages", func() { seedLanguages(ctx, tx) })
safeExec(ctx, tx, "translations", func() { seedTranslations(ctx, tx) })
if cfg.DemoData {
seedDemoData(ctx, tx)
safeExec(ctx, tx, "demo_data", func() { seedDemoData(ctx, tx) })
}
resetSequences(ctx, tx)
safeExec(ctx, tx, "sequences", func() { resetSequences(ctx, tx) })
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("db: commit seed: %w", err)
@@ -1650,6 +1653,18 @@ func seedTranslations(ctx context.Context, tx pgx.Tx) {
}
// generateUUID creates a random UUID v4 string.
// safeExec wraps a seed function in a savepoint so a single failure doesn't abort the TX.
func safeExec(ctx context.Context, tx pgx.Tx, name string, fn func()) {
tx.Exec(ctx, fmt.Sprintf("SAVEPOINT seed_%s", name))
fn()
// If the function caused a TX error, rollback to savepoint
_, err := tx.Exec(ctx, fmt.Sprintf("RELEASE SAVEPOINT seed_%s", name))
if err != nil {
log.Printf("db: seed %s had errors, rolling back to savepoint", name)
tx.Exec(ctx, fmt.Sprintf("ROLLBACK TO SAVEPOINT seed_%s", name))
}
}
func generateUUID() string {
b := make([]byte, 16)
rand.Read(b)