diff --git a/cfg/config.json b/cfg/config.json index 8563c99..c26d1d2 100644 --- a/cfg/config.json +++ b/cfg/config.json @@ -349,11 +349,25 @@ "shipping": "shipping", "billing": "billing" }, + "errors": { + "INVALID_CREDENTIALS": { + "code": "401", + "message": "Invalid credentials" + }, + "USER_NOT_LOGGED_IN": { + "code": "401", + "message": "Invalid authentication context, please log in first" + }, + "ACTION_NOT_ALLOWED": { + "code": "403", + "message": "Action not allowed on this resource" + } + }, "stubs": { "DHLSoap": { "defaults": { "ordering": { - "wsdl": "./wsdl/dhl/geschaeftskundenversand-api-3.4.0.wsdl", + "wsdl": "wsdl/dhl/geschaeftskundenversand-api-3.4.0.wsdl", "version": [3, 4, 0], "endpoint": "https://cig.dhl.de/services/sandbox/soap", "username": null, diff --git a/cfg/config_test.json b/cfg/config_test.json index c412384..931e030 100644 --- a/cfg/config_test.json +++ b/cfg/config_test.json @@ -2,7 +2,7 @@ "logger": { "console": { "handleExceptions": false, - "level": "crit", + "level": "error", "colorize": true, "prettyPrint": true } diff --git a/src/services/fulfillment.ts b/src/services/fulfillment.ts index d5ff7bb..d0be5f7 100644 --- a/src/services/fulfillment.ts +++ b/src/services/fulfillment.ts @@ -49,7 +49,7 @@ import { FulfillmentId, } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment.js'; import { Subject } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/auth.js'; -import { Address, AddressServiceDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/address.js'; +import { Address, AddressServiceDefinition, ShippingAddress } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/address.js'; import { Country, CountryServiceDefinition, @@ -136,6 +136,11 @@ export class FulfillmentService code: 404, message: '{entity} {id} has no legal address!', }, + NO_SHIPPING_ADDRESS: { + id: '', + code: 404, + message: '{entity} {id} has no shipping address!', + }, NO_LABEL: { id: '', code: 404, @@ -159,7 +164,7 @@ export class FulfillmentService message: 'SUCCESS', }, PARTIAL: { - code: 400, + code: 208, message: 'Patrial executed with errors!', }, LIMIT_EXHAUSTED: { @@ -181,6 +186,7 @@ export class FulfillmentService protected readonly country_service: Client; protected readonly tax_service: Client; protected readonly credential_service: Client; + protected readonly tech_user: Subject; protected readonly contact_point_type_ids = { legal: 'legal', shipping: 'shipping', @@ -297,6 +303,8 @@ export class FulfillmentService CredentialServiceDefinition, createChannel(cfg.get('client:credential:address')) ); + + this.tech_user = cfg.get('tech_user'); } protected handleStatusError(id: string, e: any, payload?: any): T { @@ -311,11 +319,11 @@ export class FulfillmentService } as T; } - protected handleOperationError(e: any): T { + protected handleOperationError(e: any, items?: any[]): T { this.logger?.error(e); return { - items: [], - total_count: 0, + items, + total_count: items?.length ?? 0, operation_status: { code: e?.code ?? 500, message: e?.message ?? e?.details ?? e?.toString(), @@ -423,13 +431,13 @@ export class FulfillmentService ); } - protected get( + protected async get( ids: string[], service: CRUDClient, subject?: Subject, context?: CallContext, ): Promise> { - ids = [...new Set(ids)]; + ids = [...new Set(ids)].filter(id => id); if (ids.length > 1000) { throwOperationStatusCode( @@ -438,11 +446,15 @@ export class FulfillmentService ); } + if (!ids.length) { + return {} as ResponseMap; + } + const request = ReadRequest.fromPartial({ filters: [{ filters: [ { - field: 'id', + field: '_key', operation: Filter_Operation.in, value: JSON.stringify(ids), type: Filter_ValueType.ARRAY, @@ -453,7 +465,7 @@ export class FulfillmentService subject, }); - return service.read( + return await service.read( request, context, ).then( @@ -529,7 +541,7 @@ export class FulfillmentService const customer_map = await this.get( fulfillments.map(q => q.customer_id), this.customer_service, - subject, + this.tech_user ?? subject, context, ); @@ -538,7 +550,7 @@ export class FulfillmentService this.shop_service, subject, context, - ); + ) ?? {}; const orga_map = await this.get( [ @@ -551,9 +563,9 @@ export class FulfillmentService ), ], this.organization_service, - subject, + this.tech_user ?? subject, context, - ); + ) ?? {}; const contact_point_map = await this.get( [ @@ -565,27 +577,35 @@ export class FulfillmentService ), ], this.contact_point_service, - subject, + this.tech_user ?? subject, context, - ); + ) ?? {}; const address_map = await this.get
( Object.values(contact_point_map).map( item => item.payload?.physical_address_id ), this.address_service, - subject, + this.tech_user ?? subject, context, - ); + ) ?? {}; const country_map = await this.get( - Object.values(address_map).map( - item => item.payload?.country_id - ), + [ + ...Object.values(address_map).map( + item => item.payload?.country_id + ), + ...fulfillments?.map( + item => item.packaging?.sender?.address?.country_id + ), + ...fulfillments?.map( + item => item.packaging?.recipient?.address?.country_id + ), + ], this.country_service, - subject, + this.tech_user ?? subject, context, - ); + ) ?? {}; const product_map = await this.getProductsBySuper( fulfillments.flatMap( @@ -593,7 +613,7 @@ export class FulfillmentService ), subject, context, - ); + ) ?? {}; const courier_map = await this.getCouriersBySuper( Object.values(product_map).map( @@ -601,7 +621,7 @@ export class FulfillmentService ), subject, context, - ); + ) ?? {}; const credential_map = await this.get( Object.values(courier_map).map( @@ -610,19 +630,122 @@ export class FulfillmentService this.credential_service, subject, context, - ) + ) ?? {}; const tax_map = await this.get( Object.values(product_map).flatMap( p => p.payload?.tax_ids ), this.tax_service, - subject, + this.tech_user ?? subject, context, - ); + ) ?? {}; const promises = fulfillments.map(async (item): Promise => { try { + item.packaging.sender ??= await this.getById( + shop_map, + item.shop_id, + 'Shop' + ).then( + shop => this.getById( + orga_map, + shop.payload.organization_id, + 'Organization', + ) + ).then( + orga => this.getByIds( + contact_point_map, + orga.payload.contact_point_ids, + 'ContactPoint' + ) + ).then( + async cps => { + const cp = cps.find( + cp => cp.payload.contact_point_type_ids?.includes( + this.contact_point_type_ids.shipping + ) + )?.payload; + + if (!cp) { + throwStatusCode( + 'Shop', + item.shop_id, + this.status_codes.NO_SHIPPING_ADDRESS, + ); + } + + const address = await this.getById( + address_map, + cp?.physical_address_id, + 'Address', + )?.then( + address => address.payload + ); + + delete address.meta; + return { + address, + contact: { + name: cp.name, + email: cp.email, + phone: cp.telephone, + }, + } as ShippingAddress + } + ); + + item.packaging.recipient ??= await this.getById( + customer_map, + item.customer_id, + 'Customer' + ).then( + async customer => { + const orga_id = ( + customer.payload.commercial?.organization_id + ?? customer.payload.public_sector?.organization_id + ); + + const orga = orga_id && await this.getById( + orga_map, + orga_id, + 'Organization' + ); + + const cps = await this.getByIds( + contact_point_map, + [ + ...(orga?.payload?.contact_point_ids ?? []), + ...(customer.payload?.private?.contact_point_ids ?? []) + ], + 'ContactPoint' + ); + + const cp = cps.find( + cp => cp.payload.contact_point_type_ids?.includes( + this.contact_point_type_ids.shipping + ) + )?.payload; + const address = await this.getById( + address_map, + cp?.physical_address_id, + 'Address', + )?.then( + address => address.payload + ); + + delete address.meta; + return { + address, + contact: { + name: cp.name, + email: cp.email, + phone: cp.telephone, + }, + } as ShippingAddress + } + ); + const sender_country = await this.getById( country_map, item.packaging.sender?.address?.country_id, @@ -700,8 +823,8 @@ export class FulfillmentService this.contact_point_type_ids.legal ) ) ?? throwStatusCode( - typeof (item), - item.id, + 'Shop', + item.shop_id, this.status_codes.NO_LEGAL_ADDRESS, ) ).then( @@ -731,33 +854,43 @@ export class FulfillmentService orga_map[customer.payload.commercial?.organization_id]?.payload.contact_point_ids, orga_map[customer.payload.public_sector?.organization_id]?.payload.contact_point_ids, ].flatMap(id => id).filter(id => id), - 'Country' - ).then( - cps => cps.find( - cp => cp.payload?.contact_point_type_ids.includes( - this.contact_point_type_ids.legal - ) - ) ?? throwStatusCode( - typeof (item), - item.id, - this.status_codes.NO_LEGAL_ADDRESS, - ) + 'ContactPoint' ).then( - cp => this.getById( - address_map, - cp.payload.physical_address_id, - 'Address' - ) - ).then( - address => this.getById( - country_map, - address.payload.country_id, - 'Country' - ) + async cps => { + const cp = cps.find( + cp => cp.payload?.contact_point_type_ids.includes( + this.contact_point_type_ids.legal + ) + ); + + if (cp) { + return this.getById( + address_map, + cp.payload.physical_address_id, + 'Address' + ).then( + address => this.getById( + country_map, + address.payload.country_id, + 'Country' + ) + ); + } + else if (recipient_country) { + return recipient_country + } + else { + throwStatusCode( + 'Customer', + item.customer_id, + this.status_codes.NO_LEGAL_ADDRESS, + ); + } + } ); if (evaluate) { - item.packaging.parcels.forEach( + item.packaging?.parcels?.forEach( p => { const product = product_map[p.product_id].payload; const variant = product.variants.find( @@ -812,8 +945,8 @@ export class FulfillmentService return aggreagatedFulfillment; } - catch (error) { - return this.handleStatusError(item?.id, error, item); + catch (e: any) { + return this.handleStatusError(item?.id, e, item); } }); @@ -893,6 +1026,7 @@ export class FulfillmentService } @resolves_subject() + @injects_meta_data() @access_controlled_function({ action: AuthZAction.EXECUTE, operation: Operation.isAllowed, @@ -906,6 +1040,26 @@ export class FulfillmentService context?: CallContext, ): Promise { try { + await this.getFulfillmentsByIds( + request.items.map(item => item.id), + request.subject, + context, + ).then( + response => { + if (response.operation_status?.code !== 200) { + throw response.operation_status; + } + else { + const result_map = new Map(response.items.map(item => [item.payload.id, item])); + request.items = request.items.map( + item => ({ + ...result_map.get(item.id)?.payload, + ...item + }) + ) + } + } + ); const fulfillments = await this.aggregate(request.items, request.subject, context); const flat_fulfillments = flatMapAggregatedFulfillments(fulfillments); const invalid_fulfillments = flat_fulfillments.filter(f => f.status?.code !== 200); @@ -924,8 +1078,11 @@ export class FulfillmentService ) }; } - catch (e) { - return this.handleOperationError(e); + catch (e: any) { + return this.handleOperationError( + e, + request.items?.map(item => this.handleStatusError(item.id, e, item)), + ); } } @@ -944,6 +1101,26 @@ export class FulfillmentService context?: CallContext ): Promise { try { + await this.getFulfillmentsByIds( + request.items.map(item => item.id), + request.subject, + context, + ).then( + response => { + if (response.operation_status?.code !== 200) { + throw response.operation_status; + } + else { + const result_map = new Map(response.items.map(item => [item.payload.id, item])); + request.items = request.items.map( + item => ({ + ...result_map.get(item.id)?.payload, + ...item + }) + ) + } + } + ); const fulfillments = await this.aggregate(request.items, request.subject, context); const flattened = flatMapAggregatedFulfillments(fulfillments); const valids = flattened.filter( @@ -965,6 +1142,7 @@ export class FulfillmentService const invalids = flattened.filter( f => f.status?.code !== 200 || StateRank[f.payload?.fulfillment_state] >= StateRank[FulfillmentState.SUBMITTED] ); + this.logger.debug('Submitting:', valids); const responses = await Stub.submit(valids); const merged = mergeFulfillments([ ...responses, @@ -999,10 +1177,16 @@ export class FulfillmentService ...merged.filter(item => item.payload?.labels?.length === 0) ); upsert_results.total_count = upsert_results.items.length; + if (invalids.length) { + upsert_results.operation_status = this.operation_status_codes.PARTIAL; + } return upsert_results; } - catch (e) { - return this.handleOperationError(e); + catch (e: any) { + return this.handleOperationError( + e, + request.items?.map(item => this.handleStatusError(item.id, e, item)), + ); } } @@ -1156,7 +1340,7 @@ export class FulfillmentService ), }; } - catch (e) { + catch (e: any) { return this.handleOperationError(e); } } @@ -1290,7 +1474,7 @@ export class FulfillmentService update_results.total_count = update_results.items.length; return update_results; } - catch (e) { + catch (e: any) { return this.handleOperationError(e); } } diff --git a/src/stub.ts b/src/stub.ts index f329511..b0dca00 100644 --- a/src/stub.ts +++ b/src/stub.ts @@ -14,9 +14,7 @@ export abstract class Stub static cfg: any = null; static logger: Logger = null; - get type(): string { - return this.constructor.name; - } + abstract get type(): string; constructor( public courier: Courier, diff --git a/src/stubs/dhl_soap.ts b/src/stubs/dhl_soap.ts index 7aaa54a..85ae2c8 100644 --- a/src/stubs/dhl_soap.ts +++ b/src/stubs/dhl_soap.ts @@ -20,19 +20,13 @@ import { throwOperationStatusCode, } from '../utils.js'; import { Stub } from '../stub.js'; -import { FulfillmentProduct, PackingSolutionQuery } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment_product.js'; +import { + FulfillmentProduct, + PackingSolutionQuery +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment_product.js'; dayjs.extend(customParseFormat); -type ClientMap = { [id: string]: soap.Client }; -type RequestMap = { - [id: string]: { - courier: Courier, - credential: Credential, - fulfillments: FlatAggregatedFulfillment[], - } -} - interface Origin { 'cis:country': string; @@ -145,6 +139,7 @@ interface Config username?: string, password?: string, access_token?: string, + account_number?: string, wsdl_header?: { Authentification?: { user?: string, @@ -302,9 +297,10 @@ const DHLShipmentCancelResponse2AggregatedFulfillment = ( }; export class DHLSoap extends Stub { - protected static _clients: ClientMap = {}; - protected readonly stub_defaults: any; public readonly version: number[]; + protected readonly stub_defaults: any; + private _stub_config: Config; + private _soap_client: soap.Client; protected readonly status_codes: { [key: string]: Status } = { OK: { @@ -346,10 +342,26 @@ export class DHLSoap extends Stub { code: 500, message: 'Request timeout, DHL not responding!', }, + UNAUTHORIZED: { + code: 401, + message: [ + 'DHLSoap: Connection to Courier {entity} is unauthorized.', + 'Credentials or accountNumber are missing.', + 'Credentials have to be set either in FulfillmentProduct, Courier, Credential or ServiceConfig.' + ].join(' ') + }, + UNKNOWN_RESPONSE: { + code: 500, + message: 'DHLSoap: Unexpected response from SOAP-Client! {details}', + } }; - get clients(): ClientMap { - return DHLSoap._clients; + get type(): string { + return 'DHLSoap'; + } + + get stub_config(): Config { + return this._stub_config; } constructor(courier?: Courier, kwargs?: { [key: string]: any }) { @@ -389,28 +401,36 @@ export class DHLSoap extends Stub { } } - protected DHLShipmentLabels2FulfillmentResponses ( + protected DHLResponse2FulfillmentResponses ( fulfillments: FlatAggregatedFulfillment[], response: any, error: any, ): FlatAggregatedFulfillment[] { if (error) { if (response?.html) { - this.logger?.error(`${this.constructor.name}: ${response.html.head.title}`); - throwOperationStatusCode( - 'Fulfillment', - { - code: response.statusCode, - message: response.html.head.title - } - ); + this.logger?.error(`${this.type}: ${response.html.head?.title}`, response); + if (response.html.head?.title?.startsWith('401')) { + throwOperationStatusCode( + this.courier?.id, + this.operation_status_codes.UNAUTHORIZED, + ); + } + else { + throwOperationStatusCode( + this.courier?.id, + { + code: 500, + message: response.html.head?.title + } + ); + } } else { const message = error?.root?.Envelope?.Body?.Fault?.faultstring ?? error?.response?.statusText ?? error?.toString() ?? 'Server Error!'; - this.logger?.error(`${this.constructor.name}: ${message}`); + this.logger?.error(`${this.type}: ${message}`); throwOperationStatusCode( 'Fulfillment', { @@ -421,7 +441,7 @@ export class DHLSoap extends Stub { } } else if (response?.html) { - this.logger?.error(`${this.constructor.name}: ${response.html}`); + this.logger?.error(`${this.type}: ${response.html}`); throwOperationStatusCode( 'Fulfillment', { @@ -430,20 +450,7 @@ export class DHLSoap extends Stub { }, ); } - else if (response) { - if (response?.Status?.statusCode !== 0) { - const message = JSON.stringify(response, null, 2); - this.logger?.error(`${this.constructor.name}: ${message}`); - throwOperationStatusCode( - 'Fulfillment', - { - code: response.Status?.statusCode, - message - }, - ); - } - } - else { + else if (!response) { throwOperationStatusCode( 'Fulfillment', { @@ -453,41 +460,81 @@ export class DHLSoap extends Stub { ); } - return fulfillments.map((fulfillment, i) => { - const dhl_state = response?.CreationState?.find((state: any) => state.sequenceNumber === (i + 1).toString()); - const code = dhl_state?.LabelData?.Status?.statusCode; - const state = code === 0 ? FulfillmentState.SUBMITTED : FulfillmentState.INVALID; - const status = this.DHLCode2StatusCode( - code, - fulfillment.payload?.id, - dhl_state?.LabelData?.Status?.statusMessage, - ); + if (response?.CreationState) { + return fulfillments.map((fulfillment, i) => { + const dhl_state = response.CreationState.find((state: any) => state.sequenceNumber === (i + 1).toString()); + const code = dhl_state?.LabelData?.Status?.statusCode; + const state = code === 0 ? FulfillmentState.SUBMITTED : FulfillmentState.INVALID; + const status = this.DHLCode2StatusCode( + code, + fulfillment.payload?.id, + dhl_state?.LabelData?.Status?.statusMessage, + ); + + if (state === FulfillmentState.INVALID) { + fulfillment.payload.fulfillment_state = state; + fulfillment.status = status; + return fulfillment; + } + + const label: Label = { + parcel_id: fulfillment.parcel.id, + shipment_number: dhl_state?.shipmentNumber, + url: dhl_state?.LabelData.labelUrl, + state, + status, + }; - if (state === FulfillmentState.INVALID) { fulfillment.payload.fulfillment_state = state; + fulfillment.label = label; fulfillment.status = status; return fulfillment; - } + }); + } + else if (response?.ValidationState) { + return fulfillments.map((fulfillment, i) => { + const dhl_state = response.ValidationState.find((state: any) => state.sequenceNumber === (i + 1).toString()); + const code = dhl_state?.Status?.statusCode; + const status = this.DHLCode2StatusCode( + code, + fulfillment.payload?.id, + dhl_state?.Status?.statusMessage ?? dhl_state?.Status?.statusText, + ); - const label: Label = { - parcel_id: fulfillment.parcel.id, - shipment_number: dhl_state?.shipmentNumber, - url: dhl_state?.LabelData.labelUrl, - state, - status, - }; + if (code !== 0 ) { + fulfillment.payload.fulfillment_state = FulfillmentState.INVALID; + } - fulfillment.payload.fulfillment_state = state; - fulfillment.label = label; - fulfillment.status = status; - return fulfillment; - }); + fulfillment.status = status; + return fulfillment; + }); + } + if (response?.Status) { + if (response?.Status?.statusCode !== 0) { + const message = response?.Status?.statusMessage ?? response?.Status?.statusText ?? JSON.stringify(response); + this.logger?.error(`${this.type}: ${message}`); + throwOperationStatusCode( + 'Fulfillment', + { + code: response.Status?.statusCode, + message + }, + ); + } + } + else { + throwOperationStatusCode( + this.type, + this.operation_status_codes.UNKNOWN_RESPONSE, + JSON.stringify(response) + ); + } } protected AggregatedFulfillmentRequests2DHLShipmentOrderRequest( - requests: FlatAggregatedFulfillment[] + requests: FlatAggregatedFulfillment[], ): ShipmentOrderRequest { - return { + const shipment_order_request = { Version: { majorRelease: this.version[0], minorRelease: this.version[1] @@ -543,8 +590,10 @@ export class DHLSoap extends Stub { shipmentDate: new Date().toISOString().slice(0,10), costCenter: '', customerReference: request.payload.id, - product: request.product.attributes.find(att => att.id === this.cfg.get('urns:productName')).value, - accountNumber: request.product.attributes.find(att => att.id === this.cfg.get('urns:accountNumber')).value, + product: request.product.attributes.find(att => att.id === this.cfg.get('urns:productName'))?.value, + accountNumber: request.product.attributes.find( + att => att.id === this.cfg.get('urns:accountNumber') + )?.value ?? this.stub_config?.ordering?.account_number, // Service: parseService(request.parcel.attributes), ShipmentItem: { heightInCM: request.parcel.package.size_in_cm.height, @@ -562,6 +611,8 @@ export class DHLSoap extends Stub { }; }) }; + this.logger.debug('ShipmentOrderRequest', shipment_order_request); + return shipment_order_request; } protected AggregatedFulfillment2DHLShipmentCancelRequest( @@ -576,17 +627,16 @@ export class DHLSoap extends Stub { }; } - async getSoapClient(courier: Courier, credential: Credential): Promise { - this.courier = courier = courier ?? this.courier; - if (this.clients[courier.id]) { - return this.clients[courier.id]; + async getSoapClient(credential?: Credential): Promise { + if (this._soap_client) { + return this._soap_client; } try{ - const config: Config = { + const config = this._stub_config = { ordering: { ...this.stub_defaults?.ordering, - ...JSON.parse(courier?.configuration?.value?.toString() ?? null)?.ordering, + ...JSON.parse(this.courier?.configuration?.value?.toString() ?? null)?.ordering, ...credential ? { username: credential?.user, @@ -597,7 +647,8 @@ export class DHLSoap extends Stub { } }; - this.clients[courier.id] = await soap.createClientAsync(config.ordering.wsdl).then( + this.logger.debug('Create SOAP Client with:', config); + this._soap_client = await soap.createClientAsync(config.ordering.wsdl).then( client => { client.setEndpoint(config.ordering.endpoint); client.addSoapHeader( @@ -621,64 +672,46 @@ export class DHLSoap extends Stub { ); } catch (err) { - this.logger?.error(`${this.type}:\n Failed to create Client!`); - this.logger?.error(`${this.type}: ${JSON.stringify(err, null, 2)}`); + this.logger?.error(`${this.type}:\t Failed to create Client!`); + this.logger?.error(`${this.type}:\t ${JSON.stringify(err, null, 2)}`); throw err; } - return this.clients[courier.id]; + return this._soap_client; }; protected async soapCall( funcName: string, fulfillments: FlatAggregatedFulfillment[] ): Promise { - if (fulfillments.length === 0) return []; - const request_map = fulfillments.reduce( - (a, b) => { - const c = a[b.courier?.id]; - if (c) { - c.fulfillments.push(b); - } - else if (b.courier?.id) { - a[b.courier.id] = { - courier: b.courier, - credential: b.credential, - fulfillments: [b], - }; - } - return a; - }, - {} as RequestMap + fulfillments = fulfillments.filter( + fulfillment => fulfillment.courier.id === this.courier.id ); - return await Promise.all( - Object.values(request_map).map( - ({courier, credential, fulfillments}) => this.getSoapClient(courier, credential).then( - client => new Promise( - (resolve, reject): void => { - const timer = setTimeout(reject, 30000, this.operation_status_codes.TIMEOUT); - client.GVAPI_2_0_de.GKVAPISOAP11port0[funcName]( - this.AggregatedFulfillmentRequests2DHLShipmentOrderRequest(fulfillments), - (error: any, result: any): any => { - try { - clearTimeout(timer); - resolve(this.DHLShipmentLabels2FulfillmentResponses(fulfillments, result, error)); - } - catch (e: any) { - reject(e); - } - } - ) + if (fulfillments.length === 0) { + return [] + }; + return await this.getSoapClient(fulfillments[0].credential).then( + client => new Promise( + (resolve, reject): void => { + const timer = setTimeout(reject, 30000, this.operation_status_codes.TIMEOUT); + client.GVAPI_2_0_de.GKVAPISOAP11port0[funcName]( + this.AggregatedFulfillmentRequests2DHLShipmentOrderRequest(fulfillments), + (error: any, result: any): any => { + try { + clearTimeout(timer); + resolve(this.DHLResponse2FulfillmentResponses(fulfillments, result, error)); + } + catch (e: any) { + reject(e); + } } ) - ) + } ) - ).then( - p => p.flatMap(p => p) ); } protected override async evaluateImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { - return await this.soapCall('evaluateShipmentOrder', fulfillments); + return await this.soapCall('validateShipment', fulfillments); } protected override async submitImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { diff --git a/test/fulfillment-srv-dhl.spec.ts b/test/fulfillment-srv-dhl.spec.ts index ee50493..fa701ba 100644 --- a/test/fulfillment-srv-dhl.spec.ts +++ b/test/fulfillment-srv-dhl.spec.ts @@ -152,7 +152,7 @@ describe('Testing Fulfillment Service Cluster:', () => { it(`should not create couriers based on invalid sample: ${sample_name}`, async () => { sample.items.map((item:any) => { item.configuration = { - value: Buffer.from(JSON.stringify(item.configuration ?? null)) //because Any + value: Buffer.from(JSON.stringify(item.configuration ?? null)) } return item; }); @@ -292,9 +292,9 @@ describe('Testing Fulfillment Service Cluster:', () => { } for (let [sample_name, sample] of Object.entries(samples.fulfillments.valid)) { - it(`should submit fulfillment by valid samples: ${sample_name}`, async function() { + it(`should evaluate fulfillment by valid samples: ${sample_name}`, async function() { this.timeout(60000); - const response = await fulfillment_client.submit(sample); + const response = await fulfillment_client.evaluate(sample); should.equal( response?.operationStatus?.code, 200, 'response.operationStatus.code expected to be 200', @@ -310,6 +310,27 @@ describe('Testing Fulfillment Service Cluster:', () => { 'response.items[*].status.code expected all to be 200', ); }); + } + + for (let [sample_name, sample] of Object.entries(samples.fulfillments.valid)) { + it(`should submit fulfillment by valid samples: ${sample_name}`, async function() { + this.timeout(60000); + const response = await fulfillment_client.submit(sample); + should.equal( + response?.operationStatus?.code, 208, + 'response.operationStatus.code expected to be 208', + ); + should.ok( + response?.items?.length ?? 0 > 0, + 'response.items.length expected to be greater 0', + ); + should.ok( + !response?.items?.some( + item => item.status?.code !== 200 + ), + 'response.items[*].status.code expected all to be 200', + ); + }); it(`should have received fulfillment submit event for ${sample_name}`, async function() { this.timeout(5000);