package orm import ( "fmt" "strings" "time" ) // NextByCode generates the next value for a sequence identified by its code. // Mirrors: odoo/addons/base/models/ir_sequence.py IrSequence.next_by_code() // // Uses PostgreSQL FOR UPDATE to ensure atomic increment under concurrency. // Format: prefix + LPAD(number, padding, '0') + suffix // Supports date interpolation in prefix/suffix: %(year)s, %(month)s, %(day)s func NextByCode(env *Environment, code string) (string, error) { var id int64 var prefix, suffix string var numberNext, numberIncrement, padding int err := env.tx.QueryRow(env.ctx, ` SELECT id, COALESCE(prefix, ''), COALESCE(suffix, ''), number_next, number_increment, padding FROM ir_sequence WHERE code = $1 AND active = true ORDER BY id LIMIT 1 FOR UPDATE `, code).Scan(&id, &prefix, &suffix, &numberNext, &numberIncrement, &padding) if err != nil { return "", fmt.Errorf("orm: sequence %q not found: %w", code, err) } // Format the sequence value result := FormatSequence(prefix, suffix, numberNext, padding) // Increment for next call _, err = env.tx.Exec(env.ctx, ` UPDATE ir_sequence SET number_next = number_next + $1 WHERE id = $2 `, numberIncrement, id) if err != nil { return "", fmt.Errorf("orm: sequence %q increment failed: %w", code, err) } return result, nil } // FormatSequence formats a sequence number with prefix, suffix, and zero-padding. func FormatSequence(prefix, suffix string, number, padding int) string { prefix = InterpolateDate(prefix) suffix = InterpolateDate(suffix) numStr := fmt.Sprintf("%d", number) if padding > 0 && len(numStr) < padding { numStr = strings.Repeat("0", padding-len(numStr)) + numStr } return prefix + numStr + suffix } // InterpolateDate replaces Odoo-style date placeholders in a string. // Supports: %(year)s, %(month)s, %(day)s, %(y)s (2-digit year) func InterpolateDate(s string) string { now := time.Now() s = strings.ReplaceAll(s, "%(year)s", now.Format("2006")) s = strings.ReplaceAll(s, "%(y)s", now.Format("06")) s = strings.ReplaceAll(s, "%(month)s", now.Format("01")) s = strings.ReplaceAll(s, "%(day)s", now.Format("02")) return s }