feat(work-orders): add bulk CSV export endpoint for selected work orders - #189
Conversation
arif-dewi
left a comment
There was a problem hiding this comment.
The refactor into _rowsForWorkOrder + _renderCsvRows is a clean way to share the row building, and the union-header comment explains the why nicely. A few thoughts, mostly about duplication with the RMA sibling and the edges.
| // Bulk sibling of exportWorkOrder: N ids, one combined CSV, so a large list-page | ||
| // selection downloads as a single file instead of one browser download per WO. | ||
| async function exportWorkOrdersBulk (ctx, req, rep) { | ||
| const ids = req.query.ids.split(',').map(s => s.trim()).filter(Boolean) |
There was a problem hiding this comment.
this block — ids parsing, the $or query, flattenRpcResults — is the same as exportWorkOrdersRma just above. Could pull it into a small helper like _loadWorkOrdersByIdsOrCodes(ctx, req.query.ids) and let each handler only filter and render.
| } | ||
|
|
||
| module.exports = { create, createBatch, list, byId, update, close, cancel, reopen, assign, audit, log, export: exportRoute, exportRma } | ||
| const exportBulk = { |
There was a problem hiding this comment.
exportBulk is identical to exportRma. Could share one definition (a named idsQuery used by both, or simply const exportBulk = exportRma) so the two cannot drift apart later.
| required: ['ids'], | ||
| additionalProperties: false, | ||
| properties: { | ||
| ids: { type: 'string', minLength: 1, maxLength: 4000 } |
There was a problem hiding this comment.
work order ids are randomUUID(), so 4000 chars caps this at roughly 108 ids. Selection accumulates across pages on the list screen, so a large selection could hit the cap and fail with a 400 that the UI shows as a generic "Could not export the selected work orders". Worth checking that ceiling is comfortable, or giving that case a clearer message.
|
|
||
| // Bulk sibling of renderWorkOrderCsv: many work orders, one CSV, so a large | ||
| // selection downloads as a single file instead of one browser download per WO. | ||
| function renderWorkOrdersBulkCsv (wos) { |
There was a problem hiding this comment.
when none of the ids match, wos is empty and _renderCsvRows([]) returns just a blank line — the file has no header row at all. renderRmaCsv always writes its header row. A 404, or at least a header-only CSV, would give the user some signal instead of an empty download.
| t.is(rep._headers['content-type'], 'text/csv; charset=utf-8') | ||
| t.ok(rep._headers['content-disposition'].includes('work-orders.csv')) | ||
| const lines = rep._body.trim().split('\r\n') | ||
| t.is(lines.length, 3, 'header + one row per WO') |
There was a problem hiding this comment.
small thing: _rowsForWorkOrder emits one row per parts move, so "one row per WO" only holds here because these fixtures have 0 and 1 moves. A case with a WO carrying 2+ partsMoves would pin the real behaviour — several rows sharing the same code.
Summary
GET /auth/work-orders/export/bulk?ids=..., returning one combinedCSV for many work orders in a single response — the general-purpose
sibling of the existing MicroBT-only
export/rmabulk endpoint.infoshapes, sowork.order.export.jsis refactored to share row-building between theexisting single-WO
renderWorkOrderCsvand the newrenderWorkOrdersBulkCsv, which unions headers across all selected WOsand blank-pads rows missing a given column.
moria-app-uifix for "Export Selected" downloading only 10 ofN selected work orders (browsers block bursts of automatic downloads;
one combined file sidesteps that entirely).