Serwisownia: a field CRM for an HVAC installer

A system for a company that installs and services air conditioning and heat pumps. It keeps the service calendar so nobody has to, lays out a week of technician work, and ties each sale to the specific unit standing at the customer.

Captures from a running instance; the company in them is invented

45
units in the demo records
96
service calls
23
tests, all green
3
roles with separate permissions

An installer does not lose customers. It loses dates

Several hundred customers, one to a handful of units each, every unit on its own service cycle.

A split system once a year, heat recovery every six months, a shop installation every six. On top of that, breakdowns that have to be wedged between planned visits, and enquiries for new installations arriving all through the season.

A spreadsheet holds that to about a hundred units. After that it turns into calling back customers who remembered their own service date first, and searching an inbox for who was on site last and what they did. The customer does not leave because the work was bad. They leave because nobody got in touch.

The service clock drives everything else

The whole system rests on one loop, and the remaining screens only serve it. The order below is not decoration - it is the actual flow of the work.

  1. 01

    Site

    The address the equipment stands at, with coordinates, a technical contact and access notes. One customer can have several.

  2. 02

    Unit

    Manufacturer, model, serial number, install date, warranty, and its own service interval counted in months.

  3. 03

    Due date

    The system works out the next service date from the last visit. Once it passes, the unit appears on the coordinator’s worklist without anybody putting it there.

  4. 04

    Job

    A service, a breakdown or an installation goes to a technician, with a date, a time budget and the customer’s own description of the problem.

  5. 05

    Close-out

    A finished service writes the work done into the unit’s history and resets the clock for the next cycle. The loop closes itself.

The coordinator plans; the technician sees only their own

Jobs move through four columns. A technician sees their own, the coordinator sees the whole team, and anything overdue turns red on its own instead of waiting to be noticed.

Kanban board of service jobs in the columns New, Scheduled, In progress, Done and Cancelled
Job board Number, site, type of work and due date. The star marks urgent breakdowns; the tag on the right is the technician who owns it.
Calendar view with service jobs laid out across the week
Schedule The same set of jobs in a calendar, coloured by technician. For laying out the week and wedging a breakdown between two planned services.
List of units past their service date, grouped by town
Services due The list fills itself as dates pass. Grouping by town turns it into a route for one day on the road.
Form for a single service job with a status bar and tabs
One job The status bar walks through four steps, and the buttons only offer the transitions that make sense right now. What the customer reported and what was actually done are kept apart, because they are two different texts written by two different people.

The map answers where to drive this week

A component of our own on open map tiles - no paid keys, no modules from the paid edition of Odoo. Points are coloured by how overdue the service is; the filters at the top switch the view in one click.

Map showing customer sites around Poznan
Site map Red is overdue, orange is due within 30 days, green has nothing pressing. Clicking a point opens a bubble with the customer and address, and the button in it goes straight to the record.

The history lives on the unit, not in an inbox

Who was there, when, and what they did is readable from the unit itself. No reconstructing it from memory, no digging through mail from a year ago.

Site record with a list of units and counters
Site record The counters at the top lead to the units and the jobs. Access notes - the gate code, where to park - have a place of their own, because that is the first thing a technician looks for.
Unit record with service dates and service history
Unit record Install, warranty and service dates beside the full visit history. The button opens the next service with the date and the technician already filled in.

The pipeline extends the standard, so reports need no code

Enquiries did not get a model of their own. They extend the standard Odoo pipeline with a site, a unit and a case type, so all the stage and quotation mechanics work the way they do everywhere else - and the company gets the link to a specific installation on top.

Sales pipeline in a kanban view with stages
Pipeline New enquiries, qualification, quotation and won. The case type separates a new installation from a unit replacement and from a maintenance contract.
Bar chart of jobs by month, split by type
Load over time Hours worked by month, split into services, breakdowns and installations. The season is visible, and so is how much of it breakdowns eat.
Pivot table of technician load by month
Technician load A pivot by technician and month. For the conversation about whether the crew needs a fourth person.

Four decisions you can see in the code

The calls that weigh more in a project like this than the number of screens does.

Extend the standard rather than rewrite it

The pipeline is an extended crm.lead, not an opportunity model of our own. Less code to maintain, full compatibility with the rest of Odoo, and free updates to mechanics nobody will build better than the vendor.

Demo data in a module of its own

The product installs at a customer without a single spare record, while the demo module adds an entire fictional company for a walkthrough. A script with a fixed random seed generates the set, so the demo reproduces identically from one command.

A map with no external dependencies

The map library sits in the repository, not on somebody else’s CDN. The component fetches sites in one query and works out service urgency itself, so the map runs on the free edition of Odoo with no key to a paid geocoder.

Permissions proven by test, not by claim

The rules keep a technician to their own jobs and let them read records without editing them. The matrix has a test suite of its own, because it is the one thing you cannot see on screen until it is too late.

What sits underneath

Odoo 19 Community and Postgres in containers, with the product module kept separate from the demo-data one.

Twenty-three unit tests cover the service cycle, searching on computed fields, and the permission matrix. On every change to the repository the installation starts from nothing, the suite runs in full, and a separate step checks that the demo data still matches the script that generates it.

The delivered interface is in Polish, field labels and status names included - the crew using it works in Polish, and a technician should not need a translation to read their own worklist. That is what the captures on this page show. The same build ships in whatever language the team on site actually speaks.

The company in these captures, its customers, addresses, serial numbers and staff names are invented. A script generates the whole data set from a word list, and any resemblance to a real company would be coincidental. Manufacturers and model names are real, because without them the records would not look believable.

Tell us where your dates are slipping

A few sentences on what is in the spreadsheet today and who it is hurting. We answer with scope, a timeline and a quote within 24 hours.

Free consultation

The same clock in other trades

Instead of a system off the shelf