Skip to content

Guide: Extending Core with Regional Compliance

How to “plug in” regional logic into business-m libraries without modifying core code.


1. The Strategy: “Don’t Touch the Core”

Section titled “1. The Strategy: “Don’t Touch the Core””

The goal of the Port/Adapter architecture is to keep m-core, wms, and finance completely agnostic of regional rules.

Instead of adding an if region == 'india': block in wms, we use Events and Mixins.


Most regional modules need extra fields on standard DocTypes (e.g., gstin on Address). We use Standard Python Mixins for the developer UX, and the framework’s MetadataDecoratorRegistry for system stability.

m_india/doctypes/mixins.py
class IndiaAddressMixin:
gstin: str | None = None
place_of_supply: str | None = None

The “Invisible” Infrastructure (Framework Level)

Section titled “The “Invisible” Infrastructure (Framework Level)”

During bootstrap, the framework uses the MetadataDecoratorRegistry (which you implemented) to bake these into the model:

m_india/bootstrap.py
from m_india.doctypes.mixins import IndiaAddressMixin
from framework_m.metadata import MetadataDecoratorRegistry
registry = MetadataDecoratorRegistry.get_instance()
# Register the transition. This informs the registry how to handle these fields.
registry.register_property("Address", "gstin", str, persistence="json")
registry.register_property("Address", "place_of_supply", str, persistence="json")
  1. Model Stability: When you call registry.bake(Address), you get a single unified model even if multiple regional apps are active.
  2. Transparent Persistence: Fields with persistence="json" are stored in the shared custom_fields column, avoiding day-1 migrations.
  3. No-Cliff Scaling: The same Address model is distributed to all macroservices in Enterprise mode, ensuring they all share the exact same schema.

When a core event happens (like SalesInvoice.on_submit), regional adapters can listen and perform compliance actions.

Example: E-Way Bill on DeliveryNote Submission

Section titled “Example: E-Way Bill on DeliveryNote Submission”

In wms, the core logic simply publishes an event.

wms/doctypes/delivery_note/controller.py
class DeliveryNoteController(BaseController):
async def on_submit(self, doc):
# 1. Update stock
await self.update_stock(doc)
# 2. Publish compliance event
await self.publish("compliance.transaction.submitted", doc)

In m-india, we subscribe to this event.

m_india/subscribers/wms_compliance.py
@subscribe("compliance.transaction.submitted")
async def handle_delivery_note(doc):
if doc.doctype == "DeliveryNote" and doc.total_amount > 50000:
# Trigger India-specific E-Way Bill generation
await india_compliance.generate_eway_bill(doc)

For synchronous logic like tax calculation, we use the RegionalPort dispatcher.

Example: Calculating Taxes on SalesInvoice

Section titled “Example: Calculating Taxes on SalesInvoice”

In finance, the logic calls the m-regional port.

finance/services/tax_engine.py
from m_regional import get_active_provider
async def calculate_bill_taxes(inv: SalesInvoice):
# This automatically finds 'm-india' or 'm-usa' based on context.
provider = await get_active_provider("tax", context=inv)
if provider:
inv.taxes = await provider.calculate_taxes(inv)
inv.save()

Context Core Lib Hook/Event Regional Action
Procurement finance PurchaseInvoice.on_save Calculate GST/TDS (India)
Sales finance SalesInvoice.on_submit Generate IRN (India)
Logistics wms DeliveryNote.on_submit Generate E-Way Bill (India)
Payroll people SalarySlip.on_calculate Deduct PF/PT (India)