grm-shared-library 1.1.200 → 1.1.201

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 (136) hide show
  1. package/dist/cjs/kafka/billing-topics.js +83 -0
  2. package/dist/cjs/kafka/billing-topics.js.map +1 -0
  3. package/dist/cjs/kafka/index.js +1 -0
  4. package/dist/cjs/kafka/index.js.map +1 -1
  5. package/dist/cjs/modules/partner/constants/partner-defaults.const.js +4 -0
  6. package/dist/cjs/modules/partner/constants/partner-defaults.const.js.map +1 -1
  7. package/dist/cjs/modules/partner/constants/partner-kafka.const.js +18 -1
  8. package/dist/cjs/modules/partner/constants/partner-kafka.const.js.map +1 -1
  9. package/dist/cjs/modules/partner/dtos/create-partner-integration.dto.js +34 -0
  10. package/dist/cjs/modules/partner/dtos/create-partner-integration.dto.js.map +1 -1
  11. package/dist/cjs/modules/partner/dtos/partner-member-profile-lookup.dto.js +52 -0
  12. package/dist/cjs/modules/partner/dtos/partner-member-profile-lookup.dto.js.map +1 -0
  13. package/dist/cjs/modules/partner/dtos/partner-member-profile.dto.js +91 -0
  14. package/dist/cjs/modules/partner/dtos/partner-member-profile.dto.js.map +1 -0
  15. package/dist/cjs/modules/partner/dtos/partner-membership-query.dto.js +75 -0
  16. package/dist/cjs/modules/partner/dtos/partner-membership-query.dto.js.map +1 -0
  17. package/dist/cjs/modules/partner/dtos/sync-partner-membership.dto.js +149 -0
  18. package/dist/cjs/modules/partner/dtos/sync-partner-membership.dto.js.map +1 -0
  19. package/dist/cjs/modules/partner/dtos/update-partner-membership.dto.js +38 -0
  20. package/dist/cjs/modules/partner/dtos/update-partner-membership.dto.js.map +1 -0
  21. package/dist/cjs/modules/partner/enums/partner-adapter-operation.enum.js +6 -0
  22. package/dist/cjs/modules/partner/enums/partner-adapter-operation.enum.js.map +1 -1
  23. package/dist/cjs/modules/partner/enums/partner-member-document-type.enum.js +17 -0
  24. package/dist/cjs/modules/partner/enums/partner-member-document-type.enum.js.map +1 -0
  25. package/dist/cjs/modules/partner/enums/partner-member-gender.enum.js +19 -0
  26. package/dist/cjs/modules/partner/enums/partner-member-gender.enum.js.map +1 -0
  27. package/dist/cjs/modules/partner/enums/partner-member-profile-field.enum.js +23 -0
  28. package/dist/cjs/modules/partner/enums/partner-member-profile-field.enum.js.map +1 -0
  29. package/dist/cjs/modules/partner/enums/partner-member-state.enum.js +49 -0
  30. package/dist/cjs/modules/partner/enums/partner-member-state.enum.js.map +1 -0
  31. package/dist/cjs/modules/partner/enums/partner-membership-package-scope.enum.js +24 -0
  32. package/dist/cjs/modules/partner/enums/partner-membership-package-scope.enum.js.map +1 -0
  33. package/dist/cjs/modules/partner/enums/partner-membership-status.enum.js +65 -0
  34. package/dist/cjs/modules/partner/enums/partner-membership-status.enum.js.map +1 -0
  35. package/dist/cjs/modules/partner/enums/partner-membership-subject-type.enum.js +18 -0
  36. package/dist/cjs/modules/partner/enums/partner-membership-subject-type.enum.js.map +1 -0
  37. package/dist/cjs/modules/partner/index.js +16 -0
  38. package/dist/cjs/modules/partner/index.js.map +1 -1
  39. package/dist/cjs/modules/partner/interfaces/partner-member-profile.js +3 -0
  40. package/dist/cjs/modules/partner/interfaces/partner-member-profile.js.map +1 -0
  41. package/dist/cjs/modules/partner/interfaces/partner-membership-descriptor.js +3 -0
  42. package/dist/cjs/modules/partner/interfaces/partner-membership-descriptor.js.map +1 -0
  43. package/dist/cjs/modules/partner/interfaces/partner-membership.js +3 -0
  44. package/dist/cjs/modules/partner/interfaces/partner-membership.js.map +1 -0
  45. package/dist/cjs/modules/partner/utils/partner-membership.util.js +291 -0
  46. package/dist/cjs/modules/partner/utils/partner-membership.util.js.map +1 -0
  47. package/dist/cjs/modules/partner/utils/partner-payload.util.js +84 -2
  48. package/dist/cjs/modules/partner/utils/partner-payload.util.js.map +1 -1
  49. package/dist/cjs/modules/permission/data/permissions.data.js +3 -1
  50. package/dist/cjs/modules/permission/data/permissions.data.js.map +1 -1
  51. package/dist/cjs/modules/permission/enums/permission-actions.enum.js +7 -0
  52. package/dist/cjs/modules/permission/enums/permission-actions.enum.js.map +1 -1
  53. package/dist/cjs/modules/role/data/roles.data.js +8 -0
  54. package/dist/cjs/modules/role/data/roles.data.js.map +1 -1
  55. package/dist/esm/kafka/billing-topics.js +80 -0
  56. package/dist/esm/kafka/billing-topics.js.map +1 -0
  57. package/dist/esm/kafka/index.js +1 -0
  58. package/dist/esm/kafka/index.js.map +1 -1
  59. package/dist/esm/modules/partner/constants/partner-defaults.const.js +4 -0
  60. package/dist/esm/modules/partner/constants/partner-defaults.const.js.map +1 -1
  61. package/dist/esm/modules/partner/constants/partner-kafka.const.js +17 -0
  62. package/dist/esm/modules/partner/constants/partner-kafka.const.js.map +1 -1
  63. package/dist/esm/modules/partner/dtos/create-partner-integration.dto.js +34 -0
  64. package/dist/esm/modules/partner/dtos/create-partner-integration.dto.js.map +1 -1
  65. package/dist/esm/modules/partner/dtos/partner-member-profile-lookup.dto.js +48 -0
  66. package/dist/esm/modules/partner/dtos/partner-member-profile-lookup.dto.js.map +1 -0
  67. package/dist/esm/modules/partner/dtos/partner-member-profile.dto.js +87 -0
  68. package/dist/esm/modules/partner/dtos/partner-member-profile.dto.js.map +1 -0
  69. package/dist/esm/modules/partner/dtos/partner-membership-query.dto.js +71 -0
  70. package/dist/esm/modules/partner/dtos/partner-membership-query.dto.js.map +1 -0
  71. package/dist/esm/modules/partner/dtos/sync-partner-membership.dto.js +144 -0
  72. package/dist/esm/modules/partner/dtos/sync-partner-membership.dto.js.map +1 -0
  73. package/dist/esm/modules/partner/dtos/update-partner-membership.dto.js +34 -0
  74. package/dist/esm/modules/partner/dtos/update-partner-membership.dto.js.map +1 -0
  75. package/dist/esm/modules/partner/enums/partner-adapter-operation.enum.js +6 -0
  76. package/dist/esm/modules/partner/enums/partner-adapter-operation.enum.js.map +1 -1
  77. package/dist/esm/modules/partner/enums/partner-member-document-type.enum.js +14 -0
  78. package/dist/esm/modules/partner/enums/partner-member-document-type.enum.js.map +1 -0
  79. package/dist/esm/modules/partner/enums/partner-member-gender.enum.js +16 -0
  80. package/dist/esm/modules/partner/enums/partner-member-gender.enum.js.map +1 -0
  81. package/dist/esm/modules/partner/enums/partner-member-profile-field.enum.js +20 -0
  82. package/dist/esm/modules/partner/enums/partner-member-profile-field.enum.js.map +1 -0
  83. package/dist/esm/modules/partner/enums/partner-member-state.enum.js +46 -0
  84. package/dist/esm/modules/partner/enums/partner-member-state.enum.js.map +1 -0
  85. package/dist/esm/modules/partner/enums/partner-membership-package-scope.enum.js +21 -0
  86. package/dist/esm/modules/partner/enums/partner-membership-package-scope.enum.js.map +1 -0
  87. package/dist/esm/modules/partner/enums/partner-membership-status.enum.js +62 -0
  88. package/dist/esm/modules/partner/enums/partner-membership-status.enum.js.map +1 -0
  89. package/dist/esm/modules/partner/enums/partner-membership-subject-type.enum.js +15 -0
  90. package/dist/esm/modules/partner/enums/partner-membership-subject-type.enum.js.map +1 -0
  91. package/dist/esm/modules/partner/index.js +16 -0
  92. package/dist/esm/modules/partner/index.js.map +1 -1
  93. package/dist/esm/modules/partner/interfaces/partner-member-profile.js +2 -0
  94. package/dist/esm/modules/partner/interfaces/partner-member-profile.js.map +1 -0
  95. package/dist/esm/modules/partner/interfaces/partner-membership-descriptor.js +2 -0
  96. package/dist/esm/modules/partner/interfaces/partner-membership-descriptor.js.map +1 -0
  97. package/dist/esm/modules/partner/interfaces/partner-membership.js +2 -0
  98. package/dist/esm/modules/partner/interfaces/partner-membership.js.map +1 -0
  99. package/dist/esm/modules/partner/utils/partner-membership.util.js +277 -0
  100. package/dist/esm/modules/partner/utils/partner-membership.util.js.map +1 -0
  101. package/dist/esm/modules/partner/utils/partner-payload.util.js +83 -1
  102. package/dist/esm/modules/partner/utils/partner-payload.util.js.map +1 -1
  103. package/dist/esm/modules/permission/data/permissions.data.js +3 -1
  104. package/dist/esm/modules/permission/data/permissions.data.js.map +1 -1
  105. package/dist/esm/modules/permission/enums/permission-actions.enum.js +7 -0
  106. package/dist/esm/modules/permission/enums/permission-actions.enum.js.map +1 -1
  107. package/dist/esm/modules/role/data/roles.data.js +8 -0
  108. package/dist/esm/modules/role/data/roles.data.js.map +1 -1
  109. package/dist/types/kafka/billing-topics.d.ts +77 -0
  110. package/dist/types/kafka/index.d.ts +1 -0
  111. package/dist/types/modules/partner/constants/partner-defaults.const.d.ts +4 -0
  112. package/dist/types/modules/partner/constants/partner-kafka.const.d.ts +17 -0
  113. package/dist/types/modules/partner/dtos/create-partner-integration.dto.d.ts +4 -0
  114. package/dist/types/modules/partner/dtos/partner-member-profile-lookup.dto.d.ts +45 -0
  115. package/dist/types/modules/partner/dtos/partner-member-profile.dto.d.ts +28 -0
  116. package/dist/types/modules/partner/dtos/partner-membership-query.dto.d.ts +19 -0
  117. package/dist/types/modules/partner/dtos/sync-partner-membership.dto.d.ts +114 -0
  118. package/dist/types/modules/partner/dtos/update-partner-membership.dto.d.ts +18 -0
  119. package/dist/types/modules/partner/enums/partner-adapter-operation.enum.d.ts +7 -1
  120. package/dist/types/modules/partner/enums/partner-member-document-type.enum.d.ts +12 -0
  121. package/dist/types/modules/partner/enums/partner-member-gender.enum.d.ts +14 -0
  122. package/dist/types/modules/partner/enums/partner-member-profile-field.enum.d.ts +18 -0
  123. package/dist/types/modules/partner/enums/partner-member-state.enum.d.ts +39 -0
  124. package/dist/types/modules/partner/enums/partner-membership-package-scope.enum.d.ts +19 -0
  125. package/dist/types/modules/partner/enums/partner-membership-status.enum.d.ts +53 -0
  126. package/dist/types/modules/partner/enums/partner-membership-subject-type.enum.d.ts +13 -0
  127. package/dist/types/modules/partner/index.d.ts +16 -0
  128. package/dist/types/modules/partner/interfaces/partner-integration.d.ts +22 -0
  129. package/dist/types/modules/partner/interfaces/partner-member-profile.d.ts +65 -0
  130. package/dist/types/modules/partner/interfaces/partner-membership-descriptor.d.ts +108 -0
  131. package/dist/types/modules/partner/interfaces/partner-membership.d.ts +149 -0
  132. package/dist/types/modules/partner/interfaces/partner-provider-descriptor.d.ts +16 -0
  133. package/dist/types/modules/partner/utils/partner-membership.util.d.ts +156 -0
  134. package/dist/types/modules/partner/utils/partner-payload.util.d.ts +27 -0
  135. package/dist/types/modules/permission/enums/permission-actions.enum.d.ts +2 -0
  136. package/package.json +1 -1
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Where one subject's membership with one partner has got to.
3
+ *
4
+ * A membership is an ENRICHMENT, never a gate. Nothing in this lifecycle can
5
+ * stop an emergency reaching a partner: a subject whose membership is
6
+ * INCOMPLETE_PROFILE or FAILED still has an SOS raised for them, just without a
7
+ * member id attached. That property is what makes it safe to provision
8
+ * memberships from a background worker rather than on the alert path.
9
+ */
10
+ export declare enum PartnerMembershipStatus {
11
+ /** Decided that this subject needs a membership; not yet created at the partner. */
12
+ PENDING = "PENDING",
13
+ /**
14
+ * Blocked on personal details the partner requires and the platform does
15
+ * not hold - a date of birth, most often. `missingFields` names them, so an
16
+ * operator is told exactly what to collect rather than seeing a bare error.
17
+ */
18
+ INCOMPLETE_PROFILE = "INCOMPLETE_PROFILE",
19
+ /** A create call is in flight at the partner. */
20
+ PROVISIONING = "PROVISIONING",
21
+ /** The partner has issued a member id. */
22
+ ACTIVE = "ACTIVE",
23
+ /** Every attempt failed for a reason retrying cannot fix. */
24
+ FAILED = "FAILED",
25
+ /**
26
+ * A create was made and its outcome is UNKNOWN - the reply was lost, or the
27
+ * partner could not be asked afterwards whether it landed.
28
+ *
29
+ * Deliberately a dead end rather than another retry. A partner's member API
30
+ * is not idempotent, so retrying an attempt that may have succeeded is how
31
+ * one person acquires two memberships - and on a seat-limited plan the
32
+ * duplicate quietly consumes cover belonging to somebody else. A human
33
+ * checks with the partner and resolves it, exactly as an abandoned dispatch
34
+ * is handled.
35
+ */
36
+ NEEDS_REVIEW = "NEEDS_REVIEW",
37
+ /**
38
+ * The subscription behind the membership ended. The member is left in place
39
+ * at the partner - cancelling someone's ambulance cover is the partner's
40
+ * decision to action, not a side effect of a Clarion row changing - but the
41
+ * member id is no longer quoted on new emergencies.
42
+ */
43
+ CANCELLED = "CANCELLED"
44
+ }
45
+ /**
46
+ * Statuses from which the worker may make another attempt on its own.
47
+ *
48
+ * NEEDS_REVIEW and FAILED are deliberately absent: both need a human to decide,
49
+ * and an automatic retry past either is how a duplicate member gets created.
50
+ */
51
+ export declare const RETRYABLE_PARTNER_MEMBERSHIP_STATUSES: readonly PartnerMembershipStatus[];
52
+ /** Statuses that need an operator before anything else can happen. */
53
+ export declare const PARTNER_MEMBERSHIP_ATTENTION_STATUSES: readonly PartnerMembershipStatus[];
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Who a membership belongs to, in Clarion's own vocabulary.
3
+ *
4
+ * Deliberately NOT "user": a corporate member may have no platform login, and a
5
+ * dependent is never a membership of their own - they are carried on the
6
+ * primary member's, because that is how partners price and structure cover.
7
+ */
8
+ export declare enum PartnerMembershipSubjectType {
9
+ /** The platform user who holds an individual subscription. */
10
+ INDIVIDUAL_SUBSCRIBER = "INDIVIDUAL_SUBSCRIBER",
11
+ /** One member of a corporate client, covered by that client's subscription. */
12
+ CORPORATE_MEMBER = "CORPORATE_MEMBER"
13
+ }
@@ -11,6 +11,13 @@ export * from './enums/partner-location-provider.enum';
11
11
  export * from './enums/partner-adapter-operation.enum';
12
12
  export * from './enums/incident-partner-routing-status.enum';
13
13
  export * from './enums/partner-dispatch-scope.enum';
14
+ export * from './enums/partner-membership-status.enum';
15
+ export * from './enums/partner-membership-package-scope.enum';
16
+ export * from './enums/partner-membership-subject-type.enum';
17
+ export * from './enums/partner-member-gender.enum';
18
+ export * from './enums/partner-member-document-type.enum';
19
+ export * from './enums/partner-member-profile-field.enum';
20
+ export * from './enums/partner-member-state.enum';
14
21
  export * from './interfaces/partner-provider-descriptor';
15
22
  export * from './interfaces/partner-integration';
16
23
  export * from './interfaces/partner-coverage-rule';
@@ -22,6 +29,9 @@ export * from './interfaces/incident-partner-routing';
22
29
  export * from './interfaces/partner-routing-result';
23
30
  export * from './interfaces/partner-connection-test-result';
24
31
  export * from './interfaces/partner-dispatch-events';
32
+ export * from './interfaces/partner-member-profile';
33
+ export * from './interfaces/partner-membership-descriptor';
34
+ export * from './interfaces/partner-membership';
25
35
  export * from './constants/partner-defaults.const';
26
36
  export * from './constants/partner-kafka.const';
27
37
  export * from './dtos/create-partner-integration.dto';
@@ -32,5 +42,11 @@ export * from './dtos/route-incident-to-partners.dto';
32
42
  export * from './dtos/partner-dispatch-decision.dto';
33
43
  export * from './dtos/amend-partner-dispatch.dto';
34
44
  export * from './dtos/update-incident-partner-routing.dto';
45
+ export * from './dtos/partner-member-profile.dto';
46
+ export * from './dtos/partner-member-profile-lookup.dto';
47
+ export * from './dtos/update-partner-membership.dto';
48
+ export * from './dtos/partner-membership-query.dto';
49
+ export * from './dtos/sync-partner-membership.dto';
35
50
  export * from './utils/partner-routing.util';
36
51
  export * from './utils/partner-payload.util';
52
+ export * from './utils/partner-membership.util';
@@ -1,6 +1,7 @@
1
1
  import { CountryCode } from '../../country/enums/country-code.enum';
2
2
  import { PartnerIntegrationEnvironment } from '../enums/partner-integration-environment.enum';
3
3
  import { PartnerIntegrationStatus } from '../enums/partner-integration-status.enum';
4
+ import { PartnerMembershipPackageScope } from '../enums/partner-membership-package-scope.enum';
4
5
  import { PartnerProviderCode } from '../enums/partner-provider-code.enum';
5
6
  import { PartnerRoutingMode } from '../enums/partner-routing-mode.enum';
6
7
  import { PartnerServiceCapability } from '../enums/partner-service-capability.enum';
@@ -42,9 +43,30 @@ export interface PartnerIntegration {
42
43
  enabled: boolean;
43
44
  /** Order in which partnerships are considered when several cover one incident. */
44
45
  priority: number;
46
+ /**
47
+ * Whether subscribers are registered with this partner as members ahead of
48
+ * any emergency.
49
+ *
50
+ * Off by default, and deliberately so: registering a person with a third
51
+ * party sends their name, telephone number and date of birth outside the
52
+ * platform, which is a decision an organization makes on purpose and not
53
+ * one that follows from saving a form.
54
+ */
55
+ membershipEnabled: boolean;
56
+ /** Which packages' subscribers are registered. Ignored when membership is off. */
57
+ membershipPackageScope: PartnerMembershipPackageScope;
58
+ /** Packages to register, when the scope is SELECTED. */
59
+ membershipPackageIds: number[];
45
60
  /** ISO 3166-1 alpha-2 country the partnership operates in. */
46
61
  countryCode: CountryCode;
47
62
  status: PartnerIntegrationStatus;
63
+ /**
64
+ * True when the registered adapter can create members for this provider at
65
+ * all. Served from the adapter descriptor, not stored - so a partnership
66
+ * configured before a partner gained memberships starts reporting it the
67
+ * moment the adapter does.
68
+ */
69
+ membershipSupported?: boolean;
48
70
  partnerContactName?: string | null;
49
71
  partnerContactPhone?: string | null;
50
72
  partnerContactEmail?: string | null;
@@ -0,0 +1,65 @@
1
+ import { PartnerMemberDocumentType } from '../enums/partner-member-document-type.enum';
2
+ import { PartnerMemberGender } from '../enums/partner-member-gender.enum';
3
+ /**
4
+ * A person, as a partner membership records them - provider-neutral.
5
+ *
6
+ * This is deliberately NOT a Clarion user, a billing client or a beneficiary.
7
+ * Those are three different rows in three different services, and a partner
8
+ * cares about none of that distinction: it wants a name, a number and enough
9
+ * identity to tell two people apart. Assembling one of these is the ONLY place
10
+ * that has to know where each fact lives, so adding a partner never means
11
+ * re-learning Clarion's data model.
12
+ *
13
+ * Every field except the name is optional here, because what a partner
14
+ * REQUIRES is the partner's business and is declared in its
15
+ * {@link PartnerMembershipDescriptor}. A profile that satisfies one partner may
16
+ * be short for another, and the platform reports exactly which fields are
17
+ * short rather than failing opaquely.
18
+ */
19
+ export interface PartnerMemberProfile {
20
+ /** Full name as the partner should record it. */
21
+ fullName: string;
22
+ /** E.164, e.g. `+254712345678`. Normalized before it ever reaches here. */
23
+ phoneNumber?: string | null;
24
+ email?: string | null;
25
+ /**
26
+ * ISO-8601 CALENDAR DATE, `YYYY-MM-DD` - never a timestamp.
27
+ *
28
+ * A date of birth has no time and no zone, and sending one that does is how
29
+ * a member silently acquires a birthday a day either side of their real one
30
+ * depending on where the server happens to be.
31
+ */
32
+ dateOfBirth?: string | null;
33
+ gender?: PartnerMemberGender | null;
34
+ documentType?: PartnerMemberDocumentType | null;
35
+ /** Alphanumeric, as issued. Treated as personal data: masked in every log. */
36
+ documentNumber?: string | null;
37
+ /** ISO 3166-1 alpha-2, uppercase, e.g. `KE`. */
38
+ documentCountryCode?: string | null;
39
+ /**
40
+ * Partner-declared extras, keyed by the `key` of an
41
+ * `extraProfileFields` entry in that partner's membership descriptor.
42
+ *
43
+ * The typed fields above are the facts EVERY partner asks for, and they are
44
+ * resolved centrally from the platform's own records. This is for the ones
45
+ * only one partner wants - a policy number, a plan tier, a medical-aid
46
+ * number - which the admin renders a form for from the descriptor, exactly
47
+ * as it already does for a partnership's `configFields`. Without it, the
48
+ * cost of a partner needing one unanticipated field is a shared-library
49
+ * publish and a migration in three services.
50
+ */
51
+ attributes?: Record<string, string> | null;
52
+ }
53
+ /**
54
+ * A dependent carried on somebody else's membership - a spouse or child covered
55
+ * by the same subscription.
56
+ *
57
+ * `dependentReference` is Clarion's own stable handle for them (a billing
58
+ * beneficiary id), and it is what makes syncing idempotent: a dependent already
59
+ * sent is recognised on the next sync rather than added a second time.
60
+ */
61
+ export interface PartnerMemberDependentProfile extends PartnerMemberProfile {
62
+ dependentReference: string;
63
+ /** e.g. `Spouse`. Free text; partners do not share a relationship vocabulary. */
64
+ relationship?: string | null;
65
+ }
@@ -0,0 +1,108 @@
1
+ import { PartnerMemberProfileField } from '../enums/partner-member-profile-field.enum';
2
+ import { PartnerMemberState } from '../enums/partner-member-state.enum';
3
+ /**
4
+ * One thing a partner needs to know about a person before it will register
5
+ * them, described the same way a partnership's `configFields` are.
6
+ *
7
+ * Deliberately NOT an enum. The enum below covers the facts every partner asks
8
+ * for, and those are resolved centrally from the platform's own records; this
9
+ * covers the ones only one partner wants - a policy number, a plan tier, a
10
+ * medical-aid number - which the admin renders a form for straight from the
11
+ * descriptor. Without it, a partner needing one unanticipated field costs a
12
+ * shared-library publish, a migration and a version bump in three services.
13
+ */
14
+ export interface PartnerMemberProfileFieldDescriptor {
15
+ /**
16
+ * A {@link PartnerMemberProfileField} value for a well-known fact, or any
17
+ * other key for a partner-specific one. A well-known key is populated from
18
+ * the platform's records; anything else is collected by an operator and
19
+ * stored in `PartnerMemberProfile.attributes`.
20
+ */
21
+ key: string;
22
+ label: string;
23
+ description?: string;
24
+ required: boolean;
25
+ type: 'string' | 'number' | 'boolean' | 'enum' | 'date';
26
+ allowedValues?: string[];
27
+ /** Rejected before it is sent, so a malformed value never becomes a partner 400. */
28
+ pattern?: string;
29
+ maxLength?: number;
30
+ /** Where an operator is likely to find this, shown as help text. */
31
+ sourceHint?: string;
32
+ }
33
+ /**
34
+ * Why a partner cannot yet be told about somebody.
35
+ *
36
+ * MISSING and MALFORMED are not the same problem and do not have the same
37
+ * remedy: one needs the fact collected, the other needs the fact corrected.
38
+ * Reporting both as "missing" leaves an operator staring at a field that is
39
+ * plainly filled in, with nothing to do.
40
+ */
41
+ export interface UnsatisfiedMemberProfileField {
42
+ field: string;
43
+ reason: 'MISSING' | 'MALFORMED';
44
+ /** One line an operator can act on. */
45
+ detail?: string;
46
+ }
47
+ /**
48
+ * What one partner needs before it will register a person as a member, and what
49
+ * it does with them afterwards.
50
+ *
51
+ * Published as part of the provider descriptor for exactly the same reason
52
+ * `configFields` is: the admin UI builds its "what is missing for this member"
53
+ * view from this, and the platform computes readiness from it generically, so a
54
+ * partner's requirements can never drift from the adapter that enforces them.
55
+ */
56
+ export interface PartnerMembershipDescriptor {
57
+ /**
58
+ * Every fact this partner wants, well-known or partner-specific, in the
59
+ * order an operator should be asked for them.
60
+ */
61
+ profileFields: PartnerMemberProfileFieldDescriptor[];
62
+ /**
63
+ * The well-known facts this partner REFUSES a member without.
64
+ *
65
+ * Redundant with the `required` flags above and kept because it is what
66
+ * the platform's central resolution actually reads - it says which of the
67
+ * facts the PLATFORM holds must be present, without the caller having to
68
+ * filter a descriptor list by key membership.
69
+ */
70
+ requiredProfileFields: PartnerMemberProfileField[];
71
+ /** Well-known facts the partner accepts and uses, but does not insist on. */
72
+ optionalProfileFields: PartnerMemberProfileField[];
73
+ /** True when the partner carries dependents on a member's cover. */
74
+ supportsDependents: boolean;
75
+ /** Ceiling the partner imposes on dependents per member. Null means none stated. */
76
+ maxDependents: number | null;
77
+ /**
78
+ * True when the partner accepts a member id on an EMERGENCY.
79
+ *
80
+ * This is what makes provisioning worth doing at all. A partner that has
81
+ * memberships but never reads one on a dispatch gains an emergency nothing,
82
+ * and the platform will not spend an alert-path millisecond looking one up.
83
+ */
84
+ quotesMemberOnDispatch: boolean;
85
+ /**
86
+ * Member states whose id is worth putting on an emergency for THIS partner.
87
+ *
88
+ * Defaults to {@link DEFAULT_QUOTABLE_MEMBER_STATES}. A partner that
89
+ * rejects a request naming a suspended member says so here rather than
90
+ * having every caller learn its rules.
91
+ */
92
+ quotableStates?: PartnerMemberState[];
93
+ /**
94
+ * True when the partner will register a member with no government identity
95
+ * document at all.
96
+ *
97
+ * Governs whether the platform even OFFERS to collect one. An identity
98
+ * document number cannot be rotated and outlives any credential; it is
99
+ * collected only where a partner genuinely refuses to work without it.
100
+ */
101
+ identityDocumentOptional: boolean;
102
+ /**
103
+ * Human-readable note shown beside the membership settings in the admin -
104
+ * e.g. that newly created members serve a waiting period before cover
105
+ * begins. Operators need to know that; it is not inferable from the API.
106
+ */
107
+ notes?: string;
108
+ }
@@ -0,0 +1,149 @@
1
+ import { CountryCode } from '../../country/enums/country-code.enum';
2
+ import { PartnerDispatchFailureReason } from '../enums/partner-dispatch-failure-reason.enum';
3
+ import { PartnerMemberState } from '../enums/partner-member-state.enum';
4
+ import { PartnerMembershipStatus } from '../enums/partner-membership-status.enum';
5
+ import { PartnerMembershipSubjectType } from '../enums/partner-membership-subject-type.enum';
6
+ import { PartnerProviderCode } from '../enums/partner-provider-code.enum';
7
+ import { PartnerMemberProfile } from './partner-member-profile';
8
+ import { UnsatisfiedMemberProfileField } from './partner-membership-descriptor';
9
+ /**
10
+ * One dependent's state at the partner.
11
+ *
12
+ * The important field is `partnerDependentId`: it records what the partner
13
+ * CONFIRMED, not what Clarion intended. Every diff runs against the confirmed
14
+ * set, because adding a dependent to a partner like Rescue.co is a one-way
15
+ * door - a duplicate cannot be withdrawn until the end of the term and
16
+ * permanently consumes one of the membership's seats, which the API only lets
17
+ * you increase. Reconciling against intent rather than confirmation is how a
18
+ * family's cover gets eaten by phantoms.
19
+ */
20
+ export interface PartnerMembershipDependent {
21
+ /** Clarion's stable handle - a billing beneficiary id. Idempotency key for the sync. */
22
+ dependentReference: string;
23
+ /** The platform user this dependent is, when they have one. */
24
+ userId?: number | null;
25
+ /** The partner's own id, once it has issued one. Absent means NOT yet sent. */
26
+ partnerDependentId?: string | null;
27
+ /** The name as Clarion holds it. */
28
+ name: string;
29
+ /**
30
+ * The name the partner ACCEPTED, which many partners will not let change
31
+ * afterwards. Held separately so a later correction on our side is visible
32
+ * as a divergence rather than silently believed to have applied.
33
+ */
34
+ partnerName?: string | null;
35
+ /** True once the partner has accepted this dependent. */
36
+ synced: boolean;
37
+ /**
38
+ * True when Clarion has stopped covering them but the partner will only
39
+ * drop them at the end of the term. Recorded rather than acted on, because
40
+ * the divergence is real and somebody has to be able to see it.
41
+ */
42
+ pendingRemovalAtTermEnd?: boolean;
43
+ /** Sanitized reason the last sync of THIS dependent failed. Never a credential. */
44
+ lastErrorMessage?: string | null;
45
+ syncedAt?: string | null;
46
+ }
47
+ /**
48
+ * One Clarion subject's registration with one partner.
49
+ *
50
+ * The row exists so that when an emergency is raised, "who is this person to
51
+ * Rescue.co?" is a local primary-key lookup rather than a call to a partner in
52
+ * the middle of an alert. It is a CACHE of the partner's truth with a
53
+ * lifecycle, not the truth itself - which is why a stale or failed one degrades
54
+ * to sending the emergency without a member id rather than blocking it.
55
+ */
56
+ export interface PartnerMembership {
57
+ id: number;
58
+ organizationId: number;
59
+ integrationId: number;
60
+ /** Copied from the partnership so history survives it being reconfigured. */
61
+ providerCode: PartnerProviderCode;
62
+ subjectType: PartnerMembershipSubjectType;
63
+ /**
64
+ * Clarion's stable identity for the subject, e.g. `individual-client:12` or
65
+ * `corporate-member:45`. Unique per partnership - this is what stops two
66
+ * activations creating two memberships for one person.
67
+ */
68
+ subjectReference: string;
69
+ /** The platform user, when the subject has one. The SOS-time lookup key. */
70
+ userId?: number | null;
71
+ subscriptionId?: number | null;
72
+ packageId?: number | null;
73
+ status: PartnerMembershipStatus;
74
+ /** The partner's member id (a UUID for Rescue.co). Present only when ACTIVE. */
75
+ partnerMemberId?: string | null;
76
+ /** The partner's own status word, passed through verbatim, e.g. `waitingPeriod`. */
77
+ partnerStatus?: string | null;
78
+ /**
79
+ * The partner's standing reduced to something every screen can rely on,
80
+ * without any of them learning a partner's vocabulary. The direct analogue
81
+ * of `engagementState` on a dispatch.
82
+ */
83
+ memberState?: PartnerMemberState | null;
84
+ /**
85
+ * When the partner's cover actually begins.
86
+ *
87
+ * Rescue.co returns a new member as `waitingPeriod` with a `startDate`
88
+ * some way off. Without this the admin can only say "Active", and an
89
+ * organization will tell a subscriber they are covered on the day they
90
+ * paid - which is the single most damaging thing this feature could get
91
+ * wrong.
92
+ */
93
+ coverageStartsAt?: string | null;
94
+ /** When the partner's cover ends. A membership past it is no longer quoted. */
95
+ coverageEndsAt?: string | null;
96
+ /** True when the partner has confirmed cover is in force right now. */
97
+ coverActive?: boolean;
98
+ /** Seats the partner CONFIRMED, which many partners will only let increase. */
99
+ partnerSeats?: number | null;
100
+ /** Short label quoted alongside the member id on an emergency, e.g. the package name. */
101
+ membershipLabel?: string | null;
102
+ /** The provider-neutral profile last sent, frozen for audit and drift detection. */
103
+ profile: PartnerMemberProfile;
104
+ /**
105
+ * What stops this profile being sent, and why - MISSING or MALFORMED.
106
+ *
107
+ * Empty unless the status is INCOMPLETE_PROFILE. This is what the admin
108
+ * renders as "collect a date of birth" or "this date of birth is not a
109
+ * date", rather than an operator reading a partner's 400.
110
+ */
111
+ unsatisfiedFields: UnsatisfiedMemberProfileField[];
112
+ dependents: PartnerMembershipDependent[];
113
+ attemptCount: number;
114
+ maxAttempts: number;
115
+ nextAttemptAt?: string | null;
116
+ failureReason?: PartnerDispatchFailureReason | null;
117
+ lastErrorCode?: string | null;
118
+ /** Sanitized - never contains a credential, token or document number. */
119
+ lastErrorMessage?: string | null;
120
+ countryCode: CountryCode;
121
+ /**
122
+ * The highest upstream sequence applied to this row.
123
+ *
124
+ * The fence that makes an unordered event stream safe. Billing publishes
125
+ * each lifecycle change on its OWN topic, and Kafka guarantees no ordering
126
+ * whatsoever across topics - so an `expired` and an `activated` emitted
127
+ * three seconds apart can be applied in either order. Without this, a
128
+ * cancelled subscription can end up ACTIVE with a live member id, and the
129
+ * reconciliation sweep will not correct it because the row looks healthy.
130
+ */
131
+ lastAppliedSequence?: number | null;
132
+ provisionedAt?: string | null;
133
+ lastSyncedAt?: string | null;
134
+ createdAt: string;
135
+ updatedAt: string;
136
+ }
137
+ /**
138
+ * What a dispatch is told about the caller's membership, when they have one.
139
+ *
140
+ * Resolved from the membership row before the payload is built, and OMITTED
141
+ * entirely rather than half-filled: an adapter should never have to reason
142
+ * about a membership that is present but unusable.
143
+ */
144
+ export interface PartnerDispatchMembership {
145
+ partnerMemberId: string;
146
+ label?: string | null;
147
+ /** The partner's own status at the time it was resolved, for the audit trail. */
148
+ partnerStatus?: string | null;
149
+ }
@@ -1,4 +1,5 @@
1
1
  import { PartnerIntegrationEnvironment } from '../enums/partner-integration-environment.enum';
2
+ import type { PartnerMembershipDescriptor } from './partner-membership-descriptor';
2
3
  import { PartnerProviderCode } from '../enums/partner-provider-code.enum';
3
4
  import { PartnerServiceCapability } from '../enums/partner-service-capability.enum';
4
5
  /** A credential a partnership must supply before it can dispatch. */
@@ -80,6 +81,21 @@ export interface PartnerProviderDescriptor {
80
81
  * blind retry on a non-idempotent emergency API means two ambulances.
81
82
  */
82
83
  supportsDuplicateDetection: boolean;
84
+ /**
85
+ * True when the partner registers people as MEMBERS ahead of an emergency,
86
+ * and the adapter implements that.
87
+ *
88
+ * Membership is an enrichment, never a precondition: a partner that has it
89
+ * still accepts an emergency for somebody unknown to it. The flag governs
90
+ * whether the platform provisions members in the background at all, not
91
+ * whether it will dispatch.
92
+ */
93
+ supportsMembership: boolean;
94
+ /**
95
+ * What this partner needs to register a member, and what it does with one.
96
+ * Present exactly when {@link supportsMembership} is true.
97
+ */
98
+ membership?: PartnerMembershipDescriptor;
83
99
  /** False for reserved provider codes that have no working adapter yet. */
84
100
  available: boolean;
85
101
  }
@@ -0,0 +1,156 @@
1
+ import { PartnerMemberProfileField } from '../enums/partner-member-profile-field.enum';
2
+ import { PartnerMembershipStatus } from '../enums/partner-membership-status.enum';
3
+ import { PartnerMembershipSubjectType } from '../enums/partner-membership-subject-type.enum';
4
+ import { PartnerMembershipPackageScope } from '../enums/partner-membership-package-scope.enum';
5
+ import { PartnerMembershipDescriptor, UnsatisfiedMemberProfileField } from '../interfaces/partner-membership-descriptor';
6
+ import { PartnerMemberProfile } from '../interfaces/partner-member-profile';
7
+ /**
8
+ * A profile as it comes out of a database or off the wire: every field may be
9
+ * absent OR explicitly null.
10
+ *
11
+ * {@link PartnerMemberProfile} models a profile that has been ASSEMBLED, where
12
+ * a missing field is simply absent. Readiness is checked against what is
13
+ * actually stored, and a nullable column reads back as null - so checking
14
+ * against the assembled shape would reject the very data this exists to inspect.
15
+ */
16
+ export type PartnerMemberProfileInput = {
17
+ [K in keyof PartnerMemberProfile]?: PartnerMemberProfile[K] | null;
18
+ };
19
+ /**
20
+ * Clarion's stable handle for a membership subject.
21
+ *
22
+ * Keyed on the PERSON wherever there is one, not on the subscription that paid
23
+ * for them. A partner registers a human being: an employee covered by their
24
+ * company who also buys personal cover is one member with one member id, and
25
+ * keying on the billing relationship would register them twice - two member ids
26
+ * for one person, two seats consumed, double-billed at the partner, and an
27
+ * emergency quoting whichever of the two the database happened to return first.
28
+ *
29
+ * Only a corporate member never linked to a platform login falls back to the
30
+ * relationship key, because for them there is no person key to use.
31
+ *
32
+ * Prefixed by kind so the two spaces can never collide on a bare numeric id.
33
+ * This is what the unique index on (integrationId, subjectReference) relies on
34
+ * to stop one person acquiring two memberships when an activation event is
35
+ * redelivered.
36
+ */
37
+ export declare function buildMembershipSubjectReference(subject: {
38
+ subjectType: PartnerMembershipSubjectType;
39
+ subjectId: number;
40
+ userId?: number | null;
41
+ }): string;
42
+ /**
43
+ * Which of a partner's REQUIRED profile fields this profile cannot satisfy.
44
+ *
45
+ * Generic over the descriptor rather than per-partner, so a partner that starts
46
+ * insisting on a national id needs a one-line descriptor change and no new
47
+ * validation code. An empty result means the profile is ready to send.
48
+ */
49
+ export declare function findMissingMemberProfileFields(profile: PartnerMemberProfileInput | null | undefined, descriptor: Pick<PartnerMembershipDescriptor, 'requiredProfileFields'>): PartnerMemberProfileField[];
50
+ /**
51
+ * What stops this profile being sent, and WHY.
52
+ *
53
+ * "Missing" and "malformed" are different problems with different remedies:
54
+ * one needs the fact collected, the other needs it corrected. Reporting a
55
+ * mistyped date of birth as missing leaves an operator staring at a field that
56
+ * is plainly filled in, with nothing to do - and reporting nothing at all
57
+ * leaves the membership in FAILED with an empty list.
58
+ *
59
+ * Also checks any partner-declared `profileFields` marked required, whose
60
+ * values live in `attributes` - so a partner asking for a policy number gets
61
+ * the same treatment as one asking for a date of birth.
62
+ */
63
+ export declare function findUnsatisfiedMemberProfileFields(profile: PartnerMemberProfileInput | null | undefined, descriptor: Pick<PartnerMembershipDescriptor, 'requiredProfileFields'> & Partial<Pick<PartnerMembershipDescriptor, 'profileFields'>>): UnsatisfiedMemberProfileField[];
64
+ /**
65
+ * Whether a partnership should register the subscribers of one package.
66
+ *
67
+ * The single expression of membership eligibility, used by every trigger so
68
+ * they cannot disagree. Deliberately NOT derived from coverage rules: those
69
+ * answer "where does THIS EMERGENCY go", which is an incident-shaped question
70
+ * that a package cannot be asked - a partnership whose only rule matches on
71
+ * emergency type would evaluate as covering nobody, and one whose package names
72
+ * no incident types at all (which this platform reads as "covers everything")
73
+ * would buy a paid membership for every free subscriber.
74
+ */
75
+ export declare function membershipRequired(integration: {
76
+ enabled?: boolean;
77
+ membershipEnabled?: boolean;
78
+ membershipPackageScope?: PartnerMembershipPackageScope;
79
+ membershipPackageIds?: number[] | null;
80
+ }, packageId: number | null | undefined): boolean;
81
+ /**
82
+ * Whether one profile field carries a usable value.
83
+ *
84
+ * A date of birth is checked for SHAPE as well as presence: an unparseable one
85
+ * is worse than an absent one, because it is sent, refused by the partner, and
86
+ * retried forever against a value that will never become valid.
87
+ */
88
+ export declare function isMemberProfileFieldPresent(profile: PartnerMemberProfileInput, field: PartnerMemberProfileField): boolean;
89
+ /** A real `YYYY-MM-DD` date, not merely one shaped like it (rejects 2026-02-30). */
90
+ export declare function isCalendarDate(value: string): boolean;
91
+ /**
92
+ * A date of birth reduced to the calendar date partners want.
93
+ *
94
+ * Accepts what the platform actually stores - a `Date`, a timestamp string, a
95
+ * bare date - and returns UTC, never local. Reading a stored instant in the
96
+ * server's zone is how a birthday drifts a day when a pod moves region.
97
+ */
98
+ export declare function toPartnerCalendarDate(value: Date | string | null | undefined): string | undefined;
99
+ /**
100
+ * Seats to buy for a member and their dependents.
101
+ *
102
+ * Always at least one - the member's own - and never more than the partner
103
+ * allows, so a subscription with more beneficiaries than a partner's ceiling
104
+ * buys the maximum rather than being refused outright.
105
+ */
106
+ export declare function resolveMemberSeatCount(dependentCount: number, maxDependents: number | null | undefined): number;
107
+ /**
108
+ * A person's name as a partner should record it, from whatever parts exist.
109
+ *
110
+ * Returns undefined rather than an empty or half-formed name: a member called
111
+ * " " is worse than a membership that is honestly reported as incomplete.
112
+ */
113
+ export declare function buildMemberFullName(parts: {
114
+ name?: string | null;
115
+ firstName?: string | null;
116
+ lastName?: string | null;
117
+ }): string | undefined;
118
+ /**
119
+ * Mask a document number for logs.
120
+ *
121
+ * A national id identifies a person as surely as their name and outlives any
122
+ * credential rotation, so it is treated more carefully than one: the last two
123
+ * characters are enough to match a support query against a record, and the mask
124
+ * is fixed width so the length of the original is not disclosed either.
125
+ */
126
+ export declare function maskMemberDocumentNumber(value: string | null | undefined): string;
127
+ /**
128
+ * Whether a membership may be quoted on an emergency right now.
129
+ *
130
+ * Deliberately does NOT require the partner's cover to have started. A member
131
+ * inside a waiting period is still that person's real member id, and telling
132
+ * the partner who is calling is accurate and useful whatever their cover state.
133
+ * What is excluded is a membership the partner has finished with: quoting an
134
+ * id past its term risks an outright rejection of the SOS, which costs an
135
+ * ambulance to add a field that was optional anyway.
136
+ */
137
+ export declare function isUsableMembership(membership: {
138
+ status: PartnerMembershipStatus;
139
+ partnerMemberId?: string | null;
140
+ coverageEndsAt?: string | Date | null;
141
+ }, now?: Date): boolean;
142
+ /**
143
+ * Whether the PARTNER's cover has actually begun.
144
+ *
145
+ * Separate from {@link isUsableMembership} because the two answer different
146
+ * questions and conflating them misleads a human. Rescue.co returns a new
147
+ * member as `waitingPeriod`: they exist, they are quotable, and they are not
148
+ * yet covered. An admin screen that renders a green "Active" chip the day
149
+ * somebody paid will have told them they are covered when they are not.
150
+ */
151
+ export declare function isMembershipCoverActive(membership: {
152
+ status: PartnerMembershipStatus;
153
+ partnerMemberId?: string | null;
154
+ coverageStartsAt?: string | Date | null;
155
+ coverageEndsAt?: string | Date | null;
156
+ }, now?: Date): boolean;