From 55fccb4612f1fa6c11159038cdaedbc6643f0dd3 Mon Sep 17 00:00:00 2001 From: Gerald Baulig Date: Fri, 6 Dec 2024 10:48:50 +0100 Subject: [PATCH 1/2] fix(taxes): apply tax on commercial --- src/service.ts | 132 +++++--------------------------------- src/utils.ts | 168 +++++++++++++++++++++++++++++++++++++++++++++++++ test/utils.ts | 2 +- 3 files changed, 183 insertions(+), 119 deletions(-) create mode 100644 src/utils.ts diff --git a/src/service.ts b/src/service.ts index 557bc13..63a9bc8 100644 --- a/src/service.ts +++ b/src/service.ts @@ -101,6 +101,8 @@ import { DeleteRequest, Filter_ValueType, ReadRequest, + Resource, + ResourceResponse, } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base.js'; import { OperationStatus, @@ -118,61 +120,7 @@ import { VAT } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/amount.js'; import { Subject } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/auth.js'; - -export type BigVAT = { - tax_id: string; - vat: BigNumber; -}; - -export type BigAmount = { - currency_id: string; - gross: BigNumber; - net: BigNumber; - vats: VAT[]; -}; - -export type RatioedTax = Tax & { - tax_ratio?: number; -}; - -export const toObjectMap = (items: any[]) => items.reduce( - (a, b) => { - a[b.id ?? b.payload?.id] = b; - return a; - }, - {} as T -) ?? {}; - -export type OrderMap = { [key: string]: OrderResponse }; -export type ProductMap = { [key: string]: ProductResponse }; -export type FulfillmentMap = { [key: string]: FulfillmentResponse[] }; -export type RatioedTaxMap = { [key: string]: RatioedTax }; -export type CustomerMap = { [key: string]: CustomerResponse }; -export type CurrencyMap = { [key: string]: CurrencyResponse }; -export type ShopMap = { [key: string]: ShopResponse }; -export type OrganizationMap = { [key: string]: OrganizationResponse }; -export type ContactPointMap = { [key: string]: ContactPointResponse }; -export type AddressMap = { [key: string]: AddressResponse }; -export type CountryMap = { [key: string]: CountryResponse }; -export type FulfillmentSolutionMap = { [key: string]: FulfillmentSolutionResponse }; -export type PositionMap = { [key: string]: Position }; -export type StatusMap = { [key: string]: Status }; -export type OperationStatusMap = { [key: string]: OperationStatus }; -export type VATMap = { [key: string]: VAT }; -export type ProductNature = PhysicalProduct & VirtualProduct & ServiceProduct; -export type ProductVariant = PhysicalVariant & VirtualVariant & ServiceVariant; -export type CRUDClient = Client -| Client -| Client -| Client -| Client -| Client -| Client -| Client -| Client -| Client -| Client -| Client; +import { AddressMap, BigAmount, BigVAT, ContactPointMap, CountryMap, CRUDClient, CurrencyMap, CustomerMap, DefaultUrns, FulfillmentMap, FulfillmentSolutionMap, OrderMap, OrganizationMap, PositionMap, ProductMap, ProductNature, ProductVariant, RatioedTax, RatioedTaxMap, ShopMap, toObjectMap, VATMap } from './utils.js'; const CREATE_FULFILLMENT = 'createFulfillment'; @@ -200,39 +148,7 @@ export class OrderingService }; } - private readonly urns = { - instanceType: 'urn:restorecommerce:acs:model:order:Order', - disableFulfillment: 'urn:restorecommerce:order:preferences:disableFulfillment', - disableInvoice: 'urn:restorecommerce:order:preferences:disableInvoice', - entity: 'urn:restorecommerce:acs:names:model:entity', - user: 'urn:restorecommerce:acs:model:user.User', - model: 'urn:restorecommerce:acs:model', - role: 'urn:restorecommerce:acs:names:role', - roleScopingEntity: 'urn:restorecommerce:acs:names:roleScopingEntity', - roleScopingInstance: 'urn:restorecommerce:acs:names:roleScopingInstance', - unauthenticated_user: 'urn:restorecommerce:acs:names:unauthenticated-user', - property: 'urn:restorecommerce:acs:names:model:property', - ownerIndicatoryEntity: 'urn:restorecommerce:acs:names:ownerIndicatoryEntity', - ownerInstance: 'urn:restorecommerce:acs:names:ownerInstance', - orgScope: 'urn:restorecommerce:acs:model:organization.Organization', - subjectID: 'urn:oasis:names:tc:xacml:1.0:subject:subject-id', - resourceID: 'urn:oasis:names:tc:xacml:1.0:resource:resource-id', - actionID: 'urn:oasis:names:tc:xacml:1.0:action:action-id', - action: 'urn:restorecommerce:acs:names:action', - operation: 'urn:restorecommerce:acs:names:operation', - execute: 'urn:restorecommerce:acs:names:action:execute', - permitOverrides: 'urn:oasis:names:tc:xacml:3.0:rule-combining-algorithm:permit-overrides', - denyOverrides: 'urn:oasis:names:tc:xacml:3.0:rule-combining-algorithm:deny-overrides', - create: 'urn:restorecommerce:acs:names:action:create', - read: 'urn:restorecommerce:acs:names:action:read', - modify: 'urn:restorecommerce:acs:names:action:modify', - delete: 'urn:restorecommerce:acs:names:action:delete', - organization: 'urn:restorecommerce:acs:model:organization.Organization', - aclIndicatoryEntity: 'urn:restorecommerce:acs:names:aclIndicatoryEntity', - aclInstance: 'urn:restorecommerce:acs:names:aclInstance', - skipACL: 'urn:restorecommerce:acs:names:skipACL', - maskedProperty: 'urn:restorecommerce:acs:names:obligation:maskedProperty', - }; + private readonly urns = DefaultUrns; private readonly status_codes = { OK: { @@ -334,16 +250,6 @@ export class OrderingService billing: 'billing', }; - get ApiKey(): Subject { - const apiKey = this.cfg.get('authentication:apiKey'); - return apiKey - ? { - id: 'apiKey', - token: apiKey, - } - : undefined; - } - get entityName() { return this.name; } @@ -641,13 +547,7 @@ export class OrderingService ).then( response => { if (response.operation_status?.code === 200) { - return response?.items?.reduce( - (a, b) => { - a[b.payload.id] = b as OrderResponse; - return a; - }, - {} as OrderMap - ) ?? {}; + return toObjectMap(response.items); } else { throw response.operation_status; @@ -851,12 +751,7 @@ export class OrderingService ); } else { - return response.items!.reduce( - (a, b) => { - a[b.payload.id] = b; - return a; - }, {} as ProductMap - ); + return toObjectMap(response.items); } } ); @@ -1405,7 +1300,6 @@ export class OrderingService const vats = taxes.filter( t => ( t.country_id === country?.id && - order.customer_type === CustomerType.PRIVATE && country?.economic_areas?.some( ea => billing_country?.payload?.economic_areas?.includes(ea) ) && ( @@ -1853,10 +1747,10 @@ export class OrderingService items: orders.items?.map(item => ({ order_id: item.payload.id, })), - subject: this.fulfillment_tech_user ?? this.ApiKey ?? request.subject, + subject: this.fulfillment_tech_user ?? request.subject, }, context, - toObjectMap(orders.items), + toObjectMap(orders.items), ).then( r => { r.items?.forEach( @@ -2328,7 +2222,7 @@ export class OrderingService { items: valids.map(item => item.payload), total_count: valids.length, - subject: this.fulfillment_tech_user ?? this.ApiKey ?? request.subject, + subject: this.fulfillment_tech_user ?? request.subject, }, context ).then( @@ -2407,7 +2301,7 @@ export class OrderingService { items: valids.map(item => item.payload), total_count: valids.length, - subject: this.fulfillment_tech_user ?? this.ApiKey ?? request.subject, + subject: this.fulfillment_tech_user ?? request.subject, }, context ).then( @@ -2801,14 +2695,16 @@ export class OrderingService proto => proto.payload! ); - const response = await this.invoice_service!.render( + const action = this.invoice_service.create; + const response = await action( { items: valids, total_count: valids.length, - subject: this.invoice_tech_user ?? this.ApiKey ?? request.subject, + subject: this.invoice_tech_user ?? request.subject, }, context ); + return { items: [ diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..f8b67c3 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,168 @@ +import { BigNumber } from 'bignumber.js'; +import { + Client, +} from '@restorecommerce/grpc-client'; +import { + OrderResponse, +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/order.js'; +import { + PhysicalProduct, + PhysicalVariant, + ProductResponse, + ProductServiceDefinition, + ServiceProduct, + ServiceVariant, + VirtualProduct, + VirtualVariant +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/product.js'; +import { + TaxServiceDefinition, Tax +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/tax.js'; +import { + CustomerServiceDefinition, CustomerResponse, CustomerType, +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/customer.js'; +import { + ShopServiceDefinition, ShopResponse +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/shop.js'; +import { + OrganizationResponse, OrganizationServiceDefinition +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/organization.js'; +import { + ContactPointServiceDefinition, ContactPointResponse +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/contact_point.js'; +import { + CurrencyServiceDefinition, CurrencyResponse +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/currency.js'; +import { + AddressServiceDefinition, AddressResponse +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/address.js'; +import { + CountryServiceDefinition, CountryResponse +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/country.js'; +import { + FulfillmentServiceDefinition, + FulfillmentResponse, +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment.js'; +import { + FulfillmentProductServiceDefinition, + FulfillmentSolutionResponse, +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/fulfillment_product.js'; +import { + Resource, + ResourceResponse, +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base.js'; +import { + OperationStatus, + Status, +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/status.js'; +import { + Position, + InvoiceServiceDefinition +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/invoice.js'; +import { + VAT +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/amount.js'; + +export type BigVAT = { + tax_id: string; + vat: BigNumber; +}; + +export type BigAmount = { + currency_id: string; + gross: BigNumber; + net: BigNumber; + vats: VAT[]; +}; + +export type RatioedTax = Tax & { + tax_ratio?: number; +}; + +export type Entity = Resource & ResourceResponse; +export type ObjectMap = { [key: string]: T }; +export type OrderMap = ObjectMap; +export type ProductMap = ObjectMap; +export type RatioedTaxMap = ObjectMap; +export type CustomerMap = ObjectMap; +export type CurrencyMap = ObjectMap; +export type ShopMap = ObjectMap; +export type OrganizationMap = ObjectMap; +export type ContactPointMap = ObjectMap; +export type AddressMap = ObjectMap; +export type CountryMap = ObjectMap; +export type PositionMap = ObjectMap; +export type StatusMap = ObjectMap; + +export type FulfillmentMap = { [key: string]: FulfillmentResponse[] }; +export type FulfillmentSolutionMap = { [key: string]: FulfillmentSolutionResponse }; +export type OperationStatusMap = { [key: string]: OperationStatus }; +export type VATMap = { [key: string]: VAT }; +export type ProductNature = PhysicalProduct & VirtualProduct & ServiceProduct; +export type ProductVariant = PhysicalVariant & VirtualVariant & ServiceVariant; +export type CRUDClient = Client +| Client +| Client +| Client +| Client +| Client +| Client +| Client +| Client +| Client +| Client +| Client; + + +export const toObjectMap = (items: T[]): ObjectMap => items.reduce( + (a, b) => { + a[b.id ?? b.payload?.id] = b; + return a; + }, + {} as ObjectMap +) ?? {}; + +export const DefaultUrns = { + instanceType: 'urn:restorecommerce:acs:model:order:Order', + ownerIndicatoryEntity: 'urn:restorecommerce:acs:names:ownerIndicatoryEntity', + ownerInstance: 'urn:restorecommerce:acs:names:ownerInstance', + organization: 'urn:restorecommerce:acs:model:organization.Organization', + user: 'urn:restorecommerce:acs:model:user.User', + + shop_fulfillment_create_enabled: 'urn:restorecommerce:shop:setting:order:create:fulfillment:enabled', + shop_fulfillment_submit_enabled: 'urn:restorecommerce:shop:setting:order:submit:fulfillment:enabled', + shop_invoice_create_enabled: 'urn:restorecommerce:shop:setting:order:create:invoice:enabled', + shop_invoice_render_enabled: 'urn:restorecommerce:shop:setting:order:render:invoice:enabled', + shop_invoice_send_enabled: 'urn:restorecommerce:shop:setting:order:send:invoice:enabled', + + + /* + disableFulfillment: 'urn:restorecommerce:order:preferences:disableFulfillment', + disableInvoice: 'urn:restorecommerce:order:preferences:disableInvoice', + entity: 'urn:restorecommerce:acs:names:model:entity', + model: 'urn:restorecommerce:acs:model', + role: 'urn:restorecommerce:acs:names:role', + roleScopingEntity: 'urn:restorecommerce:acs:names:roleScopingEntity', + roleScopingInstance: 'urn:restorecommerce:acs:names:roleScopingInstance', + unauthenticated_user: 'urn:restorecommerce:acs:names:unauthenticated-user', + property: 'urn:restorecommerce:acs:names:model:property', + orgScope: 'urn:restorecommerce:acs:model:organization.Organization', + subjectID: 'urn:oasis:names:tc:xacml:1.0:subject:subject-id', + resourceID: 'urn:oasis:names:tc:xacml:1.0:resource:resource-id', + actionID: 'urn:oasis:names:tc:xacml:1.0:action:action-id', + action: 'urn:restorecommerce:acs:names:action', + operation: 'urn:restorecommerce:acs:names:operation', + execute: 'urn:restorecommerce:acs:names:action:execute', + permitOverrides: 'urn:oasis:names:tc:xacml:3.0:rule-combining-algorithm:permit-overrides', + denyOverrides: 'urn:oasis:names:tc:xacml:3.0:rule-combining-algorithm:deny-overrides', + create: 'urn:restorecommerce:acs:names:action:create', + read: 'urn:restorecommerce:acs:names:action:read', + modify: 'urn:restorecommerce:acs:names:action:modify', + delete: 'urn:restorecommerce:acs:names:action:delete', + aclIndicatoryEntity: 'urn:restorecommerce:acs:names:aclIndicatoryEntity', + aclInstance: 'urn:restorecommerce:acs:names:aclInstance', + skipACL: 'urn:restorecommerce:acs:names:skipACL', + maskedProperty: 'urn:restorecommerce:acs:names:obligation:maskedProperty', + */ +}; +export type KnownUrns = typeof DefaultUrns; \ No newline at end of file diff --git a/test/utils.ts b/test/utils.ts index f303ae2..122d6c7 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -44,7 +44,7 @@ export async function mockServices(configs: { [key: string]: any }) { } if (!rules[name]) { - throw `No mocking rules for ${name} in mocks.ts!` + throw new Error(`No mocking rules for ${name} in mocks.ts!`) } return await new GrpcMockServer( From 5126475e464075df59a94d7b96dc1e410443ea1d Mon Sep 17 00:00:00 2001 From: Gerald Baulig Date: Fri, 6 Dec 2024 10:52:03 +0100 Subject: [PATCH 2/2] fix(vat): return at least 0 vat if none applies --- src/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service.ts b/src/service.ts index 63a9bc8..e707520 100644 --- a/src/service.ts +++ b/src/service.ts @@ -1325,7 +1325,7 @@ export class OrderingService currency_id: unit_price.currency_id, gross: gross.decimalPlaces(precision).toNumber(), net: net.decimalPlaces(precision).toNumber(), - vats, + vats: vats.length ? vats : [{ vat: 0 }], }; } ) ?? []);