Skip to content
04Web Development · Full-stack Development · Mobile Development · 2026Internal tool

Katwara QA

Summary

A quality-inspection platform for PT. Katwara, a furniture manufacturer: the office registers purchase orders, and inspectors document each unit on the factory floor from an Android app. As sole developer of both halves I built the Laravel API and admin panel on MySQL and the Ionic app inspectors carry, through to the signed PDF report the client receives.

Role
Sole developer, both halves
API, admin panel and Android app
Timeline
Nov 2025 – Jul 2026
At OMNI IT Consulting
Client
PT. Katwara
Furniture manufacturer
Stack
Laravel · MySQL
Ionic · Angular · Docker
Stack
  • TypeScript
  • Angular
  • Ionic
  • Capacitor
  • Laravel
  • MySQL
  • Docker
  • GitLab CI/CD
  • PHP
  • Redis
Internal tool04

Screens are under NDA

This project runs inside the client's organisation, so I can't share real screens. Below you'll find the problem, the decisions I made and the results.

Read how it works
(01)

The problem

A furniture manufacturer needed its inspectors to document every quality inspection on the factory floor, unit by unit, and turn it into a report its own client could rely on.

A purchase order holds many products (SKUs), and each product can be inspected more than once. An inspector doesn't check the whole order: they check a sample of units, and for each one they take photos and record any defects. Around that come the production quantities, an in-house test, a packaging checklist, and a final remark signed on the spot.

The office side needs to manage clients, vendors, factories, products and purchase orders, assign each order to an inspector, and hand the client a PDF report per product. Inspectors work from a phone on the factory floor, so the app has to be simple and only let them change what's theirs.

  • Purchase orders with many products, each inspected one or more times
  • An Android app for inspectors, with their own accounts
  • A four-stage inspection: production control, photos and defects per unit, a packaging checklist, and a signed final remark
  • An admin panel for clients, vendors, factories, products and purchase orders
  • A PDF inspection report per product for the client
  • Three roles: super admin, admin and inspector
(02)

How I solved it

I built both halves alone: a Laravel API and admin panel on MySQL, documented in OpenAPI, and the Angular/Ionic app inspectors use. With the DevOps team I containerised it (PHP-FPM, nginx, MySQL and a Redis-backed queue worker), deployed on every merge to production through GitLab CI. Three decisions shaped it.

01An inspection belongs to the product line, not the order

The first model hung an inspection report on the purchase order. But an order holds many products, and the same product can be inspected more than once, so the report moved to the order's line item: one order, many products; one product, many inspections. The change went through the API, the panel and the app together.

The per-unit rows changed for the same reason. They used to be created for every unit in the order when it was confirmed, so a 2,000-unit order made 2,000 rows for an inspection that checks a sample. Now the rows are created at the production-control stage, one for each unit the inspector will actually check, and the inspected quantity must be at least one and no more than the order holds.

Trade-off

Moving the link mid-project meant migrating the schema and updating the API, the panel and the app at once.

02Re-submitting is allowed; the data stays consistent

My first version made production control submit-once: a second submit got a 409 conflict, to stop duplicate rows. But an inspector sometimes needs to change the inspected quantity, the number of units they actually check, after submitting it. The duplicates were my problem to solve, not a reason to make their workflow illegal.

So re-submitting is allowed while the report is still in progress, and blocked only once it's completed or locked. Each submit re-syncs the per-unit rows: rows not yet inspected are removed, rows already inspected keep their photos and data, and new rows are added only if the count falls short. Then the completion flags are worked out again from what's there, so raising the quantity can't leave a report marked complete with units nobody looked at.

app/Models/InspectionReport.php
// drop only the un-inspected rows; completed rows (with their data) stay$items->inspectionPoItems()->where('is_completed', false)->delete(); $target = (int) $this->inspected_qty;$kept = $items->inspectionPoItems()->count(); // top up with fresh rows only if the kept rows don't meet the targetif ($kept < $target) {    $next = ((int) $items->inspectionPoItems()->max('item_number')) + 1;    for ($i = 0; $i < $target - $kept; $i++) {        $items->inspectionPoItems()->create(['item_number' => $next + $i]);    }} // recompute completion from the current rows, never trust the old flag$items->update(['is_completed' => $items->isAllInspectionPoItemsCompleted()]);
Trade-off

Re-syncing on every submit is more work than rejecting the second one.

03Two locks: the role at the route, the owner in the handler

The app signs in with a token, and only inspectors can log in to it. The API's routes are grouped by role: inspectors get the app's endpoints, admins and super admins get the panel's. But a role only says who you are, not which order is yours.

So the inspection handlers check again: the purchase order must be assigned to this inspector and in the right state before anything changes, and a completed or locked report refuses writes. The app adds its own guard and stops adding photos once a report is completed. The rule lives on the server; the app just doesn't offer what the server would refuse.

Trade-off

The inspection handlers repeat the ownership and state checks instead of relying on the route alone.

(03)

Results

data models
16
API endpoints, role-scoped
27
inspection stages, to a signed PDF
4

The platform is in production for PT. Katwara. Inspectors document purchase orders on the factory floor from the Android app, and the office manages the orders in the panel and sends the client a PDF inspection report for each product. It's an internal application, so there are no public screens to show.

Next project05 / 06

Selada

Got something in mind?

Let's build the next one.