Bring all areas to 60%: modules, reporting, i18n, views, data
Business modules deepened: - sale: tag_ids, invoice/delivery counts with computes - stock: _action_confirm/_action_done on stock.move, quant update stub - purchase: done state added - hr: parent_id, address_home_id, no_of_recruitment - project: user_id, date_start, kanban_state on tasks Reporting framework (0% → 60%): - ir.actions.report model registered - /report/html/<name>/<ids> endpoint serves styled HTML reports - Report-to-model mapping for invoice, sale, stock, purchase i18n framework (0% → 60%): - ir.translation model with src/value/lang/type fields - handleTranslations loads from DB, returns per-module structure - 140 German translations seeded (UI terms across all modules) - res_lang seeded with en_US + de_DE Views/UI improved: - Stored form views: sale.order (with editable O2M lines), account.move (with Post/Cancel buttons), res.partner (with title) - Stored list views: purchase.order, hr.employee, project.project Demo data expanded: - 5 products (templates + variants with codes) - 3 HR departments, 3 employees - 2 projects Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -401,6 +401,12 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
||||
// 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)
|
||||
@@ -415,6 +421,10 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
||||
"stock_location", "stock_picking_type", "stock_warehouse",
|
||||
"crm_stage", "crm_lead",
|
||||
"ir_config_parameter",
|
||||
"ir_translation", "ir_act_report", "res_lang",
|
||||
"product_template", "product_product",
|
||||
"hr_department", "hr_employee",
|
||||
"project_project",
|
||||
}
|
||||
for _, table := range seqs {
|
||||
tx.Exec(ctx, fmt.Sprintf(
|
||||
@@ -479,9 +489,9 @@ func seedViews(ctx context.Context, tx pgx.Tx) {
|
||||
</list>', 16, true, 'primary'),
|
||||
('partner.form', 'res.partner', 'form', '<form>
|
||||
<sheet>
|
||||
<div class="oe_title"><h1><field name="name" placeholder="Name"/></h1></div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="is_company"/>
|
||||
<field name="type"/>
|
||||
<field name="email"/>
|
||||
@@ -564,7 +574,120 @@ func seedViews(ctx context.Context, tx pgx.Tx) {
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>', 16, true, 'primary')
|
||||
</kanban>', 16, true, 'primary'),
|
||||
|
||||
('sale.form', 'sale.order', 'form', '<form>
|
||||
<header>
|
||||
<button name="action_confirm" string="Confirm" type="object" class="btn-primary" invisible="state != ''draft''"/>
|
||||
<button name="create_invoices" string="Create Invoice" type="object" class="btn-primary" invisible="state != ''sale''"/>
|
||||
<field name="state" widget="statusbar" clickable="1"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title"><h1><field name="name"/></h1></div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="partner_id"/>
|
||||
<field name="date_order"/>
|
||||
<field name="company_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="currency_id"/>
|
||||
<field name="payment_term_id"/>
|
||||
<field name="pricelist_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Order Lines">
|
||||
<field name="order_line">
|
||||
<list editable="bottom">
|
||||
<field name="product_id"/>
|
||||
<field name="name"/>
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="price_subtotal"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
<group>
|
||||
<group>
|
||||
<field name="amount_untaxed"/>
|
||||
<field name="amount_tax"/>
|
||||
<field name="amount_total"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>', 16, true, 'primary'),
|
||||
|
||||
('invoice.form', 'account.move', 'form', '<form>
|
||||
<header>
|
||||
<button name="action_post" string="Post" type="object" class="btn-primary" invisible="state != ''draft''"/>
|
||||
<button name="button_cancel" string="Cancel" type="object" invisible="state != ''draft''"/>
|
||||
<button name="button_draft" string="Reset to Draft" type="object" invisible="state != ''cancel''"/>
|
||||
<field name="state" widget="statusbar" clickable="1"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title"><h1><field name="name"/></h1></div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="partner_id"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="company_id"/>
|
||||
<field name="move_type"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
<field name="invoice_date"/>
|
||||
<field name="invoice_date_due"/>
|
||||
<field name="currency_id"/>
|
||||
<field name="payment_state"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Invoice Lines">
|
||||
<field name="invoice_line_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="quantity"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="balance"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
<group>
|
||||
<group>
|
||||
<field name="amount_untaxed"/>
|
||||
<field name="amount_tax"/>
|
||||
<field name="amount_total"/>
|
||||
<field name="amount_residual"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>', 16, true, 'primary'),
|
||||
|
||||
('purchase.list', 'purchase.order', 'list', '<list>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="date_order"/>
|
||||
<field name="state"/>
|
||||
<field name="amount_total"/>
|
||||
</list>', 16, true, 'primary'),
|
||||
|
||||
('employee.list', 'hr.employee', 'list', '<list>
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="work_email"/>
|
||||
<field name="company_id"/>
|
||||
</list>', 16, true, 'primary'),
|
||||
|
||||
('project.list', 'project.project', 'list', '<list>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="company_id"/>
|
||||
<field name="active"/>
|
||||
</list>', 16, true, 'primary')
|
||||
ON CONFLICT DO NOTHING`)
|
||||
|
||||
// Settings form view
|
||||
@@ -980,7 +1103,44 @@ func seedDemoData(ctx context.Context, tx pgx.Tx) {
|
||||
('Cloud Migration', 'opportunity', 3, 5, 28000, 1, 1, true, 'open', '0')
|
||||
ON CONFLICT DO NOTHING`)
|
||||
|
||||
log.Println("db: demo data loaded (8 contacts, 3 sale orders, 3 invoices, 4 CRM stages, 3 CRM leads)")
|
||||
// Products (templates + variants)
|
||||
tx.Exec(ctx, `INSERT INTO product_template (id, name, type, list_price, standard_price, sale_ok, purchase_ok, active) VALUES
|
||||
(1, 'Server Hosting', 'service', 50.00, 30.00, true, false, true),
|
||||
(2, 'Consulting Hours', 'service', 150.00, 80.00, true, false, true),
|
||||
(3, 'Laptop', 'consu', 1200.00, 800.00, true, true, true),
|
||||
(4, 'Monitor 27"', 'consu', 450.00, 300.00, true, true, true),
|
||||
(5, 'Office Chair', 'consu', 350.00, 200.00, true, true, true)
|
||||
ON CONFLICT (id) DO NOTHING`)
|
||||
|
||||
tx.Exec(ctx, `INSERT INTO product_product (id, product_tmpl_id, active, default_code) VALUES
|
||||
(1, 1, true, 'SRV-HOST'),
|
||||
(2, 2, true, 'SRV-CONS'),
|
||||
(3, 3, true, 'HW-LAPTOP'),
|
||||
(4, 4, true, 'HW-MON27'),
|
||||
(5, 5, true, 'HW-CHAIR')
|
||||
ON CONFLICT (id) DO NOTHING`)
|
||||
|
||||
// HR Departments
|
||||
tx.Exec(ctx, `INSERT INTO hr_department (id, name, company_id) VALUES
|
||||
(1, 'Management', 1),
|
||||
(2, 'IT', 1),
|
||||
(3, 'Sales', 1)
|
||||
ON CONFLICT (id) DO NOTHING`)
|
||||
|
||||
// HR Employees
|
||||
tx.Exec(ctx, `INSERT INTO hr_employee (id, name, department_id, company_id, work_email) VALUES
|
||||
(1, 'Marc Bauer', 1, 1, 'marc@bauer-bau.de'),
|
||||
(2, 'Anna Schmidt', 2, 1, 'anna@bauer-bau.de'),
|
||||
(3, 'Peter Weber', 3, 1, 'peter@bauer-bau.de')
|
||||
ON CONFLICT (id) DO NOTHING`)
|
||||
|
||||
// Projects
|
||||
tx.Exec(ctx, `INSERT INTO project_project (id, name, partner_id, company_id, active) VALUES
|
||||
(1, 'Website Redesign', 5, 1, true),
|
||||
(2, 'Office Migration', 3, 1, true)
|
||||
ON CONFLICT (id) DO NOTHING`)
|
||||
|
||||
log.Println("db: demo data loaded (8 contacts, 3 sale orders, 3 invoices, 4 CRM stages, 3 CRM leads, 5 products, 3 departments, 3 employees, 2 projects)")
|
||||
}
|
||||
|
||||
// SeedBaseData is the legacy function — redirects to setup with defaults.
|
||||
@@ -1025,6 +1185,7 @@ func seedSystemParams(ctx context.Context, tx pgx.Tx) {
|
||||
}{
|
||||
{"web.base.url", "http://localhost:8069"},
|
||||
{"database.uuid", dbUUID},
|
||||
{"report.url", "http://localhost:8069"},
|
||||
{"base.login_cooldown_after", "10"},
|
||||
{"base.login_cooldown_duration", "60"},
|
||||
}
|
||||
@@ -1038,6 +1199,236 @@ func seedSystemParams(ctx context.Context, tx pgx.Tx) {
|
||||
log.Printf("db: seeded %d system parameters", len(params))
|
||||
}
|
||||
|
||||
// seedLanguages inserts English and German language entries into res_lang.
|
||||
// Mirrors: odoo/addons/base/data/res_lang_data.xml
|
||||
func seedLanguages(ctx context.Context, tx pgx.Tx) {
|
||||
log.Println("db: seeding languages...")
|
||||
|
||||
// English (US) — default language
|
||||
tx.Exec(ctx, `
|
||||
INSERT INTO res_lang (name, code, iso_code, url_code, active, direction, date_format, time_format, decimal_point, thousands_sep, week_start, grouping)
|
||||
VALUES ('English (US)', 'en_US', 'en', 'en', true, 'ltr', '%%m/%%d/%%Y', '%%H:%%M:%%S', '.', ',', '7', '[3,0]')
|
||||
ON CONFLICT DO NOTHING`)
|
||||
|
||||
// German (Germany)
|
||||
tx.Exec(ctx, `
|
||||
INSERT INTO res_lang (name, code, iso_code, url_code, active, direction, date_format, time_format, decimal_point, thousands_sep, week_start, grouping)
|
||||
VALUES ('German / Deutsch', 'de_DE', 'de', 'de', true, 'ltr', '%%d.%%m.%%Y', '%%H:%%M:%%S', ',', '.', '1', '[3,0]')
|
||||
ON CONFLICT DO NOTHING`)
|
||||
|
||||
log.Println("db: languages seeded (en_US, de_DE)")
|
||||
}
|
||||
|
||||
// seedTranslations inserts German translations for core UI terms into ir_translation.
|
||||
// Mirrors: odoo/addons/base/i18n/de.po (partially)
|
||||
//
|
||||
// These translations are loaded by the web client via /web/webclient/translations
|
||||
// and used to display UI elements in German.
|
||||
func seedTranslations(ctx context.Context, tx pgx.Tx) {
|
||||
log.Println("db: seeding German translations...")
|
||||
|
||||
translations := []struct {
|
||||
src, value, module string
|
||||
}{
|
||||
// Navigation & App names
|
||||
{"Contacts", "Kontakte", "contacts"},
|
||||
{"Invoicing", "Rechnungen", "account"},
|
||||
{"Sales", "Verkauf", "sale"},
|
||||
{"Purchase", "Einkauf", "purchase"},
|
||||
{"Inventory", "Lager", "stock"},
|
||||
{"Employees", "Mitarbeiter", "hr"},
|
||||
{"Project", "Projekt", "project"},
|
||||
{"Settings", "Einstellungen", "base"},
|
||||
{"Apps", "Apps", "base"},
|
||||
{"Discuss", "Diskussion", "base"},
|
||||
{"Calendar", "Kalender", "base"},
|
||||
{"Dashboard", "Dashboard", "base"},
|
||||
{"Fleet", "Fuhrpark", "fleet"},
|
||||
{"CRM", "CRM", "crm"},
|
||||
|
||||
// Common field labels
|
||||
{"Name", "Name", "base"},
|
||||
{"Email", "E-Mail", "base"},
|
||||
{"Phone", "Telefon", "base"},
|
||||
{"Mobile", "Mobil", "base"},
|
||||
{"Company", "Unternehmen", "base"},
|
||||
{"Partner", "Partner", "base"},
|
||||
{"Active", "Aktiv", "base"},
|
||||
{"Date", "Datum", "base"},
|
||||
{"Status", "Status", "base"},
|
||||
{"Total", "Gesamt", "base"},
|
||||
{"Amount", "Betrag", "account"},
|
||||
{"Description", "Beschreibung", "base"},
|
||||
{"Reference", "Referenz", "base"},
|
||||
{"Notes", "Notizen", "base"},
|
||||
{"Tags", "Schlagwörter", "base"},
|
||||
{"Type", "Typ", "base"},
|
||||
{"Country", "Land", "base"},
|
||||
{"City", "Stadt", "base"},
|
||||
{"Street", "Straße", "base"},
|
||||
{"Zip", "PLZ", "base"},
|
||||
{"Website", "Webseite", "base"},
|
||||
{"Language", "Sprache", "base"},
|
||||
{"Currency", "Währung", "base"},
|
||||
{"Sequence", "Reihenfolge", "base"},
|
||||
{"Priority", "Priorität", "base"},
|
||||
{"Color", "Farbe", "base"},
|
||||
{"Image", "Bild", "base"},
|
||||
{"Attachment", "Anhang", "base"},
|
||||
{"Category", "Kategorie", "base"},
|
||||
{"Title", "Titel", "base"},
|
||||
|
||||
// Buttons & Actions
|
||||
{"Save", "Speichern", "web"},
|
||||
{"Discard", "Verwerfen", "web"},
|
||||
{"New", "Neu", "web"},
|
||||
{"Edit", "Bearbeiten", "web"},
|
||||
{"Delete", "Löschen", "web"},
|
||||
{"Archive", "Archivieren", "web"},
|
||||
{"Unarchive", "Dearchivieren", "web"},
|
||||
{"Duplicate", "Duplizieren", "web"},
|
||||
{"Import", "Importieren", "web"},
|
||||
{"Export", "Exportieren", "web"},
|
||||
{"Print", "Drucken", "web"},
|
||||
{"Confirm", "Bestätigen", "web"},
|
||||
{"Cancel", "Abbrechen", "web"},
|
||||
{"Close", "Schließen", "web"},
|
||||
{"Apply", "Anwenden", "web"},
|
||||
{"Ok", "Ok", "web"},
|
||||
{"Yes", "Ja", "web"},
|
||||
{"No", "Nein", "web"},
|
||||
{"Send", "Senden", "web"},
|
||||
{"Refresh", "Aktualisieren", "web"},
|
||||
{"Actions", "Aktionen", "web"},
|
||||
{"Action", "Aktion", "web"},
|
||||
{"Create", "Erstellen", "web"},
|
||||
|
||||
// Search & Filters
|
||||
{"Search...", "Suchen...", "web"},
|
||||
{"Filters", "Filter", "web"},
|
||||
{"Group By", "Gruppieren nach", "web"},
|
||||
{"Favorites", "Favoriten", "web"},
|
||||
{"Custom Filter", "Benutzerdefinierter Filter", "web"},
|
||||
|
||||
// Status values
|
||||
{"Draft", "Entwurf", "base"},
|
||||
{"Posted", "Gebucht", "account"},
|
||||
{"Cancelled", "Storniert", "base"},
|
||||
{"Confirmed", "Bestätigt", "base"},
|
||||
{"Done", "Erledigt", "base"},
|
||||
{"In Progress", "In Bearbeitung", "base"},
|
||||
{"Waiting", "Wartend", "base"},
|
||||
{"Sent", "Gesendet", "base"},
|
||||
{"Paid", "Bezahlt", "account"},
|
||||
{"Open", "Offen", "base"},
|
||||
{"Locked", "Gesperrt", "base"},
|
||||
|
||||
// View types & navigation
|
||||
{"List", "Liste", "web"},
|
||||
{"Form", "Formular", "web"},
|
||||
{"Kanban", "Kanban", "web"},
|
||||
{"Graph", "Grafik", "web"},
|
||||
{"Pivot", "Pivot", "web"},
|
||||
{"Map", "Karte", "web"},
|
||||
{"Activity", "Aktivität", "web"},
|
||||
|
||||
// Accounting terms
|
||||
{"Invoice", "Rechnung", "account"},
|
||||
{"Invoices", "Rechnungen", "account"},
|
||||
{"Bill", "Eingangsrechnung", "account"},
|
||||
{"Bills", "Eingangsrechnungen", "account"},
|
||||
{"Payment", "Zahlung", "account"},
|
||||
{"Payments", "Zahlungen", "account"},
|
||||
{"Journal", "Journal", "account"},
|
||||
{"Journals", "Journale", "account"},
|
||||
{"Account", "Konto", "account"},
|
||||
{"Tax", "Steuer", "account"},
|
||||
{"Taxes", "Steuern", "account"},
|
||||
{"Untaxed Amount", "Nettobetrag", "account"},
|
||||
{"Tax Amount", "Steuerbetrag", "account"},
|
||||
{"Total Amount", "Gesamtbetrag", "account"},
|
||||
{"Due Date", "Fälligkeitsdatum", "account"},
|
||||
{"Journal Entry", "Buchungssatz", "account"},
|
||||
{"Journal Entries", "Buchungssätze", "account"},
|
||||
{"Credit Note", "Gutschrift", "account"},
|
||||
|
||||
// Sales terms
|
||||
{"Quotation", "Angebot", "sale"},
|
||||
{"Quotations", "Angebote", "sale"},
|
||||
{"Sales Order", "Verkaufsauftrag", "sale"},
|
||||
{"Sales Orders", "Verkaufsaufträge", "sale"},
|
||||
{"Customer", "Kunde", "sale"},
|
||||
{"Customers", "Kunden", "sale"},
|
||||
{"Unit Price", "Stückpreis", "sale"},
|
||||
{"Quantity", "Menge", "sale"},
|
||||
{"Ordered Quantity", "Bestellte Menge", "sale"},
|
||||
{"Delivered Quantity", "Gelieferte Menge", "sale"},
|
||||
|
||||
// Purchase terms
|
||||
{"Purchase Order", "Bestellung", "purchase"},
|
||||
{"Purchase Orders", "Bestellungen", "purchase"},
|
||||
{"Vendor", "Lieferant", "purchase"},
|
||||
{"Vendors", "Lieferanten", "purchase"},
|
||||
{"Request for Quotation", "Angebotsanfrage", "purchase"},
|
||||
|
||||
// Inventory terms
|
||||
{"Product", "Produkt", "stock"},
|
||||
{"Products", "Produkte", "stock"},
|
||||
{"Warehouse", "Lager", "stock"},
|
||||
{"Location", "Lagerort", "stock"},
|
||||
{"Delivery", "Lieferung", "stock"},
|
||||
{"Receipt", "Wareneingang", "stock"},
|
||||
{"Picking", "Kommissionierung", "stock"},
|
||||
{"Stock", "Bestand", "stock"},
|
||||
|
||||
// HR terms
|
||||
{"Employee", "Mitarbeiter", "hr"},
|
||||
{"Department", "Abteilung", "hr"},
|
||||
{"Job Position", "Stelle", "hr"},
|
||||
{"Contract", "Vertrag", "hr"},
|
||||
|
||||
// Time & date
|
||||
{"Today", "Heute", "web"},
|
||||
{"Yesterday", "Gestern", "web"},
|
||||
{"This Week", "Diese Woche", "web"},
|
||||
{"This Month", "Dieser Monat", "web"},
|
||||
{"This Year", "Dieses Jahr", "web"},
|
||||
{"Last 7 Days", "Letzte 7 Tage", "web"},
|
||||
{"Last 30 Days", "Letzte 30 Tage", "web"},
|
||||
{"Last 365 Days", "Letzte 365 Tage", "web"},
|
||||
|
||||
// Misc UI
|
||||
{"Loading...", "Wird geladen...", "web"},
|
||||
{"No records found", "Keine Einträge gefunden", "web"},
|
||||
{"Are you sure?", "Sind Sie sicher?", "web"},
|
||||
{"Warning", "Warnung", "web"},
|
||||
{"Error", "Fehler", "web"},
|
||||
{"Success", "Erfolg", "web"},
|
||||
{"Information", "Information", "web"},
|
||||
{"Powered by", "Betrieben von", "web"},
|
||||
{"My Profile", "Mein Profil", "web"},
|
||||
{"Log out", "Abmelden", "web"},
|
||||
{"Preferences", "Einstellungen", "web"},
|
||||
{"Documentation", "Dokumentation", "web"},
|
||||
{"Support", "Support", "web"},
|
||||
{"Shortcuts", "Tastenkürzel", "web"},
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, t := range translations {
|
||||
_, err := tx.Exec(ctx,
|
||||
`INSERT INTO ir_translation (name, lang, type, src, value, module, state)
|
||||
VALUES ('code', $1, 'code', $2, $3, $4, 'translated')
|
||||
ON CONFLICT DO NOTHING`,
|
||||
"de_DE", t.src, t.value, t.module)
|
||||
if err == nil {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("db: seeded %d German translations", count)
|
||||
}
|
||||
|
||||
// generateUUID creates a random UUID v4 string.
|
||||
func generateUUID() string {
|
||||
b := make([]byte, 16)
|
||||
|
||||
Reference in New Issue
Block a user