Case study · Operations automation · SellerCloud
A bulk order splitter for SellerCloud
A multi-marketplace e-commerce brand ran its fulfilment through SellerCloud, where splitting an order meant opening each record and splitting it by hand. I designed and built, on my own, a tool that lets the operations team upload a spreadsheet of splits and download a finished one, with the new order ID or the error on every row.
Key decisions and results
Decisions
- The Spring Boot app and the Node.js worker share only a MySQL job table, so every job is on record and can be re-run by its ID.
- Rows are matched on both the fulfilled and the originally ordered SKU, and lines that ask to move more than the order holds are skipped.
- Each row records its own result, so a failed order doesn’t hold up the rest of the batch.
Results
- A batch of order splits is one upload and one download.
- Every split order traces back to its original order and SKU, with the error recorded when a split fails.
The problem
Splitting orders by hand, record by record
The brand already had an internal sales management system that collected sales, inventory and advertising data from every marketplace. But splitting orders in SellerCloud still sat outside it and still ate hours of the team’s time.
-
Splitting orders
- Some orders needed part of their contents moved into a separate order so they could ship on their own.
- In SellerCloud that meant opening each order, finding the right line item and quantity, and splitting it by hand.
- Nobody kept a record of which new order came from which original.
How it works
From an uploaded sheet to a result on every row
The team downloads a sample sheet with four columns (Order ID, Requested SKU, SellerCloud SKU, Qty), fills it in and uploads it. After that, nobody has to watch it.
- Save the upload as a jobThe system reads each row and saves it to the job table, tagged with a job ID made from the file name and date. The request finishes straight away, so the page never waits on SellerCloud.
- Hand the job to the workerA background thread calls the Node worker’s split-orders endpoint with the job ID. The worker reads the job’s rows back from MySQL, so the database is the whole agreement between the two services.
- Match every row to a real line itemFor each order, the worker fetches the order from the SellerCloud API. It matches line items on both the fulfilled SKU and the SKU the customer originally ordered, so substituted and alias SKUs go to the right line.
- Split with guardrailsThe worker combines each order’s lines into one
OrderItemId / QtyToMoverequest and skips any line that asks to move more than the order holds. The split call gets a fresh access token and one retry. - Record the result for every rowThe worker saves the new split order ID or the exact error message against every row in one bulk upsert, then marks the rows done.
- Report back in the team’s formatA job counts as complete only when every row in it is done. The page lists running and finished jobs and blocks new uploads while one is running. Each finished job downloads as a sheet with the split order ID or error on every row.
Recovery path
A second worker job rebuilds the links after the fact. It reads each original order’s related orders, picks out the split-child relationship, fetches the child order and matches its lines by SKU and quantity. That links the job rows to the new orders even when the split was done outside the tool.
A small helper sits next to it: upload a sheet of Order ID + SKU and get each line’s SellerCloud Order Item ID back. The team uses it to prepare other bulk edits.
Engineering decisions
Why it’s built this way
The database connects the two services
The Java app and the Node worker share nothing except a MySQL table. Every job is on record: you can see what was asked, what happened and what failed, and you can re-run any job by its ID.
Failures are recorded per order, not per job
One bad order never stops a batch of hundreds. Each row carries its own result, including SellerCloud’s error text after the retry fails, so the team fixes only the rows that failed.
Spreadsheets on both ends
The team already worked in Excel, so the tool takes a spreadsheet in and gives one back. Every upload has a downloadable template, and nobody had to learn a new screen.
Known limits
The hardest boundary: remote success and local persistence
The external operation and the local database update cannot share a single transaction. A split may succeed remotely while its local acknowledgement fails to persist.
Bounded retries help with transient failures, but they do not establish that repeating a state-changing command is safe. The recovery path above can link split orders back to their requests; it does not guarantee exactly-once execution or automatic recovery from every ambiguous outcome.
A further iteration would make uncertain outcomes explicit, reconcile remote state before replaying commands, and use provider-supported idempotency where available.
Outcome
What changed for the team
- A whole batch of order splits is now one upload and a download, instead of an afternoon of clicking through SellerCloud.
- Every split order is traceable back to the original order and SKU, with the reason recorded when a split fails.
Stack
Stack
- Java 17
- Spring Boot 2.7
- Spring JDBC
- Thymeleaf
- Apache POI
- MySQL
- Node.js
- Express
- SellerCloud REST API
Company details are anonymized.