import { date } from 'alga-js' import refreshTokenHelper from "../../../../utils/refreshTokenHelper" import getTokenHelper from "../../../../utils/getTokenHelper" import errorHandlingHelper from "../../../../utils/errorHandlingHelper" import fetchHelper from "../../../../utils/fetchHelper" import laravelHelper from "../../../../utils/laravelHelper" import strapiHelper from "../../../../utils/strapiHelper" import { commissionTableFkPatch } from "../../../../utils/commissionTable" import { dpdGetAuth, dpdStoreOrders, extractOrderSourceDpdCreds, xmlEscape, kgTo10GramUnits, toCm, buildVolumeString, iso2ToCustomsOrigin, type DpdCredentials } from "../../../../utils/dpdHelper" /** * DPD Multi-Parcel Label Creation * * DPD supports multiple elements inside a single , all * tracking numbers are returned in one response. This matches DHL's * single-API-call multi-parcel flow. * * DPD requires sequential shipment service calls, but we make a SINGLE call * containing multiple parcels — that fits inside one sequential slot. */ const EU_COUNTRIES = ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE'] // ---------------------------------------------------------------------------- // Strapi: save customs document to order attachments (mirrors DHL multi flow) // ---------------------------------------------------------------------------- const saveCustomsDocToOrder = async (event: any, orderId: number, orderUid: string, customsDocBase64: string, shipmentNo: string) => { const config = useRuntimeConfig() try { let strapiAttachmentId: number | null = null const existingResponse: any = await strapiHelper(event, `ad-attachments?filters[AD_Table_ID][$eq]=259&filters[Record_ID][$eq]=${orderId}`, 'GET', null) if(existingResponse?.data?.[0]?.id) { strapiAttachmentId = existingResponse.data[0].id } else { const newAttachmentResp: any = await strapiHelper(event, `ad-attachments`, 'POST', { data: { AD_Table_ID: 259, Record_ID: orderId, Record_UU: orderUid } }) if(newAttachmentResp?.data?.id) strapiAttachmentId = newAttachmentResp.data.id } if(strapiAttachmentId) { const timestamp = date.now().replace(/[-:]/g, '_').replace(' ', '-') const fileName = `CN23-DPD-${shipmentNo}-${timestamp}.pdf` const pdfBuffer = Buffer.from(customsDocBase64, 'base64') const FormData = (await import('form-data')).default const formData = new FormData() formData.append('field', 'attachment') formData.append('ref', 'api::ad-attachment.ad-attachment') formData.append('refId', String(strapiAttachmentId)) formData.append('files', pdfBuffer, { filename: fileName, contentType: 'application/pdf' }) formData.append('fileInfo', JSON.stringify({ caption: `CN23 Customs Document - DPD Multi-Parcel Shipment ${shipmentNo}`, alternativeText: `CN23-DPD-${shipmentNo}`, name: fileName })) const uploadResponse = await $fetch(`${config.api.strapi}/upload`, { method: 'POST', headers: { Authorization: `Bearer ${config.api.strapitoken}`, ...formData.getHeaders() }, body: formData }) return { success: true, strapiAttachmentId, uploadResponse } } return { success: false, error: 'Could not get or create attachment record' } } catch(error: any) { console.error('[CN23 DPD Multi] Error saving customs document to Strapi:', error) return { success: false, error: error.message || 'Unknown error' } } } // ---------------------------------------------------------------------------- // Build the multi-parcel XML // ---------------------------------------------------------------------------- interface BuildMultiOrderArgs { refNo: string depot: string customerNumber: string sender: { name1: string; name2?: string; street: string; houseNo: string; country: string; zipCode: string; city: string; email?: string; phone?: string; } recipient: { name1: string; name2?: string; street: string; houseNo: string; country: string; zipCode: string; city: string; state?: string; email?: string; phone?: string; } parcels: Array<{ weightKg: number dims?: { length: number; width: number; height: number } customsValueTotal?: number // per-parcel customs total items?: Array<{ description: string; quantity: number; value: number; weightKg: number; hsCode?: string; countryOfOrigin?: string }> }> predictEmail?: string customsCurrency?: string invoiceNo?: string isInternational: boolean incoterm: 'DAP' | 'DDP' | 'EXW' reasonForExport?: '01' | '02' | '03' shipperEori?: string } function buildMultiOrderXml(a: BuildMultiOrderArgs): string { const parts: string[] = [] parts.push(` `) parts.push(` ${xmlEscape(a.refNo.substring(0, 35))}`) parts.push(` ${xmlEscape(a.depot)}`) parts.push(` CL`) parts.push(` `) parts.push(` ${xmlEscape(a.sender.name1.substring(0, 35))}`) if (a.sender.name2) parts.push(` ${xmlEscape(a.sender.name2.substring(0, 35))}`) parts.push(` ${xmlEscape(a.sender.street.substring(0, 35))}`) parts.push(` ${xmlEscape(a.sender.houseNo.substring(0, 8))}`) parts.push(` ${xmlEscape(a.sender.country.substring(0, 2).toUpperCase())}`) parts.push(` ${xmlEscape(a.sender.zipCode.substring(0, 9))}`) parts.push(` ${xmlEscape(a.sender.city.substring(0, 35))}`) parts.push(` ${xmlEscape(a.customerNumber.substring(0, 17))}`) // DPD XSD order: customerNumber, contact, phone, mobile, fax, email, ... if (a.sender.phone) parts.push(` ${xmlEscape(a.sender.phone.substring(0, 30))}`) if (a.sender.email) parts.push(` ${xmlEscape(a.sender.email)}`) parts.push(` `) parts.push(` `) parts.push(` ${xmlEscape(a.recipient.name1.substring(0, 35))}`) if (a.recipient.name2) parts.push(` ${xmlEscape(a.recipient.name2.substring(0, 35))}`) parts.push(` ${xmlEscape(a.recipient.street.substring(0, 35))}`) if (a.recipient.houseNo) parts.push(` ${xmlEscape(a.recipient.houseNo.substring(0, 8))}`) if (a.recipient.state) parts.push(` ${xmlEscape(a.recipient.state.substring(0, 2).toUpperCase())}`) parts.push(` ${xmlEscape(a.recipient.country.substring(0, 2).toUpperCase())}`) parts.push(` ${xmlEscape(a.recipient.zipCode.substring(0, 9))}`) parts.push(` ${xmlEscape(a.recipient.city.substring(0, 35))}`) // Phone must precede email per the DPD XSD if (a.recipient.phone) parts.push(` ${xmlEscape(a.recipient.phone.substring(0, 30))}`) if (a.recipient.email) parts.push(` ${xmlEscape(a.recipient.email)}`) parts.push(` `) // softwareVersion is V4_5-only; omitted for the V4_4 production endpoint. parts.push(` `) // Each parcel as its own element for (const p of a.parcels) { parts.push(` `) parts.push(` ${kgTo10GramUnits(p.weightKg + 0.25)}`) // +0.25kg packaging if (p.dims && p.dims.length && p.dims.width && p.dims.height) { parts.push(` ${buildVolumeString(p.dims.length, p.dims.width, p.dims.height)}`) } parts.push(` `) } parts.push(` `) parts.push(` consignment`) if (a.predictEmail) { parts.push(` `) parts.push(` 1`) parts.push(` ${xmlEscape(a.predictEmail)}`) parts.push(` DE`) parts.push(` `) } if (a.isInternational) { // Aggregate all items from all parcels into a single customs declaration const allItems = a.parcels.flatMap(p => p.items || []) const totalValue = allItems.reduce((s, it) => s + (Number(it.value) * Number(it.quantity || 1)), 0) const customsAmount = Math.round(totalValue * 100) parts.push(` `) // parcelType must be first per V4_4 XSD (false = regular parcel) parts.push(` false`) parts.push(` ${customsAmount}`) parts.push(` ${xmlEscape((a.customsCurrency || 'EUR').substring(0, 3).toUpperCase())}`) parts.push(` ${customsAmount}`) parts.push(` ${xmlEscape((a.customsCurrency || 'EUR').substring(0, 3).toUpperCase())}`) // V4_4-only: 02 = sender pays duties+taxes (DDP), 01 = recipient pays parts.push(` ${a.incoterm === 'DDP' ? '02' : '01'}`) parts.push(` A`) parts.push(` true`) if (a.invoiceNo) parts.push(` ${xmlEscape(a.invoiceNo.substring(0, 20))}`) parts.push(` ${Math.min(99, allItems.length)}`) // reasonForExport is V4_5-only; omitted for V4_4 production endpoint. // V4_4: EORI as sibling commercialInvoiceConsignorVatNumber (not inside the address) if (a.shipperEori) { parts.push(` ${xmlEscape(String(a.shipperEori).substring(0, 20))}`) parts.push(` `) parts.push(` ${xmlEscape(a.sender.name1.substring(0, 35))}`) if (a.sender.name2) parts.push(` ${xmlEscape(a.sender.name2.substring(0, 35))}`) parts.push(` ${xmlEscape(a.sender.street.substring(0, 35))}`) parts.push(` ${xmlEscape(a.sender.houseNo.substring(0, 8))}`) parts.push(` ${xmlEscape(a.sender.country.substring(0, 2).toUpperCase())}`) parts.push(` ${xmlEscape(a.sender.zipCode.substring(0, 9))}`) parts.push(` ${xmlEscape(a.sender.city.substring(0, 35))}`) parts.push(` ${xmlEscape(a.customerNumber.substring(0, 17))}`) parts.push(` `) } // incoterm + paymentTerm are V4_5-only; V4_4 uses emitted above. for (let i = 0; i < allItems.length && i < 99; i++) { const it = allItems[i] parts.push(` `) parts.push(` ${i + 1}`) parts.push(` ${Math.max(1, Math.round(it.quantity))}`) // V4_4 has no dedicated HS code field — embed it in the description text const desc = it.hsCode ? `${it.description} [HS: ${it.hsCode}]` : it.description parts.push(` ${xmlEscape(String(desc).substring(0, 200))}`) parts.push(` ${Math.round(it.value * it.quantity * 100)}`) if (it.countryOfOrigin) parts.push(` ${iso2ToCustomsOrigin(it.countryOfOrigin)}`) parts.push(` ${kgTo10GramUnits(it.weightKg)}`) parts.push(` ${kgTo10GramUnits(it.weightKg)}`) // unitPrice is V4_5-only; per-line total is already in customsAmountLine. parts.push(` `) } parts.push(` `) } parts.push(` `) return ` \n${parts.join('\n')}\n ` } function buildStoreOrdersInner(orderXml: string): string { return ` PDF A6 true ${orderXml}` } // ---------------------------------------------------------------------------- // M_Inout + marketplace ack (same pattern as DHL multi) // ---------------------------------------------------------------------------- const handleInoutFunc = async (event: any, authToken: any = null, option: any) => { let data: any = {} const token = authToken ?? await getTokenHelper(event) const trackingNoArray = option.all_tracking_numbers.slice(1) const trackingPayload: any = { TrackingNo: option.tracking_number, DPD_Shipment_Code: option.shipment_code, DPD_Label_Base64: option.label_base64, IsCommissioned: true, shipping_date: date.now('', '', {timeZone: 'UTC'}).replace(' ', 'T')+'Z', shipping_service_name: option.shippingService, tableName: 'M_Inout', trackingnoarray: trackingNoArray.length > 0 ? JSON.stringify(trackingNoArray) : null } if(option.paket_type) trackingPayload['M_Paket_Type_ID'] = option.paket_type // Link the commissioning table (Tisch) so the correct camera's video can be // resolved later. No-op when the printer is unknown/unmapped. Object.assign(trackingPayload, await commissionTableFkPatch(event, option.shippingPrinter, token)) const resp: any = await fetchHelper(event, 'models/m_inout/'+option.inout_id, 'PUT', token, trackingPayload) if(resp) { data['shipment'] = resp data['status'] = 200 data['message'] = '' if(resp?.IsCommissionedConfirmed !== true) { const resp2: any = await fetchHelper(event, 'models/m_inout/'+option.inout_id+'?$expand=c_order_id,m_inoutline,c_bpartner_location_id,ad_org_id', 'GET', token, null) const formatOrderNumber = () => { const documentNo = resp2?.C_Order_ID?.DocumentNo ?? '0' const companyName = resp2?.AD_Org_ID?.companyname ?? '' const firstWord = companyName.trim().split(/\s+/)[0] || '' return firstWord ? `${documentNo}-${firstWord}` : documentNo } if(resp2?.C_Order_ID?.C_OrderSource_ID?.id) { const resp3: any = await fetchHelper(event, 'models/c_ordersource/'+resp2.C_Order_ID.C_OrderSource_ID.id, 'GET', token, null) const mailPayload = { email: option.customerEmail, isSentCustomTrackingMail: option.isSentCustomTrackingMail, orderNumber: formatOrderNumber(), name: resp2?.C_Order_ID?.C_BPartner?.identifier ?? 'Mr/Ms', company: 'LogYou GmbH', carrier: option.shippingService ?? 'DPD', lines: resp2?.m_inoutline?.filter((i: any) => i.M_Product_ID && !i.C_Charge_ID).map((i: any) => ({ description: i.M_Product_ID.identifier, quantity: i.QtyEntered })) ?? [], address1: resp2?.C_Order_ID?.C_BPartner_Location_ID?.C_Location_ID?.Address1 ?? '', address2: resp2?.C_Order_ID?.C_BPartner_Location_ID?.C_Location_ID?.Address2 ?? '', city: resp2?.C_Order_ID?.C_BPartner_Location_ID?.C_Location_ID?.City ?? '', country: resp2?.C_Order_ID?.C_BPartner_Location_ID?.C_Location_ID?.C_Country_ID?.identifier ?? '', postal: resp2?.C_Order_ID?.C_BPartner_Location_ID?.C_Location_ID?.Postal ?? '' } const markConfirmed = async () => { await fetchHelper(event, 'models/m_inout/'+option.inout_id, 'PUT', token, { IsCommissionedConfirmed: true, ack_commissioned_laravel: true, tableName: 'M_Inout' }) } if(resp2?.C_Order_ID?.shopware6_order_id && resp3?.marketplace_url) { try { const resp4: any = await laravelHelper(event, 'sales/orders/mark-shopware-order-delivery', 'POST', { marketplace_url: resp3.marketplace_url, marketplace_key: resp3.marketplace_key, marketplace_secret: resp3.marketplace_secret, id: resp2.C_Order_ID.shopware6_order_id, trackingCodes: [option.tracking_number], mail: mailPayload }) if(resp4) await markConfirmed() } catch(err: any) { data['shopware'] = errorHandlingHelper(err?.data ?? err, err?.data ?? err) } } if(resp2?.C_Order_ID?.shopify_order_id && resp3?.marketplace_url) { try { const resp4: any = await laravelHelper(event, 'sales/orders/mark-shopify-order-delivery', 'POST', { orderSource: resp3, id: resp2.C_Order_ID.shopify_order_id, trackingCodes: { number: option.tracking_number, url: option.tracking_url, company: 'DPD' }, mail: mailPayload }) if(resp4) await markConfirmed() } catch(err: any) { data['shopify'] = errorHandlingHelper(err?.data ?? err, err?.data ?? err) } } if(resp2?.C_Order_ID?.amazon_order_id && resp3?.marketplace_url) { try { const resp4: any = await laravelHelper(event, 'sales/orders/mark-amazon-order-delivery', 'POST', { orderSource: resp3, id: resp2.C_Order_ID.amazon_order_id, details: { shippingDate: trackingPayload.shipping_date, carrierCode: 'DPD', shippingMethod: 'Paket', referenceId: resp2?.DocumentNo ?? option.inout_id }, trackingCodes: { number: option.tracking_number, url: option.tracking_url }, mail: mailPayload }) if(resp4) await markConfirmed() } catch(err: any) { data['amazon'] = errorHandlingHelper(err?.data ?? err, err?.data ?? err) } } if(resp2?.C_Order_ID?.plentyone_order_id && resp3?.marketplace_url) { try { const plentyOneWeight = resp2?.m_inoutline?.reduce((acc: any, prev: any) => Number(acc['Weight'] ?? 0) + Number(prev['Weight'] ?? 0), 0) const additionalTrackingNumbers = option.all_tracking_numbers?.slice(1) ?? [] const resp4: any = await laravelHelper(event, 'sales/orders/mark-plentyone-order-delivery', 'POST', { orderSource: resp3, id: resp2.C_Order_ID.plentyone_order_id, details: { shippingDate: trackingPayload.shipping_date, carrierCode: 'DPD', shippingMethod: 'Paket', referenceId: resp2?.DocumentNo ?? option.inout_id, weight: plentyOneWeight ?? 0 }, trackingCodes: { number: option.tracking_number, url: option.tracking_url, trackingNoArray: additionalTrackingNumbers.length > 0 ? additionalTrackingNumbers : null }, mail: mailPayload }) if(resp4) await markConfirmed() } catch(err: any) { data['plentyone'] = errorHandlingHelper(err?.data ?? err, err?.data ?? err) } } if(resp2?.C_Order_ID?.ExternalOrderId && resp3?.Marketplace?.identifier === 'jtl-ffn' && resp3?.marketplace_url) { try { const resp4: any = await laravelHelper(event, 'sales/orders/mark-jtl-order-delivery', 'POST', { orderSource: resp3, id: resp2.C_Order_ID.ExternalOrderId, details: { shippingDate: trackingPayload.shipping_date, carrierCode: 'DPD', shippingMethod: 'Paket', referenceId: resp2?.DocumentNo ?? option.inout_id, weight: 0 }, trackingCodes: { number: option.tracking_number, url: option.tracking_url }, mail: mailPayload }) if(resp4) await markConfirmed() } catch(err: any) { data['jtl'] = errorHandlingHelper(err?.data ?? err, err?.data ?? err) } } if(resp2?.C_Order_ID?.ebay_order_id) { try { const resp4: any = await laravelHelper(event, 'sales/orders/mark-ebay-order-delivery', 'POST', { orderSource: resp3, id: resp2.C_Order_ID.ebay_order_id, details: { shippingDate: trackingPayload.shipping_date, carrierCode: 'DPD', shippingMethod: 'Paket', referenceId: resp2?.DocumentNo ?? option.inout_id }, trackingCodes: { number: option.tracking_number, url: option.tracking_url }, mail: mailPayload }) if(resp4) await markConfirmed() } catch(err: any) { data['ebay'] = errorHandlingHelper(err?.data ?? err, err?.data ?? err) } } } } } return data } // ---------------------------------------------------------------------------- // Main // ---------------------------------------------------------------------------- const handleFunc = async (event: any) => { let data: any = {} const body = await readBody(event) if (!body.parcels || !Array.isArray(body.parcels) || body.parcels.length === 0) { return { status: 400, message: 'Multi-parcel mode requires parcels array with at least one parcel' } } let customCreds: Partial | null = null let customerEmail = 'fulfillcustomer@logyou.de' let isSentCustomTrackingMail = false const tokenEmail = await getTokenHelper(event) const respInout: any = await fetchHelper(event, 'models/m_inout/'+body.inOutId+'?$expand=c_order_id,ad_org_id', 'GET', tokenEmail, null) if(respInout?.C_Order_ID?.C_OrderSource_ID?.id) { const respOrderSource: any = await fetchHelper(event, 'models/c_ordersource/'+respInout.C_Order_ID.C_OrderSource_ID.id, 'GET', tokenEmail, null) isSentCustomTrackingMail = respOrderSource?.isSentCustomTrackingMail ?? false if(body.email) { customerEmail = (respOrderSource?.isExcludetrackingmail ? 'fulfillcustomer@logyou.de' : body.email) } const extracted = extractOrderSourceDpdCreds(respOrderSource) if (extracted) { if (extracted.missing.length > 0) { console.warn('[DPD Multi] OrderSource missing fields:', extracted.missing, { id: respOrderSource?.id, Name: respOrderSource?.Name }) return { status: 400, message: `OrderSource ${respOrderSource?.id} (${respOrderSource?.Name || '?'}) is missing required DPD field(s): ${extracted.missing.join(', ')}. Open Settings → Order Sources → ${respOrderSource?.Name || respOrderSource?.id} and fill them in.` } } customCreds = extracted.creds } } let customerPhone = body.telephone || '+49 987654321' const companyName = respInout?.AD_Org_ID?.companyname ?? '' const firstWord = companyName.trim().split(/\s+/)[0]?.replace(/[^a-zA-Z0-9]/g, '') || '' const baseOrderNumber = body.orderNumber ?? body.inOutUId.replaceAll('-', '') const formattedOrderNumber = firstWord ? `${baseOrderNumber}-${firstWord}` : baseOrderNumber let orgSenderEmail: string = '' const orgId = respInout?.AD_Org_ID?.id if (orgId) { try { const orgRes: any = await fetchHelper(event, `models/ad_org/${orgId}?$select=C_BPartner_ID`, 'GET', tokenEmail, null) const orgBpartnerId = orgRes?.C_BPartner_ID?.id if (orgBpartnerId) { const partnerRes: any = await fetchHelper(event, `models/c_bpartner/${orgBpartnerId}?$select=freight_service_sender_mail`, 'GET', tokenEmail, null) orgSenderEmail = String(partnerRes?.freight_service_sender_mail ?? '').trim() } } catch(err: any) { console.error(`[DPD Multi] Error fetching org sender email:`, err?.message || err) } } let auth, creds try { const r = await dpdGetAuth(event, customCreds) auth = r.auth creds = r.creds } catch(loginErr: any) { data['status'] = 500 data['message'] = loginErr?.message || 'DPD authentication failed' return data } const country = String(body.country ?? '').toUpperCase().trim() const isInternational = !EU_COUNTRIES.includes(country) const orderXml = buildMultiOrderXml({ refNo: formattedOrderNumber, depot: auth.depot, customerNumber: creds.customerNumber, sender: { name1: respInout?.AD_Org_ID?.companyname && respInout?.AD_Org_ID !== 1000000 ? respInout?.AD_Org_ID?.companyname : 'LogYou GmbH', name2: respInout?.AD_Org_ID?.companyname && respInout?.AD_Org_ID !== 1000000 ? 'c/o LogYou GmbH' : undefined, street: 'Mühlenweg', houseNo: '4', country: 'DE', zipCode: '35510', city: 'Butzbach', email: orgSenderEmail || 'info@logyou.de', phone: '+4960339160570' }, recipient: { name1: String(body.name ?? '').trim(), name2: body.name2 ? String(body.name2).trim() : undefined, street: String(body.address ?? '').trim(), houseNo: String(body.houseNumber || '').trim(), country: country, zipCode: String(body.postalCode ?? '').trim(), city: String(body.city ?? '').trim(), state: body.countryState ? String(body.countryState).trim() : undefined, email: customerEmail, phone: customerPhone }, parcels: body.parcels.map((p: any) => ({ weightKg: Number(p.weight ?? 0), dims: (body.length && body.width && body.height) ? { length: toCm(body.length), width: toCm(body.width), height: toCm(body.height) } : undefined, items: (p.items || []).map((it: any) => ({ description: it.description || '', quantity: Number(it.quantity || 1), value: Number(it.value || 0), weightKg: Number(it.weight || 0.1), // taricCode = M_Product.taricCode fallback for labels created without the // customs modal normalizing it into hsCode hsCode: it.hsCode || it.taricCode || undefined, countryOfOrigin: it.countryOfOrigin || undefined })) })), predictEmail: customerEmail && /@/.test(customerEmail) ? customerEmail : undefined, customsCurrency: body.totalOrderValueCurrency || 'EUR', invoiceNo: body.orderNumber ? String(body.orderNumber).substring(0, 20) : undefined, isInternational: isInternational && body.enableCN23 === true, incoterm: (body.customsIncoterm === 'DDP' || body.customsIncoterm === 'EXW' ? body.customsIncoterm : 'DAP') as 'DAP' | 'DDP' | 'EXW', reasonForExport: (['01', '02', '03'].includes(body.customsReasonForExport) ? body.customsReasonForExport : '01') as '01' | '02' | '03', shipperEori: body.shipperEori ? String(body.shipperEori).trim() : undefined }) let dpdResult try { const { result } = await dpdStoreOrders(event, customCreds, buildStoreOrdersInner(orderXml)) dpdResult = result } catch(dpdErr: any) { const detail = dpdErr?.dpdFault ? `${dpdErr.dpdFault.code}: ${dpdErr.dpdFault.message}` : (dpdErr?.message || 'DPD API Error') console.error('[DPD Multi] DPD API rejected shipment:', detail) data['status'] = 400 data['message'] = detail data['dpd_errors'] = [detail] return data } if (dpdResult.faults && dpdResult.faults.length > 0) { const detail = dpdResult.faults.map(f => `${f.code}: ${f.message}`).join(' | ') data['status'] = 400 data['message'] = detail data['dpd_errors'] = dpdResult.faults.map(f => `${f.code}: ${f.message}`) return data } const allTrackingNumbers = dpdResult.parcelLabelNumbers if (!allTrackingNumbers.length || !dpdResult.labelPdfBase64) { data['status'] = 500 data['message'] = 'DPD response missing tracking numbers or label' return data } // Build per-parcel labels for the frontend's print loop. // parcelLabels comes from the helper: when splitByParcel=true (our case), // each in DPD's response has its own . // We pair them with tracking numbers and emit the same shape DHL multi uses // (label.b64 per parcel), so the existing commission.vue printing loop works // identically for DPD without any frontend changes. const perParcelLabels = dpdResult.parcelLabels && dpdResult.parcelLabels.length > 0 ? dpdResult.parcelLabels : allTrackingNumbers.map((tn, i) => ({ trackingNumber: tn, pdfBase64: i === 0 ? dpdResult.labelPdfBase64 : '' })) data['dpd_parcels'] = perParcelLabels.map(p => ({ shipmentNo: p.trackingNumber, routingCode: '', label: { b64: p.pdfBase64 } })) // Also emit dhl_parcels-shaped key so commission.vue's normalization that // copies dpd_parcels → dhl_parcels keeps the existing print loop happy. data['tracking_numbers'] = allTrackingNumbers data['parcel_count'] = allTrackingNumbers.length data['mpsId'] = dpdResult.mpsId // Persist try { const res2 = await handleInoutFunc(event, null, { inout_id: body.inOutId, tracking_number: allTrackingNumbers[0], tracking_url: '', shipment_code: dpdResult.mpsId || allTrackingNumbers.join(','), label_base64: dpdResult.labelPdfBase64, paket_type: body.paketType, shippingService: body.shippingService || 'DPD', shippingPrinter: body.shippingPrinter, customerEmail: customerEmail, isSentCustomTrackingMail: isSentCustomTrackingMail, all_tracking_numbers: allTrackingNumbers }) data = { ...data, ...res2 } } catch(err: any) { try { const authToken: any = await refreshTokenHelper(event) const res3 = await handleInoutFunc(event, authToken, { inout_id: body.inOutId, tracking_number: allTrackingNumbers[0], tracking_url: '', shipment_code: dpdResult.mpsId || allTrackingNumbers.join(','), label_base64: dpdResult.labelPdfBase64, paket_type: body.paketType, shippingService: body.shippingService || 'DPD', shippingPrinter: body.shippingPrinter, customerEmail: customerEmail, isSentCustomTrackingMail: isSentCustomTrackingMail, all_tracking_numbers: allTrackingNumbers }) data = { ...data, ...res3 } } catch(error: any) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) } } return data } export default defineEventHandler(async (event) => { let data: any = {} try { data = await handleFunc(event) } catch(err: any) { data = errorHandlingHelper(err?.data ?? err, err?.data ?? err) } return data })