From 51ee97932e2b831c5dd3d526d5874056af7ec0ec Mon Sep 17 00:00:00 2001 From: Gerald Baulig Date: Tue, 30 Apr 2024 12:02:07 +0200 Subject: [PATCH] fix(rebase): rebase deps up --- cfg/config.json | 17 +- cfg/config_test.json | 65 +- package-lock.json | 48 +- package.json | 4 +- src/index.ts | 5 - src/services/fulfillment.ts | 128 +-- src/services/fulfillment_courier.ts | 2 +- src/services/fulfillment_product.ts | 133 +-- src/stub.ts | 20 +- src/stubs/dhl_soap.ts | 1175 ++++++++++++++------------- src/stubs/dummy.ts | 14 +- src/stubs/index.ts | 3 + src/utils.ts | 32 +- src/worker.ts | 11 +- test/fulfillment-srv-dhl.spec.ts | 39 +- test/mocks.ts | 113 +-- 16 files changed, 945 insertions(+), 864 deletions(-) delete mode 100644 src/index.ts create mode 100644 src/stubs/index.ts diff --git a/cfg/config.json b/cfg/config.json index 369bc01..4c24ab8 100644 --- a/cfg/config.json +++ b/cfg/config.json @@ -77,7 +77,6 @@ "FULFILLED": "fulfillmentFulfilled", "WITHDRAWN": "fulfillmentWithdrawn", "CANCELLED": "fulfillmentCancelled", - "CREATE_FULFILLMENTS": "createFulfillments", "CREATE_INVOICES": "createInvoices" }, "kafka": { @@ -359,8 +358,22 @@ "preDefinedIds": { "legalAddressTypeId": "legal_address" }, + "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": { - "DHLSoapStub": { + "DHLSoap": { "defaults": { "ordering": { "wsdl": "./wsdl/dhl/geschaeftskundenversand-api-3.4.0.wsdl", diff --git a/cfg/config_test.json b/cfg/config_test.json index ad4b923..cbfc682 100644 --- a/cfg/config_test.json +++ b/cfg/config_test.json @@ -7,18 +7,61 @@ "prettyPrint": true } }, + "database": { + "main": { + "database": "fulfillment-srv-test" + } + }, + "server": { + "transports": [ + { + "name": "grpcFulfillmentSrv", + "provider": "grpc", + "addr": "0.0.0.0:50167" + } + ] + }, + "authorization": { + "client": null + }, "client": { "fulfillment": { - "address": "localhost:50067" + "address": "localhost:50167" }, "fulfillment_courier": { - "address": "localhost:50067" + "address": "localhost:50167" }, "fulfillment_product": { - "address": "localhost:50067" + "address": "localhost:50167" + }, + "acs-srv": { + "address": "localhost:50161", + "mock": { + "protoPath": "io/restorecommerce/access_control.proto", + "packageName": "io.restorecommerce.access_control", + "serviceName": "AccessControlService", + "protoLoadOptions": { + "includeDirs": [ + "node_modules/@restorecommerce/protos/" + ] + } + } + }, + "user": { + "address": "localhost:50162", + "mock": { + "protoPath": "io/restorecommerce/user.proto", + "packageName": "io.restorecommerce.user", + "serviceName": "UserService", + "protoLoadOptions": { + "includeDirs": [ + "node_modules/@restorecommerce/protos/" + ] + } + } }, "customer": { - "address": "localhost:50063", + "address": "localhost:50163", "mock": { "protoPath": "io/restorecommerce/customer.proto", "packageName": "io.restorecommerce.customer", @@ -31,7 +74,7 @@ } }, "shop": { - "address": "localhost:50063", + "address": "localhost:50165", "mock": { "protoPath": "io/restorecommerce/shop.proto", "packageName": "io.restorecommerce.shop", @@ -44,7 +87,7 @@ } }, "organization": { - "address": "localhost:50063", + "address": "localhost:50166", "mock": { "protoPath": "io/restorecommerce/organization.proto", "packageName": "io.restorecommerce.organization", @@ -57,7 +100,7 @@ } }, "contact_point": { - "address": "localhost:50063", + "address": "localhost:50172", "mock": { "protoPath": "io/restorecommerce/contact_point.proto", "packageName": "io.restorecommerce.contact_point", @@ -70,7 +113,7 @@ } }, "address": { - "address": "localhost:50063", + "address": "localhost:50168", "mock": { "protoPath": "io/restorecommerce/address.proto", "packageName": "io.restorecommerce.address", @@ -83,7 +126,7 @@ } }, "country": { - "address": "localhost:50063", + "address": "localhost:50169", "mock": { "protoPath": "io/restorecommerce/country.proto", "packageName": "io.restorecommerce.country", @@ -96,7 +139,7 @@ } }, "tax": { - "address": "localhost:50063", + "address": "localhost:50170", "mock": { "protoPath": "io/restorecommerce/tax.proto", "packageName": "io.restorecommerce.tax", @@ -109,7 +152,7 @@ } }, "product": { - "address": "localhost:50068", + "address": "localhost:50171", "mock": { "protoPath": "io/restorecommerce/product.proto", "packageName": "io.restorecommerce.product", diff --git a/package-lock.json b/package-lock.json index 065b76d..b6ce6fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,14 @@ "name": "@restorecommerce/fulfillments-srv", "version": "0.0.20", "dependencies": { - "@restorecommerce/acs-client": "^1.6.0", + "@restorecommerce/acs-client": "^1.6.6", "@restorecommerce/cart": "^1.0.5", "@restorecommerce/chassis-srv": "^1.6.0", "@restorecommerce/cluster-service": "^1.0.3", "@restorecommerce/grpc-client": "^2.2.1", "@restorecommerce/kafka-client": "^1.2.1", "@restorecommerce/logger": "^1.2.10", - "@restorecommerce/rc-grpc-clients": "^5.1.23", + "@restorecommerce/rc-grpc-clients": "^5.1.27", "@restorecommerce/resource-base-interface": "^1.6.0", "@restorecommerce/service-config": "^1.0.12", "@types/soap": "^0.21.0", @@ -2198,15 +2198,16 @@ } }, "node_modules/@restorecommerce/acs-client": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@restorecommerce/acs-client/-/acs-client-1.6.0.tgz", - "integrity": "sha512-Y7mr8E3+qij6YxP7Nkw6dndMlQtsY76GhTmi+nInVTEpP6o/VKwqCFe7fY8wbP3S+rmnzjmvZBvbnSWGPOvUyw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@restorecommerce/acs-client/-/acs-client-1.6.6.tgz", + "integrity": "sha512-6UfcleChboHqAMoX3GnXymhDAch3zP0qNrDLju31HV1LPtX8vbHfYlpctHmr/+a30GWHgK0a2keHZrHrUZF52g==", "dependencies": { "@restorecommerce/grpc-client": "^2.2.1", - "@restorecommerce/kafka-client": "^1.2.1", + "@restorecommerce/kafka-client": "^1.2.5", "@restorecommerce/logger": "^1.2.10", - "@restorecommerce/rc-grpc-clients": "^5.1.23", + "@restorecommerce/rc-grpc-clients": "^5.1.27", "@restorecommerce/service-config": "^1.0.12", + "deepdash": "^5.3.9", "lodash": "^4.17.21", "nconf": "^0.12.1", "node-eval": "^2.0.0", @@ -2546,12 +2547,12 @@ } }, "node_modules/@restorecommerce/kafka-client": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@restorecommerce/kafka-client/-/kafka-client-1.2.1.tgz", - "integrity": "sha512-BQ7g55hro9hYq3PtTQYdnPzLg7+eW9MiosEUITH2/VjAz/cgWnzLzhavsw283lbsCh/iLgTK//MEe35fvzGF2w==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@restorecommerce/kafka-client/-/kafka-client-1.2.5.tgz", + "integrity": "sha512-tvEhiWsTewnoA9kEFLqE1op+62ItGOKjoV6pF85zisznxin7BYXXcn7zCd3d4JQpao0HHBPuzgILCYRyaIdn9w==", "dependencies": { "@restorecommerce/logger": "^1.2.10", - "@restorecommerce/rc-grpc-clients": "^5.1.23", + "@restorecommerce/rc-grpc-clients": "^5.1.27", "async": "^3.2.5", "cls-rtracer": "^2.6.3", "events": "^3.3.0", @@ -2582,14 +2583,14 @@ } }, "node_modules/@restorecommerce/protos": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@restorecommerce/protos/-/protos-6.8.2.tgz", - "integrity": "sha512-id/ojKw2yRH++fmUabhpBc1jc/RUO2WIfMcvX88AX8u5GSzKElqjsXGKnl9GMs/duiBnATEYrWkeMztZk+5GmQ==" + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@restorecommerce/protos/-/protos-6.8.5.tgz", + "integrity": "sha512-IbFpZFsYBTzYdrntDQ+lThelgsgS9HJkhMgj23IH5U8ewtaHj5jRsC8t3y/sNspQUyGPuvwGjephOsflmzuw7g==" }, "node_modules/@restorecommerce/rc-grpc-clients": { - "version": "5.1.23", - "resolved": "https://registry.npmjs.org/@restorecommerce/rc-grpc-clients/-/rc-grpc-clients-5.1.23.tgz", - "integrity": "sha512-n6bLx55PP86MIa6obzbNvfs1cRUNwu42zAyMNPCIZ+KkO0sYgK2WmQfdB/uhKRCMDOkMM3fCYDVo8yzQw9Kgrg==", + "version": "5.1.27", + "resolved": "https://registry.npmjs.org/@restorecommerce/rc-grpc-clients/-/rc-grpc-clients-5.1.27.tgz", + "integrity": "sha512-zjPI8/CSjV93yDWVei2NiidblleFjQwD7NfgjchKkFc1rgzFJTd7ZHyOqoP6EV5VdbMgc3B3DRodxn0vlt1EtQ==", "dependencies": { "@grpc/grpc-js": "^1.9.11", "@restorecommerce/grpc-client": "^2.2.1", @@ -5563,6 +5564,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepdash": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/deepdash/-/deepdash-5.3.9.tgz", + "integrity": "sha512-GRzJ0q9PDj2T+J2fX+b+TlUa2NlZ11l6vJ8LHNKVGeZ8CfxCuJaCychTq07iDRTvlfO8435jlvVS1QXBrW9kMg==", + "dependencies": { + "lodash": "^4.17.21", + "lodash-es": "^4.17.21" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -9201,9 +9211,7 @@ "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "dev": true, - "peer": true + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "node_modules/lodash.camelcase": { "version": "4.3.0", diff --git a/package.json b/package.json index 63039b6..b294e6f 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,14 @@ }, "type": "module", "dependencies": { - "@restorecommerce/acs-client": "^1.6.0", + "@restorecommerce/acs-client": "^1.6.6", "@restorecommerce/cart": "^1.0.5", "@restorecommerce/chassis-srv": "^1.6.0", "@restorecommerce/cluster-service": "^1.0.3", "@restorecommerce/grpc-client": "^2.2.1", "@restorecommerce/kafka-client": "^1.2.1", "@restorecommerce/logger": "^1.2.10", - "@restorecommerce/rc-grpc-clients": "^5.1.23", + "@restorecommerce/rc-grpc-clients": "^5.1.27", "@restorecommerce/resource-base-interface": "^1.6.0", "@restorecommerce/service-config": "^1.0.12", "@types/soap": "^0.21.0", diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 77efdeb..0000000 --- a/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './utils.js'; -export * from './stub.js'; - -// Register Stubs here! -export { DHL_Soap as DHL } from './stubs/dhl_soap'; \ No newline at end of file diff --git a/src/services/fulfillment.ts b/src/services/fulfillment.ts index 4d3bff2..c863977 100644 --- a/src/services/fulfillment.ts +++ b/src/services/fulfillment.ts @@ -12,7 +12,7 @@ import { DefaultResourceFactory, injects_meta_data } from '@restorecommerce/acs-client'; -import { database } from '@restorecommerce/chassis-srv'; +import { type DatabaseProvider } from '@restorecommerce/chassis-srv'; import { Topic } from '@restorecommerce/kafka-client'; import { DeleteRequest, @@ -34,7 +34,7 @@ import { createClient, } from '@restorecommerce/grpc-client'; import { - State, + FulfillmentState, FulfillmentListResponse, Fulfillment, FulfillmentList, @@ -57,9 +57,18 @@ import { ContactPointResponse, ContactPointServiceDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/contact_point.js'; -import { Customer, CustomerServiceDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/customer.js'; -import { Shop, ShopServiceDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/shop.js'; -import { Organization, OrganizationServiceDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/organization.js'; +import { + Customer, + CustomerServiceDefinition +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/customer.js'; +import { + Shop, + ShopServiceDefinition +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/shop.js'; +import { + Organization, + OrganizationServiceDefinition +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/organization.js'; import { VAT } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/amount.js'; import { FulfillmentProduct } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment_product.js'; import { FulfillmentCourierService } from './fulfillment_courier.js'; @@ -80,6 +89,7 @@ import { } from './../utils.js'; import { Stub } from './../stub.js'; + @access_controlled_service export class FulfillmentService extends ServiceBase @@ -87,10 +97,10 @@ export class FulfillmentService { private static async ACSContextFactory( self: FulfillmentService, - request: FulfillmentList & FulfillmentIdList & FulfillmentInvoiceRequestList, + request: FulfillmentList & FulfillmentIdList & FulfillmentInvoiceRequestList & DeleteRequest, context: any, ): Promise { - const ids = request.items?.map((item: any) => item.id); + const ids = request.ids ?? request.items?.map((item: any) => item.id); const resources = await self.getFulfillmentsByIds(ids, request.subject, context); return { ...context, @@ -143,6 +153,10 @@ export class FulfillmentService code: 500, message: 'Query limit 1000 exhausted!', }, + TIMEOUT: { + code: 500, + message: 'Request timeout, API not responding!', + }, }; protected readonly emitters: any; @@ -159,7 +173,7 @@ export class FulfillmentService readonly fulfillmentCourierSrv: FulfillmentCourierService, readonly fulfillmentProductSrv: FulfillmentProductService, readonly topic: Topic, - readonly db: database.DatabaseProvider, + readonly db: DatabaseProvider, readonly cfg: any, readonly logger: any, ) { @@ -197,7 +211,7 @@ export class FulfillmentService logger } as GrpcClientConfig, CustomerServiceDefinition, - createChannel(cfg.get('client:customer').address) + createChannel(cfg.get('client:customer:address')) ); this.shop_service = createClient( @@ -206,7 +220,7 @@ export class FulfillmentService logger } as GrpcClientConfig, ShopServiceDefinition, - createChannel(cfg.get('client:shop').address) + createChannel(cfg.get('client:shop:address')) ); this.organization_service = createClient( @@ -215,7 +229,7 @@ export class FulfillmentService logger } as GrpcClientConfig, OrganizationServiceDefinition, - createChannel(cfg.get('client:organization').address) + createChannel(cfg.get('client:organization:address')) ); this.contact_point_service = createClient( @@ -224,7 +238,7 @@ export class FulfillmentService logger } as GrpcClientConfig, ContactPointServiceDefinition, - createChannel(cfg.get('client:contact_point').address) + createChannel(cfg.get('client:contact_point:address')) ); this.address_service = createClient( @@ -233,7 +247,7 @@ export class FulfillmentService logger } as GrpcClientConfig, AddressServiceDefinition, - createChannel(cfg.get('client:address').address) + createChannel(cfg.get('client:address:address')) ); this.country_service = createClient( @@ -242,7 +256,7 @@ export class FulfillmentService logger } as GrpcClientConfig, CountryServiceDefinition, - createChannel(cfg.get('client:country').address) + createChannel(cfg.get('client:country:address')) ); this.tax_service = createClient( @@ -251,7 +265,7 @@ export class FulfillmentService logger } as GrpcClientConfig, TaxServiceDefinition, - createChannel(cfg.get('client:tax').address) + createChannel(cfg.get('client:tax:address')) ); } @@ -360,7 +374,7 @@ export class FulfillmentService throw { code: 500, message: 'Query for fulfillments exceeds limit of 1000!' - } as OperationStatus + } as OperationStatus; } const request = ReadRequest.fromPartial({ @@ -590,7 +604,7 @@ export class FulfillmentService customer_country.payload, !!customer.payload.private?.user_id, ) - ) + ); const gross = price.sale ? price.sale_price : price.regular_price; const vats = taxes.map((tax): VAT => ({ tax_id: tax.id, @@ -604,7 +618,7 @@ export class FulfillmentService gross, net, vats, - } + }; } ); } @@ -666,8 +680,8 @@ export class FulfillmentService ) { request?.items?.forEach( item => { - if (!item.state || item.state === State.UNRECOGNIZED) { - item.state = State.CREATED; + if (!item.fulfillment_state || item.fulfillment_state === FulfillmentState.UNRECOGNIZED) { + item.fulfillment_state = FulfillmentState.PENDING; } } ); @@ -762,7 +776,7 @@ export class FulfillmentService if (f.status?.code !== 200) { return false; } - else if (StateRank[f.payload?.state] >= StateRank[State.SUBMITTED]) { + else if (StateRank[f.payload?.fulfillment_state] >= StateRank[FulfillmentState.SUBMITTED]) { f.status = createStatusCode( this.name, f.payload?.id, @@ -774,7 +788,7 @@ export class FulfillmentService } ); const invalids = flattened.filter( - f => f.status?.code !== 200 || StateRank[f.payload?.state] >= StateRank[State.SUBMITTED] + f => f.status?.code !== 200 || StateRank[f.payload?.fulfillment_state] >= StateRank[FulfillmentState.SUBMITTED] ); const responses = await Stub.submit(valids); const merged = mergeFulfillments([ @@ -794,13 +808,13 @@ export class FulfillmentService }, context); upsert_results.items.forEach(item => { - if (item.payload.state in this.emitters) { - switch (item.payload.state) { - case State.INVALID: - case State.FAILED: - this.topic.emit(this.emitters[item.payload.state], item); + if (item.payload.fulfillment_state in this.emitters) { + switch (item.payload.fulfillment_state) { + case FulfillmentState.INVALID: + case FulfillmentState.FAILED: + this.topic.emit(this.emitters[item.payload.fulfillment_state], item); default: - this.topic.emit(this.emitters[item.payload.state], item.payload); + this.topic.emit(this.emitters[item.payload.fulfillment_state], item.payload); break; } } @@ -877,7 +891,7 @@ export class FulfillmentService aggregated.filter( item => { response_map[item.payload?.id ?? item.status?.id] = item as FulfillmentResponse; - return item.status?.code === 200 + return item.status?.code === 200; } ) ).filter( @@ -900,8 +914,8 @@ export class FulfillmentService } switch (f.label.state) { - case State.SUBMITTED: - case State.IN_TRANSIT: + case FulfillmentState.SUBMITTED: + case FulfillmentState.IN_TRANSIT: return true; default: f.label.status = createStatusCode( @@ -942,13 +956,13 @@ export class FulfillmentService updates => updates.items.forEach( item => { response_map[item.payload?.id ?? item.status?.id] = item as FulfillmentResponse; - if (item.payload.state in this.emitters) { - switch (item.payload.state) { - case State.INVALID: - case State.FAILED: - this.topic.emit(this.emitters[item.payload.state], item); + if (item.payload.fulfillment_state in this.emitters) { + switch (item.payload.fulfillment_state) { + case FulfillmentState.INVALID: + case FulfillmentState.FAILED: + this.topic.emit(this.emitters[item.payload.fulfillment_state], item); default: - this.topic.emit(this.emitters[item.payload.state], item.payload); + this.topic.emit(this.emitters[item.payload.fulfillment_state], item.payload); break; } } @@ -965,7 +979,7 @@ export class FulfillmentService this.name, this.operation_status_codes.SUCCESS, ), - } + }; } catch (e) { return this.handleOperationError(e); @@ -1025,25 +1039,25 @@ export class FulfillmentService const id = f.status?.id; if (!f.payload?.labels?.length) { f.status = { - id: id, + id, code: 400, message: `Fulfillment ${id} has no labels!` - } + }; } - else if (f.payload?.state !== State.SUBMITTED && f.payload?.state !== State.IN_TRANSIT) { + else if (f.payload?.fulfillment_state !== FulfillmentState.SUBMITTED && f.payload?.fulfillment_state !== FulfillmentState.IN_TRANSIT) { f.status = { - id: id, + id, code: 400, message: `For canceling Fulfillment ${ id - } is expected to be ${ - State.SUBMITTED - } or ${ - State.IN_TRANSIT - } but is ${ - f.payload?.state - }!` - } + } is expected to be ${ + FulfillmentState.SUBMITTED + } or ${ + FulfillmentState.IN_TRANSIT + } but is ${ + f.payload?.fulfillment_state + }!` + }; } return f; @@ -1061,7 +1075,7 @@ export class FulfillmentService !shipment_numbers?.length || shipment_numbers.find( s => s === f.payload?.labels[0]?.shipment_number - ) + ); } )); @@ -1081,13 +1095,13 @@ export class FulfillmentService }, context); update_results.items.forEach(item => { - if (item.payload.state in this.emitters) { - switch (item.payload.state) { - case State.INVALID: - case State.FAILED: - this.topic.emit(this.emitters[item.payload.state], item); + if (item.payload.fulfillment_state in this.emitters) { + switch (item.payload.fulfillment_state) { + case FulfillmentState.INVALID: + case FulfillmentState.FAILED: + this.topic.emit(this.emitters[item.payload.fulfillment_state], item); default: - this.topic.emit(this.emitters[item.payload.state], item.payload); + this.topic.emit(this.emitters[item.payload.fulfillment_state], item.payload); break; } } diff --git a/src/services/fulfillment_courier.ts b/src/services/fulfillment_courier.ts index c238842..a572b3f 100644 --- a/src/services/fulfillment_courier.ts +++ b/src/services/fulfillment_courier.ts @@ -72,7 +72,7 @@ export class FulfillmentCourierService throw { code: 500, message: 'Query for fulfillments exceeds limit of 1000!' - } as OperationStatus + } as OperationStatus; } const request = ReadRequest.fromPartial({ diff --git a/src/services/fulfillment_product.ts b/src/services/fulfillment_product.ts index 4bf4405..573daab 100644 --- a/src/services/fulfillment_product.ts +++ b/src/services/fulfillment_product.ts @@ -1,4 +1,4 @@ -import { randomUUID } from 'crypto'; +import { randomUUID } from 'node:crypto'; import { createClient, createChannel, @@ -18,8 +18,7 @@ import { injects_meta_data } from '@restorecommerce/acs-client'; import { Topic } from '@restorecommerce/kafka-client'; -import { DeepPartial } from '@restorecommerce/kafka-client/lib/protos.js'; -import { +import { DeleteRequest, ReadRequest } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base.js'; @@ -32,7 +31,7 @@ import { Courier as Packer, Offer } from '@restorecommerce/cart/lib/model/impl/C import { Container } from '@restorecommerce/cart/lib/model/impl/bin/Container.js'; import { IItem } from '@restorecommerce/cart/lib/model/IItem.js'; import { Subject } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/auth.js'; -import { +import { Amount, VAT } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/amount.js'; @@ -68,7 +67,6 @@ import { import { FulfillmentCourierService } from './index.js'; import { CRUDClient, - Courier, Payload, Response, ResponseList, @@ -119,8 +117,8 @@ const countItems = (goods: Item[], container: Container) => { }; @access_controlled_service -export class FulfillmentProductService - extends ServiceBase +export class FulfillmentProductService + extends ServiceBase implements FulfillmentProductServiceImplementation { private static async ACSContextFactory( @@ -171,6 +169,10 @@ export class FulfillmentProductService code: 500, message: 'Query limit 1000 exhausted!', }, + COURIERS_NOT_FOUND: { + code: 404, + message: 'Couriers not found!', + } }; protected readonly legal_address_type_id: string; @@ -257,7 +259,7 @@ export class FulfillmentProductService AddressServiceDefinition, createChannel(cfg.get('client:address').address) ); - + this.country_service = createClient( { ...cfg.get('client:country'), @@ -394,7 +396,7 @@ export class FulfillmentProductService ); } - async getById(map: { [id:string]: T }, id: string): Promise { + async getById(map: { [id: string]: T }, id: string): Promise { if (id in map) { return map[id]; } @@ -407,23 +409,23 @@ export class FulfillmentProductService } } - async getByIds(map: { [id:string]: T }, ids: string[]): Promise { + async getByIds(map: { [id: string]: T }, ids: string[]): Promise { return Promise.all(ids.map( id => this.getById(map, id) )); } - protected getFulfillmentProductsByIds( + protected async getFulfillmentProductsByIds( ids: string[], subject?: Subject, context?: any, - ): Promise> { + ): Promise { ids = [...new Set(ids).values()]; if (ids.length > 1000) { throw { code: 500, message: 'Query for fulfillmentProducts exceeds limit of 1000!' - } as OperationStatus + } as OperationStatus; } const request = ReadRequest.fromPartial({ @@ -437,14 +439,23 @@ export class FulfillmentProductService }], subject }); - return super.read(request, context); + return await super.read(request, context).then( + resp => { + if (resp.operation_status?.code !== 200) { + throw resp.operation_status; + } + else { + return resp; + } + } + ); } protected async findCouriers( queries: PackageSolutionTotals[], subject?: Subject, context?: any, - ): Promise> { + ): Promise { const call = ReadRequest.fromPartial({ filters: [{ filters: queries.flatMap( @@ -460,21 +471,29 @@ export class FulfillmentProductService }], subject, }); - return this.courier_srv.read(call, context); + return await this.courier_srv.read(call, context).then( + resp => { + if (resp.operation_status?.code !== 200) { + throw resp.operation_status; + } + else { + return resp; + } + } + ); } protected async findFulfillmentProducts( queries: PackageSolutionTotals[], - stubs?: Stub[], subject?: Subject, context?: any, ): Promise { - stubs = stubs || await this.findCouriers( + const stubs = await this.findCouriers( queries, subject, context, ).then( - (resp: FulfillmentCourierListResponse) => resp.items.map( + (resp: FulfillmentCourierListResponse) => resp.items?.map( item => Stub.getInstance( item.payload, { @@ -482,9 +501,15 @@ export class FulfillmentProductService logger: this.logger } ) + ).filter( + s => !!s ) ); + if (!stubs?.length) { + throw this.operation_status_codes.COURIERS_NOT_FOUND; + } + const call = ReadRequest.fromPartial({ filters: [{ filters: [{ @@ -498,7 +523,16 @@ export class FulfillmentProductService subject, }); - return super.read(call, context); + return await super.read(call, context).then( + resp => { + if (resp.operation_status?.code !== 200) { + throw resp.operation_status; + } + else { + return resp; + } + } + ); } @access_controlled_function({ @@ -609,7 +643,7 @@ export class FulfillmentProductService request.subject, context, ); - + const address_map = await this.get
( Object.values(contact_point_map).map( item => item.payload?.physical_address_id @@ -628,25 +662,8 @@ export class FulfillmentProductService context, ); - const stubs = await this.findCouriers( - queries, - request.subject, - context, - ).then( - resp => resp.items.map( - item => Stub.getInstance( - item.payload as Courier, - { - cfg: this.cfg, - logger: this.logger - } - ) - ) - ); - const product_map = await this.findFulfillmentProducts( queries, - stubs, request.subject, context, ).then( @@ -696,12 +713,12 @@ export class FulfillmentProductService price: 0.0, // placeholder taxType: 'vat_standard' // placeholder })); - + const packer = new Packer({ source: JSON.stringify({ zones: [] }), shipping: null }); - + const shop_country = await this.getById( shop_map, query.shop_id @@ -733,12 +750,12 @@ export class FulfillmentProductService ).then( address => this.getById(country_map, address.payload.country_id) ); - + const customer = await this.getById( customer_map, query.customer_id ); - + const customer_country = await this.getByIds( contact_point_map, [ @@ -767,7 +784,7 @@ export class FulfillmentProductService address.payload.country_id ) ); - + const solutions: PackingSolution[] = offer_lists.map( offers => packer.canFit(offers, goods) ).map( @@ -788,14 +805,14 @@ export class FulfillmentProductService customer_country.payload, !!customer.payload.private?.user_id, ) - ) + ); const gross = price.sale ? price.sale_price : price.regular_price; const vats = taxes.map((tax): VAT => ({ tax_id: tax.id, vat: gross * tax.rate, })); const net = vats.reduce((a, b) => a + b.vat, gross); - + return { id: randomUUID(), product_id, @@ -817,9 +834,9 @@ export class FulfillmentProductService net, vats, } - } + }; }); - + const amounts = Object.values( parcels.reduce( (a: { [key: string]: Amount }, b) => { @@ -837,7 +854,7 @@ export class FulfillmentProductService {} ) ); - + amounts.forEach(amount => { amount.vats = Object.values(amount.vats?.reduce( (a: { [id: string]: VAT }, b) => { @@ -849,9 +866,9 @@ export class FulfillmentProductService return a; }, {} - )) + )); }); - + return { parcels, amounts, @@ -859,10 +876,10 @@ export class FulfillmentProductService homogeneity: 1, score: 1, reference: query.reference, - } + }; } ); - + const solution: PackingSolutionResponse = { solutions, status: { @@ -877,11 +894,10 @@ export class FulfillmentProductService }` } }; - + return solution; } - catch (e) { - console.error(e); + catch (e: any) { const solution: PackingSolutionResponse = { solutions: [], status: this.catchStatusError( @@ -891,7 +907,7 @@ export class FulfillmentProductService }; return solution; } - }); + }); const items = await Promise.all(promises); return { @@ -903,9 +919,8 @@ export class FulfillmentProductService ) }; } - catch (e) { - console.error(e); - this.catchOperationError(e); + catch (e: any) { + return this.catchOperationError(e); } } diff --git a/src/stub.ts b/src/stub.ts index 8ca9dff..dd56d2d 100644 --- a/src/stub.ts +++ b/src/stub.ts @@ -1,9 +1,9 @@ import { Logger } from 'winston'; import { Status } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/status.js'; import { - Courier, - FlatAggregatedFulfillment, - extractCouriers, + Courier, + FlatAggregatedFulfillment, + extractCouriers, } from './utils.js'; export abstract class Stub @@ -22,7 +22,7 @@ export abstract class Stub ) {} public abstract getTariffCode(fulfillment: FlatAggregatedFulfillment): Promise; - + protected abstract evaluateImpl (fulfillments: FlatAggregatedFulfillment[]): Promise; protected abstract submitImpl (fulfillments: FlatAggregatedFulfillment[]): Promise; protected abstract trackImpl (fulfillments: FlatAggregatedFulfillment[]): Promise; @@ -83,7 +83,7 @@ export abstract class Stub } } as T; } - + public readonly evaluate = (fulfillments: FlatAggregatedFulfillment[]) => this.evaluateImpl( fulfillments.filter(f => f.product?.courier_id === this.courier.id) ); @@ -108,7 +108,7 @@ export abstract class Stub fulfillments: FlatAggregatedFulfillment[], kwargs?: any ) { - return Promise.all(Object.values(extractCouriers(fulfillments)).map( + return await Promise.all(Object.values(extractCouriers(fulfillments)).map( (courier) => Stub.getInstance( courier, { @@ -128,7 +128,7 @@ export abstract class Stub fulfillments: FlatAggregatedFulfillment[], kwargs?: any ) { - return Promise.all(Object.values(extractCouriers(fulfillments)).map( + return await Promise.all(Object.values(extractCouriers(fulfillments)).map( (courier) => Stub.getInstance( courier, { @@ -148,7 +148,7 @@ export abstract class Stub fulfillments: FlatAggregatedFulfillment[], kwargs?: any ) { - return Promise.all(Object.values(extractCouriers(fulfillments)).map( + return await Promise.all(Object.values(extractCouriers(fulfillments)).map( (courier) => Stub.getInstance( courier, { @@ -168,7 +168,7 @@ export abstract class Stub fulfillments: FlatAggregatedFulfillment[], kwargs?: any ) { - return Promise.all(Object.values(extractCouriers(fulfillments)).map( + return await Promise.all(Object.values(extractCouriers(fulfillments)).map( (courier) => Stub.getInstance( courier, { @@ -190,7 +190,7 @@ export abstract class Stub ) { Stub.STUB_TYPES[typeName] = type; } - + public static getInstance(courier: Courier, kwargs?: { [key: string]: any }): Stub { let stub = Stub.REGISTER[courier.id]; diff --git a/src/stubs/dhl_soap.ts b/src/stubs/dhl_soap.ts index 39bbb55..fa8e7f9 100644 --- a/src/stubs/dhl_soap.ts +++ b/src/stubs/dhl_soap.ts @@ -5,7 +5,7 @@ import customParseFormat from 'dayjs/plugin/customParseFormat.js'; import { Event, Label, - State, + FulfillmentState, Tracking, } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment.js'; import { @@ -13,678 +13,681 @@ import { } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment_courier.js'; import { OperationStatus, Status } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/status.js'; import { - Stub, FlatAggregatedFulfillment, throwOperationStatusCode, -} from '../index.js'; +} from '../utils.js'; +import { Stub } from '../stub.js'; dayjs.extend(customParseFormat); -export namespace DHL_Soap +type ClientMap = { [id: string]: soap.Client }; + +interface Origin { - export type ClientMap = { [id: string]: soap.Client }; + country: string; + countryISOCode: string; +} - interface Origin - { - country: string; - countryISOCode: string; - } +interface Communication +{ + phone?: string; + email?: string; + contactPerson?: string; +} - interface Communication - { - phone?: string; - email?: string; - contactPerson?: string; - } +interface Shipper +{ + Name: { + name1: string; + name2?: string; + name3?: string; + }; + Address: { + streetName: string; + streetNumber: string; + zip: string; + city: string; + Origin: Origin; + }; + Communication: Communication; +} - interface Shipper - { - Name: { - name1: string; - name2?: string; - name3?: string; - }; - Address: { - streetName: string; - streetNumber: string; - zip: string; - city: string; - Origin: Origin; - }; - Communication: Communication; - } +interface Receiver +{ + name1: string; + Address: { + name2?: string; + name3?: string; + streetName: string; + streetNumber: string; + zip: string; + city: string; + Origin: Origin; + }; + Communication: Communication; +} - interface Receiver - { - name1: string; - Address: { - name2?: string; - name3?: string; - streetName: string; - streetNumber: string; - zip: string; - city: string; - Origin: Origin; - }; - Communication: Communication; - } +interface ShipmentItem +{ + weightInKG: number; + lengthInCM: number; + widthInCM: number; + heightInCM: number; +} - interface ShipmentItem - { - weightInKG: number; - lengthInCM: number; - widthInCM: number; - heightInCM: number; - } +interface ShipmentDetails +{ + shipmentDate: string; + product: string; + accountNumber: string; + ShipmentItem: ShipmentItem; + customerReference?: string; + costCenter?: string; + Service?: any; + Notification?: { + recipientEmailAddress?: string; + }; +} - interface ShipmentDetails - { - shipmentDate: string; - product: string; - accountNumber: string; - ShipmentItem: ShipmentItem; - customerReference?: string; - costCenter?: string; - Service?: any; - Notification?: { - recipientEmailAddress?: string; +interface Shipment +{ + ShipmentDetails: ShipmentDetails; + Shipper: Shipper; + Receiver: Receiver; + PrintOnlyIfCodeable?: { + attributes: { + active: 1; }; - } + }; +} - interface Shipment - { - ShipmentDetails: ShipmentDetails; - Shipper: Shipper; - Receiver: Receiver; - PrintOnlyIfCodeable?: { - attributes: { - active: 1; - }; - }; - } +interface ShipmentOrder +{ + sequenceNumber?: number; + Shipment: Shipment; +} - interface ShipmentOrder - { - sequenceNumber?: number; - Shipment: Shipment; - } +interface ShipmentOrderRequest +{ + Version: { + majorRelease: number; + minorRelease: number; + }; + ShipmentOrder: ShipmentOrder[]; +} - interface ShipmentOrderRequest - { - Version: { - majorRelease: number; - minorRelease: number; +interface ShipmentCancelRequest +{ + Version: { + majorRelease: number; + minorRelease: number; + }; + shipmentNumber: string[]; +} + +const parseService = (attributes: any[]) => attributes.filter((att: any) => + att.id.startsWith('urn:restorecommerce:fufs:names:service:attr:dhl') +).map((att: any) => ({ + [att.value]: { + attributes: Object.assign({}, ...att.attribute.map((att: any) => ({ + [att.id]: att.value + })))} +})); + +const DHLEvent2FulfillmentEvent = (attributes: any): Event => ({ + timestamp: dayjs(attributes['event-timestamp'], 'DD.MM.YYYY HH:mm').toDate(), + location: attributes['event-location'], + details: { + type_url: null, + value: Buffer.from(JSON.stringify(attributes)), + }, + status: { + id: attributes['event-short-status'], + code: attributes['standard-event-code'], + message: attributes['event-text'] + } +}); + +const DHLTracking2FulfillmentTracking = async ( + fulfillment: FlatAggregatedFulfillment, + response: Response, + error?: any +): Promise => { + if (error) { + fulfillment.tracking = { + shipment_number: fulfillment.label.shipment_number, + events: null, + details: null, + status: { + id: fulfillment.payload.id, + code: error?.code ?? 500, + message: error?.message ?? error.details ?? error?.msg ?? JSON.stringify(error, null, 2) + } }; - ShipmentOrder: ShipmentOrder[]; } - - interface ShipmentCancelRequest - { - Version: { - majorRelease: number; - minorRelease: number; + else if (response?.status !== 200) { + fulfillment.tracking = { + shipment_number: fulfillment.label.shipment_number, + events: null, + details: null, + status: { + id: fulfillment.payload.id, + code: response?.status ?? 500, + message: await response.text().catch(() => response?.statusText) ?? 'Unknown Error!', + } }; - shipmentNumber: string[]; } - - const parseService = (attributes: any[]) => attributes.filter((att: any) => - att.id.startsWith('urn:restorecommerce:fufs:names:service:attr:dhl') - ).map((att: any) => ({ - [att.value]: { - attributes: Object.assign({}, ...att.attribute.map((att: any) => ({ - [att.id]: att.value - })))} - })); - - const DHLEvent2FulfillmentEvent = (attributes: any): Event => ({ - timestamp: dayjs(attributes['event-timestamp'], 'DD.MM.YYYY HH:mm').toDate(), - location: attributes['event-location'], - details: { - type_url: null, - value: Buffer.from(JSON.stringify(attributes)), - }, - status: { - id: attributes['event-short-status'], - code: attributes['standard-event-code'], - message: attributes['event-text'] - } - }); - - const DHLTracking2FulfillmentTracking = async ( - fulfillment: FlatAggregatedFulfillment, - response: Response, - error?: any - ): Promise => { - if (error) { - fulfillment.tracking = { - shipment_number: fulfillment.label.shipment_number, - events: null, - details: null, - status: { - id: fulfillment.payload.id, - code: error?.code ?? 500, - message: error?.message ?? error.details ?? error?.msg ?? JSON.stringify(error, null, 2) - } - }; - } - else if (response?.status !== 200) { - fulfillment.tracking = { - shipment_number: fulfillment.label.shipment_number, - events: null, - details: null, - status: { - id: fulfillment.payload.id, - code: response?.status ?? 500, - message: await response.text().catch(() => response?.statusText) ?? 'Unknown Error!', + else { + fulfillment.tracking = await response.text().then( + (text: string): Tracking => { + const response = xml2js(text); + if (response?.elements?.[0]?.attributes?.code === '0') { + const status = { + id: fulfillment.payload.id, + code: 200, + message: response.elements[0].attributes.error ?? response.elements[0].elements[0].attributes.status + }; + fulfillment.label.state = response.elements[0].elements[0].attributes['delivery-event-flag'] ? FulfillmentState.COMPLETED : FulfillmentState.IN_TRANSIT; + fulfillment.label.status = status; + fulfillment.payload.fulfillment_state = fulfillment.label.state; + return { + shipment_number: fulfillment.label.shipment_number, + events: response.elements[0].elements[0].elements[0].elements.map((element: any) => DHLEvent2FulfillmentEvent(element.attributes)), + details: { + type_url: null, + value: Buffer.from(JSON.stringify(response.elements[0].elements[0].attributes)) + }, + status + }; } - }; - } - else { - fulfillment.tracking = await response.text().then( - (text: string): Tracking => { - const response = xml2js(text); - if (response?.elements?.[0]?.attributes?.code === '0') { - const status = { - id: fulfillment.payload.id, - code: 200, - message: response.elements[0].attributes.error ?? response.elements[0].elements[0].attributes.status - }; - fulfillment.label.state = response.elements[0].elements[0].attributes['delivery-event-flag'] ? State.FULFILLED : State.IN_TRANSIT; - fulfillment.label.status = status; - fulfillment.payload.state = fulfillment.label.state; - return { - shipment_number: fulfillment.label.shipment_number, - events: response.elements[0].elements[0].elements[0].elements.map((element: any) => DHLEvent2FulfillmentEvent(element.attributes)), - details: { - type_url: null, - value: Buffer.from(JSON.stringify(response.elements[0].elements[0].attributes)) - }, - status - }; - } - else { - return { - shipment_number: fulfillment.label.shipment_number, - events: null, - details: null, - status: { - id: fulfillment.payload.id, - code: response?.elements?.[0]?.attributes?.code ?? 500, - message: response?.elements?.[0]?.attributes?.error ?? 'Error Unknown!' - } - }; - } - }, - (err: any): Tracking => { + else { return { shipment_number: fulfillment.label.shipment_number, events: null, details: null, status: { id: fulfillment.payload.id, - code: err?.code ?? 500, - message: err?.message ?? err?.msg ?? err?.details ?? JSON.stringify(err, null, 2) + code: response?.elements?.[0]?.attributes?.code ?? 500, + message: response?.elements?.[0]?.attributes?.error ?? 'Error Unknown!' } }; } - ); - } - - fulfillment.status = fulfillment.tracking.status; - return fulfillment; - }; - - const DHLShipmentCancelResponse2AggregatedFulfillment = ( - fulfillment_map: {[k: string]: FlatAggregatedFulfillment}, - response: any, - err?: any - ): FlatAggregatedFulfillment[] => { - if (err) { - return Object.values(fulfillment_map).map(fulfillment => { - const status = { - id: fulfillment.payload.id, - code: err.code ?? err.statusCode ?? 500, - message: err.message ?? err.statusText ?? JSON.stringify(err) + }, + (err: any): Tracking => { + return { + shipment_number: fulfillment.label.shipment_number, + events: null, + details: null, + status: { + id: fulfillment.payload.id, + code: err?.code ?? 500, + message: err?.message ?? err?.msg ?? err?.details ?? JSON.stringify(err, null, 2) + } }; - fulfillment.label.status = status; - fulfillment.status = status; - return fulfillment; - }); - } + } + ); + } - return response.DeletionState.map((state: any) => { - const fulfillment = fulfillment_map[state.shipmentNumber]; - fulfillment.label.state = state.Status.statusCode == 0 ? State.CANCELLED : fulfillment.label.state; + fulfillment.status = fulfillment.tracking.status; + return fulfillment; +}; + +const DHLShipmentCancelResponse2AggregatedFulfillment = ( + fulfillment_map: {[k: string]: FlatAggregatedFulfillment}, + response: any, + err?: any +): FlatAggregatedFulfillment[] => { + if (err) { + return Object.values(fulfillment_map).map(fulfillment => { const status = { id: fulfillment.payload.id, - code: state.Status.statusCode, - message: state.Status.statusText + code: err.code ?? err.statusCode ?? 500, + message: err.message ?? err.statusText ?? JSON.stringify(err) }; fulfillment.label.status = status; fulfillment.status = status; return fulfillment; }); + } + + return response.DeletionFulfillmentState.map((state: any) => { + const fulfillment = fulfillment_map[state.shipmentNumber]; + fulfillment.label.state = state.Status.statusCode == 0 ? FulfillmentState.CANCELLED : fulfillment.label.state; + const status = { + id: fulfillment.payload.id, + code: state.Status.statusCode, + message: state.Status.statusText + }; + fulfillment.label.status = status; + fulfillment.status = status; + return fulfillment; + }); +}; + +class DHLSoap extends Stub { + protected static _clients: ClientMap = {}; + protected readonly stub_defaults: any; + public readonly version: number[]; + + protected readonly status_codes: { [key: string]: Status } = { + OK: { + id: '', + code: 200, + message: 'OK', + }, + NOT_FOUND: { + id: '', + code: 404, + message: '{entity} {id} not found! {details}', + }, + INVALID_ADDRESS: { + id: '', + code: 400, + message: '{entity} {id} address invalid! {details}', + }, + UNKNOWN_ERROR: { + id: '', + code: 500, + message: '{entity} {id} caused unknown error! {details}', + }, }; - class DHLSoapStub extends Stub { - protected static _clients: ClientMap = {}; - protected readonly stub_defaults: any; - public readonly version: number[]; + protected readonly operation_status_codes: { [key: string]: OperationStatus } = { + SUCCESS: { + code: 200, + message: 'SUCCESS', + }, + PARTIAL: { + code: 400, + message: 'Patrial executed with errors!', + }, + LIMIT_EXHAUSTED: { + code: 500, + message: 'Query limit 1000 exhausted!', + }, + TIMEOUT: { + code: 500, + message: 'Request timeout, DHL not responding!', + }, + }; - protected readonly status_codes: { [key: string]: Status } = { - OK: { - id: '', - code: 200, - message: 'OK', - }, - NOT_FOUND: { - id: '', - code: 404, - message: '{entity} {id} not found! {details}', - }, - INVALID_ADDRESS: { - id: '', - code: 400, - message: '{entity} {id} address invalid! {details}', - }, - UNKNOWN_ERROR: { - id: '', - code: 500, - message: '{entity} {id} caused unknown error! {details}', - }, - }; - - protected readonly operation_status_codes: { [key: string]: OperationStatus } = { - SUCCESS: { - code: 200, - message: 'SUCCESS', - }, - PARTIAL: { - code: 400, - message: 'Patrial executed with errors!', - }, - LIMIT_EXHAUSTED: { - code: 500, - message: 'Query limit 1000 exhausted!', - }, - }; + get type(): string { + return this.constructor.name; // 'DHLSoap'; + } - get type(): string { - return this.constructor.name; //'DHLSoapStub'; - } + get clients(): ClientMap { + return DHLSoap._clients; + } - get clients(): ClientMap { - return DHLSoapStub._clients; - } + constructor(courier?: Courier, kwargs?: { [key: string]: any }) { + super(courier, kwargs?.cfg, kwargs?.logger); + this.stub_defaults = this.cfg?.get(`stubs:DHLSoap:${courier?.id}`) ?? this.cfg?.get('stubs:DHLSoap:defaults'); + this.version = this.stub_defaults?.ordering?.version ?? [3, 4, 0]; - constructor(courier?: Courier, kwargs?: { [key: string]: any }) { - super(courier, kwargs?.cfg, kwargs?.logger); - this.stub_defaults = this.cfg?.get(`stubs:DHLSoapStub:${courier?.id}`) ?? this.cfg?.get('stubs:DHLSoapStub:defaults'); - this.version = this.stub_defaults?.ordering?.version ?? [3, 4, 0]; + this.status_codes = { + ...this.status_codes, + ...this.cfg?.get('statusCodes'), + }; - this.status_codes = { - ...this.status_codes, - ...this.cfg?.get('statusCodes'), - }; - - this.operation_status_codes = { - ...this.operation_status_codes, - ...this.cfg?.get('operationStatusCodes'), - }; + this.operation_status_codes = { + ...this.operation_status_codes, + ...this.cfg?.get('operationStatusCodes'), + }; + } + + protected DHLCode2StatusCode( + code: number, + id: string, + details?: string + ) { + switch (code) { + case 0: return this.createStatusCode( + 'Fulfillment', + id, + this.status_codes.OK, + details, + ); + default: return this.createStatusCode( + 'Fulfillment', + id, + this.status_codes.UNKOWN_ERROR, + details, + ); } + } - protected DHLCode2StatusCode( - code: number, - id: string, - details?: string - ) { - switch (code) { - case 0: return this.createStatusCode( - 'Fulfillment', - id, - this.status_codes.OK, - details, - ); - default: return this.createStatusCode( + protected DHLShipmentLabels2FulfillmentResponses ( + fulfillments: FlatAggregatedFulfillment[], + response: any, + error: any, + ): FlatAggregatedFulfillment[] { + if (error) { + if (response?.html) { + this.logger?.error(`${this.constructor.name}: ${response.html.head.title}`); + throwOperationStatusCode( 'Fulfillment', - id, - this.status_codes.UNKOWN_ERROR, - details, + { + code: response.statusCode, + message: response.html.head.title + } ); } - } - - protected DHLShipmentLabels2FulfillmentResponses ( - 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 - } - ); - } - else { - const message = error?.root?.Envelope?.Body?.Fault?.faultstring - ?? error?.response?.statusText - ?? error?.toString() - ?? 'Server Error!'; - this.logger?.error(`${this.constructor.name}: ${message}`); - throwOperationStatusCode( - 'Fulfillment', - { - code: error?.response?.status ?? 500, - message - }, - ); - } - } - else if (response?.html) { - this.logger?.error(`${this.constructor.name}: ${response.html}`); + else { + const message = error?.root?.Envelope?.Body?.Fault?.faultstring + ?? error?.response?.statusText + ?? error?.toString() + ?? 'Server Error!'; + this.logger?.error(`${this.constructor.name}: ${message}`); throwOperationStatusCode( 'Fulfillment', { - code: response?.statusCode, - message: response?.html + code: error?.response?.status ?? 500, + message }, ); } - 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?.html) { + this.logger?.error(`${this.constructor.name}: ${response.html}`); + throwOperationStatusCode( + 'Fulfillment', + { + code: response?.statusCode, + message: response?.html + }, + ); + } + 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: 500, - message: 'Unexpected Error: No Response!', - } + code: response.Status?.statusCode, + message + }, ); } - - 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 ? State.SUBMITTED : State.INVALID; - const status = this.DHLCode2StatusCode( - code, - fulfillment.payload?.id, - dhl_state?.LabelData?.Status?.statusMessage, - ); - - if (state === State.INVALID) { - fulfillment.payload.state = state; - fulfillment.status = status; - return fulfillment; + } + else { + throwOperationStatusCode( + 'Fulfillment', + { + code: 500, + message: 'Unexpected Error: No Response!', } + ); + } - const label: Label = { - parcel_id: fulfillment.parcel.id, - shipment_number: dhl_state?.shipmentNumber, - url: dhl_state?.LabelData.labelUrl, - state, - status, - }; - - fulfillment.payload.state = state; - fulfillment.label = label; + return fulfillments.map((fulfillment, i) => { + const dhl_state = response?.CreationFulfillmentState?.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; - }); - } + } - protected AggregatedFulfillmentRequests2DHLShipmentOrderRequest( - requests: FlatAggregatedFulfillment[] - ): ShipmentOrderRequest { - return { - Version: { - majorRelease: this.version[0], - minorRelease: this.version[1] - }, - ShipmentOrder: requests.map((request, i): ShipmentOrder => { - const packaging = request.payload.packaging; - return { - sequenceNumber: i + 1, - Shipment: { - Shipper: { - Name: { - name1: packaging.sender.address.residential_address?.family_name ?? packaging.sender.address.business_address?.name, - name2: packaging.sender.address.residential_address?.given_name, - name3: packaging.sender.address.residential_address?.mid_name, - }, - Address: { - streetName: packaging.sender.address?.street, - streetNumber: packaging.sender.address?.building_number, - zip: packaging.sender.address?.postcode, - city: packaging.sender.address?.region, - Origin: { - country: request.sender_country?.name, - countryISOCode: request.sender_country?.country_code - } - }, - Communication: { - contactPerson: packaging.sender?.contact?.name, - email: packaging.sender?.contact.email, - phone: packaging.sender?.contact.phone, - } + const label: Label = { + parcel_id: fulfillment.parcel.id, + shipment_number: dhl_state?.shipmentNumber, + url: dhl_state?.LabelData.labelUrl, + state, + status, + }; + + fulfillment.payload.fulfillment_state = state; + fulfillment.label = label; + fulfillment.status = status; + return fulfillment; + }); + } + + protected AggregatedFulfillmentRequests2DHLShipmentOrderRequest( + requests: FlatAggregatedFulfillment[] + ): ShipmentOrderRequest { + return { + Version: { + majorRelease: this.version[0], + minorRelease: this.version[1] + }, + ShipmentOrder: requests.map((request, i): ShipmentOrder => { + const packaging = request.payload.packaging; + return { + sequenceNumber: i + 1, + Shipment: { + Shipper: { + Name: { + name1: packaging.sender.address.residential_address?.family_name ?? packaging.sender.address.business_address?.name, + name2: packaging.sender.address.residential_address?.given_name, + name3: packaging.sender.address.residential_address?.mid_name, }, - Receiver: { - name1: packaging.recipient.address.residential_address?.family_name ?? packaging.recipient.address.business_address?.name, - Address: { - name2: packaging.recipient.address.residential_address?.given_name, - name3: packaging.recipient.address.residential_address?.mid_name, - streetName: packaging.recipient.address?.street, - streetNumber: packaging.recipient.address?.building_number, - zip: packaging.recipient.address?.postcode, - city: packaging.recipient.address?.region, - Origin: { - country: request.recipient_country?.name, - countryISOCode: request.recipient_country?.country_code - }, - }, - Communication: { - contactPerson: packaging.recipient.contact?.name, - email: packaging.recipient.contact?.email, - phone: packaging.recipient.contact?.phone, + Address: { + streetName: packaging.sender.address?.street, + streetNumber: packaging.sender.address?.building_number, + zip: packaging.sender.address?.postcode, + city: packaging.sender.address?.region, + Origin: { + country: request.sender_country?.name, + countryISOCode: request.sender_country?.country_code } }, - ShipmentDetails: { - 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, - // Service: parseService(request.parcel.attributes), - ShipmentItem: { - heightInCM: request.parcel.package.size_in_cm.height, - lengthInCM: request.parcel.package.size_in_cm.length, - widthInCM: request.parcel.package.size_in_cm.width, - weightInKG: request.parcel.package.weight_in_kg, + Communication: { + contactPerson: packaging.sender?.contact?.name, + email: packaging.sender?.contact.email, + phone: packaging.sender?.contact.phone, + } + }, + Receiver: { + name1: packaging.recipient.address.residential_address?.family_name ?? packaging.recipient.address.business_address?.name, + Address: { + name2: packaging.recipient.address.residential_address?.given_name, + name3: packaging.recipient.address.residential_address?.mid_name, + streetName: packaging.recipient.address?.street, + streetNumber: packaging.recipient.address?.building_number, + zip: packaging.recipient.address?.postcode, + city: packaging.recipient.address?.region, + Origin: { + country: request.recipient_country?.name, + countryISOCode: request.recipient_country?.country_code }, - /* No longer supported!!! - Notification: { - recipientEmailAddress: packaging.notify - } - */ + }, + Communication: { + contactPerson: packaging.recipient.contact?.name, + email: packaging.recipient.contact?.email, + phone: packaging.recipient.contact?.phone, } + }, + ShipmentDetails: { + 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, + // Service: parseService(request.parcel.attributes), + ShipmentItem: { + heightInCM: request.parcel.package.size_in_cm.height, + lengthInCM: request.parcel.package.size_in_cm.length, + widthInCM: request.parcel.package.size_in_cm.width, + weightInKG: request.parcel.package.weight_in_kg, + }, + /* No longer supported!!! + Notification: { + recipientEmailAddress: packaging.notify + } + */ } } - }) - }; - } - - protected AggregatedFulfillment2DHLShipmentCancelRequest( - requests: FlatAggregatedFulfillment[] - ): ShipmentCancelRequest{ - return { - Version: { - majorRelease: this.version[0], - minorRelease: this.version[1] - }, - shipmentNumber: requests.map(request => request.label.shipment_number) - }; - } - - async registerSoapClient(courier?: Courier): Promise { - this.courier = courier = courier ?? this.courier; - if (this.clients[courier.id]) { - return this.clients[courier.id]; - } + }; + }) + }; + } - try{ - const configs = JSON.parse(courier?.configuration?.value?.toString() ?? null)?.ordering ?? this.stub_defaults?.ordering; - const wsdl = configs?.wsdl ?? this.stub_defaults?.ordering?.wsdl; - const username = configs?.username ?? this.stub_defaults?.ordering?.username; - const password = configs?.password ?? this.stub_defaults?.ordering?.password; - const endpoint = configs?.endpoint ?? this.stub_defaults?.ordering?.endpoint; - const wsdlHeader = configs?.wsdl_header ?? this.stub_defaults?.ordering?.wsdl_header; - this.clients[courier.id] = await soap.createClientAsync(wsdl).then(client => { - client.setEndpoint(endpoint); - client.addSoapHeader(typeof(wsdlHeader) === 'string' ? JSON.parse(wsdlHeader) : wsdlHeader); - client.setSecurity(new soap.BasicAuthSecurity(username, password)); - return client; - }); - } - catch (err) { - this.logger?.error(`${this.constructor.name}:\n Failed to create Client!`); - this.logger?.error(`${this.constructor.name}: ${JSON.stringify(err, null, 2)}`); - throw err; - } - return this.clients[courier.id]; + protected AggregatedFulfillment2DHLShipmentCancelRequest( + requests: FlatAggregatedFulfillment[] + ): ShipmentCancelRequest{ + return { + Version: { + majorRelease: this.version[0], + minorRelease: this.version[1] + }, + shipmentNumber: requests.map(request => request.label.shipment_number) }; + } - protected override async evaluateImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { - if (fulfillments.length === 0) return []; - const dhl_order_request = this.AggregatedFulfillmentRequests2DHLShipmentOrderRequest(fulfillments); - const client = await this.registerSoapClient(); - return new Promise((resolve): void => { - client.GVAPI_2_0_de.GKVAPISOAP11port0.evaluateShipmentOrder( - dhl_order_request, - (error: any, result: any): any => { - resolve(this.DHLShipmentLabels2FulfillmentResponses(fulfillments, result, error)); - } - ); - }); + async registerSoapClient(courier?: Courier): Promise { + this.courier = courier = courier ?? this.courier; + if (this.clients[courier.id]) { + return this.clients[courier.id]; } - protected override async submitImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { - if (fulfillments.length === 0) return []; - const dhl_order_request = this.AggregatedFulfillmentRequests2DHLShipmentOrderRequest(fulfillments); - const client = await this.registerSoapClient(); - return new Promise((resolve): void => { - client.GVAPI_2_0_de.GKVAPISOAP11port0.createShipmentOrder( - dhl_order_request, - (error: any, result: any): any => { - resolve(this.DHLShipmentLabels2FulfillmentResponses(fulfillments, result, error)); - } - ); + try{ + const configs = JSON.parse(courier?.configuration?.value?.toString() ?? null)?.ordering ?? this.stub_defaults?.ordering; + const wsdl = configs?.wsdl ?? this.stub_defaults?.ordering?.wsdl; + const username = configs?.username ?? this.stub_defaults?.ordering?.username; + const password = configs?.password ?? this.stub_defaults?.ordering?.password; + const endpoint = configs?.endpoint ?? this.stub_defaults?.ordering?.endpoint; + const wsdlHeader = configs?.wsdl_header ?? this.stub_defaults?.ordering?.wsdl_header; + this.clients[courier.id] = await soap.createClientAsync(wsdl).then(client => { + client.setEndpoint(endpoint); + client.addSoapHeader(typeof(wsdlHeader) === 'string' ? JSON.parse(wsdlHeader) : wsdlHeader); + client.setSecurity(new soap.BasicAuthSecurity(username, password)); + return client; }); } + catch (err) { + this.logger?.error(`${this.constructor.name}:\n Failed to create Client!`); + this.logger?.error(`${this.constructor.name}: ${JSON.stringify(err, null, 2)}`); + throw err; + } + return this.clients[courier.id]; + }; - protected override async trackImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { - const promises = fulfillments.map(async item => { - try { - const options = JSON.parse(item?.options?.value?.toString() ?? null); - const config = JSON.parse(this.courier?.configuration?.value?.toString() ?? null)?.tracking ?? this.stub_defaults?.tracking; - const client = { - appname: config?.appname ?? this.stub_defaults?.tracking?.appname, - username: config?.username ?? this.stub_defaults?.tracking?.username, - password: config?.password ?? this.stub_defaults?.tracking?.password, - endpoint: config?.endpoint ?? this.stub_defaults?.tracking?.endpoint, - secret: config?.secret ?? this.stub_defaults?.tracking?.secret, - }; - const auth = 'Basic ' + Buffer.from(`${client.username}:${client.password}`).toString('base64'); - const attributes = { - appname: client.appname, - password: client.secret, - 'piece-code': item.label.shipment_number, - 'language-code': options?.['language-code'] ?? 'de', - request: options?.request ?? 'd-get-piece-detail' - }; - const xml = `${ - js2xml({ data: { _attributes: attributes } }, { compact: true }) - }`; - const params = new URLSearchParams(); - params.append('xml', xml); - const payload = { - method: 'post', - headers: { - Host: 'cig.dhl.de', - Authorization: auth, - Connection: 'Keep-Alive', - }, - body: params, - }; - - return await fetch(client.endpoint, payload).then( - response => DHLTracking2FulfillmentTracking(item, response), - err => { - this.logger?.error(`${this.constructor.name}: ${err}`); - return DHLTracking2FulfillmentTracking(item, null, err); - } - ); - } catch (err) { - this.logger?.error(`${this.constructor.name}: ${err}`); - return { - ...item, - status: { - id: item.payload.id, - code: 500, - message: JSON.stringify(err) - } - }; + protected override async evaluateImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { + if (fulfillments.length === 0) return []; + const dhl_order_request = this.AggregatedFulfillmentRequests2DHLShipmentOrderRequest(fulfillments); + const client = await this.registerSoapClient(); + return new Promise((resolve): void => { + client.GVAPI_2_0_de.GKVAPISOAP11port0.evaluateShipmentOrder( + dhl_order_request, + (error: any, result: any): any => { + resolve(this.DHLShipmentLabels2FulfillmentResponses(fulfillments, result, error)); } - }); + ); + }); + } - return await Promise.all(promises); - }; + protected override async submitImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { + if (fulfillments.length === 0) return []; + const dhl_order_request = this.AggregatedFulfillmentRequests2DHLShipmentOrderRequest(fulfillments); + const client = await this.registerSoapClient(); + return new Promise((resolve, reject): void => { + const timer = setTimeout(reject, 30000, this.operation_status_codes.TIMEOUT); + client.GVAPI_2_0_de.GKVAPISOAP11port0.createShipmentOrder( + dhl_order_request, + (error: any, result: any): any => { + clearTimeout(timer); + resolve(this.DHLShipmentLabels2FulfillmentResponses(fulfillments, result, error)); + } + ); + }); + } - protected override async cancelImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { - if (fulfillments.length === 0) return []; - const fulfillment_map: { [k: string]: FlatAggregatedFulfillment } = {}; - fulfillments.forEach(a => fulfillment_map[a.label.shipment_number] = a); - const dhl_cancel_request = this.AggregatedFulfillment2DHLShipmentCancelRequest(fulfillments); - const client = await this.registerSoapClient(); - return await new Promise((resolve, reject: (v: FlatAggregatedFulfillment[]) => void): void => { - client.GVAPI_2_0_de.GKVAPISOAP11port0.deleteShipmentOrder(dhl_cancel_request, - (err: any, result: any): any => { - if (err) { - if (result?.html) { - this.logger?.error(`${this.constructor.name}: ${result.html.head.title}`); - reject(DHLShipmentCancelResponse2AggregatedFulfillment(fulfillment_map, null, result.html.head.title)); - } - else { - this.logger?.error(`${this.constructor.name}: ${err}`); - reject(DHLShipmentCancelResponse2AggregatedFulfillment(fulfillment_map, null, err)); - } + protected override async trackImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { + const promises = fulfillments.map(async item => { + try { + const options = JSON.parse(item?.options?.value?.toString() ?? null); + const config = JSON.parse(this.courier?.configuration?.value?.toString() ?? null)?.tracking ?? this.stub_defaults?.tracking; + const client = { + appname: config?.appname ?? this.stub_defaults?.tracking?.appname, + username: config?.username ?? this.stub_defaults?.tracking?.username, + password: config?.password ?? this.stub_defaults?.tracking?.password, + endpoint: config?.endpoint ?? this.stub_defaults?.tracking?.endpoint, + secret: config?.secret ?? this.stub_defaults?.tracking?.secret, + }; + const auth = 'Basic ' + Buffer.from(`${client.username}:${client.password}`).toString('base64'); + const attributes = { + appname: client.appname, + password: client.secret, + 'piece-code': item.label.shipment_number, + 'language-code': options?.['language-code'] ?? 'de', + request: options?.request ?? 'd-get-piece-detail' + }; + const xml = `${ + js2xml({ data: { _attributes: attributes } }, { compact: true }) + }`; + const params = new URLSearchParams(); + params.append('xml', xml); + const payload = { + method: 'post', + headers: { + Host: 'cig.dhl.de', + Authorization: auth, + Connection: 'Keep-Alive', + }, + body: params, + }; + + return await fetch(client.endpoint, payload).then( + response => DHLTracking2FulfillmentTracking(item, response), + err => { + this.logger?.error(`${this.constructor.name}: ${err}`); + return DHLTracking2FulfillmentTracking(item, null, err); + } + ); + } catch (err) { + this.logger?.error(`${this.constructor.name}: ${err}`); + return { + ...item, + status: { + id: item.payload.id, + code: 500, + message: JSON.stringify(err) + } + }; + } + }); + + return await Promise.all(promises); + }; + + protected override async cancelImpl (fulfillments: FlatAggregatedFulfillment[]): Promise { + if (fulfillments.length === 0) return []; + const fulfillment_map: { [k: string]: FlatAggregatedFulfillment } = {}; + fulfillments.forEach(a => fulfillment_map[a.label.shipment_number] = a); + const dhl_cancel_request = this.AggregatedFulfillment2DHLShipmentCancelRequest(fulfillments); + const client = await this.registerSoapClient(); + return await new Promise((resolve, reject: (v: FlatAggregatedFulfillment[]) => void): void => { + client.GVAPI_2_0_de.GKVAPISOAP11port0.deleteShipmentOrder(dhl_cancel_request, + (err: any, result: any): any => { + if (err) { + if (result?.html) { + this.logger?.error(`${this.constructor.name}: ${result.html.head.title}`); + reject(DHLShipmentCancelResponse2AggregatedFulfillment(fulfillment_map, null, result.html.head.title)); } else { - resolve(DHLShipmentCancelResponse2AggregatedFulfillment(fulfillment_map, result)); + this.logger?.error(`${this.constructor.name}: ${err}`); + reject(DHLShipmentCancelResponse2AggregatedFulfillment(fulfillment_map, null, err)); } - }); - }); - }; - - async getTariffCode(fulfillment: FlatAggregatedFulfillment): Promise { - return fulfillment.recipient_country.country_code; - } + } + else { + resolve(DHLShipmentCancelResponse2AggregatedFulfillment(fulfillment_map, result)); + } + }); + }); }; - Stub.register('DHLSoapStub', DHLSoapStub); -} \ No newline at end of file + async getTariffCode(fulfillment: FlatAggregatedFulfillment): Promise { + return fulfillment.recipient_country.country_code; + } +}; + +Stub.register('DHLSoap', DHLSoap); \ No newline at end of file diff --git a/src/stubs/dummy.ts b/src/stubs/dummy.ts index 05d7b99..97c038a 100644 --- a/src/stubs/dummy.ts +++ b/src/stubs/dummy.ts @@ -1,9 +1,7 @@ -import { - Stub, - FlatAggregatedFulfillment -} from '../index.js'; +import { FlatAggregatedFulfillment } from '../utils.js'; +import { Stub } from '../stub.js'; -export class DummyStub extends Stub { +class DummyStub extends Stub { get type(): string { return this.constructor.name; @@ -32,10 +30,12 @@ export class DummyStub extends Stub { ): Promise { return fulfillments; } - + protected override async cancelImpl( fulfillments: FlatAggregatedFulfillment[] ): Promise { return fulfillments; } -} \ No newline at end of file +} + +Stub.register('Dummy', DummyStub); \ No newline at end of file diff --git a/src/stubs/index.ts b/src/stubs/index.ts new file mode 100644 index 0000000..574a7c6 --- /dev/null +++ b/src/stubs/index.ts @@ -0,0 +1,3 @@ +// Register Stubs here! +import './dhl_soap.js'; +import './dummy.js'; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index e2469dc..6171abf 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,10 @@ -import { randomUUID } from 'crypto'; +import { randomUUID } from 'node:crypto'; import { Client } from '@restorecommerce/grpc-client'; import { FulfillmentResponse, Label, Parcel, - State, + FulfillmentState, Tracking, } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment.js'; import { @@ -69,15 +69,15 @@ export type CourierResponse = FulfillmentCourierResponse; export type CourierMap = { [id: string]: Courier }; export type Payload = { id?: string }; -export type Response = { - payload?: T, - status?: Status, +export type Response = { + payload?: T; + status?: Status; }; export type ResponseList = { - items?: Response[], - operation_status?: OperationStatus, -} -export type ResponseMap = { [id: string]: Response } + items?: Response[]; + operation_status?: OperationStatus; +}; +export type ResponseMap = { [id: string]: Response }; export const filterTax = ( tax: Tax, @@ -95,9 +95,9 @@ export const filterTax = ( ) ); -export const StateRank = Object.values(State).reduce((a, b, i) => { +export const StateRank = Object.values(FulfillmentState).reduce((a, b, i) => { a[b] = i; - return a; + return a; }, {} as { [key: string]: number }); export interface AggregatedFulfillment extends FulfillmentResponse @@ -186,7 +186,7 @@ export const extractCouriers = (fulfillments: FlatAggregatedFulfillment[]): Cour }, {} as CourierMap ); -} +}; export const flatMapAggregatedFulfillments = (fulfillments: AggregatedFulfillment[]): FlatAggregatedFulfillment[] => { return fulfillments.flatMap((fulfillment) => { @@ -200,7 +200,7 @@ export const flatMapAggregatedFulfillments = (fulfillments: AggregatedFulfillmen uuid, payload: { ...fulfillment.payload, - state: label?.state ?? fulfillment.payload?.state, + fulfillment_state: label?.state ?? fulfillment.payload?.fulfillment_state, }, sender_country: fulfillment.sender_country?.payload, recipient_country: fulfillment.recipient_country?.payload, @@ -212,9 +212,9 @@ export const flatMapAggregatedFulfillments = (fulfillments: AggregatedFulfillmen options: fulfillment.options, status: tracking?.status ?? label?.status ?? fulfillment.status, }; - }) + }); }); -} +}; export const mergeFulfillments = (fulfillments: FlatAggregatedFulfillment[]): FulfillmentResponse[] => { const merged_fulfillments: { [uuid: string]: FulfillmentResponse } = {}; @@ -225,7 +225,7 @@ export const mergeFulfillments = (fulfillments: FlatAggregatedFulfillment[]): Fu if (a.parcel) c.payload.packaging.parcels.push(a.parcel); if (a.label) c.payload.labels.push(a.label); if (a.tracking) c.payload.trackings.push(a.tracking); - c.payload.state = StateRank[b.state] < StateRank[c.payload.state] ? b.state : c.payload.state; + c.payload.fulfillment_state = StateRank[b.fulfillment_state] < StateRank[c.payload.fulfillment_state] ? b.fulfillment_state : c.payload.fulfillment_state; c.status = c.status.code > a.status.code ? c.status : a.status; c.payload.total_amounts = a.payload.total_amounts?.reduce( (a, b) => { diff --git a/src/worker.ts b/src/worker.ts index 6c5c88f..c299c4d 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -16,7 +16,7 @@ import { RedisClientType as RedisClient, createClient } from 'redis'; import { Arango } from '@restorecommerce/chassis-srv/lib/database/provider/arango/base.js'; import { Logger } from 'winston'; import { BindConfig } from '@restorecommerce/chassis-srv/lib/microservice/transport/provider/grpc/index.js'; -import { +import { FulfillmentServiceDefinition, protoMetadata as FulfillmentMeta, } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment.js'; @@ -28,7 +28,7 @@ import { FulfillmentProductServiceDefinition, protoMetadata as FulfillmentProductMeta, } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment_product.js'; -import { +import { CommandInterfaceServiceDefinition, protoMetadata as CommandInterfaceMeta, } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/commandinterface.js'; @@ -38,6 +38,7 @@ import { FulfillmentService } from './services/fulfillment.js'; import { FulfillmentCourierService } from './services/fulfillment_courier.js'; import { FulfillmentProductService } from './services/fulfillment_product.js'; import { FulfillmentCommandInterface } from './services/fulfillment_command_interface.js'; +import './stubs/index.js'; registerProtoMeta( FulfillmentMeta, @@ -147,8 +148,8 @@ export class Worker { (err: any) => this.logger.error(`Job ${msg?.type} failed: ${err}`) ); } - } - + }; + protected bindHandler(serviceName: string, functionName: string) { this.logger.debug(`Bind event to handler: ${serviceName}.${functionName}`); return (msg: any, context: any, config: any, eventName: string): Promise => { @@ -198,7 +199,7 @@ export class Worker { eventName as string, this.serviceActions.get(eventName), { startingOffset: offsetValue } - ) + ); } ); this.topics.set(key, topic); diff --git a/test/fulfillment-srv-dhl.spec.ts b/test/fulfillment-srv-dhl.spec.ts index ad34d95..0dce7de 100644 --- a/test/fulfillment-srv-dhl.spec.ts +++ b/test/fulfillment-srv-dhl.spec.ts @@ -8,7 +8,7 @@ import { Client } from '@restorecommerce/grpc-client'; import { Events, Topic } from '@restorecommerce/kafka-client'; -import { State } from '@restorecommerce/rc-grpc-clients/dist/generated/io/restorecommerce/fulfillment'; +import { FulfillmentState } from '@restorecommerce/rc-grpc-clients/dist/generated/io/restorecommerce/fulfillment'; import { Fulfillment } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment'; import { FulfillmentServiceDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated/io/restorecommerce/fulfillment'; import { FulfillmentCourierServiceDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated/io/restorecommerce/fulfillment_courier'; @@ -75,9 +75,27 @@ describe('Testing Fulfillment Service Cluster:', () => { after(async function() { this.timeout(30000); await Promise.allSettled([ - courier_client?.delete({ collection: true }), - product_client?.delete({ collection: true }), - fulfillment_client?.delete({ collection: true }), + courier_client?.delete({ + collection: true, + subject: { + id: 'superadmin', + token: 'superadmin', + }, + }), + product_client?.delete({ + collection: true, + subject: { + id: 'superadmin', + token: 'superadmin', + }, + }), + fulfillment_client?.delete({ + collection: true, + subject: { + id: 'superadmin', + token: 'superadmin', + }, + }), events?.stop(), ]); await worker?.stop(); @@ -178,27 +196,27 @@ describe('Testing Fulfillment Service Cluster:', () => { const fulfillmentCancelledSemaphore = new Semaphore(0); const onFulfillmentCreated = (msg: Fulfillment, context?:any): void => { - should.equal(msg?.state, State.CREATED); + should.equal(msg?.fulfillment_state, FulfillmentState.PENDING); fulfillmentCreatedSemaphore.release(1); }; const onFulfillmentSubmitted = (msg: Fulfillment, context?:any): void => { - should.equal(msg?.state, State.SUBMITTED); + should.equal(msg?.fulfillment_state, FulfillmentState.SUBMITTED); fulfillmentSubmittedSemaphore.release(1); }; const onFulfillmentFulfilled = (msg: Fulfillment, context?:any): void => { - should.equal(msg?.state, State.FULFILLED); + should.equal(msg?.fulfillment_state, FulfillmentState.COMPLETED); fulfillmentFulfilledSemaphore.release(1); }; const onFulfillmentWithdrawn = (msg: Fulfillment, context?:any): void => { - should.equal(msg?.state, State.WITHDRAWN); + should.equal(msg?.fulfillment_state, FulfillmentState.WITHDRAWN); fulfillmentWithdrawnSemaphore.release(1); }; const onFulfillmentCancelled = (msg: Fulfillment, context?:any): void => { - should.equal(msg?.state, State.CANCELLED); + should.equal(msg?.fulfillment_state, FulfillmentState.CANCELLED); fulfillmentCancelledSemaphore.release(1); }; @@ -251,8 +269,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() { - this.timeout(30000); + this.timeout(60000); const response = await fulfillment_client.submit(sample); + console.log(JSON.stringify(response, null, 2)); should.equal( response?.operationStatus?.code, 200, 'response.operationStatus.code expected to be 200', diff --git a/test/mocks.ts b/test/mocks.ts index 8062c28..0ca434a 100644 --- a/test/mocks.ts +++ b/test/mocks.ts @@ -47,7 +47,7 @@ import { import { FulfillmentIdList, FulfillmentList, - State as FulfillmentState + FulfillmentState } from '@restorecommerce/rc-grpc-clients/dist/generated/io/restorecommerce/fulfillment'; import { OperationStatus @@ -66,9 +66,6 @@ import { import { FulfillmentCourierList } from '@restorecommerce/rc-grpc-clients/dist/generated/io/restorecommerce/fulfillment_courier'; -import { - State -} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment'; import { HierarchicalScope } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/auth'; @@ -76,6 +73,7 @@ import { getRedisInstance, logger } from './utils'; +import { Subject } from '@restorecommerce/rc-grpc-clients/dist/generated/io/restorecommerce/auth'; type Address = ShippingAddress & BillingAddress; @@ -129,6 +127,19 @@ const subMeta = { ] }; +const subjects: { [key: string]: Subject } = { + superadmin: { + id: 'superadmin', + scope: 'main', + token: 'superadmin', + }, + admin: { + id: 'admin', + scope: 'sub', + token: 'admin', + }, +}; + const operationStatus: OperationStatus = { code: 200, message: 'OK', @@ -379,7 +390,7 @@ const validCouriers: { [key: string]: FulfillmentCourierList } = { description: '', logo: 'DHL.png', website: 'https://www.dhl.com/', - stubType: 'DHLSoapStub', + stubType: 'DHLSoap', shopIds: [ 'shop_1' ], @@ -408,7 +419,7 @@ const validCouriers: { [key: string]: FulfillmentCourierList } = { description: '', logo: 'DHL.png', website: 'https://www.dhl.com/', - stubType: 'DHLSoapStub', + stubType: 'DHLSoap', shopIds: [ 'shop_1' ], @@ -433,9 +444,7 @@ const validCouriers: { [key: string]: FulfillmentCourierList } = { } ], totalCount: 2, - subject: { - unauthenticated: true - } + subject: subjects.superadmin } }; @@ -709,9 +718,7 @@ const validFulfillmentProducts: { [key:string]: FulfillmentProductList } = { } ], totalCount: 4, - subject: { - unauthenticated: true - } + subject: subjects.superadmin } }; @@ -741,9 +748,7 @@ const validPackingSolutionQueries: { [key: string]: PackingSolutionQueryList } = ] } ], - subject: { - unauthenticated: true - } + subject: subjects.superadmin } }; @@ -795,25 +800,8 @@ const validFulfillments: { [key: string]: FulfillmentList } = { labels: [], trackings: [], totalAmounts: [], - state: FulfillmentState.CREATED, - meta: { - created: new Date(), - modified: new Date(), - modifiedBy: 'SYSTEM', - acls: [], - owners: [ - { - id: 'urn:restorecommerce:acs:names:ownerIndicatoryEntity', - value: 'urn:restorecommerce:acs:model:user.User', - attributes: [] - }, - { - id: 'urn:restorecommerce:acs:names:ownerInstance', - value: 'UserID', - attributes: [] - } - ] - } + fulfillmentState: FulfillmentState.PENDING, + meta: mainMeta, }, { id: 'validFulfillment_2', @@ -884,7 +872,7 @@ const validFulfillments: { [key: string]: FulfillmentList } = { { parcelId: '1', shipmentNumber: '00340434161094015902', - state: State.SUBMITTED, + state: FulfillmentState.SUBMITTED, status: { id: 'validFulfillment_2', code: 200, @@ -894,31 +882,12 @@ const validFulfillments: { [key: string]: FulfillmentList } = { ], trackings: [], totalAmounts: [], - state: FulfillmentState.CREATED, - meta: { - created: new Date(), - modified: new Date(), - modifiedBy: 'SYSTEM', - acls: [], - owners: [ - { - id: 'urn:restorecommerce:acs:names:ownerIndicatoryEntity', - value: 'urn:restorecommerce:acs:model:user.User', - attributes: [] - }, - { - id: 'urn:restorecommerce:acs:names:ownerInstance', - value: 'UserID', - attributes: [] - } - ] - } + fulfillmentState: FulfillmentState.PENDING, + meta: mainMeta, }, ], totalCount: 2, - subject: { - unauthenticated: true - } + subject: subjects.superadmin } }; @@ -933,9 +902,7 @@ const validTrackingRequests: { [key: string]: FulfillmentIdList } = { } ], totalCount: 1, - subject: { - unauthenticated: true - } + subject: subjects.superadmin } }; @@ -1135,23 +1102,23 @@ export const rules = { 'acs-srv': { isAllowed: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: Response) => void, ) => callback(null, { decision: Response_Decision.PERMIT, }), whatIsAllowed: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: ReverseQuery) => void, ) => callback(null, whatIsAllowed), }, user: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: UserListResponse) => void, ) => callback(null, {}), findByToken: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: UserResponse) => void, ) => { getRedisInstance().then( async client => { @@ -1175,7 +1142,7 @@ export const rules = { shop: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: ShopListResponse) => void, ) => callback(null, { items: shops, totalCount: shops.length, @@ -1185,7 +1152,7 @@ export const rules = { organization: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: OrganizationListResponse) => void, ) => callback(null, { items: organizations, totalCount: organizations.length, @@ -1195,7 +1162,7 @@ export const rules = { customer: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: CustomerListResponse) => void, ) => callback(null, { items: customers, totalCount: 1, @@ -1205,7 +1172,7 @@ export const rules = { contact_point: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: ContactPointListResponse) => void, ) => callback(null, { items: contactPoints, totalCount: contactPoints.length, @@ -1215,7 +1182,7 @@ export const rules = { address: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: AddressListResponse) => void, ) => callback(null, { items: [ ...residentialAddresses, @@ -1235,7 +1202,7 @@ export const rules = { country: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: CountryListResponse) => void, ) => callback(null, { items: countries, totalCount: countries.length, @@ -1245,7 +1212,7 @@ export const rules = { product: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: ProductListResponse) => void, ) => callback(null, { items: products, totalCount: products.length, @@ -1255,7 +1222,7 @@ export const rules = { tax: { read: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: TaxListResponse) => void, )=> callback(null, { items: taxes, totalCount: 1, @@ -1265,7 +1232,7 @@ export const rules = { invoice: { create: ( call: any, - callback: (error: any, response: DeepPartial) => void, + callback: (error: any, response: InvoiceListResponse) => void, ) => callback(null, { items: [{ payload: {