Back

What is the Purchase scope?

The Purchase scope is where you keep track of the companies and people you buy from. In the current release, this means two things: a list of Vendors (the businesses that supply you) and Vendor Categories (the groupings you use to analyse, filter, and route them).

Everything you'd expect on top of that β€” Requests for Quotation, Purchase Orders, Goods Receipts, Vendor Bills β€” is on the product roadmap but not yet implemented in this module. If you need those today, pair this scope with Accounting (for vendor bills) until the procurement modules land.

ℹ️

Honest scope note

The Purchase scope currently ships one module: core_purchase. It focuses on vendor records and categorisation. The Procure-to-Pay transaction flow is tracked in the Roadmap section below.

Where Purchase fits in Procure-to-Pay

Vendor onboarded β†’ RFQ β†’ Purchase Order β†’ Goods Receipt β†’ Vendor Bill β†’ Payment

Filled blue box = shipping today. Hollow boxes = future modules. Vendor Bill & Payment can already be handled by core_accounting; the rest are awaiting their dedicated modules.

What ships in core_purchase

CapabilityModelSource
Vendor flag + vendor code on partnersbasepartner (inherited)models/base_partner.py
Vendor Category masterpurchasevendorcategorymodels/purchase_vendor_category.py
Auto-generated Vendor Code sequencebasesequence (code=purchase.vendor)data/vendor_sequence_data.xml
Vendor list / form / categories viewsbaseuiview, baseactionactwindowviews/*.xml
Security rules & ACLsbasegroup, baserulesecurity/*.xml

Prerequisites

  • base module installed (it owns the basepartner and basesequence models the scope extends).
  • Vendor partner type exists as base.base_partner_type_vendor. It ships with base β€” check it is present before first use.
  • User has Purchase / Vendor Management access. Administrators can assign this from the user form.

Install & bootstrap

Install the module

Run the install command against your alias:

hmx update install core_purchase --alias=default --noinput

Verify the Vendor Sequence

The module ships a basesequence with code purchase.vendor. Go to Settings β†’ Technical β†’ Sequences and confirm it is present and active.

Sequence: purchase.vendor
NameCodePrefixPaddingNext
Vendorpurchase.vendorV5V00001
Vendor sequence β€” auto-generates codes like V00001, V00002…

Open the Vendor menu

After refresh, the Purchase top-level menu appears with a Vendor sub-menu. Click Purchase β†’ Vendor β†’ Vendors to land on the (initially empty) vendor list.

Optional: set up vendor categories first

Categories are optional but useful if you need to segment suppliers (e.g. Raw Material, Logistics, Services). Create them under Purchase β†’ Configuration β†’ Vendor β†’ Vendor Category before your first vendor if you want them ready to tag.

How the install wires up

  • __hmx__.py declares dependency on base and loads, in order: security/security.xml β†’ security/base.model.access.csv β†’ data/vendor_sequence_data.xml β†’ the three view XMLs.
  • vendor_sequence_data.xml creates the basesequence record with code=purchase.vendor, consumed at create-time by BasePartner._generate_vendor_id.
  • Menu tree: menu_core_purchase_root β†’ menu_purchase_vendor_sub β†’ action purchase_vendor_action (domain: [('is_vendor','=',True)]).

What is a Vendor?

A Vendor in HMX is any Partner with the Vendor type turned on. The same Partner record can also be a Customer β€” the two flags are independent. The Vendor list shown under Purchase β†’ Vendor β†’ Vendors is simply all Partners filtered by is_vendor = True.

When a Partner becomes a Vendor for the first time, the system automatically stamps two fields: Vendor Code (from the purchase.vendor sequence) and Vendor Created On (the exact UTC timestamp). Both are locked after creation β€” they cannot be edited by users or even by most code paths.

How to create your first vendor

Open the Vendor list

Navigate to Purchase β†’ Vendor β†’ Vendors. You'll see only Partners flagged as vendors β€” hiding the Customer records.

Vendors
Vendor NamePhoneEmail
(empty β€” no vendors yet)
+ Create
Vendor list β€” filtered by is_vendor=True

Click Create

The standard Partner form opens. You can fill in all the usual fields (Name, Email, Phone, Address).

Tag the Partner Type as Vendor

In the Partner Types field, add the Vendor tag. This is what flips is_vendor to True.

New Partner
PT Sumber Makmur
Vendor
+62 21 5555 1234
sales@sumbermakmur.co.id
Mark the partner type as Vendor before saving

Save the record

On save, the system does three things automatically: adds the internal Vendor type (if missing), generates a Vendor Code from the sequence, and stamps Vendor Created On.

PT Sumber Makmur Vendor
V00001 auto
Vendor

GeneralPurchase
19 Feb 2026, 14:02 UTC readonly
After save β€” Vendor Code & Created On are locked

Return to the list

The new vendor shows up immediately. The Vendor Code is used anywhere you need a stable, unique reference β€” invoices, reports, integrations.

Vendor Code rules explained

πŸ”’

Vendor Code is set once, never edited

Even through admin tools, attempting to change vendor_code or vendor_creation_time raises a Vendor Code cannot be modified error. This guarantees stable external references (think: printed on legacy invoices you cannot recall).

πŸ”

A Partner can become a Vendor later

You may have an existing Customer that later starts supplying you. Simply add the Vendor partner type. The system detects the transition and stamps the Vendor Code + Created On at that moment β€” not at the original partner creation.

⚠️

Removing the Vendor tag does NOT clear the code

By design, if you later untag the vendor type, the vendor_code stays. That way, historical references to V00023 still match the same Partner even if they've stopped being an active vendor.

How the vendor flag is computed

is_vendor is a stored computed field depending on partner_types. _compute_is_vendor resolves the vendor type reference once per batch and sets rec.is_vendor = vendor_type_id in rec.partner_types.ids. The helper also handles new records where partner_types are still _origin references.

Create flow (BasePartner.create)

  1. If is_vendor is requested but partner_types not supplied, the override injects the Vendor type automatically (6,0,[vendor_type_id]).
  2. Calls super().create.
  3. For every created record that qualifies as a vendor (_is_vendor()), it calls super().write with an internal context (skip_vendor_lock=False equivalent) to stamp vendor_code and vendor_creation_time β€” bypassing its own write guard because the values are missing.

Write flow (BasePartner.write)

  1. Rejects any attempt to write vendor_code or vendor_creation_time unless skip_vendor_lock is set in context.
  2. If partner_types is being modified, simulates the resulting set (handles 6/4/3 commands), and if the record transitions from non-vendor β†’ vendor, it stamps the code & timestamp as part of the same write.

What is a Vendor Category?

A freeform label you create to group vendors β€” Raw Material, Logistics, Services, IT Hardware. Categories are multi-company aware: if you assign a category to HQ only, users working in a subsidiary won't see it.

Note: in the current module the category is a master list β€” it is not yet attached as a tag on the Vendor form. Use it as a reference master that your custom fields, reports, or analytics can filter against.

How to create a vendor category

Open the Category list

Navigate to Purchase β†’ Configuration β†’ Vendor β†’ Vendor Category.

Vendor Category
NameDescriptionCompanies
(empty)
+ Create
Empty category list β€” click Create to add the first

Click Create & fill the form

Enter a Name, optional Description, and pick one or more Companies the category should apply to.

New Vendor Category
Raw Material
Suppliers of unprocessed inputs β€” steel, plastic, paper.
HQ Jakarta Plant Bekasi
Category scoped to two companies only

Save β€” the category is live

After save, Created By and Created On are stamped (both readonly). Users in the assigned companies see it in their search/filter lists; users in other companies don't.

Add more categories as needed

Typical starter set: Raw Material, Services, Logistics, IT Hardware, Office Supplies. Leave Companies empty to make a category globally visible.

Company scoping rules

🏒

Empty Companies = visible to all

When the Companies field is left empty, search_read returns the category for every user regardless of active company. Use this for global categories like Services.

πŸ”

Form view respects your current company set

fields_view_get injects a domain on the Companies field so you can only assign categories to companies you have access to. This prevents cross-company data leakage at the UI layer.

How the company filter is enforced

Two layers:

  1. search_read is overridden to extend the domain with ['|',('companies','in',allowed_ids),('companies','=',False)].
  2. fields_view_get rewrites the Companies field's domain via lxml, so the form picker only suggests allowed companies.

Both rely on selected_company() which prefers env.companies.ids (active switcher set), falling back to the user's assigned companies.

Status today

CapabilityTarget ModuleStatusWorkaround Today
Vendor master & codecore_purchaseShippingβ€”
Vendor Category mastercore_purchaseShippingβ€”
Request for Quotation (RFQ)core_purchasePlannedTrack offline or via a custom model.
Purchase Order (PO)core_purchasePlannedCapture as a manual Journal Entry in Accounting if needed.
Goods Receipt / Three-way matchcore_purchase + core_inventoryPlannedManual Inventory receipt + supporting doc.
Vendor Billcore_accountingShippingCreate an Account Document with source type = vendor bill.
Vendor Paymentcore_accountingShippingRegister payment on the vendor bill in Accounting.
Vendor Analyticscore_purchase + OLAPPlannedQuery the database directly; no OLAP sync yet.

Bridging the gap today

If you need an end-to-end procurement trail now, the pragmatic workflow is:

  1. Create the Vendor in Purchase β†’ Vendor β†’ Vendors.
  2. Capture procurement decisions offline (email, spreadsheet, shared doc) β€” the RFQ & PO modules aren't live yet.
  3. When the goods arrive, record an Inventory receipt against the right product/warehouse using the Inventory module.
  4. When the invoice arrives, open Accounting β†’ Documents β†’ New, pick the journal with source type vendor bill, attach lines against the vendor's Account Payable.
  5. Register payment on that document β€” exactly the same flow as regular customer receipts, just reversed direction.
πŸ’‘

Don't fork core_purchase yet

The RFQ / PO models are planned to live in this exact module. If you build a temporary extension, keep the model names generic (e.g. purchase.order) so you can retire your custom code cleanly once the official version ships.

Modules in the Purchase scope

ModulePurpose
core_purchaseVendor master data (via basepartner extension) and Vendor Category master. Ships vendor sequence, menu tree, and company-scoped category access.

Key data models

ModelSourceRole
basepartner (inherited)core_purchase/models/base_partner.pyAdds vendor_code (unique, editable=False), vendor_creation_time (datetime), is_vendor (computed, stored).
purchasevendorcategorycore_purchase/models/purchase_vendor_category.pyName + description + M2M companies; ordered by name. Active-company filtering in search_read & fields_view_get.
basesequence (data)core_purchase/data/vendor_sequence_data.xmlSequence with code purchase.vendor β€” consumed via env["basesequence"].next_by_code("purchase.vendor").

basepartner β€” vendor fields

FieldTypeAttributes
vendor_codeCharField(16)unique=True Β· editable=False Β· null=True Β· blank=True
vendor_creation_timeDateTimeFieldeditable=False Β· null=True Β· blank=True
is_vendorBooleanFieldcompute="_compute_is_vendor" Β· store=True

Extension hooks (BasePartner)

  • _get_vendor_type() β€” resolves base.base_partner_type_vendor. Override if you use a custom vendor type ref.
  • _is_vendor() β€” returns True iff the vendor type id is in partner_types.ids.
  • _generate_vendor_id() β€” wraps basesequence.next_by_code("purchase.vendor"). Raises ValidationError if the sequence is missing.
  • @api.depends("partner_types") _compute_is_vendor β€” recomputes stored flag on any partner_types change.
  • create / write β€” stamp + lock vendor_code / vendor_creation_time. Use context skip_vendor_lock=True to bypass (admin tools only).

Extension hooks (PurchaseVendorCategory)

  • selected_company() β€” prefers env.companies.ids, falls back to env.user.companies.ids. Override to widen/narrow visibility.
  • fields_view_get β€” rewrites the companies field's domain on the form to allowed ids. Override if adding more scoped fields.
  • search_read β€” extends domain to match records with at least one allowed company (or none set). This is the authoritative company gate for category listings.

Views, actions, menu tree

ElementXML idNotes
Vendor listpurchase_list_vendor_viewColumns: name, phone_number, email. Model = basepartner.
Vendor form (inherit)purchase_form_vendor_inheritAdds vendor_code after partner_types; adds a Purchase notebook page with vendor_creation_time (readonly).
Vendor actionpurchase_vendor_actionDomain [('is_vendor','=',True)]; uses base partner search view.
Vendor Category listpurchase_vendor_category_listColumns: name, description, companies (many2many_tags).
Vendor Category formpurchase_vendor_category_formStandard form; created_by / created_at are readonly after save.
Root menumenu_core_purchase_rootTop-level "Purchase" with icon icon-fin-purchase.

API endpoints

No Ninja API endpoints are defined in core_purchase today. Vendor and Vendor Category records are reached via the generic HMX data APIs.