Project Meru

03 / Architecture

Three modules, one contract.

Sumeru is not one repository you fork. It is a core Go module, a module of standard business addons, and your own workspace module that imports both.

Docs. Install and configuration live in Documentation -> Installation. Kernel details are under Concepts. This page stays as a visual architecture overview.
sumeru_custom_addons your workspace · runs the server
main.gosumeru.confaddonimports/addons/...
replace sumeru_addons
sumeru_addons standard business addons
contactsproductcrmsale sale_crmaccountpurchasehr
replace sumeru
sumeru the core framework
ormmoduleengineserverevent sdkschedulerapplogcache
in-tree apps: base · mail · automation · sumeru_ai
lib/pq
PostgreSQL one database · many companies

Nothing imports sideways

No addon imports another addon's Go package. Invoicing subscribes to record.updated, checks whether sale.order is in the registry, and only then builds an invoice.

Dependencies are declared, not imported

The depends array in the manifest drives install order and nothing else: sale needs product, contacts and crm installed, but never links against them.

// sumeru_addons/account/models/account_journal.go
package models

import "sumeru/core/sdk"

type AccountJournal struct {
    sdk.BaseModel
}

func (AccountJournal) ModelName() string { return "account.journal" }

func (AccountJournal) Fields() []sdk.FieldDefinition {
    return []sdk.FieldDefinition{
        {Name: "name", Type: sdk.Char, String: "Journal Name", Required: true},
        {Name: "code", Type: sdk.Char, String: "Short Code", Required: true},
        {Name: "active", Type: sdk.Boolean, String: "Active", DefaultVal: true},
    }
}

func init() {
    sdk.RegisterModel(sdk.RegisterModelInput{Model: &AccountJournal{}, Module: "account"})
}
// sumeru_addons/account/manifest.json
{
  "name": "account",
  "display_name": "Invoicing",
  "version": "1.0.0",
  "depends": ["product", "contacts"],
  "data": [
    "security/security.xml",
    "security/sys.access.csv",
    "data/account_data.xml",
    "views/account_views.xml",
    "views/menus.xml"
  ],
  "application": true
}
<!-- sumeru_addons/account/views/ -->
<action id="action_account_moves_out"
        type="window" model="account.move"
        name="Customer Invoices" view_mode="tree,form"/>

<menuitem id="menu_account_invoices" name="Invoices"
          action="account.action_account_moves_out"/>
// sumeru_addons/account/init.go
func init() {
    event.Subscribe("record.updated", onSaleOrderToInvoice)
}

func onSaleOrderToInvoice(ctx context.Context, ev event.Event) error {
    if model, _ := ev.Payload["model"].(string); model != "sale.order" {
        return nil
    }
    if _, ok := orm.Registry["sale.order"]; !ok {
        return nil // Sales is not installed
    }
    // create draft invoice idempotently
    return nil
}
# sumeru_addons/account/security/sys.access.csv
id,name,model,group_id,perm_read,perm_write,perm_create,perm_unlink
access_account_move_user,...,account.move,group_account_invoice,1,1,1,0
access_account_move_manager,...,account.move,group_account_manager,1,1,1,1