Simplify: O2M batch, domain dedup, db.go split, constants
Performance: - O2M formatO2MFields: N+1 → single batched query per O2M field (collect parent IDs, WHERE inverse IN (...), group by parent) Code dedup: - domain.go compileSimpleCondition: 80 lines → 12 lines by delegating to compileQualifiedCondition (eliminates duplicate operator handling) - db.go SeedWithSetup: 170-line monolith → 5 focused sub-functions (seedCurrencyAndCountry, seedCompanyAndAdmin, seedJournalsAndSequences, seedChartOfAccounts, resetSequences) Quality: - Magic numbers (80, 200, 8) replaced with named constants - Net -34 lines Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -242,87 +242,18 @@ func (dc *DomainCompiler) compileCondition(c Condition) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *DomainCompiler) compileSimpleCondition(column, operator string, value Value) (string, error) {
|
func (dc *DomainCompiler) compileSimpleCondition(column, operator string, value Value) (string, error) {
|
||||||
paramIdx := len(dc.params) + 1
|
// Operators that need special handling (not in compileQualifiedCondition)
|
||||||
|
|
||||||
switch operator {
|
switch operator {
|
||||||
case "=", "!=", "<", ">", "<=", ">=":
|
|
||||||
if value == nil || value == false {
|
|
||||||
if operator == "=" {
|
|
||||||
return fmt.Sprintf("%q IS NULL", column), nil
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%q IS NOT NULL", column), nil
|
|
||||||
}
|
|
||||||
dc.params = append(dc.params, value)
|
|
||||||
return fmt.Sprintf("%q %s $%d", column, operator, paramIdx), nil
|
|
||||||
|
|
||||||
case "in":
|
|
||||||
vals := normalizeSlice(value)
|
|
||||||
if vals == nil {
|
|
||||||
return "", fmt.Errorf("'in' operator requires a slice value")
|
|
||||||
}
|
|
||||||
if len(vals) == 0 {
|
|
||||||
return "FALSE", nil
|
|
||||||
}
|
|
||||||
placeholders := make([]string, len(vals))
|
|
||||||
for i, v := range vals {
|
|
||||||
dc.params = append(dc.params, v)
|
|
||||||
placeholders[i] = fmt.Sprintf("$%d", paramIdx+i)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%q IN (%s)", column, strings.Join(placeholders, ", ")), nil
|
|
||||||
|
|
||||||
case "not in":
|
|
||||||
vals := normalizeSlice(value)
|
|
||||||
if vals == nil {
|
|
||||||
return "", fmt.Errorf("'not in' operator requires a slice value")
|
|
||||||
}
|
|
||||||
if len(vals) == 0 {
|
|
||||||
return "TRUE", nil
|
|
||||||
}
|
|
||||||
placeholders := make([]string, len(vals))
|
|
||||||
for i, v := range vals {
|
|
||||||
dc.params = append(dc.params, v)
|
|
||||||
placeholders[i] = fmt.Sprintf("$%d", paramIdx+i)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%q NOT IN (%s)", column, strings.Join(placeholders, ", ")), nil
|
|
||||||
|
|
||||||
case "like":
|
|
||||||
dc.params = append(dc.params, wrapLikeValue(value))
|
|
||||||
return fmt.Sprintf("%q LIKE $%d", column, paramIdx), nil
|
|
||||||
|
|
||||||
case "not like":
|
|
||||||
dc.params = append(dc.params, wrapLikeValue(value))
|
|
||||||
return fmt.Sprintf("%q NOT LIKE $%d", column, paramIdx), nil
|
|
||||||
|
|
||||||
case "ilike":
|
|
||||||
dc.params = append(dc.params, wrapLikeValue(value))
|
|
||||||
return fmt.Sprintf("%q ILIKE $%d", column, paramIdx), nil
|
|
||||||
|
|
||||||
case "not ilike":
|
|
||||||
dc.params = append(dc.params, wrapLikeValue(value))
|
|
||||||
return fmt.Sprintf("%q NOT ILIKE $%d", column, paramIdx), nil
|
|
||||||
|
|
||||||
case "=like":
|
|
||||||
dc.params = append(dc.params, value)
|
|
||||||
return fmt.Sprintf("%q LIKE $%d", column, paramIdx), nil
|
|
||||||
|
|
||||||
case "=ilike":
|
|
||||||
dc.params = append(dc.params, value)
|
|
||||||
return fmt.Sprintf("%q ILIKE $%d", column, paramIdx), nil
|
|
||||||
|
|
||||||
case "child_of":
|
case "child_of":
|
||||||
return dc.compileHierarchyOp(column, value, true)
|
return dc.compileHierarchyOp(column, value, true)
|
||||||
|
|
||||||
case "parent_of":
|
case "parent_of":
|
||||||
return dc.compileHierarchyOp(column, value, false)
|
return dc.compileHierarchyOp(column, value, false)
|
||||||
|
|
||||||
case "any":
|
case "any":
|
||||||
return dc.compileAnyOp(column, value, false)
|
return dc.compileAnyOp(column, value, false)
|
||||||
|
|
||||||
case "not any":
|
case "not any":
|
||||||
return dc.compileAnyOp(column, value, true)
|
return dc.compileAnyOp(column, value, true)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unhandled operator: %q", operator)
|
return dc.compileQualifiedCondition(fmt.Sprintf("%q", column), operator, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -575,7 +575,7 @@ func (s *Server) dispatchORM(env *orm.Environment, params CallKWParams) (interfa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
limit := 8
|
limit := defaultNameSearchLimit
|
||||||
if v, ok := params.KW["limit"].(float64); ok {
|
if v, ok := params.KW["limit"].(float64); ok {
|
||||||
limit = int(v)
|
limit = int(v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import (
|
|||||||
"odoo-go/pkg/orm"
|
"odoo-go/pkg/orm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultWebSearchLimit = 80
|
||||||
|
defaultO2MFetchLimit = 200
|
||||||
|
defaultNameSearchLimit = 8
|
||||||
|
)
|
||||||
|
|
||||||
// handleWebSearchRead implements the web_search_read method.
|
// handleWebSearchRead implements the web_search_read method.
|
||||||
// Mirrors: odoo/addons/web/models/models.py web_search_read()
|
// Mirrors: odoo/addons/web/models/models.py web_search_read()
|
||||||
// Returns {length: N, records: [...]} instead of just records.
|
// Returns {length: N, records: [...]} instead of just records.
|
||||||
@@ -39,7 +45,7 @@ func handleWebSearchRead(env *orm.Environment, model string, params CallKWParams
|
|||||||
|
|
||||||
// Parse offset, limit, order
|
// Parse offset, limit, order
|
||||||
offset := 0
|
offset := 0
|
||||||
limit := 80
|
limit := defaultWebSearchLimit
|
||||||
order := ""
|
order := ""
|
||||||
if v, ok := params.KW["offset"].(float64); ok {
|
if v, ok := params.KW["offset"].(float64); ok {
|
||||||
offset = int(v)
|
offset = int(v)
|
||||||
@@ -178,6 +184,8 @@ func formatO2MFields(env *orm.Environment, modelName string, records []orm.Value
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect all parent IDs from records
|
||||||
|
var parentIDs []int64
|
||||||
for _, rec := range records {
|
for _, rec := range records {
|
||||||
parentID, ok := rec["id"].(int64)
|
parentID, ok := rec["id"].(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -185,38 +193,58 @@ func formatO2MFields(env *orm.Environment, modelName string, records []orm.Value
|
|||||||
parentID = int64(pid)
|
parentID = int64(pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if parentID == 0 {
|
if parentID > 0 {
|
||||||
rec[fieldName] = []interface{}{}
|
parentIDs = append(parentIDs, parentID)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Search child records where inverse_field = parent_id
|
// Initialize all records with empty slices
|
||||||
childRS := env.Model(comodel)
|
for _, rec := range records {
|
||||||
domain := orm.And(orm.Leaf(inverseField, "=", parentID))
|
rec[fieldName] = []interface{}{}
|
||||||
found, err := childRS.Search(domain, orm.SearchOpts{Limit: 200})
|
}
|
||||||
if err != nil || found.IsEmpty() {
|
|
||||||
rec[fieldName] = []interface{}{}
|
if len(parentIDs) == 0 {
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single batched search: WHERE inverse_field IN (all parent IDs)
|
||||||
|
childRS := env.Model(comodel)
|
||||||
|
domain := orm.And(orm.Leaf(inverseField, "in", parentIDs))
|
||||||
|
found, err := childRS.Search(domain, orm.SearchOpts{Limit: defaultO2MFetchLimit})
|
||||||
|
if err != nil || found.IsEmpty() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single batched read
|
||||||
|
childRecords, err := found.Read(childFields)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format child records (M2O fields, dates, nulls)
|
||||||
|
formatM2OFields(env, comodel, childRecords, subFieldsSpec)
|
||||||
|
formatDateFields(comodel, childRecords)
|
||||||
|
normalizeNullFields(comodel, childRecords)
|
||||||
|
|
||||||
|
// Group child records by their inverse field (parent ID)
|
||||||
|
grouped := make(map[int64][]interface{})
|
||||||
|
for _, cr := range childRecords {
|
||||||
|
if pid, ok := orm.ToRecordID(cr[inverseField]); ok && pid > 0 {
|
||||||
|
grouped[pid] = append(grouped[pid], cr)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read child records
|
// Assign grouped children to each parent record
|
||||||
childRecords, err := found.Read(childFields)
|
for _, rec := range records {
|
||||||
if err != nil {
|
parentID, ok := rec["id"].(int64)
|
||||||
rec[fieldName] = []interface{}{}
|
if !ok {
|
||||||
continue
|
if pid, ok := rec["id"].(int32); ok {
|
||||||
|
parentID = int64(pid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if children, ok := grouped[parentID]; ok {
|
||||||
// Format child records (M2O fields, dates, nulls)
|
rec[fieldName] = children
|
||||||
formatM2OFields(env, comodel, childRecords, subFieldsSpec)
|
|
||||||
formatDateFields(comodel, childRecords)
|
|
||||||
normalizeNullFields(comodel, childRecords)
|
|
||||||
|
|
||||||
// Convert to []interface{} for JSON
|
|
||||||
lines := make([]interface{}, len(childRecords))
|
|
||||||
for i, cr := range childRecords {
|
|
||||||
lines[i] = cr
|
|
||||||
}
|
}
|
||||||
rec[fieldName] = lines
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,8 +307,46 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
|
|
||||||
log.Printf("db: seeding database for %q...", cfg.CompanyName)
|
log.Printf("db: seeding database for %q...", cfg.CompanyName)
|
||||||
|
|
||||||
// 1. Currency (EUR)
|
if err := seedCurrencyAndCountry(ctx, tx, cfg); err != nil {
|
||||||
_, err = tx.Exec(ctx, `
|
return err
|
||||||
|
}
|
||||||
|
if err := seedCompanyAndAdmin(ctx, tx, cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := seedJournalsAndSequences(ctx, tx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seedChartOfAccounts(ctx, tx, cfg)
|
||||||
|
seedStockData(ctx, tx)
|
||||||
|
seedViews(ctx, tx)
|
||||||
|
seedActions(ctx, tx)
|
||||||
|
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`)
|
||||||
|
|
||||||
|
seedSystemParams(ctx, tx)
|
||||||
|
seedLanguages(ctx, tx)
|
||||||
|
seedTranslations(ctx, tx)
|
||||||
|
|
||||||
|
if cfg.DemoData {
|
||||||
|
seedDemoData(ctx, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSequences(ctx, tx)
|
||||||
|
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
return fmt.Errorf("db: commit seed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("db: database seeded successfully for %q", cfg.CompanyName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// seedCurrencyAndCountry seeds the default currency (EUR) and country.
|
||||||
|
func seedCurrencyAndCountry(ctx context.Context, tx pgx.Tx, cfg SetupConfig) error {
|
||||||
|
_, err := tx.Exec(ctx, `
|
||||||
INSERT INTO res_currency (id, name, symbol, decimal_places, rounding, active, "position")
|
INSERT INTO res_currency (id, name, symbol, decimal_places, rounding, active, "position")
|
||||||
VALUES (1, 'EUR', '€', 2, 0.01, true, 'after')
|
VALUES (1, 'EUR', '€', 2, 0.01, true, 'after')
|
||||||
ON CONFLICT (id) DO NOTHING`)
|
ON CONFLICT (id) DO NOTHING`)
|
||||||
@@ -316,7 +354,6 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
return fmt.Errorf("db: seed currency: %w", err)
|
return fmt.Errorf("db: seed currency: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Country
|
|
||||||
_, err = tx.Exec(ctx, `
|
_, err = tx.Exec(ctx, `
|
||||||
INSERT INTO res_country (id, name, code, phone_code)
|
INSERT INTO res_country (id, name, code, phone_code)
|
||||||
VALUES (1, $1, $2, $3)
|
VALUES (1, $1, $2, $3)
|
||||||
@@ -324,9 +361,12 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("db: seed country: %w", err)
|
return fmt.Errorf("db: seed country: %w", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Company partner
|
// seedCompanyAndAdmin seeds the company partner, company, admin partner, and admin user.
|
||||||
_, err = tx.Exec(ctx, `
|
func seedCompanyAndAdmin(ctx context.Context, tx pgx.Tx, cfg SetupConfig) error {
|
||||||
|
_, err := tx.Exec(ctx, `
|
||||||
INSERT INTO res_partner (id, name, is_company, active, type, lang, email, phone, street, zip, city, country_id, vat)
|
INSERT INTO res_partner (id, name, is_company, active, type, lang, email, phone, street, zip, city, country_id, vat)
|
||||||
VALUES (1, $1, true, true, 'contact', 'de_DE', $2, $3, $4, $5, $6, 1, $7)
|
VALUES (1, $1, true, true, 'contact', 'de_DE', $2, $3, $4, $5, $6, 1, $7)
|
||||||
ON CONFLICT (id) DO NOTHING`,
|
ON CONFLICT (id) DO NOTHING`,
|
||||||
@@ -335,7 +375,6 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
return fmt.Errorf("db: seed company partner: %w", err)
|
return fmt.Errorf("db: seed company partner: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Company
|
|
||||||
_, err = tx.Exec(ctx, `
|
_, err = tx.Exec(ctx, `
|
||||||
INSERT INTO res_company (id, name, partner_id, currency_id, country_id, active, sequence, street, zip, city, email, phone, vat)
|
INSERT INTO res_company (id, name, partner_id, currency_id, country_id, active, sequence, street, zip, city, email, phone, vat)
|
||||||
VALUES (1, $1, 1, 1, 1, true, 10, $2, $3, $4, $5, $6, $7)
|
VALUES (1, $1, 1, 1, 1, true, 10, $2, $3, $4, $5, $6, $7)
|
||||||
@@ -345,7 +384,6 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
return fmt.Errorf("db: seed company: %w", err)
|
return fmt.Errorf("db: seed company: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Admin partner
|
|
||||||
adminName := "Administrator"
|
adminName := "Administrator"
|
||||||
_, err = tx.Exec(ctx, `
|
_, err = tx.Exec(ctx, `
|
||||||
INSERT INTO res_partner (id, name, is_company, active, type, email, lang)
|
INSERT INTO res_partner (id, name, is_company, active, type, email, lang)
|
||||||
@@ -355,7 +393,6 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
return fmt.Errorf("db: seed admin partner: %w", err)
|
return fmt.Errorf("db: seed admin partner: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Admin user
|
|
||||||
_, err = tx.Exec(ctx, `
|
_, err = tx.Exec(ctx, `
|
||||||
INSERT INTO res_users (id, login, password, active, partner_id, company_id)
|
INSERT INTO res_users (id, login, password, active, partner_id, company_id)
|
||||||
VALUES (1, $1, $2, true, 2, 1)
|
VALUES (1, $1, $2, true, 2, 1)
|
||||||
@@ -363,9 +400,12 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("db: seed admin user: %w", err)
|
return fmt.Errorf("db: seed admin user: %w", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 7. Journals
|
// seedJournalsAndSequences seeds accounting journals and IR sequences.
|
||||||
_, err = tx.Exec(ctx, `
|
func seedJournalsAndSequences(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
_, err := tx.Exec(ctx, `
|
||||||
INSERT INTO account_journal (id, name, code, type, company_id, active, sequence) VALUES
|
INSERT INTO account_journal (id, name, code, type, company_id, active, sequence) VALUES
|
||||||
(1, 'Ausgangsrechnungen', 'INV', 'sale', 1, true, 10),
|
(1, 'Ausgangsrechnungen', 'INV', 'sale', 1, true, 10),
|
||||||
(2, 'Eingangsrechnungen', 'BILL', 'purchase', 1, true, 20),
|
(2, 'Eingangsrechnungen', 'BILL', 'purchase', 1, true, 20),
|
||||||
@@ -377,7 +417,6 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
return fmt.Errorf("db: seed journals: %w", err)
|
return fmt.Errorf("db: seed journals: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. Sequences
|
|
||||||
_, err = tx.Exec(ctx, `
|
_, err = tx.Exec(ctx, `
|
||||||
INSERT INTO ir_sequence (id, name, code, prefix, padding, number_next, number_increment, active, implementation) VALUES
|
INSERT INTO ir_sequence (id, name, code, prefix, padding, number_next, number_increment, active, implementation) VALUES
|
||||||
(1, 'Buchungssatz', 'account.move', 'MISC/', 4, 1, 1, true, 'standard'),
|
(1, 'Buchungssatz', 'account.move', 'MISC/', 4, 1, 1, true, 'standard'),
|
||||||
@@ -389,59 +428,34 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("db: seed sequences: %w", err)
|
return fmt.Errorf("db: seed sequences: %w", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 9. Chart of Accounts (if selected)
|
// seedChartOfAccounts seeds the chart of accounts and tax definitions if configured.
|
||||||
if cfg.Chart == "skr03" || cfg.Chart == "skr04" {
|
func seedChartOfAccounts(ctx context.Context, tx pgx.Tx, cfg SetupConfig) {
|
||||||
// Currently only SKR03 is implemented
|
if cfg.Chart != "skr03" && cfg.Chart != "skr04" {
|
||||||
for _, acc := range l10n_de.SKR03Accounts {
|
return
|
||||||
tx.Exec(ctx, `
|
|
||||||
INSERT INTO account_account (code, name, account_type, company_id, reconcile)
|
|
||||||
VALUES ($1, $2, $3, 1, $4) ON CONFLICT DO NOTHING`,
|
|
||||||
acc.Code, acc.Name, acc.AccountType, acc.Reconcile)
|
|
||||||
}
|
|
||||||
log.Printf("db: seeded %d SKR03 accounts", len(l10n_de.SKR03Accounts))
|
|
||||||
|
|
||||||
// Taxes
|
|
||||||
for _, tax := range l10n_de.SKR03Taxes {
|
|
||||||
tx.Exec(ctx, `
|
|
||||||
INSERT INTO account_tax (name, amount, type_tax_use, amount_type, company_id, active, sequence, is_base_affected)
|
|
||||||
VALUES ($1, $2, $3, 'percent', 1, true, 1, true) ON CONFLICT DO NOTHING`,
|
|
||||||
tax.Name, tax.Amount, tax.TypeUse)
|
|
||||||
}
|
|
||||||
log.Printf("db: seeded %d German tax definitions", len(l10n_de.SKR03Taxes))
|
|
||||||
}
|
}
|
||||||
|
// Currently only SKR03 is implemented
|
||||||
// 10. Stock reference data (locations, picking types, warehouse)
|
for _, acc := range l10n_de.SKR03Accounts {
|
||||||
seedStockData(ctx, tx)
|
tx.Exec(ctx, `
|
||||||
|
INSERT INTO account_account (code, name, account_type, company_id, reconcile)
|
||||||
// 11. UI Views for key models
|
VALUES ($1, $2, $3, 1, $4) ON CONFLICT DO NOTHING`,
|
||||||
seedViews(ctx, tx)
|
acc.Code, acc.Name, acc.AccountType, acc.Reconcile)
|
||||||
|
|
||||||
// 12. Actions (ir_act_window + ir_model_data for XML IDs)
|
|
||||||
seedActions(ctx, tx)
|
|
||||||
|
|
||||||
// 13. Menus (ir_ui_menu + ir_model_data for XML IDs)
|
|
||||||
seedMenus(ctx, tx)
|
|
||||||
|
|
||||||
// 14. 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`)
|
|
||||||
|
|
||||||
// 14b. System parameters (ir.config_parameter)
|
|
||||||
seedSystemParams(ctx, tx)
|
|
||||||
|
|
||||||
// 14c. Languages (res.lang — seed German alongside English)
|
|
||||||
seedLanguages(ctx, tx)
|
|
||||||
|
|
||||||
// 14d. Translations (ir.translation — German translations for core UI terms)
|
|
||||||
seedTranslations(ctx, tx)
|
|
||||||
|
|
||||||
// 15. Demo data
|
|
||||||
if cfg.DemoData {
|
|
||||||
seedDemoData(ctx, tx)
|
|
||||||
}
|
}
|
||||||
|
log.Printf("db: seeded %d SKR03 accounts", len(l10n_de.SKR03Accounts))
|
||||||
|
|
||||||
// 15. Reset sequences (each individually — pgx doesn't support multi-statement)
|
for _, tax := range l10n_de.SKR03Taxes {
|
||||||
|
tx.Exec(ctx, `
|
||||||
|
INSERT INTO account_tax (name, amount, type_tax_use, amount_type, company_id, active, sequence, is_base_affected)
|
||||||
|
VALUES ($1, $2, $3, 'percent', 1, true, 1, true) ON CONFLICT DO NOTHING`,
|
||||||
|
tax.Name, tax.Amount, tax.TypeUse)
|
||||||
|
}
|
||||||
|
log.Printf("db: seeded %d German tax definitions", len(l10n_de.SKR03Taxes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetSequences resets all auto-increment sequences to their current max values.
|
||||||
|
func resetSequences(ctx context.Context, tx pgx.Tx) {
|
||||||
seqs := []string{
|
seqs := []string{
|
||||||
"res_currency", "res_country", "res_partner", "res_company",
|
"res_currency", "res_country", "res_partner", "res_company",
|
||||||
"res_users", "ir_sequence", "account_journal", "account_account",
|
"res_users", "ir_sequence", "account_journal", "account_account",
|
||||||
@@ -460,13 +474,6 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
`SELECT setval('%s_id_seq', GREATEST((SELECT COALESCE(MAX(id),0) FROM %q), 1))`,
|
`SELECT setval('%s_id_seq', GREATEST((SELECT COALESCE(MAX(id),0) FROM %q), 1))`,
|
||||||
table, table))
|
table, table))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(ctx); err != nil {
|
|
||||||
return fmt.Errorf("db: commit seed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("db: database seeded successfully for %q", cfg.CompanyName)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// seedStockData creates stock locations, picking types, and a default warehouse.
|
// seedStockData creates stock locations, picking types, and a default warehouse.
|
||||||
|
|||||||
Reference in New Issue
Block a user