From 2e5cd7222c1f7ec0efdfc9b73f716c8bdcb12404 Mon Sep 17 00:00:00 2001 From: Caesar Mukama Date: Fri, 26 Jun 2026 15:14:58 +0300 Subject: [PATCH 1/2] Fix: persist register-WO notes, writable issue field, fresh WO detail - registerSparePart/Batch store the operator note as info.notes on the work order (single omitted it; batch wrote info.note) - allow issue inside the work-order update schema's info object - stop caching GET /work-orders/:id so log/issue writes reflect immediately Claude-Session: https://claude.ai/code/session_01FxFopChofyQ21y8Q2ZzYrT --- tests/unit/handlers/spare.parts.handlers.test.js | 7 ++++--- workers/lib/server/handlers/spare.parts.handlers.js | 5 +++-- workers/lib/server/routes/work.orders.routes.js | 8 +------- workers/lib/server/schemas/work.orders.schemas.js | 1 + 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/unit/handlers/spare.parts.handlers.test.js b/tests/unit/handlers/spare.parts.handlers.test.js index 15399d3..478dcb7 100644 --- a/tests/unit/handlers/spare.parts.handlers.test.js +++ b/tests/unit/handlers/spare.parts.handlers.test.js @@ -285,7 +285,7 @@ test('handlers: registerSparePart fires part + Type-1 WO pushActions in parallel const { ctx, pushed } = buildRegisterCtx() const out = await handlers.registerSparePart(ctx, { ...userMeta(), - body: { rackId: PART_RACK, info: { deviceType: 'psu', deviceModel: 'PSU-WM-CB6_V5', serialNum: 'SN-99' } } + body: { rackId: PART_RACK, info: { deviceType: 'psu', deviceModel: 'PSU-WM-CB6_V5', serialNum: 'SN-99', notes: 'NOTES' } } }) t.is(pushed.length, 2, 'one part action, one WO action') @@ -294,6 +294,7 @@ test('handlers: registerSparePart fires part + Type-1 WO pushActions in parallel t.is(partAction.action, 'registerThing') t.is(woAction.action, 'registerThing') t.is(woAction.params[0].info.type, 1, 'Type-1 WO') + t.is(woAction.params[0].info.notes, 'NOTES', 'operator note recorded on the register WO') t.is(woAction.params[0].info.partsMoves[0].fromLocation, null) t.is(woAction.params[0].info.partsMoves[0].toLocation, 'site.warehouse') t.is(woAction.params[0].info.partsMoves[0].partId, out.partId, 'WO partsMoves entry links to the pre-generated partId') @@ -339,11 +340,11 @@ test('handlers: registerSparePartsBatch creates one shared register WO carrying t.is(woInfo.type, 1, 'single Type-1 register WO') t.is(woInfo.deviceCount, 3) t.is(woInfo.partsMoves.length, 3, 'register WO carries every part') - t.is(woInfo.note, 'Pallets 1-3', 'note recorded on the register WO') + t.is(woInfo.notes, 'Pallets 1-3', 'note recorded on the register WO') t.alike(woInfo.partsMoves.map(m => m.partId).sort(), out.parts.map(p => p.partId).sort(), 'WO links every returned part') for (const pa of partActions) { - t.is(pa.params[0].info.note, 'Pallets 1-3', 'note attached to each registered part') + t.is(pa.params[0].info.notes, 'Pallets 1-3', 'note attached to each registered part') } t.is(out.parts.length, 3, 'returns a result row per part') t.alike(out.errors, [], 'no errors on happy path') diff --git a/workers/lib/server/handlers/spare.parts.handlers.js b/workers/lib/server/handlers/spare.parts.handlers.js index f94d7e0..b64f611 100644 --- a/workers/lib/server/handlers/spare.parts.handlers.js +++ b/workers/lib/server/handlers/spare.parts.handlers.js @@ -205,6 +205,7 @@ async function registerSparePart (ctx, req) { deviceIdentifier: info.serialNum, createdBy: voter, createdAt: ts, + ...(info.notes ? { notes: info.notes } : {}), partsMoves: [{ partId, fromLocation: null, @@ -275,7 +276,7 @@ async function registerSparePartsBatch (ctx, req) { ...part, location: part.location ?? SPARE_PART_INITIAL_LOCATION } - if (note) partInfo.note = note + if (note) partInfo.notes = note return { partId: randomUUID(), part, partInfo } }) @@ -301,7 +302,7 @@ async function registerSparePartsBatch (ctx, req) { user: voter })) } - if (note) woInfo.note = note + if (note) woInfo.notes = note const pushSingleAction = (rack, id, info) => ctx.dataProxy.requestData('pushAction', { action: 'registerThing', diff --git a/workers/lib/server/routes/work.orders.routes.js b/workers/lib/server/routes/work.orders.routes.js index c80b2f5..2bab74f 100644 --- a/workers/lib/server/routes/work.orders.routes.js +++ b/workers/lib/server/routes/work.orders.routes.js @@ -68,13 +68,7 @@ module.exports = (ctx) => [ method: HTTP_METHODS.GET, url: ENDPOINTS.WORK_ORDER_BY_ID, schema: schemas.byId, - ...createCachedAuthRoute( - ctx, - (req) => ['work-orders', req.params.id], - ENDPOINTS.WORK_ORDER_BY_ID, - getWorkOrder, - [AUTH_PERMISSIONS.WORK_ORDER] - ) + ...createAuthRoute(ctx, getWorkOrder, [AUTH_PERMISSIONS.WORK_ORDER]) }, { method: HTTP_METHODS.GET, diff --git a/workers/lib/server/schemas/work.orders.schemas.js b/workers/lib/server/schemas/work.orders.schemas.js index 63670f0..bd34700 100644 --- a/workers/lib/server/schemas/work.orders.schemas.js +++ b/workers/lib/server/schemas/work.orders.schemas.js @@ -132,6 +132,7 @@ const update = { type: 'object', additionalProperties: false, properties: { + issue: { type: 'string', minLength: 1, maxLength: 2000 }, notes: { type: 'string', maxLength: 4000 }, remarks: { type: 'string', maxLength: 4000 }, site: { type: 'string', maxLength: 200 }, From 5fd3e472a7a67866c8ece701e6aa76a07b63dbb0 Mon Sep 17 00:00:00 2001 From: Caesar Mukama Date: Fri, 26 Jun 2026 17:29:50 +0300 Subject: [PATCH 2/2] Fix: pass empty opts on spare-part registerThing so miner racks accept it Miner racks require opts on registerThing; the register-device flow now sends opts:{} on the device/part registration (not the WO), letting a warehouse miner register into inventory without relaxing the template validation or adding FE credential fields. Claude-Session: https://claude.ai/code/session_01FxFopChofyQ21y8Q2ZzYrT --- tests/unit/handlers/spare.parts.handlers.test.js | 4 ++++ workers/lib/server/handlers/spare.parts.handlers.js | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/unit/handlers/spare.parts.handlers.test.js b/tests/unit/handlers/spare.parts.handlers.test.js index 478dcb7..771830d 100644 --- a/tests/unit/handlers/spare.parts.handlers.test.js +++ b/tests/unit/handlers/spare.parts.handlers.test.js @@ -293,6 +293,8 @@ test('handlers: registerSparePart fires part + Type-1 WO pushActions in parallel const woAction = pushed.find(p => p.params[0].rackId === WO_RACK) t.is(partAction.action, 'registerThing') t.is(woAction.action, 'registerThing') + t.alike(partAction.params[0].opts, {}, 'part registration carries empty opts so device racks (miners) accept it') + t.is(woAction.params[0].opts, undefined, 'WO registration carries no opts') t.is(woAction.params[0].info.type, 1, 'Type-1 WO') t.is(woAction.params[0].info.notes, 'NOTES', 'operator note recorded on the register WO') t.is(woAction.params[0].info.partsMoves[0].fromLocation, null) @@ -343,8 +345,10 @@ test('handlers: registerSparePartsBatch creates one shared register WO carrying t.is(woInfo.notes, 'Pallets 1-3', 'note recorded on the register WO') t.alike(woInfo.partsMoves.map(m => m.partId).sort(), out.parts.map(p => p.partId).sort(), 'WO links every returned part') + t.is(woAction.params[0].opts, undefined, 'WO registration carries no opts') for (const pa of partActions) { t.is(pa.params[0].info.notes, 'Pallets 1-3', 'note attached to each registered part') + t.alike(pa.params[0].opts, {}, 'each part registration carries empty opts so device racks accept it') } t.is(out.parts.length, 3, 'returns a result row per part') t.alike(out.errors, [], 'no errors on happy path') diff --git a/workers/lib/server/handlers/spare.parts.handlers.js b/workers/lib/server/handlers/spare.parts.handlers.js index b64f611..5a113b2 100644 --- a/workers/lib/server/handlers/spare.parts.handlers.js +++ b/workers/lib/server/handlers/spare.parts.handlers.js @@ -216,10 +216,10 @@ async function registerSparePart (ctx, req) { }] } - const pushSingleAction = (rack, id, info) => ctx.dataProxy.requestData('pushAction', { + const pushSingleAction = (rack, id, info, opts) => ctx.dataProxy.requestData('pushAction', { action: 'registerThing', query: { rack }, - params: [{ rackId: rack, id, info }], + params: [{ rackId: rack, id, info, ...(opts ? { opts } : {}) }], voter, authPerms }, (res, arr) => { @@ -228,7 +228,7 @@ async function registerSparePart (ctx, req) { }) const [partResults, woResults] = await Promise.all([ - pushSingleAction(rackId, partId, partInfo), + pushSingleAction(rackId, partId, partInfo, {}), pushSingleAction(workOrderRackId, woId, woInfo) ]) @@ -304,10 +304,10 @@ async function registerSparePartsBatch (ctx, req) { } if (note) woInfo.notes = note - const pushSingleAction = (rack, id, info) => ctx.dataProxy.requestData('pushAction', { + const pushSingleAction = (rack, id, info, opts) => ctx.dataProxy.requestData('pushAction', { action: 'registerThing', query: { rack }, - params: [{ rackId: rack, id, info }], + params: [{ rackId: rack, id, info, ...(opts ? { opts } : {}) }], voter, authPerms }, (res, arr) => { @@ -317,7 +317,7 @@ async function registerSparePartsBatch (ctx, req) { const [woResults, ...partResultsList] = await Promise.all([ pushSingleAction(workOrderRackId, woId, woInfo), - ...prepared.map(({ partId, partInfo }) => pushSingleAction(rackId, partId, partInfo)) + ...prepared.map(({ partId, partInfo }) => pushSingleAction(rackId, partId, partInfo, {})) ]) const partsOut = prepared.map(({ partId }, i) => ({