@bisondesk/core-sdk 1.0.667 → 1.0.668

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.
@@ -338,6 +338,15 @@ export type RelatedVehicle = {
338
338
  stockNumber: string;
339
339
  };
340
340
 
341
+ export type VehicleBranchHistoryEntry = {
342
+ branch: string;
343
+ stockNumber: string;
344
+ administrativeNumber?: string;
345
+ changedAt: string;
346
+ changedBy: string;
347
+ reason?: string;
348
+ };
349
+
341
350
  export type VehicleInternalInfo = {
342
351
  createdAt: string;
343
352
  description: {
@@ -355,6 +364,11 @@ export type VehicleInternalInfo = {
355
364
  licensePlate: string;
356
365
  changedAt: string;
357
366
  }[];
367
+ /**
368
+ * One entry per branch the vehicle has left, oldest first. Records the identity it carried
369
+ * there (stock and administrative numbers are redrawn on a move) and who moved it.
370
+ */
371
+ branchHistory?: VehicleBranchHistoryEntry[];
358
372
 
359
373
  /**
360
374
  * This ID is used to link different vehicle objects that represent the same real world asset.
@@ -890,8 +904,99 @@ export enum VehicleActions {
890
904
  ViewPurchase = 'view_purchase',
891
905
  ViewPriceHistory = 'view_price_history',
892
906
  DeleteVehicle = 'delete_vehicle',
907
+ ChangeBranch = 'change_branch',
908
+ }
909
+
910
+ export type VehicleBranchChangeRequest = {
911
+ branch: string;
912
+ reason?: string;
913
+ /** Required when the preview carries a warning that needs acknowledgement. */
914
+ acknowledged?: boolean;
915
+ };
916
+
917
+ export enum VehicleBranchChangeBlockerCode {
918
+ SameBranch = 'same_branch',
919
+ VehicleDeleted = 'vehicle_deleted',
920
+ CurrencyMismatch = 'currency_mismatch',
921
+ FinanceDocumentsExist = 'finance_documents_exist',
922
+ PurchasePaymentsExist = 'purchase_payments_exist',
923
+ DealCommitted = 'deal_committed',
924
+ VehicleSold = 'vehicle_sold',
925
+ LeasingContractExists = 'leasing_contract_exists',
926
+ RelatedVehicles = 'related_vehicles',
927
+ InternalDealQuote = 'internal_deal_quote',
928
+ IntercompanyLinkLive = 'intercompany_link_live',
929
+ HyperdmsOrganisationChange = 'hyperdms_organisation_change',
930
+ StockNumberTaken = 'stock_number_taken',
931
+ }
932
+
933
+ export enum VehicleBranchChangeWarningCode {
934
+ DealsLoseQuotes = 'deals_lose_quotes',
935
+ DealPaymentsExist = 'deal_payments_exist',
936
+ OpenTransports = 'open_transports',
937
+ CopiesWithdrawn = 'copies_withdrawn',
938
+ CopiesCreated = 'copies_created',
939
+ TargetWithoutAccounting = 'target_without_accounting',
940
+ TargetWithoutStorecove = 'target_without_storecove',
893
941
  }
894
942
 
943
+ export type VehicleBranchChangeBlocker = {
944
+ code: VehicleBranchChangeBlockerCode;
945
+ data?: Record<string, unknown>;
946
+ };
947
+
948
+ export type VehicleBranchChangeWarning = {
949
+ code: VehicleBranchChangeWarningCode;
950
+ /** The write refuses unless the request says `acknowledged: true`. */
951
+ requiresAcknowledgement: boolean;
952
+ data?: Record<string, unknown>;
953
+ };
954
+
955
+ export enum VehicleBranchChangeDealOutcome {
956
+ /** Branches are quote-compatible: the deal and its quotes move together. */
957
+ FollowsWithQuotes = 'follows_with_quotes',
958
+ /** The deal moves; its quotes stay archived on the old branch and it needs a new one. */
959
+ FollowsNeedsRequote = 'follows_needs_requote',
960
+ AlreadyOnTarget = 'already_on_target',
961
+ StaysClosed = 'stays_closed',
962
+ /** In Preparation or later: contracts and paperwork exist, so this deal blocks the move. */
963
+ Committed = 'committed',
964
+ }
965
+
966
+ export type VehicleBranchChangeDealImpact = {
967
+ opportunityId: string;
968
+ status: string;
969
+ branch: string;
970
+ outcome: VehicleBranchChangeDealOutcome;
971
+ hasActiveQuote: boolean;
972
+ paymentsCount: number;
973
+ };
974
+
975
+ export type VehicleBranchChangeIdentity = {
976
+ branch: string;
977
+ stockNumber: string;
978
+ administrativeNumber?: string;
979
+ };
980
+
981
+ export type VehicleBranchChangePreview = {
982
+ from: VehicleBranchChangeIdentity;
983
+ /** The administrative number is only known once drawn, so the preview leaves it out. */
984
+ to: Omit<VehicleBranchChangeIdentity, 'administrativeNumber'>;
985
+ blockers: VehicleBranchChangeBlocker[];
986
+ warnings: VehicleBranchChangeWarning[];
987
+ /** Counts of the records that follow are not reported: the warnings above already name them. */
988
+ impact: { deals: VehicleBranchChangeDealImpact[] };
989
+ };
990
+
991
+ export type VehicleBranchChangeResult = {
992
+ vehicle: Vehicle;
993
+ from: VehicleBranchChangeIdentity;
994
+ to: VehicleBranchChangeIdentity;
995
+ deals: VehicleBranchChangeDealImpact[];
996
+ /** Repair steps that failed after the vehicle moved; running the move again retries them. */
997
+ failedSteps: string[];
998
+ };
999
+
895
1000
  export enum PurchaseActions {
896
1001
  ViewDeal = 'view_deal',
897
1002
  ViewSupplierOrganization = 'view_supplier_organization',
@@ -0,0 +1,47 @@
1
+ import { Branch } from '../types/tenants.js';
2
+ import { areBranchesQuoteCompatible } from './tenants.js';
3
+
4
+ const createBranch = (override?: Partial<Branch>): Branch => ({
5
+ id: 'TTC',
6
+ name: 'TTC Belgium',
7
+ country: 'be',
8
+ language: 'nl',
9
+ currency: 'EUR',
10
+ adminNumberPrefix: 'AT',
11
+ stockNumberPrefix: 'TC',
12
+ vatNumber: 'BE0123456789',
13
+ vatRates: { standard: '21' },
14
+ location: { country: 'be' },
15
+ ...override,
16
+ });
17
+
18
+ describe('areBranchesQuoteCompatible', () => {
19
+ it('accepts two branches that agree on country, currency, vat rate and quote mode', () => {
20
+ const a = createBranch();
21
+ const b = createBranch({ id: 'TTC2', name: 'TTC Antwerp', stockNumberPrefix: 'TA' });
22
+
23
+ expect(areBranchesQuoteCompatible(a, b)).toBe(true);
24
+ });
25
+
26
+ it('is true for a branch compared with itself', () => {
27
+ const branch = createBranch();
28
+
29
+ expect(areBranchesQuoteCompatible(branch, branch)).toBe(true);
30
+ });
31
+
32
+ it.each([
33
+ ['country', { country: 'fr' }],
34
+ ['currency', { currency: 'USD' }],
35
+ ['vat rate', { vatRates: { standard: '20' } }],
36
+ ['external quote mode', { externalQuote: true }],
37
+ ])('rejects branches that differ in %s', (_label, override) => {
38
+ expect(areBranchesQuoteCompatible(createBranch(), createBranch(override))).toBe(false);
39
+ });
40
+
41
+ it('treats a missing externalQuote as false rather than as a difference', () => {
42
+ const implicit = createBranch();
43
+ const explicit = createBranch({ externalQuote: false });
44
+
45
+ expect(areBranchesQuoteCompatible(implicit, explicit)).toBe(true);
46
+ });
47
+ });
@@ -21,3 +21,14 @@ export const getBranchStandardVatRate = (tenant: Tenant, branchId: string): stri
21
21
  const branch: Branch = getBranch(tenant, branchId);
22
22
  return branch.vatRates.standard;
23
23
  };
24
+
25
+ /**
26
+ * Whether a quote computed for one branch is still right for the other. A quote's VAT regime,
27
+ * currency, calculator strategy and conditions all come from these four fields, so when they
28
+ * agree the quote can move with its deal; otherwise the deal needs a new one.
29
+ */
30
+ export const areBranchesQuoteCompatible = (a: Branch, b: Branch): boolean =>
31
+ a.country === b.country &&
32
+ a.currency === b.currency &&
33
+ a.vatRates.standard === b.vatRates.standard &&
34
+ (a.externalQuote ?? false) === (b.externalQuote ?? false);