05Shared codebase
Tenders, imported from spreadsheets and totalled per currency
A tender-management module in the group’s project platform: construction, design and consultancy packages, from issue to award.
- The problem
- Tenders arrived in spreadsheets, and contract totals in several currencies added up to a number that meant nothing.
- What I built
- A validated spreadsheet import, cancellation with required reasons, fields synced to the ERP, and contract totals per currency.
- The result
- An imported tender goes through the same rules as a typed one, and totals are reported per currency.
Second-largest contributor to the module — 2,963 of 7,184 backend lines, about 41%, of which 1,742 are tests — plus 658 of 3,869 frontend lines. The dashboard’s layout and most of its analytics are a colleague’s work; the currency totals behind one tile are mine.
Tenders exist in spreadsheets long before anyone types them into a system, so I wrote the import. Each row is validated against the same rules the create and update forms apply, with one deliberate relaxation: operation zones may be left empty and added later from the interface. A status in the sheet is applied as an update rather than written straight to the column, so the status log, the end date and the cancellation bookkeeping run exactly as they do when a person clicks the button. A contract price on anything not yet awarded is refused, because the price lives on an approval form that only an awarded tender has.
Cancelling a tender takes one of six required reasons, with a free-text note required only for "other", and reverting a cancellation clears the end date it set. I also extended the queued job that pushes each tender to the company ERP, so the cancellation reason and phase travel with it, and wrote the command that pulls tender types back from the ERP.
Contract prices come in more than one currency, and adding them into one figure produces a number that means nothing. The dashboard total is grouped by currency, largest first, counts only the latest approval form for each tender, and names a single currency only when there is exactly one.
Screens

The tender register. References are generated from the project and tender type; project and phase names are blurred. 
Staging data. The contract-price tile reads the per-currency totals: one figure, and a count of the currencies it did not add in.
- Laravel
- PHP
- PostgreSQL
- Queued Jobs
- Spreadsheet Import
- Next.js
- PHPUnit
Need something like this built?
Taking freelance work · ~20h/week.