@bisondesk/core-sdk 1.0.591 → 1.0.593
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/apis/activities.d.ts +9 -0
- package/lib/apis/activities.d.ts.map +1 -0
- package/lib/apis/activities.js +62 -0
- package/lib/apis/activities.js.map +1 -0
- package/lib/types/country-pricing.d.ts +9 -3
- package/lib/types/country-pricing.d.ts.map +1 -1
- package/lib/types/country-pricing.js.map +1 -1
- package/lib/types/transports.d.ts.map +1 -1
- package/lib/types/transports.js.map +1 -1
- package/lib/types/vehicles.d.ts +14 -1
- package/lib/types/vehicles.d.ts.map +1 -1
- package/lib/types/vehicles.js.map +1 -1
- package/lib/utils/country-pricing.d.ts +5 -0
- package/lib/utils/country-pricing.d.ts.map +1 -0
- package/lib/utils/country-pricing.js +17 -0
- package/lib/utils/country-pricing.js.map +1 -0
- package/lib/utils/country-pricing.test.d.ts +2 -0
- package/lib/utils/country-pricing.test.d.ts.map +1 -0
- package/lib/utils/country-pricing.test.js +65 -0
- package/lib/utils/country-pricing.test.js.map +1 -0
- package/package.json +1 -1
- package/src/apis/activities.ts +97 -0
- package/src/types/country-pricing.ts +19 -4
- package/src/types/transports.ts +1 -1
- package/src/types/vehicles.ts +17 -1
- package/src/utils/country-pricing.test.ts +82 -0
- package/src/utils/country-pricing.ts +44 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { TENANT_ID_ADMIN_HEADER } from '@bisondesk/commons-sdk/constants';
|
|
2
|
+
import { cleanHeaders, fetchJson, getAdminAuth } from '@bisondesk/commons-sdk/fetch';
|
|
3
|
+
import { NextList } from '@bisondesk/commons-sdk/types';
|
|
4
|
+
import { BusinessEntityIds } from '../constants.js';
|
|
5
|
+
import { Activity, ActivityUpdate, ActivityV2Meta, NewActivity } from '../types/activities.js';
|
|
6
|
+
import { DataRecord } from '../types/utils.js';
|
|
7
|
+
|
|
8
|
+
export const createActivity = async <Meta = ActivityV2Meta>(
|
|
9
|
+
tenantId: string,
|
|
10
|
+
activity: NewActivity<Meta>,
|
|
11
|
+
): Promise<DataRecord<Activity<Meta>>> => {
|
|
12
|
+
const auth = await getAdminAuth();
|
|
13
|
+
const response: DataRecord<Activity<Meta>> | undefined = await fetchJson(
|
|
14
|
+
`${process.env.CORE_API_ORIGIN}/api/activities`,
|
|
15
|
+
{
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: cleanHeaders({
|
|
18
|
+
Authorization: auth,
|
|
19
|
+
[TENANT_ID_ADMIN_HEADER]: tenantId,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
}),
|
|
22
|
+
body: JSON.stringify(activity),
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (response == null) {
|
|
27
|
+
throw new Error('Activity not created');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return response;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const listActivitiesByRecord = async (
|
|
34
|
+
tenantId: string,
|
|
35
|
+
businessEntityId: BusinessEntityIds,
|
|
36
|
+
recordId: string,
|
|
37
|
+
nextToken?: string,
|
|
38
|
+
): Promise<NextList<DataRecord<Activity>>> => {
|
|
39
|
+
const auth = await getAdminAuth();
|
|
40
|
+
const url = new URL(
|
|
41
|
+
`/api/activities/${businessEntityId}/${recordId}`,
|
|
42
|
+
process.env.CORE_API_ORIGIN,
|
|
43
|
+
);
|
|
44
|
+
if (nextToken != null) {
|
|
45
|
+
url.searchParams.append('nextToken', nextToken);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const response: NextList<DataRecord<Activity>> | undefined = await fetchJson(url, {
|
|
49
|
+
headers: cleanHeaders({
|
|
50
|
+
Authorization: auth,
|
|
51
|
+
[TENANT_ID_ADMIN_HEADER]: tenantId,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (response == null) {
|
|
56
|
+
throw new Error('Failed to list activities');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return response;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const updateActivity = async (
|
|
63
|
+
tenantId: string,
|
|
64
|
+
activityId: string,
|
|
65
|
+
update: ActivityUpdate,
|
|
66
|
+
): Promise<DataRecord<Activity>> => {
|
|
67
|
+
const auth = await getAdminAuth();
|
|
68
|
+
const response: DataRecord<Activity> | undefined = await fetchJson(
|
|
69
|
+
`${process.env.CORE_API_ORIGIN}/api/activities/${activityId}`,
|
|
70
|
+
{
|
|
71
|
+
method: 'PATCH',
|
|
72
|
+
headers: cleanHeaders({
|
|
73
|
+
Authorization: auth,
|
|
74
|
+
[TENANT_ID_ADMIN_HEADER]: tenantId,
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
}),
|
|
77
|
+
body: JSON.stringify(update),
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (response == null) {
|
|
82
|
+
throw new Error('Activity not updated');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return response;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const deleteActivity = async (tenantId: string, activityId: string): Promise<void> => {
|
|
89
|
+
const auth = await getAdminAuth();
|
|
90
|
+
await fetchJson(`${process.env.CORE_API_ORIGIN}/api/activities/${activityId}`, {
|
|
91
|
+
method: 'DELETE',
|
|
92
|
+
headers: cleanHeaders({
|
|
93
|
+
Authorization: auth,
|
|
94
|
+
[TENANT_ID_ADMIN_HEADER]: tenantId,
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
};
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { Categories } from '../constants.js';
|
|
2
2
|
|
|
3
|
+
type DeliveryFields =
|
|
4
|
+
| {
|
|
5
|
+
/** Flat cost in `currency` per vehicle category, added after conversion. */
|
|
6
|
+
deliveryCostByCategory: Partial<Record<Categories, number>>;
|
|
7
|
+
/** Human-readable, e.g., "Iquique (Chile)". */
|
|
8
|
+
deliveryLocation: string;
|
|
9
|
+
}
|
|
10
|
+
| { deliveryCostByCategory?: never; deliveryLocation?: never };
|
|
11
|
+
|
|
3
12
|
export type CountryPricingRule = {
|
|
4
13
|
countryCode: string; // lower-cased ISO 3166-1 alpha-2 (e.g., "cl")
|
|
5
14
|
currency: string; // ISO 4217 (e.g., "USD")
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Fixed multiplier applied to a price expressed in the owning branch's
|
|
17
|
+
* currency (`branch.currency`) to produce a price in `rule.currency`.
|
|
18
|
+
* For a tenant whose primary branch is EUR, a Bolivia rule with
|
|
19
|
+
* `currency: "USD"` carries `rate ≈ 1.08`. For a tenant whose primary
|
|
20
|
+
* branch is USD, a Belgium rule with `currency: "EUR"` carries
|
|
21
|
+
* `rate ≈ 0.92`.
|
|
22
|
+
*/
|
|
23
|
+
rate: number;
|
|
9
24
|
leasing?: boolean; // Override for leasing visibility
|
|
10
|
-
};
|
|
25
|
+
} & DeliveryFields;
|
package/src/types/transports.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Organization } from './crm.js';
|
|
|
3
3
|
import { Opportunity } from './opportunities.js';
|
|
4
4
|
import { DeliveryTypes } from './quotes.js';
|
|
5
5
|
import { BaseSearchRequest, SortOrder } from './search.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ReferenceData } from './utils.js';
|
|
7
7
|
import { Vehicle, VehiclePurchase } from './vehicles.js';
|
|
8
8
|
|
|
9
9
|
export enum TransportType {
|
package/src/types/vehicles.ts
CHANGED
|
@@ -360,6 +360,7 @@ export type VehicleInternalInfo = {
|
|
|
360
360
|
};
|
|
361
361
|
|
|
362
362
|
export type VehicleOriginalInfo = {
|
|
363
|
+
/** IMMEDIATE source tenant (the predecessor in a sync chain). */
|
|
363
364
|
tenantId: string;
|
|
364
365
|
tenantChain?: string[];
|
|
365
366
|
identification: {
|
|
@@ -367,6 +368,7 @@ export type VehicleOriginalInfo = {
|
|
|
367
368
|
branchId: string;
|
|
368
369
|
stockNumber: string;
|
|
369
370
|
metaId?: string;
|
|
371
|
+
country: string;
|
|
370
372
|
};
|
|
371
373
|
salesConditions: {
|
|
372
374
|
leasing?: boolean;
|
|
@@ -378,6 +380,16 @@ export type VehicleOriginalInfo = {
|
|
|
378
380
|
available?: boolean;
|
|
379
381
|
};
|
|
380
382
|
};
|
|
383
|
+
/**
|
|
384
|
+
* Deepest-origin tenant/branch in a multi-hop sync chain. Carries forward
|
|
385
|
+
* verbatim across hops so consumers can answer "where did this vehicle
|
|
386
|
+
* actually originate?" without walking the chain.
|
|
387
|
+
*/
|
|
388
|
+
owner: {
|
|
389
|
+
tenantId: string;
|
|
390
|
+
branchId: string;
|
|
391
|
+
country: string;
|
|
392
|
+
};
|
|
381
393
|
};
|
|
382
394
|
|
|
383
395
|
export type VehicleAxlesInfo = {
|
|
@@ -523,7 +535,10 @@ export type VehicleExternalInfo = {
|
|
|
523
535
|
remarks?: string;
|
|
524
536
|
available?: boolean;
|
|
525
537
|
};
|
|
526
|
-
}
|
|
538
|
+
} & (
|
|
539
|
+
| { deliveryCost: number; deliveryLocation: string }
|
|
540
|
+
| { deliveryCost?: never; deliveryLocation?: never }
|
|
541
|
+
);
|
|
527
542
|
superstructure?: VehicleSuperstructure;
|
|
528
543
|
weights?: {
|
|
529
544
|
gvw?: number;
|
|
@@ -670,6 +685,7 @@ export enum VehiclePurchaseDeliveryType {
|
|
|
670
685
|
|
|
671
686
|
export type VehicleStatus = {
|
|
672
687
|
deleted?: boolean;
|
|
688
|
+
deletedReason?: string;
|
|
673
689
|
};
|
|
674
690
|
|
|
675
691
|
export type VehiclePriceList = {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Categories } from '../constants.js';
|
|
2
|
+
import { CountryPricingRule } from '../types/country-pricing.js';
|
|
3
|
+
import {
|
|
4
|
+
convertFromBranchCurrency,
|
|
5
|
+
getApplicableDeliveryCost,
|
|
6
|
+
shouldApplyDeliveryCost,
|
|
7
|
+
} from './country-pricing.js';
|
|
8
|
+
|
|
9
|
+
const baseRule: CountryPricingRule = {
|
|
10
|
+
countryCode: 'bo',
|
|
11
|
+
currency: 'USD',
|
|
12
|
+
rate: 1.08,
|
|
13
|
+
deliveryCostByCategory: { [Categories.Truck]: 500 },
|
|
14
|
+
deliveryLocation: 'La Paz (Bolivia)',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('convertFromBranchCurrency', () => {
|
|
18
|
+
test('multiplies by rate', () => {
|
|
19
|
+
expect(convertFromBranchCurrency(1000, { rate: 1.08 })).toBe(1080);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('uses Math.ceil to avoid underselling on partial cents', () => {
|
|
23
|
+
expect(convertFromBranchCurrency(999.5, { rate: 1.0 })).toBe(1000);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('shouldApplyDeliveryCost', () => {
|
|
28
|
+
test('returns false when origin equals rule country', () => {
|
|
29
|
+
expect(shouldApplyDeliveryCost({ countryCode: 'bo' }, 'bo')).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('is case-insensitive', () => {
|
|
33
|
+
expect(shouldApplyDeliveryCost({ countryCode: 'bo' }, 'BO')).toBe(false);
|
|
34
|
+
expect(shouldApplyDeliveryCost({ countryCode: 'BO' }, 'bo')).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('returns true when origin differs from rule country', () => {
|
|
38
|
+
expect(shouldApplyDeliveryCost({ countryCode: 'bo' }, 'be')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('returns true when origin is undefined (safe default)', () => {
|
|
42
|
+
expect(shouldApplyDeliveryCost({ countryCode: 'bo' }, undefined)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('getApplicableDeliveryCost', () => {
|
|
47
|
+
test('returns undefined when same country, even with a configured cost', () => {
|
|
48
|
+
expect(getApplicableDeliveryCost(baseRule, Categories.Truck, 'bo')).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('returns undefined when category is undefined', () => {
|
|
52
|
+
expect(getApplicableDeliveryCost(baseRule, undefined, 'be')).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('returns the matching cost for a different country and known category', () => {
|
|
56
|
+
expect(getApplicableDeliveryCost(baseRule, Categories.Truck, 'be')).toBe(500);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('returns 0 when the category is explicitly mapped to 0 (opt-in with no fee)', () => {
|
|
60
|
+
const zeroFeeRule: CountryPricingRule = {
|
|
61
|
+
countryCode: 'bo',
|
|
62
|
+
currency: 'USD',
|
|
63
|
+
rate: 1.08,
|
|
64
|
+
deliveryCostByCategory: { [Categories.Truck]: 0 },
|
|
65
|
+
deliveryLocation: 'Bolivia',
|
|
66
|
+
};
|
|
67
|
+
expect(getApplicableDeliveryCost(zeroFeeRule, Categories.Truck, 'be')).toBe(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('returns undefined when category has no configured cost', () => {
|
|
71
|
+
expect(getApplicableDeliveryCost(baseRule, Categories.Van, 'be')).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('returns undefined when rule has no deliveryCostByCategory at all', () => {
|
|
75
|
+
const pureRateRule: CountryPricingRule = {
|
|
76
|
+
countryCode: 'bo',
|
|
77
|
+
currency: 'USD',
|
|
78
|
+
rate: 1.08,
|
|
79
|
+
};
|
|
80
|
+
expect(getApplicableDeliveryCost(pureRateRule, Categories.Truck, 'be')).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Categories } from '../constants.js';
|
|
2
|
+
import { CountryPricingRule } from '../types/country-pricing.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Should the delivery cost defined by `rule` be applied for a vehicle whose
|
|
6
|
+
* origin branch is in `originCountry`? Returns `false` when the vehicle is
|
|
7
|
+
* already located in the rule's country (case-insensitive ISO-2 compare).
|
|
8
|
+
*/
|
|
9
|
+
export const shouldApplyDeliveryCost = (
|
|
10
|
+
rule: Pick<CountryPricingRule, 'countryCode'>,
|
|
11
|
+
originCountry: string | undefined,
|
|
12
|
+
): boolean => {
|
|
13
|
+
if (originCountry == null) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return originCountry.toLowerCase() !== rule.countryCode.toLowerCase();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Look up the per-category delivery cost on the rule. Returns `undefined` when delivery is
|
|
21
|
+
* not applicable (same-country suppression, missing category, or category not in the rule's
|
|
22
|
+
* map). Returns the configured number — including `0` — when the rule explicitly covers the
|
|
23
|
+
* category. A `0` is a legitimate value: the operator has opted in for this category but is
|
|
24
|
+
* charging no fee on top (e.g. for vehicles already at the rule's `deliveryLocation`).
|
|
25
|
+
*/
|
|
26
|
+
export const getApplicableDeliveryCost = (
|
|
27
|
+
rule: CountryPricingRule,
|
|
28
|
+
category: string | undefined,
|
|
29
|
+
originCountry: string | undefined,
|
|
30
|
+
): number | undefined => {
|
|
31
|
+
if (!shouldApplyDeliveryCost(rule, originCountry)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
if (category == null) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return rule.deliveryCostByCategory?.[category as Categories];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** Convert an amount in the owning branch's currency to `rule.currency`. */
|
|
41
|
+
export const convertFromBranchCurrency = (
|
|
42
|
+
branchAmount: number,
|
|
43
|
+
rule: Pick<CountryPricingRule, 'rate'>,
|
|
44
|
+
): number => Math.ceil(branchAmount * rule.rate);
|