@bisondesk/core-sdk 1.0.644 → 1.0.646

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.
Files changed (43) hide show
  1. package/lib/types/delivery-settings.d.ts +1 -0
  2. package/lib/types/delivery-settings.d.ts.map +1 -1
  3. package/lib/types/delivery-settings.js.map +1 -1
  4. package/lib/types/internal-events.d.ts +2 -1
  5. package/lib/types/internal-events.d.ts.map +1 -1
  6. package/lib/types/internal-events.js +1 -0
  7. package/lib/types/internal-events.js.map +1 -1
  8. package/lib/types/journeys.d.ts +1 -0
  9. package/lib/types/journeys.d.ts.map +1 -1
  10. package/lib/types/journeys.js.map +1 -1
  11. package/lib/types/offers.d.ts +2 -0
  12. package/lib/types/offers.d.ts.map +1 -1
  13. package/lib/types/offers.js +1 -0
  14. package/lib/types/offers.js.map +1 -1
  15. package/lib/types/quotes.d.ts +6 -0
  16. package/lib/types/quotes.d.ts.map +1 -1
  17. package/lib/types/quotes.js.map +1 -1
  18. package/lib/types/transports.d.ts +93 -21
  19. package/lib/types/transports.d.ts.map +1 -1
  20. package/lib/types/transports.js +18 -2
  21. package/lib/types/transports.js.map +1 -1
  22. package/lib/types/vehicles.d.ts +2 -0
  23. package/lib/types/vehicles.d.ts.map +1 -1
  24. package/lib/types/vehicles.js.map +1 -1
  25. package/lib/utils/search.d.ts +4 -2
  26. package/lib/utils/search.d.ts.map +1 -1
  27. package/lib/utils/search.js +19 -1
  28. package/lib/utils/search.js.map +1 -1
  29. package/lib/utils/transports.d.ts +9 -0
  30. package/lib/utils/transports.d.ts.map +1 -0
  31. package/lib/utils/transports.js +15 -0
  32. package/lib/utils/transports.js.map +1 -0
  33. package/package.json +1 -1
  34. package/src/types/delivery-settings.ts +1 -0
  35. package/src/types/internal-events.ts +1 -0
  36. package/src/types/journeys.ts +5 -0
  37. package/src/types/offers.ts +5 -0
  38. package/src/types/quotes.ts +6 -0
  39. package/src/types/transports.ts +153 -28
  40. package/src/types/vehicles.ts +2 -0
  41. package/src/utils/search.ts +31 -2
  42. package/src/utils/transports.ts +37 -0
  43. package/tsconfig.tsbuildinfo +1 -1
@@ -1,9 +1,15 @@
1
- import { LocationValue, NextList, PaginatedList } from '@bisondesk/commons-sdk/types';
1
+ import { GPSCoord, NextList, PaginatedList } from '@bisondesk/commons-sdk/types';
2
2
  import { Organization } from './crm.js';
3
- import { Opportunity } from './opportunities.js';
4
- import { DeliveryTypes } from './quotes.js';
3
+ import { BaseEvent } from './internal-events.js';
4
+ import { Opportunity, OpportunityPaymentStatus } from './opportunities.js';
5
+ import {
6
+ AddressDeliveryType,
7
+ DeliveryTypes,
8
+ PickUpDeliveryType,
9
+ PortDeliverytype,
10
+ } from './quotes.js';
5
11
  import { BaseSearchRequest, SortOrder } from './search.js';
6
- import { ReferenceData } from './utils.js';
12
+ import { DataRecord, ReferenceData } from './utils.js';
7
13
  import { Vehicle, VehiclePurchase } from './vehicles.js';
8
14
 
9
15
  export enum TransportType {
@@ -42,49 +48,66 @@ export enum TransportPriority {
42
48
  Urgent = 'URGENT',
43
49
  }
44
50
 
45
- export const TransportDeliveryType = {
51
+ export const TransportSalesDeliveryType = {
46
52
  Address: DeliveryTypes.ADDRESS,
47
53
  Port: DeliveryTypes.PORT,
48
54
  PortAndShip: DeliveryTypes.PORT_AND_SHIP,
55
+ PickUp: DeliveryTypes.PICK_UP,
49
56
  } as const;
50
57
 
51
- export type TransportDeliveryType =
52
- (typeof TransportDeliveryType)[keyof typeof TransportDeliveryType];
58
+ /**
59
+ * The customer collects the vehicle. Nothing is driven anywhere, but the papers still have to be
60
+ * arranged and paid for — transit plates, technical control, insurance — which is why it is a
61
+ * delivery type administration reviews rather than the absence of one.
62
+ *
63
+ * `dateOfPickUpByCustomer` is dropped from the quote's shape: the transport's own `date` is the
64
+ * collection day, and two fields for one date only ever disagree.
65
+ */
66
+ export type TransportPickUpDeliveryType = Omit<PickUpDeliveryType, 'dateOfPickUpByCustomer'>;
67
+
68
+ export type TransportSalesDeliveryType = (
69
+ | PortDeliverytype
70
+ | AddressDeliveryType
71
+ | TransportPickUpDeliveryType
72
+ ) & {
73
+ cleaningNeeded?: boolean;
74
+ driver?: string;
75
+ transportCompany?: string;
76
+ date?: string; // Pick-up (purchase) | Delivery (OPP) (leaves parking)
77
+ adminCost?: string; // user-entered administrative cost summed into totalCostExclVat
78
+ stackingVehicleCostPrice?: string; // overrides the stacking rate from the delivery settings
79
+ };
80
+
81
+ export type TransportPurchaseDeliveryType = AddressDeliveryType & {
82
+ cleaningNeeded?: boolean;
83
+ driver?: string;
84
+ transportCompany?: string;
85
+ date?: string; // Pick-up (purchase) | Delivery (OPP) (leaves parking)
86
+ adminCost?: string; // user-entered administrative cost summed into totalCostExclVat
87
+ stackingVehicleCostPrice?: string; // overrides the stacking rate from the delivery settings
88
+ };
53
89
 
54
90
  export type Transport = {
55
91
  id: string;
56
92
  vehicleId: string;
57
93
  opportunityId?: string;
58
94
 
59
- type: TransportType;
60
- deliveryType: TransportDeliveryType;
61
95
  status: TransportStatus;
62
-
63
96
  priority: TransportPriority;
64
97
 
65
98
  // administrative fields
66
99
  adminApproved: boolean;
67
-
100
+ adminApprovedAt?: string;
101
+ adminApprovedBy?: string;
102
+ transportWithoutPayment?: boolean;
68
103
  notes?: string;
69
104
 
70
- // semi-trailer specific fields
71
- cleaningNeeded?: boolean;
72
- stacking?: boolean;
73
- stackingDetails?: string;
74
-
75
- port?: {
76
- sourcePort?: string;
77
- destinationPort?: string;
78
-
79
- deliveryLocation?: string;
80
- bookingRef?: string;
81
- shippingCompany?: string;
82
- ets?: string;
83
- eta?: string;
105
+ totals: {
106
+ totalRetailAmountExclVat: string;
107
+ totalCostExclVat: string;
108
+ estimatedTotalCostExclVat?: string;
84
109
  };
85
110
 
86
- address?: LocationValue;
87
-
88
111
  log: Array<{
89
112
  status: TransportStatus;
90
113
  completedAt: string;
@@ -100,7 +123,16 @@ export type Transport = {
100
123
  createdBy: string;
101
124
  updatedAt: string;
102
125
  updatedBy: string;
103
- };
126
+ } & (
127
+ | {
128
+ type: TransportType.PurchasePickUp;
129
+ delivery: TransportPurchaseDeliveryType;
130
+ }
131
+ | {
132
+ type: TransportType.SaleDelivery;
133
+ delivery: TransportSalesDeliveryType;
134
+ }
135
+ );
104
136
 
105
137
  export enum TransportWarningCode {
106
138
  PurchaseLogisticsNotReady = 'purchase_logistics_not_ready',
@@ -111,15 +143,108 @@ export type TransportWarning = {
111
143
  details?: Record<string, string | number | boolean>;
112
144
  };
113
145
 
146
+ export type TransportCost = Pick<Transport, 'totals'>;
147
+
148
+ export type TransportRecord = DataRecord<Transport>;
149
+
150
+ /**
151
+ * What a requester may change on a transport, field group by field group. The server filters an
152
+ * update through these and returns the ones granted on the record, so the drawer disables what the
153
+ * user cannot touch instead of deciding for itself from the user's roles.
154
+ */
155
+ export enum TransportActions {
156
+ /** The route: addresses, ports, dates, costs. */
157
+ SetDelivery = 'set_delivery',
158
+ SetNotes = 'set_notes',
159
+ /** The administrative sign-off, and the clearance to move before the payment. */
160
+ SetApproval = 'set_approval',
161
+ /** The board column. */
162
+ SetStatus = 'set_status',
163
+ SetPriority = 'set_priority',
164
+ SetBookingRef = 'set_booking_ref',
165
+ }
166
+
167
+ /**
168
+ * Every field a write may set, in one shape. `delivery` is replaced whole when it is sent — there is
169
+ * no partial merge into the stored one — which is why `bookingRef` rides alongside as a scalar the
170
+ * server writes into the delivery it ends up with.
171
+ */
172
+ export type TransportUpdate = {
173
+ delivery?: TransportSalesDeliveryType | TransportPurchaseDeliveryType;
174
+ notes?: string | null;
175
+ adminApproved?: boolean;
176
+ transportWithoutPayment?: boolean;
177
+ status?: TransportStatus;
178
+ priority?: TransportPriority;
179
+ bookingRef?: string | null;
180
+ };
181
+
182
+ type BaseTransportEvent = {
183
+ actionAt: string;
184
+ userId: string;
185
+ tenantId: string;
186
+ transportId: string;
187
+ };
188
+
189
+ export type TransportCreateEvent = BaseTransportEvent & {
190
+ action: 'create';
191
+ data: Transport;
192
+ previousData?: undefined;
193
+ };
194
+
195
+ export type TransportUpdateEvent = BaseTransportEvent & {
196
+ action: 'update';
197
+ data: Transport;
198
+ previousData: Transport;
199
+ };
200
+
201
+ /** Published whenever a transport is written. Carries both sides so subscribers judge relevance. */
202
+ export type TransportEvent = BaseEvent & (TransportCreateEvent | TransportUpdateEvent);
203
+
204
+ /**
205
+ * Where the money stands on the record the transport serves: the customer's payments on the
206
+ * opportunity for a sale delivery, ours to the supplier for a purchase pick-up. Denormalised at
207
+ * indexing time so the board can show it without joining.
208
+ */
209
+ export type TransportPayment = {
210
+ status: OpportunityPaymentStatus;
211
+ /** Only a sale delivery knows the ratio — a pick-up has milestones, not a running total. */
212
+ percentagePaid?: string;
213
+ };
214
+
215
+ /** Which address the map pin came from, so the UI can tell an exact pin from an approximate one. */
216
+ export enum TransportLocationSource {
217
+ DeliveryAddress = 'delivery_address',
218
+ OriginPort = 'origin_port',
219
+ OrgAddress = 'org_address',
220
+ }
221
+
114
222
  export type SearchTransport = {
115
223
  transport: Transport;
116
224
  vehicle: Vehicle;
117
225
  org: Organization;
118
226
  opportunity?: Opportunity;
119
227
  purchase: VehiclePurchase;
228
+ tasks: {
229
+ totalTasks: number;
230
+ openTasks: number;
231
+ nextDueAt?: string;
232
+ };
120
233
  custom: {
121
234
  commercialUserIds: string[];
122
235
  warnings: TransportWarning[];
236
+ priorityOrdinal: number;
237
+ payment: TransportPayment;
238
+ /**
239
+ * Where the transport happens: the delivery address, or the origin port for a shipped delivery.
240
+ * Denormalised into one field because the map clusters over a single geo_point, and a port
241
+ * delivery carries no address of its own.
242
+ *
243
+ * Deliberately narrower than `GPSCoord`: a geo_point field rejects any key beyond lat/lon, so
244
+ * the map zoom the coordinates picker stores must not reach the index.
245
+ */
246
+ location?: Pick<GPSCoord, 'lat' | 'lon'>;
247
+ locationSource?: TransportLocationSource;
123
248
  };
124
249
  };
125
250
 
@@ -142,6 +142,7 @@ export type NewVehicleRequest = {
142
142
  deliveryType?: VehiclePurchaseDeliveryType;
143
143
  pickupAddress?: LocationValue;
144
144
  status: VehiclePurchaseLogisticsStatus;
145
+ transportWithoutPayment?: boolean;
145
146
  };
146
147
  prices: {
147
148
  minimum?: string;
@@ -855,6 +856,7 @@ export type VehiclePurchase = {
855
856
  deliveryType?: VehiclePurchaseDeliveryType;
856
857
  pickupAddress?: LocationValue;
857
858
  availableWeek?: string; // ISOWEEK, e.g. 2016-W02
859
+ transportWithoutPayment?: boolean;
858
860
  };
859
861
  };
860
862
 
@@ -5,6 +5,7 @@ import { LeasingContract } from '../types/leasing.js';
5
5
  import { DebtorsDossier, SearchDebtorsDossier } from '../types/leasing-debtors.js';
6
6
  import { SearchLeasingContract } from '../types/leasing-search.js';
7
7
  import { Opportunity, SearchOpportunity } from '../types/opportunities.js';
8
+ import { SearchTransport, Transport } from '../types/transports.js';
8
9
  import { SearchVehicle, Vehicle } from '../types/vehicles.js';
9
10
 
10
11
  export type GlobalBusinessEntity =
@@ -12,13 +13,15 @@ export type GlobalBusinessEntity =
12
13
  | Vehicle
13
14
  | Organization
14
15
  | LeasingContract
15
- | DebtorsDossier;
16
+ | DebtorsDossier
17
+ | Transport;
16
18
  export type GlobalSearchEntity =
17
19
  | SearchOpportunity
18
20
  | SearchVehicle
19
21
  | SearchOrganization
20
22
  | SearchLeasingContract
21
- | SearchDebtorsDossier;
23
+ | SearchDebtorsDossier
24
+ | SearchTransport;
22
25
 
23
26
  export const getCustomerInformation = (org?: Organization, contact?: Contact) => {
24
27
  if (org != null) {
@@ -41,6 +44,7 @@ export const mappings: {
41
44
  [key in BusinessEntityIds]?: {
42
45
  getMainEntity: (entity: any) => GlobalBusinessEntity;
43
46
  getTitle: (entity: any) => string;
47
+ getTitleForSearchEntity?: (entity: any) => string;
44
48
  getTags: (entity: any) => string[];
45
49
  getTagsForSearchEntity?: (entity: any) => string[];
46
50
  getDescription?: (entity: any) => string;
@@ -123,6 +127,30 @@ export const mappings: {
123
127
  getTitle: (dossier: DebtorsDossier) => dossier.id,
124
128
  getTags: (dossier: DebtorsDossier) => [dossier.id].filter(Boolean) as string[],
125
129
  },
130
+ // A transport on its own knows only ids, so the readable title and description come from the
131
+ // search entity (vehicle + customer). The bare-record variants stay on the id as a fallback.
132
+ [BusinessEntityIds.Transports]: {
133
+ getMainEntity: (entity: SearchTransport) => entity.transport,
134
+ getTitle: (transport: Transport) => transport.id,
135
+ getTitleForSearchEntity: (entity: SearchTransport) =>
136
+ entity.vehicle?.external.identification?.stockNumber ?? entity.transport.id,
137
+ getDescription: (entity: SearchTransport) => {
138
+ const customer = getCustomerInformation(entity.org);
139
+ const parts = [customer?.code, customer?.title].filter(Boolean) as string[];
140
+ // The API rejects an empty association description, so never return one.
141
+ return parts.length > 0 ? joinWithSeparator(parts) : entity.transport.id;
142
+ },
143
+ getTags: (transport: Transport) => [transport.id],
144
+ getTagsForSearchEntity: (entity: SearchTransport) => {
145
+ const vehicleTags =
146
+ entity.vehicle != null
147
+ ? getTagsForBusinessEntity(BusinessEntityIds.Vehicles, entity.vehicle)
148
+ : [];
149
+ return [...vehicleTags, entity.transport.id, entity.transport.opportunityId].filter(
150
+ Boolean,
151
+ ) as string[];
152
+ },
153
+ },
126
154
  };
127
155
 
128
156
  const getMapping = (businessEntityId: BusinessEntityIds) => {
@@ -186,6 +214,7 @@ export const getTitleForSearchBusinessEntity = (
186
214
  businessEntityId: BusinessEntityIds,
187
215
  searchEntity: GlobalSearchEntity,
188
216
  ) =>
217
+ getMapping(businessEntityId).getTitleForSearchEntity?.(searchEntity) ??
189
218
  getTitleForBusinessEntity(
190
219
  businessEntityId,
191
220
  getMainEntityFromSearchEntity(businessEntityId, searchEntity),
@@ -0,0 +1,37 @@
1
+ import { Opportunity } from '../types/opportunities.js';
2
+ import { SearchTransport, Transport, TransportType } from '../types/transports.js';
3
+ import { VehiclePurchase, VehiclePurchaseLogisticsStatus } from '../types/vehicles.js';
4
+
5
+ /**
6
+ * A transport is finished when the journey it describes has demonstrably happened: the customer has
7
+ * the vehicle for a sale delivery, the vehicle is on our park for a purchase pick-up. From then on
8
+ * the record is history and the server refuses edits to it.
9
+ *
10
+ * Not the same thing as the transport's own `doneAt`. Delivery can be declared on the opportunity
11
+ * (a CMR, a delivery confirmation) and arrival on the purchase, without the transport ever being
12
+ * moved — and reversing either of those is what unlocks the transport again, which a status stamped
13
+ * on the transport itself could not express.
14
+ *
15
+ * Shared so the board, the drawer and the server all answer the question the same way.
16
+ */
17
+ export const isTransportCompleted = (
18
+ transport: Transport,
19
+ context: { opportunity?: Opportunity; purchase?: VehiclePurchase },
20
+ ): boolean => {
21
+ if (transport.type === TransportType.SaleDelivery) {
22
+ return context.opportunity?.deliveredAt != null;
23
+ }
24
+
25
+ const status = context.purchase?.logistics.status;
26
+ return (
27
+ status === VehiclePurchaseLogisticsStatus.Arrived ||
28
+ status === VehiclePurchaseLogisticsStatus.CheckedIn
29
+ );
30
+ };
31
+
32
+ /** The same question asked of a search document, which already carries both sides. */
33
+ export const isSearchTransportCompleted = (record: SearchTransport): boolean =>
34
+ isTransportCompleted(record.transport, {
35
+ opportunity: record.opportunity,
36
+ purchase: record.purchase,
37
+ });