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:
@@ -570,7 +570,6 @@ func initResPartnerPurchaseExtension() {
|
|||||||
orm.Integer("purchase_order_count", orm.FieldOpts{
|
orm.Integer("purchase_order_count", orm.FieldOpts{
|
||||||
String: "Purchase Order Count", Compute: "_compute_purchase_order_count",
|
String: "Purchase Order Count", Compute: "_compute_purchase_order_count",
|
||||||
}),
|
}),
|
||||||
orm.Integer("supplier_rank", orm.FieldOpts{String: "Vendor Rank"}),
|
|
||||||
orm.Monetary("purchase_order_total", orm.FieldOpts{
|
orm.Monetary("purchase_order_total", orm.FieldOpts{
|
||||||
String: "Total Purchases", Compute: "_compute_purchase_order_total", CurrencyField: "currency_id",
|
String: "Total Purchases", Compute: "_compute_purchase_order_total", CurrencyField: "currency_id",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ func initSaleOrderWarnMsg() {
|
|||||||
|
|
||||||
m.AddFields(
|
m.AddFields(
|
||||||
orm.Many2one("order_id", "sale.order", orm.FieldOpts{String: "Sale Order"}),
|
orm.Many2one("order_id", "sale.order", orm.FieldOpts{String: "Sale Order"}),
|
||||||
orm.Text("display_name", orm.FieldOpts{String: "Warning"}),
|
orm.Text("cancel_reason", orm.FieldOpts{String: "Cancellation Reason"}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// action_cancel: Confirm the cancellation of the sale order.
|
// action_cancel: Confirm the cancellation of the sale order.
|
||||||
|
|||||||
@@ -162,6 +162,10 @@ func (m *Model) addMagicFields() {
|
|||||||
// AddField adds a field to this model.
|
// AddField adds a field to this model.
|
||||||
func (m *Model) AddField(f *Field) *Model {
|
func (m *Model) AddField(f *Field) *Model {
|
||||||
f.model = m
|
f.model = m
|
||||||
|
// Skip duplicate field (ExtendModel may add same field from multiple modules)
|
||||||
|
if _, exists := m.fields[f.Name]; exists {
|
||||||
|
return m
|
||||||
|
}
|
||||||
m.fields[f.Name] = f
|
m.fields[f.Name] = f
|
||||||
m.allFields[f.Name] = f
|
m.allFields[f.Name] = f
|
||||||
m.fieldOrder = append(m.fieldOrder, f.Name)
|
m.fieldOrder = append(m.fieldOrder, f.Name)
|
||||||
|
|||||||
@@ -316,26 +316,29 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
if err := seedJournalsAndSequences(ctx, tx); err != nil {
|
if err := seedJournalsAndSequences(ctx, tx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
seedChartOfAccounts(ctx, tx, cfg)
|
// Each seed function wrapped in savepoint to prevent TX abort on non-critical errors
|
||||||
seedStockData(ctx, tx)
|
safeExec(ctx, tx, "chart_of_accounts", func() { seedChartOfAccounts(ctx, tx, cfg) })
|
||||||
seedViews(ctx, tx)
|
safeExec(ctx, tx, "stock_data", func() { seedStockData(ctx, tx) })
|
||||||
seedAccountReports(ctx, tx)
|
safeExec(ctx, tx, "views", func() { seedViews(ctx, tx) })
|
||||||
seedActions(ctx, tx)
|
safeExec(ctx, tx, "account_reports", func() { seedAccountReports(ctx, tx) })
|
||||||
seedMenus(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)
|
// Settings record
|
||||||
|
safeExec(ctx, tx, "settings", func() {
|
||||||
tx.Exec(ctx, `INSERT INTO res_config_settings (id, company_id, show_effect, create_uid, write_uid)
|
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`)
|
VALUES (1, 1, true, 1, 1) ON CONFLICT (id) DO NOTHING`)
|
||||||
|
})
|
||||||
|
|
||||||
seedSystemParams(ctx, tx)
|
safeExec(ctx, tx, "system_params", func() { seedSystemParams(ctx, tx) })
|
||||||
seedLanguages(ctx, tx)
|
safeExec(ctx, tx, "languages", func() { seedLanguages(ctx, tx) })
|
||||||
seedTranslations(ctx, tx)
|
safeExec(ctx, tx, "translations", func() { seedTranslations(ctx, tx) })
|
||||||
|
|
||||||
if cfg.DemoData {
|
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 {
|
if err := tx.Commit(ctx); err != nil {
|
||||||
return fmt.Errorf("db: commit seed: %w", err)
|
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.
|
// 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 {
|
func generateUUID() string {
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
rand.Read(b)
|
rand.Read(b)
|
||||||
|
|||||||
Reference in New Issue
Block a user