Skip to content

Companies Developer Guide

Ed Mozley edited this page Nov 14, 2026 · 1 revision

πŸ› οΈ Companies - Developer Guide

Since 5.3.0 Β· #279 Β· API: REST API: Companies Β· User guide: Multi-company

A support business wanted its ~3,000 small-business customers in FreeITSM as companies, kept in step from an ERP - in Greece a business is known by its tax ID (AFM), not its name or domain, and many use Gmail. This slice adds the reference, the tax ID, the API to sync them, and a fix for their mail.

πŸ“ Files

File Role
tenants.external_ref VARCHAR(100), tenants.tax_id VARCHAR(50) Unique when set (uq_tenants_external_ref, uq_tenants_tax_id; NULLs never collide)
includes/services/companies.php CompaniesService - every rule once: save(), upsert(), findByExternalRef(), get(), serialize(), refsReady()
api/system/save_tenant.php System β†’ Companies' save - now a thin call to the service
api/system/get_tenants.php, system/companies/index.php The list returns and shows the two references; the dialog edits them; a search box filters by name, reference, tax ID, ticket code and domain
includes/people.php, people/company.php The company's People page shows them
api/v1/resources/companies.php GET /companies[/{id}], POST /companies, PATCH /companies/{id}, POST /companies/upsert; apiCompaniesRequireManager()
api/v1/resources/users.php employee_id read, written and filtered (apiUserEmployeeIdReady())
includes/tenancy.php resolveTicketTenantForEmail() step (3c) - a known person's company
tests/companies-279.php (24), tests/api-companies.php (60)

πŸ”‘ The rules - CompaniesService::save()

  • A name is required (150 characters at most).
  • A ticket code, an outside reference and a tax ID are each unique when set, compared ignoring case after trimming and collapsing spaces; a clash is a conflict ServiceError (HTTP 409) naming the company that has it.
  • The Default company cannot be made inactive.
  • Only the keys present change on an update; an empty string clears the code, the reference or the tax ID.
  • Before Database Verification the two columns do not exist: refsReady() is false, the System page simply does not send them, and the API answers "run Database Verification" if they are sent.

upsert() finds the company by external_ref and calls save() with its id, or with none.

πŸ”’ Who may write

The System page is administrators only. The API holds a key to the same rule, copied from apiAnalystsRequireProvisioner(): scoped to all companies, and acting as a System administrator. A PATCH checks the company is within the key's scope first (404, the same as unknown), then the administrator rule (403).

πŸ“¨ Mail from a known person - (3c)

resolveTicketTenantForEmail() used to reach a person's company only through a linked address (#308). A person whose main address is a Gmail address, filed under a company on their People page, still went to triage unless the address was also added as one of the company's specific senders - which is what the reporter hit. Step (3c), after a mapped domain and before triage: the person whose main address it is (users.email is unique, so one or nobody), if they have a company. Last on purpose - a company's specific senders and a mapped domain still win.

πŸ§ͺ Verifying

FREEITSM_TEST_DATABASE=freeitsm_prtest FREEITSM_TEST_URL=http://localhost/freeitsm-prtest php tests/companies-279.php
FREEITSM_TEST_DATABASE=freeitsm_prtest FREEITSM_TEST_URL=http://localhost/freeitsm-prtest php tests/api-companies.php

⬜ Still to come on #279

Custom fields on companies and people (on the shared TypedFields engine), and a searchable company picker in place of drop-downs once there are hundreds.

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Responses to feedback

Blue sky thinking

Bugs resolved

Pull requests

Links

Clone this wiki locally