@sneat/extension-debtus-ui 0.2.2 → 0.2.3

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 (17) hide show
  1. package/fesm2022/{sneat-extension-debtus-ui-new-transfer-page.component-YaGgYqKB.mjs → sneat-extension-debtus-ui-new-transfer-page.component-DUkdGP92.mjs} +10 -4
  2. package/fesm2022/sneat-extension-debtus-ui-new-transfer-page.component-DUkdGP92.mjs.map +1 -0
  3. package/fesm2022/sneat-extension-debtus-ui-source-obligations-page.component-BJ6Oe79f.mjs +992 -0
  4. package/fesm2022/sneat-extension-debtus-ui-source-obligations-page.component-BJ6Oe79f.mjs.map +1 -0
  5. package/fesm2022/sneat-extension-debtus-ui-source-repayment-form-CZ7EtdUp.mjs +30 -0
  6. package/fesm2022/sneat-extension-debtus-ui-source-repayment-form-CZ7EtdUp.mjs.map +1 -0
  7. package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-BSDIDPAb.mjs +254 -0
  8. package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-BSDIDPAb.mjs.map +1 -0
  9. package/fesm2022/sneat-extension-debtus-ui.mjs +153 -6
  10. package/fesm2022/sneat-extension-debtus-ui.mjs.map +1 -1
  11. package/package.json +7 -4
  12. package/types/sneat-extension-debtus-ui.d.ts +31 -2
  13. package/fesm2022/sneat-extension-debtus-ui-new-transfer-page.component-YaGgYqKB.mjs.map +0 -1
  14. package/fesm2022/sneat-extension-debtus-ui-source-obligations-page.component-6E9_pHCq.mjs +0 -423
  15. package/fesm2022/sneat-extension-debtus-ui-source-obligations-page.component-6E9_pHCq.mjs.map +0 -1
  16. package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-B6iQS1YJ.mjs +0 -105
  17. package/fesm2022/sneat-extension-debtus-ui-transfer-details-page.component-B6iQS1YJ.mjs.map +0 -1
@@ -1,423 +0,0 @@
1
- import { DatePipe } from '@angular/common';
2
- import * as i0 from '@angular/core';
3
- import { inject, Injectable, DestroyRef, signal, Component } from '@angular/core';
4
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
- import { IonBackButton } from '@ionic/angular/ion-back-button';
6
- import { IonBadge } from '@ionic/angular/ion-badge';
7
- import { IonButton } from '@ionic/angular/ion-button';
8
- import { IonButtons } from '@ionic/angular/ion-buttons';
9
- import { IonCardContent } from '@ionic/angular/ion-card-content';
10
- import { IonCardHeader } from '@ionic/angular/ion-card-header';
11
- import { IonCardTitle } from '@ionic/angular/ion-card-title';
12
- import { IonCard } from '@ionic/angular/ion-card';
13
- import { IonContent } from '@ionic/angular/ion-content';
14
- import { IonHeader } from '@ionic/angular/ion-header';
15
- import { IonItem } from '@ionic/angular/ion-item';
16
- import { IonLabel } from '@ionic/angular/ion-label';
17
- import { IonList } from '@ionic/angular/ion-list';
18
- import { IonNote } from '@ionic/angular/ion-note';
19
- import { IonSpinner } from '@ionic/angular/ion-spinner';
20
- import { IonTitle } from '@ionic/angular/ion-title';
21
- import { IonToolbar } from '@ionic/angular/ion-toolbar';
22
- import { CONTACTUS_SPACE_SERVICE } from '@sneat/extension-contactus-contract';
23
- import { SpacePageBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
24
- import { ClassName } from '@sneat/ui';
25
- import { SpaceServiceModule } from '@sneat/space-services';
26
- import { map, BehaviorSubject, combineLatest, switchMap, of, first, catchError } from 'rxjs';
27
- import { HttpParams } from '@angular/common/http';
28
- import { SneatApiService } from '@sneat/api';
29
-
30
- class SourceObligationsApiService {
31
- api = inject(SneatApiService);
32
- getStatus(source) {
33
- return this.api
34
- .get('api4debtus/source-obligations', sourceParams(source))
35
- .pipe(map((value) => parseSourceObligationsStatus(value, source)));
36
- }
37
- getActivities(source, cursor) {
38
- let params = sourceParams(source).set('pageSize', '25');
39
- if (cursor) {
40
- params = params.set('cursor', cursor);
41
- }
42
- return this.api
43
- .get('api4debtus/source-obligation-activities', params)
44
- .pipe(map((value) => parseSourceObligationActivitiesPage(value, source)));
45
- }
46
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SourceObligationsApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
47
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SourceObligationsApiService, providedIn: 'root' });
48
- }
49
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SourceObligationsApiService, decorators: [{
50
- type: Injectable,
51
- args: [{ providedIn: 'root' }]
52
- }] });
53
- function sourceParams(source) {
54
- return new HttpParams()
55
- .set('spaceID', source.spaceID)
56
- .set('sourceNamespace', source.namespace)
57
- .set('sourceRecordID', source.recordID);
58
- }
59
- function parseSourceObligationsStatus(value, expectedSource) {
60
- const input = record(value, 'source obligations status');
61
- const source = sourceRef(input['source']);
62
- if (source.namespace !== expectedSource.namespace ||
63
- source.spaceID !== expectedSource.spaceID ||
64
- source.recordID !== expectedSource.recordID) {
65
- throw new TypeError('source obligations response identity does not match the request');
66
- }
67
- const receipt = record(input['latestReceipt'], 'latestReceipt');
68
- const postingStatus = oneOf(input['postingStatus'], ['pending', 'posting', 'applied', 'attention'], 'postingStatus');
69
- const attentionReason = optionalString(input['attentionReason'], 'attentionReason');
70
- if (postingStatus === 'attention' ? !attentionReason : !!attentionReason) {
71
- throw new TypeError('attentionReason presence does not match postingStatus');
72
- }
73
- return {
74
- source,
75
- latestReceipt: {
76
- receiptID: id(inputString(receipt, 'receiptID'), 'latestReceipt.receiptID'),
77
- revision: positiveIntegerString(receipt['revision'], 'latestReceipt.revision'),
78
- operationKey: id(inputString(receipt, 'operationKey'), 'latestReceipt.operationKey'),
79
- inputDigest: id(inputString(receipt, 'inputDigest'), 'latestReceipt.inputDigest'),
80
- },
81
- postingStatus,
82
- ...(attentionReason ? { attentionReason } : {}),
83
- obligations: boundedArray(input['obligations'], 'obligations').map((obligation) => sourceObligation(obligation, source.spaceID)),
84
- };
85
- }
86
- function parseSourceObligationActivitiesPage(value, expectedSource) {
87
- const input = record(value, 'source obligation activities');
88
- const nextCursor = optionalCursor(input['nextCursor']);
89
- return {
90
- activities: boundedArray(input['activities'], 'activities').map((activity) => sourceActivity(activity, expectedSource.spaceID)),
91
- ...(nextCursor ? { nextCursor } : {}),
92
- };
93
- }
94
- function sourceRef(value) {
95
- const input = record(value, 'source');
96
- const namespace = inputString(input, 'namespace');
97
- if (namespace !== 'splitus') {
98
- throw new TypeError('source.namespace must be splitus');
99
- }
100
- return {
101
- namespace,
102
- spaceID: id(inputString(input, 'spaceID'), 'source.spaceID'),
103
- recordID: id(inputString(input, 'recordID'), 'source.recordID'),
104
- };
105
- }
106
- function sourceContact(value, label, expectedSpaceID) {
107
- const input = record(value, label);
108
- const spaceID = id(inputString(input, 'spaceID'), `${label}.spaceID`);
109
- if (spaceID !== expectedSpaceID) {
110
- throw new TypeError(`${label}.spaceID does not match the requested Space`);
111
- }
112
- return {
113
- spaceID,
114
- contactID: id(inputString(input, 'contactID'), `${label}.contactID`),
115
- };
116
- }
117
- function sourceObligation(value, expectedSpaceID) {
118
- const input = record(value, 'obligation');
119
- return {
120
- lineID: id(inputString(input, 'lineID'), 'obligation.lineID'),
121
- obligationIDs: stringArray(input['obligationIDs'], 'obligation.obligationIDs'),
122
- debtor: sourceContact(input['debtor'], 'obligation.debtor', expectedSpaceID),
123
- creditor: sourceContact(input['creditor'], 'obligation.creditor', expectedSpaceID),
124
- currency: currency(input['currency']),
125
- principalMinor: nonNegativeIntegerString(input['principalMinor'], 'principalMinor'),
126
- outstandingMinor: nonNegativeIntegerString(input['outstandingMinor'], 'outstandingMinor'),
127
- repaidMinor: nonNegativeIntegerString(input['repaidMinor'], 'repaidMinor'),
128
- creditMinor: nonNegativeIntegerString(input['creditMinor'], 'creditMinor'),
129
- status: oneOf(input['status'], ['unsettled', 'part_settled', 'settled'], 'obligation.status'),
130
- };
131
- }
132
- function sourceActivity(value, expectedSpaceID) {
133
- const input = record(value, 'activity');
134
- const occurredAt = text(inputString(input, 'occurredAt'), 'activity.occurredAt');
135
- if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/.test(occurredAt)) {
136
- throw new TypeError('activity.occurredAt must be a UTC timestamp');
137
- }
138
- return {
139
- activityID: id(inputString(input, 'activityID'), 'activity.activityID'),
140
- rootActivityID: id(inputString(input, 'rootActivityID'), 'activity.rootActivityID'),
141
- lineIDs: stringArray(input['lineIDs'], 'activity.lineIDs'),
142
- kind: oneOf(input['kind'], [
143
- 'obligation',
144
- 'repayment',
145
- 'adjustment',
146
- 'cancellation',
147
- 'credit',
148
- ], 'activity.kind'),
149
- from: sourceContact(input['from'], 'activity.from', expectedSpaceID),
150
- to: sourceContact(input['to'], 'activity.to', expectedSpaceID),
151
- currency: currency(input['currency']),
152
- amountMinor: positiveIntegerString(input['amountMinor'], 'activity.amountMinor'),
153
- occurredAt,
154
- };
155
- }
156
- function record(value, label) {
157
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
158
- throw new TypeError(`${label} must be an object`);
159
- }
160
- return value;
161
- }
162
- function boundedArray(value, label) {
163
- if (!Array.isArray(value) || value.length > 100) {
164
- throw new TypeError(`${label} must be an array with at most 100 items`);
165
- }
166
- return value;
167
- }
168
- function inputString(input, key) {
169
- const value = input[key];
170
- if (typeof value !== 'string') {
171
- throw new TypeError(`${key} must be a string`);
172
- }
173
- return value;
174
- }
175
- function optionalString(value, label) {
176
- if (value === undefined) {
177
- return undefined;
178
- }
179
- if (typeof value !== 'string' || !value.trim() || value.length > 2048) {
180
- throw new TypeError(`${label} must be a bounded non-empty string`);
181
- }
182
- return value;
183
- }
184
- function optionalCursor(value) {
185
- if (value === undefined) {
186
- return undefined;
187
- }
188
- if (typeof value !== 'string' || !value || value.length > 4096) {
189
- throw new TypeError('nextCursor must be a bounded non-empty string');
190
- }
191
- return value;
192
- }
193
- function text(value, label) {
194
- if (!value.trim() || value.length > 2048) {
195
- throw new TypeError(`${label} must be a bounded non-empty string`);
196
- }
197
- return value;
198
- }
199
- function id(value, label) {
200
- if (!value ||
201
- value.trim() !== value ||
202
- value.length > 512 ||
203
- Array.from(value).some((character) => {
204
- const code = character.charCodeAt(0);
205
- return code < 0x20 || code === 0x7f;
206
- })) {
207
- throw new TypeError(`${label} is not a valid bounded identifier`);
208
- }
209
- return value;
210
- }
211
- function stringArray(value, label) {
212
- const values = boundedArray(value, label).map((item) => {
213
- if (typeof item !== 'string') {
214
- throw new TypeError(`${label} entries must be strings`);
215
- }
216
- return id(item, label);
217
- });
218
- if (!values.length || new Set(values).size !== values.length) {
219
- throw new TypeError(`${label} must contain unique values`);
220
- }
221
- return values;
222
- }
223
- function nonNegativeIntegerString(value, label) {
224
- if (typeof value !== 'string' || !/^(0|[1-9]\d*)$/.test(value)) {
225
- throw new TypeError(`${label} must be an exact non-negative integer string`);
226
- }
227
- return value;
228
- }
229
- function positiveIntegerString(value, label) {
230
- if (typeof value !== 'string' || !/^[1-9]\d*$/.test(value)) {
231
- throw new TypeError(`${label} must be an exact positive integer string`);
232
- }
233
- return value;
234
- }
235
- function currency(value) {
236
- if (typeof value !== 'string' || !/^[A-Z]{3}$/.test(value)) {
237
- throw new TypeError('currency must be three uppercase ASCII letters');
238
- }
239
- return value;
240
- }
241
- function oneOf(value, allowed, label) {
242
- if (typeof value !== 'string' || !allowed.includes(value)) {
243
- throw new TypeError(`${label} has an unsupported value`);
244
- }
245
- return value;
246
- }
247
-
248
- class SourceObligationsPageComponent extends SpacePageBaseComponent {
249
- sourceApi = inject(SourceObligationsApiService);
250
- contactusSpaceService = inject(CONTACTUS_SPACE_SERVICE);
251
- componentDestroyRef = inject(DestroyRef);
252
- reload = new BehaviorSubject(undefined);
253
- $loading = signal(true, /* @ts-ignore */
254
- ...(ngDevMode ? [{ debugName: "$loading" }] : /* istanbul ignore next */ []));
255
- $loadingMore = signal(false, /* @ts-ignore */
256
- ...(ngDevMode ? [{ debugName: "$loadingMore" }] : /* istanbul ignore next */ []));
257
- $error = signal(undefined, /* @ts-ignore */
258
- ...(ngDevMode ? [{ debugName: "$error" }] : /* istanbul ignore next */ []));
259
- $activityError = signal(undefined, /* @ts-ignore */
260
- ...(ngDevMode ? [{ debugName: "$activityError" }] : /* istanbul ignore next */ []));
261
- $status = signal(undefined, /* @ts-ignore */
262
- ...(ngDevMode ? [{ debugName: "$status" }] : /* istanbul ignore next */ []));
263
- $activities = signal([], /* @ts-ignore */
264
- ...(ngDevMode ? [{ debugName: "$activities" }] : /* istanbul ignore next */ []));
265
- $nextCursor = signal(undefined, /* @ts-ignore */
266
- ...(ngDevMode ? [{ debugName: "$nextCursor" }] : /* istanbul ignore next */ []));
267
- $contactTitles = signal(new Map(), /* @ts-ignore */
268
- ...(ngDevMode ? [{ debugName: "$contactTitles" }] : /* istanbul ignore next */ []));
269
- constructor() {
270
- super();
271
- this.$defaultBackUrlSpacePath.set('debts');
272
- combineLatest([this.spaceIDChanged$, this.route.queryParamMap, this.reload])
273
- .pipe(switchMap(([spaceID, params]) => {
274
- this.$loading.set(true);
275
- this.$error.set(undefined);
276
- this.$activityError.set(undefined);
277
- this.$status.set(undefined);
278
- this.$activities.set([]);
279
- this.$nextCursor.set(undefined);
280
- const sourceNamespace = params.get('sourceNamespace');
281
- const sourceRecordID = params.get('sourceRecordID')?.trim();
282
- if (!spaceID || sourceNamespace !== 'splitus' || !sourceRecordID) {
283
- return of({
284
- error: new Error('Invalid source-obligations route'),
285
- message: 'This settlement link is incomplete or invalid.',
286
- });
287
- }
288
- const source = {
289
- namespace: 'splitus',
290
- spaceID,
291
- recordID: sourceRecordID,
292
- };
293
- return combineLatest({
294
- status: this.sourceApi.getStatus(source),
295
- activityPage: this.sourceApi.getActivities(source),
296
- contacts: this.contactusSpaceService
297
- .watchContactBriefs(spaceID)
298
- .pipe(first()),
299
- }).pipe(map((data) => ({ data })), catchError((error) => of({
300
- error,
301
- message: 'We could not load this bill settlement.',
302
- })));
303
- }), takeUntilDestroyed())
304
- .subscribe((result) => {
305
- this.$loading.set(false);
306
- if ('error' in result) {
307
- this.$error.set(result.message);
308
- this.errorLogger.logError(result.error, result.message);
309
- return;
310
- }
311
- this.$status.set(result.data.status);
312
- this.$activities.set(result.data.activityPage.activities);
313
- this.$nextCursor.set(result.data.activityPage.nextCursor);
314
- this.$contactTitles.set(new Map(result.data.contacts.map((contact) => [
315
- contact.id,
316
- contact.brief?.title ||
317
- contact.brief?.shortTitle ||
318
- 'Unknown contact',
319
- ])));
320
- });
321
- }
322
- retry() {
323
- this.reload.next();
324
- }
325
- loadMore() {
326
- const source = this.$status()?.source;
327
- const cursor = this.$nextCursor();
328
- if (!source || !cursor || this.$loadingMore()) {
329
- return;
330
- }
331
- this.$loadingMore.set(true);
332
- this.$activityError.set(undefined);
333
- this.sourceApi
334
- .getActivities(source, cursor)
335
- .pipe(takeUntilDestroyed(this.componentDestroyRef))
336
- .subscribe({
337
- next: (page) => {
338
- this.$activities.update((activities) => [
339
- ...activities,
340
- ...page.activities,
341
- ]);
342
- this.$nextCursor.set(page.nextCursor);
343
- this.$loadingMore.set(false);
344
- },
345
- error: (error) => {
346
- this.$activityError.set('We could not load more activity.');
347
- this.$loadingMore.set(false);
348
- this.errorLogger.logError(error, 'Failed to load more Debtus activity');
349
- },
350
- });
351
- }
352
- contactTitle(contactID) {
353
- return this.$contactTitles().get(contactID) ?? 'Unknown contact';
354
- }
355
- formatMinorAmount(minor, currency) {
356
- if (!['EUR', 'GBP', 'USD'].includes(currency)) {
357
- return `${minor} minor units ${currency}`;
358
- }
359
- const value = BigInt(minor);
360
- const whole = value / 100n;
361
- const fraction = (value % 100n).toString().padStart(2, '0');
362
- return `${currency} ${whole}.${fraction}`;
363
- }
364
- settlementColor(status) {
365
- switch (status) {
366
- case 'settled':
367
- return 'success';
368
- case 'part_settled':
369
- return 'warning';
370
- default:
371
- return 'medium';
372
- }
373
- }
374
- activityTitle(kind) {
375
- switch (kind) {
376
- case 'obligation':
377
- return 'Obligation recorded';
378
- case 'repayment':
379
- return 'Repayment recorded';
380
- case 'adjustment':
381
- return 'Adjustment recorded';
382
- case 'cancellation':
383
- return 'Obligation cancelled';
384
- case 'credit':
385
- return 'Credit recorded';
386
- }
387
- }
388
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SourceObligationsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
389
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SourceObligationsPageComponent, isStandalone: true, selector: "debtus-source-obligations-page", providers: [
390
- { provide: ClassName, useValue: 'SourceObligationsPageComponent' },
391
- SpaceComponentBaseParams,
392
- ], usesInheritance: true, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>Bill settlement</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n <p>Loading settlement\u2026</p>\n </div>\n } @else if ($error()) {\n <ion-card>\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"retry()\">\n Try again\n </ion-button>\n </ion-card-content>\n </ion-card>\n } @else if ($status(); as status) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>Splitus bill settlement</ion-card-title>\n </ion-card-header>\n <ion-list lines=\"full\">\n <ion-item>\n <ion-label>\n <p>Posting</p>\n <h2>{{ status.postingStatus }}</h2>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"status.postingStatus === 'applied' ? 'success' : 'warning'\"\n >\n {{ status.postingStatus }}\n </ion-badge>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>Debtus receipt</p>\n <h2>Recorded in Debtus</h2>\n <ion-note>Revision {{ status.latestReceipt.revision }}</ion-note>\n </ion-label>\n </ion-item>\n @if (status.attentionReason) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>Needs attention</p>\n <h2>{{ status.attentionReason }}</h2>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Who owes what</ion-card-title>\n </ion-card-header>\n @if (status.obligations.length) {\n <ion-list lines=\"full\">\n @for (obligation of status.obligations; track obligation.lineID) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n {{ contactTitle(obligation.debtor.contactID) }} owes\n {{ contactTitle(obligation.creditor.contactID) }}\n </h2>\n <p>\n Outstanding:\n {{\n formatMinorAmount(\n obligation.outstandingMinor,\n obligation.currency\n )\n }}\n </p>\n <ion-note>\n Original\n {{\n formatMinorAmount(\n obligation.principalMinor,\n obligation.currency\n )\n }}\n \u00B7 Repaid\n {{\n formatMinorAmount(\n obligation.repaidMinor,\n obligation.currency\n )\n }}\n @if (obligation.creditMinor !== '0') {\n \u00B7 Credit\n {{\n formatMinorAmount(\n obligation.creditMinor,\n obligation.currency\n )\n }}\n }\n </ion-note>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"settlementColor(obligation.status)\"\n >\n {{\n obligation.status === 'part_settled'\n ? 'part settled'\n : obligation.status\n }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No obligations were recorded for this bill.</ion-note>\n </ion-card-content>\n }\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Activity</ion-card-title>\n </ion-card-header>\n @if ($activities().length) {\n <ion-list lines=\"full\">\n @for (activity of $activities(); track activity.activityID) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>{{ activityTitle(activity.kind) }}</h2>\n <p>\n {{ contactTitle(activity.from.contactID) }} \u2192\n {{ contactTitle(activity.to.contactID) }}\n </p>\n <ion-note>{{ activity.occurredAt | date: 'medium' }}</ion-note>\n </ion-label>\n <ion-badge slot=\"end\">\n {{ formatMinorAmount(activity.amountMinor, activity.currency) }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No Debtus activity has been recorded yet.</ion-note>\n </ion-card-content>\n }\n @if ($activityError()) {\n <ion-card-content>\n <ion-note color=\"danger\">{{ $activityError() }}</ion-note>\n </ion-card-content>\n }\n @if ($nextCursor()) {\n <ion-card-content>\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n [disabled]=\"$loadingMore()\"\n (click)=\"loadMore()\"\n >\n @if ($loadingMore()) {\n <ion-spinner name=\"dots\" />\n } @else {\n Load more activity\n }\n </ion-button>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n", dependencies: [{ kind: "ngmodule", type: SpaceServiceModule }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"], outputs: ["ionFocus", "ionBlur"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"], outputs: ["ionScrollStart", "ionScroll", "ionScrollEnd"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "pipe", type: DatePipe, name: "date" }] });
393
- }
394
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SourceObligationsPageComponent, decorators: [{
395
- type: Component,
396
- args: [{ selector: 'debtus-source-obligations-page', imports: [
397
- SpaceServiceModule,
398
- DatePipe,
399
- IonBackButton,
400
- IonBadge,
401
- IonButton,
402
- IonButtons,
403
- IonCard,
404
- IonCardContent,
405
- IonCardHeader,
406
- IonCardTitle,
407
- IonContent,
408
- IonHeader,
409
- IonItem,
410
- IonLabel,
411
- IonList,
412
- IonNote,
413
- IonSpinner,
414
- IonTitle,
415
- IonToolbar,
416
- ], providers: [
417
- { provide: ClassName, useValue: 'SourceObligationsPageComponent' },
418
- SpaceComponentBaseParams,
419
- ], template: "<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>Bill settlement</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n <p>Loading settlement\u2026</p>\n </div>\n } @else if ($error()) {\n <ion-card>\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"retry()\">\n Try again\n </ion-button>\n </ion-card-content>\n </ion-card>\n } @else if ($status(); as status) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>Splitus bill settlement</ion-card-title>\n </ion-card-header>\n <ion-list lines=\"full\">\n <ion-item>\n <ion-label>\n <p>Posting</p>\n <h2>{{ status.postingStatus }}</h2>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"status.postingStatus === 'applied' ? 'success' : 'warning'\"\n >\n {{ status.postingStatus }}\n </ion-badge>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>Debtus receipt</p>\n <h2>Recorded in Debtus</h2>\n <ion-note>Revision {{ status.latestReceipt.revision }}</ion-note>\n </ion-label>\n </ion-item>\n @if (status.attentionReason) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>Needs attention</p>\n <h2>{{ status.attentionReason }}</h2>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Who owes what</ion-card-title>\n </ion-card-header>\n @if (status.obligations.length) {\n <ion-list lines=\"full\">\n @for (obligation of status.obligations; track obligation.lineID) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n {{ contactTitle(obligation.debtor.contactID) }} owes\n {{ contactTitle(obligation.creditor.contactID) }}\n </h2>\n <p>\n Outstanding:\n {{\n formatMinorAmount(\n obligation.outstandingMinor,\n obligation.currency\n )\n }}\n </p>\n <ion-note>\n Original\n {{\n formatMinorAmount(\n obligation.principalMinor,\n obligation.currency\n )\n }}\n \u00B7 Repaid\n {{\n formatMinorAmount(\n obligation.repaidMinor,\n obligation.currency\n )\n }}\n @if (obligation.creditMinor !== '0') {\n \u00B7 Credit\n {{\n formatMinorAmount(\n obligation.creditMinor,\n obligation.currency\n )\n }}\n }\n </ion-note>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"settlementColor(obligation.status)\"\n >\n {{\n obligation.status === 'part_settled'\n ? 'part settled'\n : obligation.status\n }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No obligations were recorded for this bill.</ion-note>\n </ion-card-content>\n }\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Activity</ion-card-title>\n </ion-card-header>\n @if ($activities().length) {\n <ion-list lines=\"full\">\n @for (activity of $activities(); track activity.activityID) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>{{ activityTitle(activity.kind) }}</h2>\n <p>\n {{ contactTitle(activity.from.contactID) }} \u2192\n {{ contactTitle(activity.to.contactID) }}\n </p>\n <ion-note>{{ activity.occurredAt | date: 'medium' }}</ion-note>\n </ion-label>\n <ion-badge slot=\"end\">\n {{ formatMinorAmount(activity.amountMinor, activity.currency) }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No Debtus activity has been recorded yet.</ion-note>\n </ion-card-content>\n }\n @if ($activityError()) {\n <ion-card-content>\n <ion-note color=\"danger\">{{ $activityError() }}</ion-note>\n </ion-card-content>\n }\n @if ($nextCursor()) {\n <ion-card-content>\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n [disabled]=\"$loadingMore()\"\n (click)=\"loadMore()\"\n >\n @if ($loadingMore()) {\n <ion-spinner name=\"dots\" />\n } @else {\n Load more activity\n }\n </ion-button>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n" }]
420
- }], ctorParameters: () => [] });
421
-
422
- export { SourceObligationsPageComponent };
423
- //# sourceMappingURL=sneat-extension-debtus-ui-source-obligations-page.component-6E9_pHCq.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sneat-extension-debtus-ui-source-obligations-page.component-6E9_pHCq.mjs","sources":["../../../../../../libs/extensions/debtus/ui/src/lib/pages/source-obligations/source-obligations-api.service.ts","../../../../../../libs/extensions/debtus/ui/src/lib/pages/source-obligations/source-obligations-page.component.ts","../../../../../../libs/extensions/debtus/ui/src/lib/pages/source-obligations/source-obligations-page.component.html"],"sourcesContent":["import { HttpParams } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { SneatApiService } from '@sneat/api';\nimport { Observable, map } from 'rxjs';\n\nexport interface ISourceRef {\n readonly namespace: 'splitus';\n readonly spaceID: string;\n readonly recordID: string;\n}\n\nexport type SourcePostingStatus =\n 'pending' | 'posting' | 'applied' | 'attention';\nexport type SourceSettlementStatus = 'unsettled' | 'part_settled' | 'settled';\nexport type SourceActivityKind =\n 'obligation' | 'repayment' | 'adjustment' | 'cancellation' | 'credit';\n\nexport interface ISourceContactRef {\n readonly spaceID: string;\n readonly contactID: string;\n}\n\nexport interface ISourceObligation {\n readonly lineID: string;\n readonly obligationIDs: readonly string[];\n readonly debtor: ISourceContactRef;\n readonly creditor: ISourceContactRef;\n readonly currency: string;\n readonly principalMinor: string;\n readonly outstandingMinor: string;\n readonly repaidMinor: string;\n readonly creditMinor: string;\n readonly status: SourceSettlementStatus;\n}\n\nexport interface ISourceObligationsStatus {\n readonly source: ISourceRef;\n readonly latestReceipt: {\n readonly receiptID: string;\n readonly revision: string;\n readonly operationKey: string;\n readonly inputDigest: string;\n };\n readonly postingStatus: SourcePostingStatus;\n readonly attentionReason?: string;\n readonly obligations: readonly ISourceObligation[];\n}\n\nexport interface ISourceObligationActivity {\n readonly activityID: string;\n readonly rootActivityID: string;\n readonly lineIDs: readonly string[];\n readonly kind: SourceActivityKind;\n readonly from: ISourceContactRef;\n readonly to: ISourceContactRef;\n readonly currency: string;\n readonly amountMinor: string;\n readonly occurredAt: string;\n}\n\nexport interface ISourceObligationActivitiesPage {\n readonly activities: readonly ISourceObligationActivity[];\n readonly nextCursor?: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SourceObligationsApiService {\n private readonly api = inject(SneatApiService);\n\n public getStatus(source: ISourceRef): Observable<ISourceObligationsStatus> {\n return this.api\n .get<unknown>('api4debtus/source-obligations', sourceParams(source))\n .pipe(map((value) => parseSourceObligationsStatus(value, source)));\n }\n\n public getActivities(\n source: ISourceRef,\n cursor?: string,\n ): Observable<ISourceObligationActivitiesPage> {\n let params = sourceParams(source).set('pageSize', '25');\n if (cursor) {\n params = params.set('cursor', cursor);\n }\n return this.api\n .get<unknown>('api4debtus/source-obligation-activities', params)\n .pipe(map((value) => parseSourceObligationActivitiesPage(value, source)));\n }\n}\n\nfunction sourceParams(source: ISourceRef): HttpParams {\n return new HttpParams()\n .set('spaceID', source.spaceID)\n .set('sourceNamespace', source.namespace)\n .set('sourceRecordID', source.recordID);\n}\n\nexport function parseSourceObligationsStatus(\n value: unknown,\n expectedSource: ISourceRef,\n): ISourceObligationsStatus {\n const input = record(value, 'source obligations status');\n const source = sourceRef(input['source']);\n if (\n source.namespace !== expectedSource.namespace ||\n source.spaceID !== expectedSource.spaceID ||\n source.recordID !== expectedSource.recordID\n ) {\n throw new TypeError(\n 'source obligations response identity does not match the request',\n );\n }\n const receipt = record(input['latestReceipt'], 'latestReceipt');\n const postingStatus = oneOf(\n input['postingStatus'],\n ['pending', 'posting', 'applied', 'attention'] as const,\n 'postingStatus',\n );\n const attentionReason = optionalString(\n input['attentionReason'],\n 'attentionReason',\n );\n if (postingStatus === 'attention' ? !attentionReason : !!attentionReason) {\n throw new TypeError(\n 'attentionReason presence does not match postingStatus',\n );\n }\n return {\n source,\n latestReceipt: {\n receiptID: id(\n inputString(receipt, 'receiptID'),\n 'latestReceipt.receiptID',\n ),\n revision: positiveIntegerString(\n receipt['revision'],\n 'latestReceipt.revision',\n ),\n operationKey: id(\n inputString(receipt, 'operationKey'),\n 'latestReceipt.operationKey',\n ),\n inputDigest: id(\n inputString(receipt, 'inputDigest'),\n 'latestReceipt.inputDigest',\n ),\n },\n postingStatus,\n ...(attentionReason ? { attentionReason } : {}),\n obligations: boundedArray(input['obligations'], 'obligations').map(\n (obligation) => sourceObligation(obligation, source.spaceID),\n ),\n };\n}\n\nexport function parseSourceObligationActivitiesPage(\n value: unknown,\n expectedSource: ISourceRef,\n): ISourceObligationActivitiesPage {\n const input = record(value, 'source obligation activities');\n const nextCursor = optionalCursor(input['nextCursor']);\n return {\n activities: boundedArray(input['activities'], 'activities').map(\n (activity) => sourceActivity(activity, expectedSource.spaceID),\n ),\n ...(nextCursor ? { nextCursor } : {}),\n };\n}\n\nfunction sourceRef(value: unknown): ISourceRef {\n const input = record(value, 'source');\n const namespace = inputString(input, 'namespace');\n if (namespace !== 'splitus') {\n throw new TypeError('source.namespace must be splitus');\n }\n return {\n namespace,\n spaceID: id(inputString(input, 'spaceID'), 'source.spaceID'),\n recordID: id(inputString(input, 'recordID'), 'source.recordID'),\n };\n}\n\nfunction sourceContact(\n value: unknown,\n label: string,\n expectedSpaceID: string,\n): ISourceContactRef {\n const input = record(value, label);\n const spaceID = id(inputString(input, 'spaceID'), `${label}.spaceID`);\n if (spaceID !== expectedSpaceID) {\n throw new TypeError(`${label}.spaceID does not match the requested Space`);\n }\n return {\n spaceID,\n contactID: id(inputString(input, 'contactID'), `${label}.contactID`),\n };\n}\n\nfunction sourceObligation(\n value: unknown,\n expectedSpaceID: string,\n): ISourceObligation {\n const input = record(value, 'obligation');\n return {\n lineID: id(inputString(input, 'lineID'), 'obligation.lineID'),\n obligationIDs: stringArray(\n input['obligationIDs'],\n 'obligation.obligationIDs',\n ),\n debtor: sourceContact(\n input['debtor'],\n 'obligation.debtor',\n expectedSpaceID,\n ),\n creditor: sourceContact(\n input['creditor'],\n 'obligation.creditor',\n expectedSpaceID,\n ),\n currency: currency(input['currency']),\n principalMinor: nonNegativeIntegerString(\n input['principalMinor'],\n 'principalMinor',\n ),\n outstandingMinor: nonNegativeIntegerString(\n input['outstandingMinor'],\n 'outstandingMinor',\n ),\n repaidMinor: nonNegativeIntegerString(input['repaidMinor'], 'repaidMinor'),\n creditMinor: nonNegativeIntegerString(input['creditMinor'], 'creditMinor'),\n status: oneOf(\n input['status'],\n ['unsettled', 'part_settled', 'settled'] as const,\n 'obligation.status',\n ),\n };\n}\n\nfunction sourceActivity(\n value: unknown,\n expectedSpaceID: string,\n): ISourceObligationActivity {\n const input = record(value, 'activity');\n const occurredAt = text(\n inputString(input, 'occurredAt'),\n 'activity.occurredAt',\n );\n if (!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z$/.test(occurredAt)) {\n throw new TypeError('activity.occurredAt must be a UTC timestamp');\n }\n return {\n activityID: id(inputString(input, 'activityID'), 'activity.activityID'),\n rootActivityID: id(\n inputString(input, 'rootActivityID'),\n 'activity.rootActivityID',\n ),\n lineIDs: stringArray(input['lineIDs'], 'activity.lineIDs'),\n kind: oneOf(\n input['kind'],\n [\n 'obligation',\n 'repayment',\n 'adjustment',\n 'cancellation',\n 'credit',\n ] as const,\n 'activity.kind',\n ),\n from: sourceContact(input['from'], 'activity.from', expectedSpaceID),\n to: sourceContact(input['to'], 'activity.to', expectedSpaceID),\n currency: currency(input['currency']),\n amountMinor: positiveIntegerString(\n input['amountMinor'],\n 'activity.amountMinor',\n ),\n occurredAt,\n };\n}\n\nfunction record(value: unknown, label: string): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new TypeError(`${label} must be an object`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction boundedArray(value: unknown, label: string): readonly unknown[] {\n if (!Array.isArray(value) || value.length > 100) {\n throw new TypeError(`${label} must be an array with at most 100 items`);\n }\n return value;\n}\n\nfunction inputString(input: Record<string, unknown>, key: string): string {\n const value = input[key];\n if (typeof value !== 'string') {\n throw new TypeError(`${key} must be a string`);\n }\n return value;\n}\n\nfunction optionalString(value: unknown, label: string): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (typeof value !== 'string' || !value.trim() || value.length > 2048) {\n throw new TypeError(`${label} must be a bounded non-empty string`);\n }\n return value;\n}\n\nfunction optionalCursor(value: unknown): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (typeof value !== 'string' || !value || value.length > 4096) {\n throw new TypeError('nextCursor must be a bounded non-empty string');\n }\n return value;\n}\n\nfunction text(value: string, label: string): string {\n if (!value.trim() || value.length > 2048) {\n throw new TypeError(`${label} must be a bounded non-empty string`);\n }\n return value;\n}\n\nfunction id(value: string, label: string): string {\n if (\n !value ||\n value.trim() !== value ||\n value.length > 512 ||\n Array.from(value).some((character) => {\n const code = character.charCodeAt(0);\n return code < 0x20 || code === 0x7f;\n })\n ) {\n throw new TypeError(`${label} is not a valid bounded identifier`);\n }\n return value;\n}\n\nfunction stringArray(value: unknown, label: string): readonly string[] {\n const values = boundedArray(value, label).map((item) => {\n if (typeof item !== 'string') {\n throw new TypeError(`${label} entries must be strings`);\n }\n return id(item, label);\n });\n if (!values.length || new Set(values).size !== values.length) {\n throw new TypeError(`${label} must contain unique values`);\n }\n return values;\n}\n\nfunction nonNegativeIntegerString(value: unknown, label: string): string {\n if (typeof value !== 'string' || !/^(0|[1-9]\\d*)$/.test(value)) {\n throw new TypeError(\n `${label} must be an exact non-negative integer string`,\n );\n }\n return value;\n}\n\nfunction positiveIntegerString(value: unknown, label: string): string {\n if (typeof value !== 'string' || !/^[1-9]\\d*$/.test(value)) {\n throw new TypeError(`${label} must be an exact positive integer string`);\n }\n return value;\n}\n\nfunction currency(value: unknown): string {\n if (typeof value !== 'string' || !/^[A-Z]{3}$/.test(value)) {\n throw new TypeError('currency must be three uppercase ASCII letters');\n }\n return value;\n}\n\nfunction oneOf<const T extends readonly string[]>(\n value: unknown,\n allowed: T,\n label: string,\n): T[number] {\n if (typeof value !== 'string' || !allowed.includes(value as T[number])) {\n throw new TypeError(`${label} has an unsupported value`);\n }\n return value;\n}\n","import { DatePipe } from '@angular/common';\nimport { Component, DestroyRef, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { IonBackButton } from '@ionic/angular/ion-back-button';\nimport { IonBadge } from '@ionic/angular/ion-badge';\nimport { IonButton } from '@ionic/angular/ion-button';\nimport { IonButtons } from '@ionic/angular/ion-buttons';\nimport { IonCardContent } from '@ionic/angular/ion-card-content';\nimport { IonCardHeader } from '@ionic/angular/ion-card-header';\nimport { IonCardTitle } from '@ionic/angular/ion-card-title';\nimport { IonCard } from '@ionic/angular/ion-card';\nimport { IonContent } from '@ionic/angular/ion-content';\nimport { IonHeader } from '@ionic/angular/ion-header';\nimport { IonItem } from '@ionic/angular/ion-item';\nimport { IonLabel } from '@ionic/angular/ion-label';\nimport { IonList } from '@ionic/angular/ion-list';\nimport { IonNote } from '@ionic/angular/ion-note';\nimport { IonSpinner } from '@ionic/angular/ion-spinner';\nimport { IonTitle } from '@ionic/angular/ion-title';\nimport { IonToolbar } from '@ionic/angular/ion-toolbar';\nimport { IIdAndBrief } from '@sneat/core';\nimport {\n CONTACTUS_SPACE_SERVICE,\n IContactBrief,\n} from '@sneat/extension-contactus-contract';\nimport {\n SpaceComponentBaseParams,\n SpacePageBaseComponent,\n} from '@sneat/space-components';\nimport { ClassName } from '@sneat/ui';\nimport { SpaceServiceModule } from '@sneat/space-services';\nimport {\n BehaviorSubject,\n catchError,\n combineLatest,\n first,\n map,\n of,\n switchMap,\n} from 'rxjs';\nimport {\n ISourceObligationActivitiesPage,\n ISourceObligationActivity,\n ISourceObligationsStatus,\n ISourceRef,\n SourceObligationsApiService,\n SourceSettlementStatus,\n} from './source-obligations-api.service';\n\ninterface ILoadedSourceObligations {\n readonly status: ISourceObligationsStatus;\n readonly activityPage: ISourceObligationActivitiesPage;\n readonly contacts: readonly IIdAndBrief<IContactBrief>[];\n}\n\ntype SourceLoadResult =\n | { readonly data: ILoadedSourceObligations }\n | { readonly error: unknown; readonly message: string };\n\n@Component({\n selector: 'debtus-source-obligations-page',\n templateUrl: './source-obligations-page.component.html',\n imports: [\n SpaceServiceModule,\n DatePipe,\n IonBackButton,\n IonBadge,\n IonButton,\n IonButtons,\n IonCard,\n IonCardContent,\n IonCardHeader,\n IonCardTitle,\n IonContent,\n IonHeader,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n IonSpinner,\n IonTitle,\n IonToolbar,\n ],\n providers: [\n { provide: ClassName, useValue: 'SourceObligationsPageComponent' },\n SpaceComponentBaseParams,\n ],\n})\nexport class SourceObligationsPageComponent extends SpacePageBaseComponent {\n private readonly sourceApi = inject(SourceObligationsApiService);\n private readonly contactusSpaceService = inject(CONTACTUS_SPACE_SERVICE);\n private readonly componentDestroyRef = inject(DestroyRef);\n private readonly reload = new BehaviorSubject<void>(undefined);\n\n protected readonly $loading = signal(true);\n protected readonly $loadingMore = signal(false);\n protected readonly $error = signal<string | undefined>(undefined);\n protected readonly $activityError = signal<string | undefined>(undefined);\n protected readonly $status = signal<ISourceObligationsStatus | undefined>(\n undefined,\n );\n protected readonly $activities = signal<readonly ISourceObligationActivity[]>(\n [],\n );\n protected readonly $nextCursor = signal<string | undefined>(undefined);\n private readonly $contactTitles = signal<ReadonlyMap<string, string>>(\n new Map(),\n );\n\n constructor() {\n super();\n this.$defaultBackUrlSpacePath.set('debts');\n combineLatest([this.spaceIDChanged$, this.route.queryParamMap, this.reload])\n .pipe(\n switchMap(([spaceID, params]) => {\n this.$loading.set(true);\n this.$error.set(undefined);\n this.$activityError.set(undefined);\n this.$status.set(undefined);\n this.$activities.set([]);\n this.$nextCursor.set(undefined);\n const sourceNamespace = params.get('sourceNamespace');\n const sourceRecordID = params.get('sourceRecordID')?.trim();\n if (!spaceID || sourceNamespace !== 'splitus' || !sourceRecordID) {\n return of<SourceLoadResult>({\n error: new Error('Invalid source-obligations route'),\n message: 'This settlement link is incomplete or invalid.',\n });\n }\n const source: ISourceRef = {\n namespace: 'splitus',\n spaceID,\n recordID: sourceRecordID,\n };\n return combineLatest({\n status: this.sourceApi.getStatus(source),\n activityPage: this.sourceApi.getActivities(source),\n contacts: this.contactusSpaceService\n .watchContactBriefs(spaceID)\n .pipe(first()),\n }).pipe(\n map((data): SourceLoadResult => ({ data })),\n catchError((error: unknown) =>\n of<SourceLoadResult>({\n error,\n message: 'We could not load this bill settlement.',\n }),\n ),\n );\n }),\n takeUntilDestroyed(),\n )\n .subscribe((result) => {\n this.$loading.set(false);\n if ('error' in result) {\n this.$error.set(result.message);\n this.errorLogger.logError(result.error, result.message);\n return;\n }\n this.$status.set(result.data.status);\n this.$activities.set(result.data.activityPage.activities);\n this.$nextCursor.set(result.data.activityPage.nextCursor);\n this.$contactTitles.set(\n new Map(\n result.data.contacts.map((contact) => [\n contact.id,\n contact.brief?.title ||\n contact.brief?.shortTitle ||\n 'Unknown contact',\n ]),\n ),\n );\n });\n }\n\n protected retry(): void {\n this.reload.next();\n }\n\n protected loadMore(): void {\n const source = this.$status()?.source;\n const cursor = this.$nextCursor();\n if (!source || !cursor || this.$loadingMore()) {\n return;\n }\n this.$loadingMore.set(true);\n this.$activityError.set(undefined);\n this.sourceApi\n .getActivities(source, cursor)\n .pipe(takeUntilDestroyed(this.componentDestroyRef))\n .subscribe({\n next: (page) => {\n this.$activities.update((activities) => [\n ...activities,\n ...page.activities,\n ]);\n this.$nextCursor.set(page.nextCursor);\n this.$loadingMore.set(false);\n },\n error: (error: unknown) => {\n this.$activityError.set('We could not load more activity.');\n this.$loadingMore.set(false);\n this.errorLogger.logError(\n error,\n 'Failed to load more Debtus activity',\n );\n },\n });\n }\n\n protected contactTitle(contactID: string): string {\n return this.$contactTitles().get(contactID) ?? 'Unknown contact';\n }\n\n protected formatMinorAmount(minor: string, currency: string): string {\n if (!['EUR', 'GBP', 'USD'].includes(currency)) {\n return `${minor} minor units ${currency}`;\n }\n const value = BigInt(minor);\n const whole = value / 100n;\n const fraction = (value % 100n).toString().padStart(2, '0');\n return `${currency} ${whole}.${fraction}`;\n }\n\n protected settlementColor(status: SourceSettlementStatus): string {\n switch (status) {\n case 'settled':\n return 'success';\n case 'part_settled':\n return 'warning';\n default:\n return 'medium';\n }\n }\n\n protected activityTitle(kind: ISourceObligationActivity['kind']): string {\n switch (kind) {\n case 'obligation':\n return 'Obligation recorded';\n case 'repayment':\n return 'Repayment recorded';\n case 'adjustment':\n return 'Adjustment recorded';\n case 'cancellation':\n return 'Obligation cancelled';\n case 'credit':\n return 'Credit recorded';\n }\n }\n}\n","<ion-header>\n <ion-toolbar color=\"light\">\n <ion-buttons slot=\"start\">\n <ion-back-button [defaultHref]=\"$defaultBackUrl()\" />\n </ion-buttons>\n <ion-title>Bill settlement</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"cardy\">\n @if ($loading()) {\n <div class=\"ion-padding ion-text-center\">\n <ion-spinner name=\"dots\" />\n <p>Loading settlement…</p>\n </div>\n } @else if ($error()) {\n <ion-card>\n <ion-card-content>\n <ion-note color=\"danger\">{{ $error() }}</ion-note>\n <ion-button expand=\"block\" fill=\"outline\" (click)=\"retry()\">\n Try again\n </ion-button>\n </ion-card-content>\n </ion-card>\n } @else if ($status(); as status) {\n <ion-card>\n <ion-card-header>\n <ion-card-title>Splitus bill settlement</ion-card-title>\n </ion-card-header>\n <ion-list lines=\"full\">\n <ion-item>\n <ion-label>\n <p>Posting</p>\n <h2>{{ status.postingStatus }}</h2>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"status.postingStatus === 'applied' ? 'success' : 'warning'\"\n >\n {{ status.postingStatus }}\n </ion-badge>\n </ion-item>\n <ion-item>\n <ion-label>\n <p>Debtus receipt</p>\n <h2>Recorded in Debtus</h2>\n <ion-note>Revision {{ status.latestReceipt.revision }}</ion-note>\n </ion-label>\n </ion-item>\n @if (status.attentionReason) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>Needs attention</p>\n <h2>{{ status.attentionReason }}</h2>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Who owes what</ion-card-title>\n </ion-card-header>\n @if (status.obligations.length) {\n <ion-list lines=\"full\">\n @for (obligation of status.obligations; track obligation.lineID) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>\n {{ contactTitle(obligation.debtor.contactID) }} owes\n {{ contactTitle(obligation.creditor.contactID) }}\n </h2>\n <p>\n Outstanding:\n {{\n formatMinorAmount(\n obligation.outstandingMinor,\n obligation.currency\n )\n }}\n </p>\n <ion-note>\n Original\n {{\n formatMinorAmount(\n obligation.principalMinor,\n obligation.currency\n )\n }}\n · Repaid\n {{\n formatMinorAmount(\n obligation.repaidMinor,\n obligation.currency\n )\n }}\n @if (obligation.creditMinor !== '0') {\n · Credit\n {{\n formatMinorAmount(\n obligation.creditMinor,\n obligation.currency\n )\n }}\n }\n </ion-note>\n </ion-label>\n <ion-badge\n slot=\"end\"\n [color]=\"settlementColor(obligation.status)\"\n >\n {{\n obligation.status === 'part_settled'\n ? 'part settled'\n : obligation.status\n }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No obligations were recorded for this bill.</ion-note>\n </ion-card-content>\n }\n </ion-card>\n\n <ion-card>\n <ion-card-header>\n <ion-card-title>Activity</ion-card-title>\n </ion-card-header>\n @if ($activities().length) {\n <ion-list lines=\"full\">\n @for (activity of $activities(); track activity.activityID) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>{{ activityTitle(activity.kind) }}</h2>\n <p>\n {{ contactTitle(activity.from.contactID) }} →\n {{ contactTitle(activity.to.contactID) }}\n </p>\n <ion-note>{{ activity.occurredAt | date: 'medium' }}</ion-note>\n </ion-label>\n <ion-badge slot=\"end\">\n {{ formatMinorAmount(activity.amountMinor, activity.currency) }}\n </ion-badge>\n </ion-item>\n }\n </ion-list>\n } @else {\n <ion-card-content>\n <ion-note>No Debtus activity has been recorded yet.</ion-note>\n </ion-card-content>\n }\n @if ($activityError()) {\n <ion-card-content>\n <ion-note color=\"danger\">{{ $activityError() }}</ion-note>\n </ion-card-content>\n }\n @if ($nextCursor()) {\n <ion-card-content>\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n [disabled]=\"$loadingMore()\"\n (click)=\"loadMore()\"\n >\n @if ($loadingMore()) {\n <ion-spinner name=\"dots\" />\n } @else {\n Load more activity\n }\n </ion-button>\n </ion-card-content>\n }\n </ion-card>\n }\n</ion-content>\n"],"names":[],"mappings":"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"}