From 0ad6c4e58e2af4dea301da8a6e84c87cdcdbbd65 Mon Sep 17 00:00:00 2001 From: Gerald Baulig Date: Wed, 23 Oct 2024 13:34:02 +0200 Subject: [PATCH 1/2] fix(find): filter fulfillmentProducts for start and dest zones --- cfg/config.json | 11 +- src/services/fulfillment.ts | 15 +- src/services/fulfillment_product.ts | 331 +++++++++++++++++----------- src/stub.ts | 8 +- src/stubs/dhl_soap.ts | 9 +- 5 files changed, 238 insertions(+), 136 deletions(-) diff --git a/cfg/config.json b/cfg/config.json index aceb176..8563c99 100644 --- a/cfg/config.json +++ b/cfg/config.json @@ -340,9 +340,14 @@ ] } }, - "preDefinedIds": { - "legalAddressTypeId": "legal", - "shippingAddressTypeId": "shipping" + "tech_user": { + "id": "root_tech_user", + "token": "1a4c6789-6435-487a-9308-64d06384acf9" + }, + "contact_point_type_ids": { + "legal": "legal", + "shipping": "shipping", + "billing": "billing" }, "stubs": { "DHLSoap": { diff --git a/src/services/fulfillment.ts b/src/services/fulfillment.ts index 199cbac..d5ff7bb 100644 --- a/src/services/fulfillment.ts +++ b/src/services/fulfillment.ts @@ -173,7 +173,6 @@ export class FulfillmentService }; protected readonly emitters: any; - protected readonly legal_address_type_id: string; protected readonly customer_service: Client; protected readonly shop_service: Client; protected readonly organization_service: Client; @@ -182,6 +181,11 @@ export class FulfillmentService protected readonly country_service: Client; protected readonly tax_service: Client; protected readonly credential_service: Client; + protected readonly contact_point_type_ids = { + legal: 'legal', + shipping: 'shipping', + billing: 'billing', + }; constructor( readonly fulfillmentCourierSrv: FulfillmentCourierService, @@ -217,7 +221,10 @@ export class FulfillmentService }; this.emitters = cfg.get('events:emitters'); - this.legal_address_type_id = this.cfg.get('preDefinedIds:legalAddressTypeId'); + this.contact_point_type_ids = { + ...this.contact_point_type_ids, + ...cfg.get('contactPointTypeIds') + }; this.customer_service = createClient( { @@ -690,7 +697,7 @@ export class FulfillmentService ).then( cpts => cpts.find( cpt => cpt.payload?.contact_point_type_ids.includes( - this.legal_address_type_id + this.contact_point_type_ids.legal ) ) ?? throwStatusCode( typeof (item), @@ -728,7 +735,7 @@ export class FulfillmentService ).then( cps => cps.find( cp => cp.payload?.contact_point_type_ids.includes( - this.legal_address_type_id + this.contact_point_type_ids.legal ) ) ?? throwStatusCode( typeof (item), diff --git a/src/services/fulfillment_product.ts b/src/services/fulfillment_product.ts index 35b7046..050bb8e 100644 --- a/src/services/fulfillment_product.ts +++ b/src/services/fulfillment_product.ts @@ -162,16 +162,16 @@ export class FulfillmentProductService code: 404, message: '{entity} {id} has no shipping address!', }, - NO_SHOP_ID: { + NO_ENTITY_ID: { id: '', code: 400, - message: 'Shop ID not provided!' + message: '{entity} ID not provided!' }, - NO_CUSTOMER_ID: { + MISSING_PACKAGING_INFO: { id: '', - code: 400, - message: 'Customer ID not provided!' - } + code: 500, + message: '{entity} {id} is missing packaging info: {error}' + }, }; protected readonly operation_status_codes: { [key: string]: OperationStatus } = { @@ -193,8 +193,6 @@ export class FulfillmentProductService } }; - protected readonly legal_address_type_id: string; - protected readonly shipping_address_type_id: string; protected readonly customer_service: Client; protected readonly shop_service: Client; protected readonly organization_service: Client; @@ -202,6 +200,12 @@ export class FulfillmentProductService protected readonly address_service: Client; protected readonly country_service: Client; protected readonly tax_service: Client; + protected readonly tech_user: Subject; + protected readonly contact_point_type_ids = { + legal: 'legal', + shipping: 'shipping', + billing: 'billing', + }; constructor( protected readonly courier_srv: FulfillmentCourierService, @@ -232,8 +236,10 @@ export class FulfillmentProductService ...cfg.get('operationStatusCodes'), }; - this.legal_address_type_id = this.cfg.get('preDefinedIds:legalAddressTypeId'); - this.shipping_address_type_id = this.cfg.get('preDefinedIds:shippingAddressTypeId'); + this.contact_point_type_ids = { + ...this.contact_point_type_ids, + ...cfg.get('contactPointTypeIds') + }; this.customer_service = createClient( { @@ -297,6 +303,8 @@ export class FulfillmentProductService TaxServiceDefinition, createChannel(cfg.get('client:tax').address) ); + + this.tech_user = cfg.get('tech_user'); } protected createStatusCode( @@ -327,7 +335,8 @@ export class FulfillmentProductService throw this.createStatusCode( entity, id, - status,error + status, + error ); } @@ -416,22 +425,22 @@ export class FulfillmentProductService ); } - async getById(map: { [id: string]: T }, id: string): Promise { + async getById(map: { [id: string]: T }, id: string, entity: string): Promise { if (id in map) { return map[id]; } else { throw this.createStatusCode( - ({} as new() => T).name, + entity, id, this.status_codes.NOT_FOUND ); } } - async getByIds(map: { [id: string]: T }, ids: string[]): Promise { + async getByIds(map: { [id: string]: T }, ids: string[], entity: string): Promise { return Promise.all(ids.map( - id => this.getById(map, id) + id => this.getById(map, id, entity) )); } @@ -477,32 +486,28 @@ export class FulfillmentProductService context?: any, ): Promise { const call = ReadRequest.fromPartial({ - filters: [{ - filters: [{ - filters: [{ - filters: query.preferences?.couriers?.map( + filters: [ + { + filters: [ + { + field: 'shop_ids', + operation: Filter_Operation.in, + value: query.shop_id + }, + ...(query.preferences?.couriers?.map( att => ({ field: att.id, operation: Filter_Operation.eq, value: att.value, }) - ).filter(item => !!item), - operator: FilterOp_Operator.or - }] - },{ - filters: [{ - filters: [{ - field: 'shop_ids', - operation: Filter_Operation.in, - value: query.shop_id - }], - operator: FilterOp_Operator.and - }], - }], - operator: FilterOp_Operator.and - }], + ).filter(item => !!item) ?? []) + ], + operator: FilterOp_Operator.and + } + ], subject, }); + const response = await this.courier_srv.read(call, context).then( resp => { if (resp.operation_status?.code !== 200) { @@ -513,12 +518,14 @@ export class FulfillmentProductService } } ); - this.logger.debug('Available couriers', response); + this.logger.debug('Available Couriers:', response); return response; } protected async findFulfillmentProducts( query: PackageSolutionTotals, + sender_country: Country, + recipient_country: Country, subject?: Subject, context?: any, ): Promise { @@ -546,18 +553,30 @@ export class FulfillmentProductService const call = ReadRequest.fromPartial({ filters: [{ - filters: [{ - field: 'courier_id', - operation: Filter_Operation.in, - value: JSON.stringify(stubs.map(stub => stub.courier.id)), - type: Filter_ValueType.ARRAY - }], - operator: FilterOp_Operator.or + filters: [ + { + field: 'courier_id', + operation: Filter_Operation.in, + value: JSON.stringify(stubs.map(stub => stub.courier.id)), + type: Filter_ValueType.ARRAY + }, + { + field: 'start_zones', + operation: Filter_Operation.in, + value: sender_country.country_code + }, + { + field: 'destination_zones', + operation: Filter_Operation.in, + value: recipient_country.country_code + } + ], + operator: FilterOp_Operator.and }], subject, }); - return await super.read(call, context).then( + const response = await super.read(call, context).then( resp => { if (resp.operation_status?.code !== 200) { throw resp.operation_status; @@ -567,6 +586,9 @@ export class FulfillmentProductService } } ); + + this.logger.debug('Available Fulfillment Products:', response); + return response; } public superRead( @@ -645,7 +667,7 @@ export class FulfillmentProductService const customer_map = await this.get( queries.map(q => q.customer_id), this.customer_service, - request.subject, + this.tech_user ?? request.subject, context, ) ?? {}; @@ -667,7 +689,7 @@ export class FulfillmentProductService ), ], this.organization_service, - request.subject, + this.tech_user ?? request.subject, context, ) ?? {}; @@ -681,7 +703,7 @@ export class FulfillmentProductService ), ], this.contact_point_service, - request.subject, + this.tech_user ?? request.subject, context, ) ?? {}; @@ -690,7 +712,7 @@ export class FulfillmentProductService item => item.payload?.physical_address_id ), this.address_service, - request.subject, + this.tech_user ?? request.subject, context, ) ?? {}; @@ -703,7 +725,7 @@ export class FulfillmentProductService ...queries.map(query => query.recipient?.address?.country_id) ], this.country_service, - request.subject, + this.tech_user ?? request.subject, context, ) ?? {}; @@ -713,94 +735,44 @@ export class FulfillmentProductService this.throwStatusCode( 'Shop', query.reference?.instance_id, - this.status_codes.NO_SHOP_ID, + this.status_codes.NO_ENTITY_ID, ); } if (!query.customer_id) { this.throwStatusCode( 'Customer', query.reference?.instance_id, - this.status_codes.NO_CUSTOMER_ID, + this.status_codes.NO_ENTITY_ID, ); } - const product_map = await this.findFulfillmentProducts( - query, - request.subject, - context, - ).then( - response => response.items.reduce( - (a: ResponseMap, b) => { - a[b.payload?.id ?? b.status?.id!] = b; - return a; - }, - {} as ResponseMap - ) - ); - - const tax_map = await this.get( - Object.values(product_map).flatMap( - p => p.payload.tax_ids - ), - this.tax_service, - request.subject, - context, - ); - - const offer_lists = Object.values(product_map).map( - (product): Offer[] => product.payload?.variants?.map( - (variant): Offer => ( - { - name: `${product.payload?.id}\t${variant.id}`, - price: variant.price.sale ? variant.price.sale_price : variant.price.regular_price, - maxWeight: variant.max_weight, - width: variant.max_size?.width, - height: variant.max_size?.height, - depth: variant.max_size?.length, - type: 'parcel' - } - ) - ) - ); - - const goods = query.items.map((good): IItem => ({ - desc: `${good.product_id}\t${good.variant_id}`, - quantity: good.quantity, - weight: good.package.weight_in_kg, - width: good.package.size_in_cm.width, - height: good.package.size_in_cm.height, - depth: good.package.size_in_cm.length, - price: 0.0, // placeholder - taxType: 'vat_standard' // placeholder - })); - - const packer = new Packer({ - source: JSON.stringify({ zones: [] }), - shipping: null - }); - - const shop_country = query.sender?.address?.country_id + const shop_country = ( + query.sender?.address?.country_id ? await this.getById( country_map, - query.sender.address.country_id + query.sender.address.country_id, + 'Country', ) : await this.getById( shop_map, - query.shop_id + query.shop_id, + 'Shop', ).then( shop => this.getById( orga_map, - shop.payload.organization_id + shop.payload.organization_id, + 'Organization', ) ).then( orga => this.getByIds( contact_point_map, - orga.payload.contact_point_ids + orga.payload.contact_point_ids, + 'ContactPoint', ) ).then( cpts => cpts.find( cpt => cpt.payload?.contact_point_type_ids.includes( - this.legal_address_type_id + this.contact_point_type_ids.legal ) ) ?? this.throwStatusCode( 'Shop', @@ -810,24 +782,31 @@ export class FulfillmentProductService ).then( contact_point => this.getById( address_map, - contact_point.payload.physical_address_id + contact_point.payload.physical_address_id, + 'Address', ) ).then( address => this.getById( country_map, - address.payload.country_id + address.payload.country_id, + 'Country', ) - ); + ) + ); + this.logger.debug('Shop Country:', shop_country); const customer = await this.getById( customer_map, - query.customer_id + query.customer_id, + 'Customer', ); - const customer_country = query.recipient?.address?.country_id + const customer_country = ( + query.recipient?.address?.country_id ? await this.getById( country_map, - query.recipient.address.country_id + query.recipient.address.country_id, + 'Country', ) : await this.getByIds( contact_point_map, @@ -835,11 +814,12 @@ export class FulfillmentProductService customer.payload.private?.contact_point_ids, 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) + ].flatMap(id => id).filter(id => id), + 'ContactPoint', ).then( cps => cps.find( cp => cp.payload?.contact_point_type_ids.includes( - this.shipping_address_type_id + this.contact_point_type_ids.shipping ) ) ?? this.throwStatusCode( 'Customer', @@ -850,14 +830,117 @@ export class FulfillmentProductService cp => this.getById( address_map, cp.payload.physical_address_id, + 'Address', ) ).then( address => this.getById( country_map, - address.payload.country_id + address.payload.country_id, + 'Country', ) - ); + ) + ); + this.logger.debug('Customer Country:', customer_country); + + const product_map = await this.findFulfillmentProducts( + query, + shop_country.payload, + customer_country.payload, + request.subject, + context, + ).then( + response => response.items.reduce( + (a: ResponseMap, b) => { + a[b.payload?.id ?? b.status?.id!] = b; + return a; + }, + {} as ResponseMap + ) + ); + + const tax_map = await this.get( + Object.values(product_map).flatMap( + p => p.payload.tax_ids + ), + this.tax_service, + this.tech_user ?? request.subject, + context, + ); + + const offer_lists = Object.values(product_map).map( + (product): Offer[] => product.payload?.variants?.map( + (variant): Offer => ( + { + name: `${product.payload?.id}\t${variant.id}`, + price: variant.price.sale ? variant.price.sale_price : variant.price.regular_price, + maxWeight: variant.max_weight ?? this.throwStatusCode( + 'FulfillmentProduct', + product.payload?.id, + this.status_codes.MISSING_PACKAGING_INFO, + 'Weight' + ), + width: variant.max_size?.width ?? this.throwStatusCode( + 'FulfillmentProduct', + product.payload?.id, + this.status_codes.MISSING_PACKAGING_INFO, + 'Width' + ), + height: variant.max_size?.height ?? this.throwStatusCode( + 'FulfillmentProduct', + product.payload?.id, + this.status_codes.MISSING_PACKAGING_INFO, + 'Height' + ), + depth: variant.max_size?.length ?? this.throwStatusCode( + 'FulfillmentProduct', + product.payload?.id, + this.status_codes.MISSING_PACKAGING_INFO, + 'Length' + ), + type: 'parcel' + } + ) + ) + ); + this.logger.debug('Offer List:', offer_lists); + + const goods = query.items.map((good): IItem => ({ + desc: `${good.product_id}\t${good.variant_id}`, + quantity: good.quantity, + weight: good.package?.weight_in_kg ?? this.throwStatusCode( + 'Product', + good.product_id, + this.status_codes.MISSING_PACKAGING_INFO, + 'Weight' + ), + width: good.package?.size_in_cm.width ?? this.throwStatusCode( + 'Product', + good.product_id, + this.status_codes.MISSING_PACKAGING_INFO, + 'Width', + ), + height: good.package?.size_in_cm.height ?? this.throwStatusCode( + 'Product', + good.product_id, + this.status_codes.MISSING_PACKAGING_INFO, + 'Height' + ), + depth: good.package?.size_in_cm.length ?? this.throwStatusCode( + 'Product', + good.product_id, + this.status_codes.MISSING_PACKAGING_INFO, + 'Length', + ), + price: 0.0, // placeholder + taxType: 'vat_standard' // placeholder + })); + this.logger.debug('Goods:', goods); + const packer = new Packer({ + source: JSON.stringify({ zones: [] }), + shipping: null + }); + const solutions: PackingSolution[] = offer_lists.map( offers => packer.canFit(offers, goods) ).map( @@ -945,10 +1028,6 @@ export class FulfillmentProductService return { parcels, amounts, - compactness: 1, - homogeneity: 1, - score: 1, - reference: query.reference, }; } ).sort( @@ -960,6 +1039,7 @@ export class FulfillmentProductService ); const solution: PackingSolutionResponse = { + reference: query.reference, solutions, status: { id: query.reference?.instance_id, @@ -978,6 +1058,7 @@ export class FulfillmentProductService } catch (e: any) { const solution: PackingSolutionResponse = { + reference: query.reference, solutions: [], status: this.catchStatusError( query.reference?.instance_id, diff --git a/src/stub.ts b/src/stub.ts index 112785e..f329511 100644 --- a/src/stub.ts +++ b/src/stub.ts @@ -5,6 +5,7 @@ import { FlatAggregatedFulfillment, extractCouriers, } from './utils.js'; +import { FulfillmentProduct, PackingSolutionQuery } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment_product.js'; export abstract class Stub { @@ -23,8 +24,11 @@ export abstract class Stub public logger?: Logger ) {} - public abstract getTariffCode(fulfillment: FlatAggregatedFulfillment): Promise; - + public abstract matchesZone( + product: FulfillmentProduct, + query: PackingSolutionQuery, + helper?: any + ): Promise; protected abstract evaluateImpl (fulfillments: FlatAggregatedFulfillment[]): Promise; protected abstract submitImpl (fulfillments: FlatAggregatedFulfillment[]): Promise; protected abstract trackImpl (fulfillments: FlatAggregatedFulfillment[]): Promise; diff --git a/src/stubs/dhl_soap.ts b/src/stubs/dhl_soap.ts index c511584..7aaa54a 100644 --- a/src/stubs/dhl_soap.ts +++ b/src/stubs/dhl_soap.ts @@ -20,6 +20,7 @@ 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'; dayjs.extend(customParseFormat); @@ -754,8 +755,12 @@ export class DHLSoap extends Stub { return await Promise.all(promises); }; - async getTariffCode(fulfillment: FlatAggregatedFulfillment): Promise { - return fulfillment.recipient_country.country_code; + async matchesZone( + product: FulfillmentProduct, + query: PackingSolutionQuery, + helper: any, + ): Promise { + return false; } }; From 08f07d39101918366cd9e462f624c48a6d3b862e Mon Sep 17 00:00:00 2001 From: Gerald Baulig Date: Wed, 23 Oct 2024 14:03:59 +0200 Subject: [PATCH 2/2] fix(counting): count quantity of items per parcel --- src/services/fulfillment_product.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/fulfillment_product.ts b/src/services/fulfillment_product.ts index 050bb8e..ee8d0ea 100644 --- a/src/services/fulfillment_product.ts +++ b/src/services/fulfillment_product.ts @@ -905,6 +905,7 @@ export class FulfillmentProductService this.logger.debug('Offer List:', offer_lists); const goods = query.items.map((good): IItem => ({ + sku: `${good.product_id}\t${good.variant_id}`, desc: `${good.product_id}\t${good.variant_id}`, quantity: good.quantity, weight: good.package?.weight_in_kg ?? this.throwStatusCode(