JALIL

plan · 03

Projects

Four projects from my work at Parfumerie. Three are still in production and one was discontinued. I'm a junior developer, so for each one I explain what the problem was, what I did myself, what belonged to the team, and how it ended. The numbers come from real operations.

01

Multi-store POS with electronic invoicing

In production

Role
Development, analysis, training and rollout
Context
Express backend and Next.js frontend. It replaced the third-party POS software the group was using. It has 100+ test suites and 40+ database migrations.

What it's about

It's the system the stores use to charge customers: it brings together checkout, AFIP electronic invoicing, cash register and reporting, and connects to card terminals, MercadoPago and gift cards. I built it solo, end to end, to replace the previous system.

The problem

The riskiest part of a POS is taking the money, and the previous system made it painful: it came from an old ERP still in use at the company, charging required many manual steps and it was slow. When something failed halfway through a payment, the cashier didn't know whether the sale had gone through and got stuck at the register, with the risk of charging twice.

What I did

  • I prevented double charges with idempotency: every payment attempt (cart + payment method + amount) gets a key with a unique index in the database, so two simultaneous requests end up as a single charge.
  • I persist the attempt before charging, not after. That meant splitting where the duplication risk lived (the POST that triggers the terminal) from where the long wait lived (the polling query, which is safe to repeat).
  • Every failed charge fails cleanly within 30 seconds or less, with an error the cashier can understand, and they can check the real status of the payment without creating a second transaction.
  • I built the financial reports (cash register sheet, sales detail, VAT sales ledger, cash reconciliation and issued receipts), with Excel export and access based on each user's profile.
  • I added automatic routing between terminals based on the card's issuing bank, to make the most of bank promotions, with SiTef/CliSiTef integration.
  • I added a retry queue for the invoices AFIP rejects.

How it turned out

  • Because the protection is a unique index and doesn't depend on timing, a retry can never duplicate a charge, even if the network or the terminal misbehave.
  • I saved stores and admin staff hours of checking. Before, closing the register could take over 30 minutes, and admin assistants checked every store's register daily. Today the check takes very little time, because the margin for error is minimal.

500+ sales per day · 7 of 20 stores live (gradual rollout)

  • Node.js
  • Express
  • MySQL2
  • Prisma/Payway
  • MercadoPago
  • SiTef / CliSiTef
  • SOAP
  • Pino
  • FTP
  • Docker
  • Next.js 15
  • React 19
  • Zustand
  • AFIP
  • Node test runner
  • Playwright

02

Order orchestrator across Modula, ERP and Magento

In production

Role
Development and analysis, alongside another developer
Context
TypeScript monorepo with 104 test suites, 106 migrations and 229 Prisma models. Most features came from a previous PHP/Laravel backend, so I joined a project that already existed.

What it's about

It's the system that decides how each online order is prepared: whether it leaves the Modula robotic pick stack, the manual warehouse, or is split between the two. It also coordinates carriers, stores and tracking. Almost everything already existed when I joined; I worked on the new parts and on the migration.

The problem

When I arrived, the operation combined three systems with duplicated logic and out-of-sync data that cost money. The code was in PHP/Laravel, with little test coverage and a 229-table base that was hard to touch without breaking something.

What I did

  • Mine: stock validation against BigQuery with a safety buffer (stock ≥ requested qty + committed qty + buffer), all-or-nothing per order. It shrinks the window where the ERP, the warehouse and the online store could disagree.
  • Mine: scheduled jobs with Cloud Scheduler. Order ingestion every 3 minutes (with a guard so runs don't overlap), anomaly detection, SLA and cancellation monitors, automatic invoicing and ERP synchronization.
  • With the other developer: we migrated the PHP code to TypeScript, Turborepo and Prisma bit by bit, without stopping the operation.
  • From the team: order routing that can use a single engine or both, with the ability to revert a decision and keep an audit trail.
  • From the team: Fill Rate, OTIF and Perfect Order dashboards, with the timings of each stage of the process.

How it turned out

  • What reduced overselling in store pickup the most was shrinking that window of inconsistency between systems.
  • The biggest challenge was supporting a gradual migration without stopping operations.

900 orders per day · 5,000+ at peak (Black Friday, CyberMonday, Mother's Day, Christmas) · 5% mixed, 20% fully robotic, 75% manual with PDAs

  • TypeScript
  • Node.js
  • Express
  • Prisma ORM
  • Google BigQuery
  • MySQL / MariaDB
  • Google Cloud Run
  • Cloud Scheduler
  • Cloud Build
  • Docker
  • Next.js 15
  • Turborepo
  • Vitest
  • (previous base: PHP / Laravel)

03

Multi-source ELT pipeline into the POS

In production

Role
Development
Context
Python service, serverless, on Cloud Run.

What it's about

Every morning, before the registers open, this service pulls five types of master data (customers, products, EANs, price lists and sales reps) from BigQuery, FTP, Notion and Google Sheets into the POS database, so prices and promotions are up to date.

The problem

The data was scattered: the ERP over FTP, products and customers in BigQuery, campaigns in Notion and exceptions in Sheets. Each register ended up working with a different version of reality.

What I did

  • The architecture is simple: Cloud Scheduler triggers a FastAPI service on Cloud Run, which reads from BigQuery, FTP, Notion and Sheets and writes to Cloud SQL. Between runs it consumes nothing.
  • For promotions, the pipeline joins the campaigns (validity window, channel, store, discount) with the price lists and the catalog, and leaves a flat product × store × discount table that the register reads directly.
  • Each data type runs as an independent, repeatable job: if the sales-rep job fails, prices still get updated.
  • There's an endpoint to reprocess a failed run, without waiting for the next day's window.

How it turned out

  • Promotion logic is now resolved earlier, in the pipeline, instead of at the moment of sale.
  • Marketing can load campaigns and exceptions without waiting for a deploy or depending on a developer.

70,000+ products processed per day · 200+ promotions per day

  • Python 3
  • FastAPI
  • Pandas
  • google-cloud-bigquery
  • Google Cloud Run
  • Cloud Scheduler
  • Cloud SQL
  • Cloud Logging
  • Notion API
  • Google Sheets API
  • Pytest
  • Docker

04

Product-centric picking engine with scoring

Discontinued · business decision

Role
Development
Context
Async Python service.

What it's about

It flips order picking on its head: the operator scans any product at hand and the system tells them which order it belongs to, prioritizing the most urgent one. The goal was to stop people hunting for products off a spreadsheet.

The problem

Picking used to go order by order: the operator read a spreadsheet and went looking for the product. To invert it, the system had to decide instantly, given a scanned SKU, which order to serve first, and reserve it so two operators wouldn't grab the same one.

What I did

  • A configurable scoring system: score = time_weight × age + priority_weight × shipping_method. There are 11 shipping methods with their own values, adjustable in the database without deploying.
  • Orders are materialized from BigQuery into an operational table indexed by (sku, score, status). That makes «give me the highest-priority order that needs this SKU» an index read.
  • Optimistic locking with a state machine, and dual writes to the previous circuit so both can coexist during the transition.
  • Fully async (FastAPI + async SQLAlchemy + aiomysql) to handle several scans at once without blocking the event loop.

How it turned out

  • In tests, average time per order dropped by about 30%.
  • We designed it from day one to coexist with the previous system and to be able to roll back if needed.
  • It was discontinued for a business decision, outside my area. The code is still in the repository.
  • Python 3.13
  • FastAPI
  • SQLAlchemy 2 (async)
  • Pydantic Settings
  • google-cloud-bigquery
  • httpx
  • uv
  • Ruff
  • Google Cloud Run
  • Docker