diff --git a/.env.example b/.env.example index dcc2a9e..cfb0350 100644 --- a/.env.example +++ b/.env.example @@ -15,7 +15,8 @@ BC_ITEMS_WRITE_METHOD="PATCH" BC_ITEMS_WRITE_URL_TEMPLATE="{{itemsUrl}}('{{itemNo}}')" BC_ITEMS_WRITE_BODY_TEMPLATE='' BC_UOM_WRITE_METHOD="PATCH" -BC_UOM_WRITE_URL_TEMPLATE="{{itemUnitsOfMeasureUrl}}('{{itemNo}}')" +BC_UOM_CODE="OUTER" +BC_UOM_WRITE_URL_TEMPLATE="{{itemUnitsOfMeasureUrl}}(itemNo='{{itemNo}}',code='{{itemUnitsCode}}')" BC_UOM_WRITE_BODY_TEMPLATE='' # APP_URL: The URL where this applet is hosted. diff --git a/README.md b/README.md index 958f40b..dd93624 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,6 @@ View your app in AI Studio: https://ai.studio/apps/bac9908e-4996-4e4c-8ec7-3add5 `BC_WRITE_METHOD`, `BC_WRITE_URL_TEMPLATE`, `BC_WRITE_BODY_TEMPLATE` 5. For the safer BC sync preview/apply flow, you can also set: `BC_ITEMS_WRITE_METHOD`, `BC_ITEMS_WRITE_URL_TEMPLATE`, `BC_ITEMS_WRITE_BODY_TEMPLATE`, - `BC_UOM_WRITE_METHOD`, `BC_UOM_WRITE_URL_TEMPLATE`, `BC_UOM_WRITE_BODY_TEMPLATE` + `BC_UOM_CODE`, `BC_UOM_WRITE_METHOD`, `BC_UOM_WRITE_URL_TEMPLATE`, `BC_UOM_WRITE_BODY_TEMPLATE` 6. Run the app: `npm run dev` diff --git a/bc-runtime.js b/bc-runtime.js index cb41c18..44a6c2b 100644 --- a/bc-runtime.js +++ b/bc-runtime.js @@ -16,8 +16,9 @@ export function getBcConfig(env = process.env) { itemsWriteMethod: String(env.BC_ITEMS_WRITE_METHOD || legacyWriteMethod).toUpperCase(), itemsWriteUrlTemplate: env.BC_ITEMS_WRITE_URL_TEMPLATE || legacyWriteUrlTemplate, itemsWriteBodyTemplate: env.BC_ITEMS_WRITE_BODY_TEMPLATE || null, + itemUnitsCode: env.BC_UOM_CODE || 'OUTER', itemUnitsWriteMethod: env.BC_UOM_WRITE_METHOD ? String(env.BC_UOM_WRITE_METHOD).toUpperCase() : 'PATCH', - itemUnitsWriteUrlTemplate: env.BC_UOM_WRITE_URL_TEMPLATE || `{{itemUnitsOfMeasureUrl}}('{{itemNo}}')`, + itemUnitsWriteUrlTemplate: env.BC_UOM_WRITE_URL_TEMPLATE || `{{itemUnitsOfMeasureUrl}}(itemNo='{{itemNo}}',code='{{itemUnitsCode}}')`, itemUnitsWriteBodyTemplate: env.BC_UOM_WRITE_BODY_TEMPLATE || null, }; } @@ -30,6 +31,10 @@ export function getItemsUrl(config) { return `https://api.businesscentral.dynamics.com/v2.0/${config.tenantId}/production/api/craze/integrations/v1.0/companies(${config.companyId})/items`; } +export function getItemUnitsOfMeasureUrl(config) { + return `https://api.businesscentral.dynamics.com/v2.0/${config.tenantId}/production/api/craze/integrations/v1.0/companies(${config.companyId})/itemUnitsOfMeasure2`; +} + let tokenCache = { token: null, expiresAt: 0 }; export async function getBCToken(config) { diff --git a/bc-sync-runtime.js b/bc-sync-runtime.js index 3c48bd6..d4e0d76 100644 --- a/bc-sync-runtime.js +++ b/bc-sync-runtime.js @@ -1,9 +1,5 @@ import crypto from 'crypto'; -import { getBcConfig, getBCToken, getItemsUrl } from './bc-runtime.js'; - -function getItemUnitsOfMeasureUrl(config) { - return `https://api.businesscentral.dynamics.com/v2.0/${config.tenantId}/production/api/craze/integrations/v1.0/companies(${config.companyId})/itemUnitsOfMeasure`; -} +import { getBcConfig, getBCToken, getItemsUrl, getItemUnitsOfMeasureUrl } from './bc-runtime.js'; function findHeaderIndex(headers, patterns) { const normalized = headers.map(h => String(h || '').toLowerCase()); @@ -89,13 +85,11 @@ export function buildBusinessCentralMappingPreview(headers, row) { const itemUnitsOfMeasurePayload = { itemNo: getValue(row, articleNoIdx), - qtyPerUnitOfMeasure6: toBcDecimal(getValue(row, unitsOuterIdx)), - width4: toBcDecimal(getValue(row, innerWIdx)), - length4: toBcDecimal(getValue(row, innerLIdx)), - height4: toBcDecimal(getValue(row, innerHIdx)), - width6: toBcDecimal(getValue(row, outerWIdx)), - length6: toBcDecimal(getValue(row, outerLIdx)), - height6: toBcDecimal(getValue(row, outerHIdx)), + code: 'OUTER', + qtyPerUnitOfMeasure: toBcDecimal(getValue(row, unitsOuterIdx)), + width: toBcDecimal(getValue(row, outerWIdx)), + length: toBcDecimal(getValue(row, outerLIdx)), + height: toBcDecimal(getValue(row, outerHIdx)), }; return { @@ -115,13 +109,10 @@ export function buildBusinessCentralMappingPreview(headers, row) { ], itemUnitsFields: [ makeFieldPreview('Article No.', articleNoIdx, 'itemNo', itemUnitsOfMeasurePayload.itemNo), - makeFieldPreview('Units Outer', unitsOuterIdx, 'qtyPerUnitOfMeasure6', itemUnitsOfMeasurePayload.qtyPerUnitOfMeasure6), - makeFieldPreview('CDU/Inner W (cm)', innerWIdx, 'width4', itemUnitsOfMeasurePayload.width4), - makeFieldPreview('CDU/Inner L (cm)', innerLIdx, 'length4', itemUnitsOfMeasurePayload.length4), - makeFieldPreview('CDU/Inner H (cm)', innerHIdx, 'height4', itemUnitsOfMeasurePayload.height4), - makeFieldPreview('Outer W (cm)', outerWIdx, 'width6', itemUnitsOfMeasurePayload.width6), - makeFieldPreview('Outer L (cm)', outerLIdx, 'length6', itemUnitsOfMeasurePayload.length6), - makeFieldPreview('Outer H (cm)', outerHIdx, 'height6', itemUnitsOfMeasurePayload.height6), + makeFieldPreview('Units Outer', unitsOuterIdx, 'qtyPerUnitOfMeasure', itemUnitsOfMeasurePayload.qtyPerUnitOfMeasure), + makeFieldPreview('Outer W (cm)', outerWIdx, 'width', itemUnitsOfMeasurePayload.width), + makeFieldPreview('Outer L (cm)', outerLIdx, 'length', itemUnitsOfMeasurePayload.length), + makeFieldPreview('Outer H (cm)', outerHIdx, 'height', itemUnitsOfMeasurePayload.height), ], }; } @@ -238,13 +229,21 @@ function getItemsSelect() { function getItemUnitsSelect() { return [ 'itemNo', - 'qtyPerUnitOfMeasure6', - 'width4', - 'length4', - 'height4', - 'width6', - 'length6', - 'height6', + 'code', + 'qtyPerUnitOfMeasure', + 'qtyRoundingPrecision', + 'length', + 'width', + 'height', + 'cubage', + 'weight', + 'layerPerPalet2CRZ', + 'outerPerLayer2CRZ', + 'barCodeCRZ', + 'layerPerPaletCRZ', + 'outerPerLayerCRZ', + 'netWeightCRZ', + 'innerTypeBCT', ].join(','); } @@ -270,7 +269,7 @@ export async function fetchBusinessCentralSnapshot(config, token, articleNo) { const { json: itemsJson, etag: itemsEtag } = await fetchJsonOrThrow(itemsUrl, token, 'BC GET items'); const item = Array.isArray(itemsJson.value) && itemsJson.value.length > 0 ? itemsJson.value[0] : null; - const uomFilter = encodeURIComponent(`itemNo eq '${articleNo}'`); + const uomFilter = encodeURIComponent(`itemNo eq '${articleNo}' and code eq '${config.itemUnitsCode || 'OUTER'}'`); const uomUrl = `${getItemUnitsOfMeasureUrl(config)}?$filter=${uomFilter}&$select=${getItemUnitsSelect()}`; const { json: uomJson, etag: uomEtag } = await fetchJsonOrThrow(uomUrl, token, 'BC GET itemUnitsOfMeasure'); const itemUnitsOfMeasure = Array.isArray(uomJson.value) && uomJson.value.length > 0 ? uomJson.value[0] : null; @@ -388,13 +387,15 @@ function renderTemplate(template, context) { const DECIMAL_FIELDS = new Set([ 'minimumOrderQuantity', - 'qtyPerUnitOfMeasure6', - 'width4', - 'length4', - 'height4', - 'width6', - 'length6', - 'height6', + 'qtyPerUnitOfMeasure', + 'length', + 'width', + 'height', + 'cubage', + 'weight', + 'layerPerPalet2CRZ', + 'outerPerLayer2CRZ', + 'netWeightCRZ', ]); const EMPTY_STRING_FIELDS = new Set([ @@ -518,6 +519,7 @@ export async function applyBusinessCentralSync(config, token, headers, row, prev itemUnitsOfMeasureUrl: getItemUnitsOfMeasureUrl(config), articleNo: mapping.articleNo, itemNo: mapping.articleNo, + itemUnitsCode: config.itemUnitsCode || 'OUTER', systemId: snapshot.item?.systemId || '', cpnpNo: mapping.itemsPayload.cpnpNo, itemsPayload: preview.items.desired, diff --git a/src/components/MatrixView.tsx b/src/components/MatrixView.tsx index 2331fde..93f797d 100644 --- a/src/components/MatrixView.tsx +++ b/src/components/MatrixView.tsx @@ -497,7 +497,7 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
-

API `itemUnitsOfMeasure` preview

+

API `itemUnitsOfMeasure2` preview

SKU {bcMappingPreview.articleNo}

diff --git a/src/services/businessCentralMapping.ts b/src/services/businessCentralMapping.ts index 550b6be..1c42cbe 100644 --- a/src/services/businessCentralMapping.ts +++ b/src/services/businessCentralMapping.ts @@ -81,9 +81,6 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel const cpnpIdx = findHeaderIndex(headers, ['cpnp']); const unitsOuterIdx = findHeaderIndex(headers, ['units', 'outer']); - const innerWIdx = findHeaderIndex(headers, ['inner', 'w']); - const innerLIdx = findHeaderIndex(headers, ['inner', 'l']); - const innerHIdx = findHeaderIndex(headers, ['inner', 'h']); const outerWIdx = findHeaderIndex(headers, ['outer', 'w']); const outerLIdx = findHeaderIndex(headers, ['outer', 'l']); const outerHIdx = findHeaderIndex(headers, ['outer', 'h']); @@ -104,13 +101,11 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel const itemUnitsOfMeasurePayload = { itemNo: getValue(row, articleNoIdx), - qtyPerUnitOfMeasure6: toBcDecimal(getValue(row, unitsOuterIdx)), - width4: toBcDecimal(getValue(row, innerWIdx)), - length4: toBcDecimal(getValue(row, innerLIdx)), - height4: toBcDecimal(getValue(row, innerHIdx)), - width6: toBcDecimal(getValue(row, outerWIdx)), - length6: toBcDecimal(getValue(row, outerLIdx)), - height6: toBcDecimal(getValue(row, outerHIdx)), + code: 'OUTER', + qtyPerUnitOfMeasure: toBcDecimal(getValue(row, unitsOuterIdx)), + width: toBcDecimal(getValue(row, outerWIdx)), + length: toBcDecimal(getValue(row, outerLIdx)), + height: toBcDecimal(getValue(row, outerHIdx)), }; return { @@ -130,13 +125,10 @@ export function buildBusinessCentralMappingPreview(headers: string[], row: Excel ], itemUnitsFields: [ makeFieldPreview('Article No.', articleNoIdx, 'itemNo', itemUnitsOfMeasurePayload.itemNo), - makeFieldPreview('Units Outer', unitsOuterIdx, 'qtyPerUnitOfMeasure6', itemUnitsOfMeasurePayload.qtyPerUnitOfMeasure6), - makeFieldPreview('CDU/Inner W (cm)', innerWIdx, 'width4', itemUnitsOfMeasurePayload.width4), - makeFieldPreview('CDU/Inner L (cm)', innerLIdx, 'length4', itemUnitsOfMeasurePayload.length4), - makeFieldPreview('CDU/Inner H (cm)', innerHIdx, 'height4', itemUnitsOfMeasurePayload.height4), - makeFieldPreview('Outer W (cm)', outerWIdx, 'width6', itemUnitsOfMeasurePayload.width6), - makeFieldPreview('Outer L (cm)', outerLIdx, 'length6', itemUnitsOfMeasurePayload.length6), - makeFieldPreview('Outer H (cm)', outerHIdx, 'height6', itemUnitsOfMeasurePayload.height6), + makeFieldPreview('Units Outer', unitsOuterIdx, 'qtyPerUnitOfMeasure', itemUnitsOfMeasurePayload.qtyPerUnitOfMeasure), + makeFieldPreview('Outer W (cm)', outerWIdx, 'width', itemUnitsOfMeasurePayload.width), + makeFieldPreview('Outer L (cm)', outerLIdx, 'length', itemUnitsOfMeasurePayload.length), + makeFieldPreview('Outer H (cm)', outerHIdx, 'height', itemUnitsOfMeasurePayload.height), ], }; }