Odoo ERP ported to Go — complete backend + original OWL frontend

Full port of Odoo's ERP system from Python to Go, with the original
Odoo JavaScript frontend (OWL framework) running against the Go server.

Backend (10,691 LoC Go):
- Custom ORM: CRUD, domains→SQL with JOINs, computed fields, sequences
- 93 models across 14 modules (base, account, sale, stock, purchase, hr,
  project, crm, fleet, product, l10n_de, google_address/translate/calendar)
- Auth with bcrypt + session cookies
- Setup wizard (company, SKR03 chart, admin, demo data)
- Double-entry bookkeeping constraint
- Sale→Invoice workflow (confirm SO → generate invoice → post)
- SKR03 chart of accounts (110 accounts) + German taxes (USt/VSt)
- Record rules (multi-company filter)
- Google integrations as opt-in modules (Maps, Translate, Calendar)

Frontend:
- Odoo's original OWL webclient (503 JS modules, 378 XML templates)
- JS transpiled via Odoo's js_transpiler (ES modules → odoo.define)
- SCSS compiled to CSS (675KB) via dart-sass
- XML templates compiled to registerTemplate() JS calls
- Static file serving from Odoo source addons
- Login page, session management, menu navigation
- Contacts list view renders with real data from PostgreSQL

Infrastructure:
- 14MB single binary (CGO_ENABLED=0)
- Docker Compose (Go server + PostgreSQL 16)
- Zero phone-home (no outbound calls to odoo.com)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-03-31 01:45:09 +02:00
commit 0ed29fe2fd
90 changed files with 12133 additions and 0 deletions

120
tools/transpile_assets.py Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""
Transpile Odoo ES module JS files to odoo.define() format.
Uses Odoo's built-in js_transpiler.py to convert:
import { X } from "@web/core/foo" → odoo.define("@web/...", [...], function(require) {...})
Usage:
python3 tools/transpile_assets.py <odoo_source_path> <output_dir>
Example:
python3 tools/transpile_assets.py ../odoo build/js
"""
import os
import sys
import shutil
def main():
if len(sys.argv) < 3:
print("Usage: transpile_assets.py <odoo_source_path> <output_dir>")
sys.exit(1)
odoo_path = sys.argv[1]
output_dir = sys.argv[2]
# Mock odoo.tools.misc.OrderedSet before importing transpiler
import types
odoo_mock = types.ModuleType('odoo')
odoo_tools_mock = types.ModuleType('odoo.tools')
odoo_misc_mock = types.ModuleType('odoo.tools.misc')
class OrderedSet(dict):
"""Minimal OrderedSet replacement using dict keys."""
def __init__(self, iterable=None):
super().__init__()
if iterable:
for item in iterable:
self[item] = None
def add(self, item):
self[item] = None
def __iter__(self):
return iter(self.keys())
def __contains__(self, item):
return dict.__contains__(self, item)
odoo_misc_mock.OrderedSet = OrderedSet
odoo_tools_mock.misc = odoo_misc_mock
odoo_mock.tools = odoo_tools_mock
sys.modules['odoo'] = odoo_mock
sys.modules['odoo.tools'] = odoo_tools_mock
sys.modules['odoo.tools.misc'] = odoo_misc_mock
# Import the transpiler directly
import importlib.util
transpiler_path = os.path.join(odoo_path, 'odoo', 'tools', 'js_transpiler.py')
spec = importlib.util.spec_from_file_location("js_transpiler", transpiler_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
transpile_javascript = mod.transpile_javascript
is_odoo_module = mod.is_odoo_module
# Read the JS bundle file list
bundle_file = os.path.join(os.path.dirname(__file__), '..', 'pkg', 'server', 'assets_js.txt')
with open(bundle_file) as f:
js_files = [line.strip() for line in f if line.strip()]
addons_dirs = [
os.path.join(odoo_path, 'addons'),
os.path.join(odoo_path, 'odoo', 'addons'),
]
transpiled = 0
copied = 0
errors = 0
for url_path in js_files:
# url_path is like /web/static/src/env.js
# Find the real file
rel_path = url_path.lstrip('/')
source_file = None
for addons_dir in addons_dirs:
candidate = os.path.join(addons_dir, rel_path)
if os.path.isfile(candidate):
source_file = candidate
break
if not source_file:
print(f" SKIP (not found): {url_path}")
continue
# Output path
out_file = os.path.join(output_dir, rel_path)
os.makedirs(os.path.dirname(out_file), exist_ok=True)
# Read source
with open(source_file, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
# Check if it's an odoo module (has import/export)
if is_odoo_module(url_path, content):
try:
result = transpile_javascript(url_path, content)
with open(out_file, 'w', encoding='utf-8') as f:
f.write(result)
transpiled += 1
except Exception as e:
print(f" ERROR transpiling {url_path}: {e}")
# Copy as-is on error
shutil.copy2(source_file, out_file)
errors += 1
else:
# Not an ES module — copy as-is (e.g., libraries, legacy code)
shutil.copy2(source_file, out_file)
copied += 1
print(f"\nDone: {transpiled} transpiled, {copied} copied as-is, {errors} errors")
print(f"Output: {output_dir}")
if __name__ == '__main__':
main()