@sneat/extension-debtus 0.2.0 → 0.2.2
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.
|
@@ -1,196 +1,51 @@
|
|
|
1
|
+
import { HttpParams } from '@angular/common/http';
|
|
1
2
|
import * as i0 from '@angular/core';
|
|
2
3
|
import { inject, Injectable } from '@angular/core';
|
|
3
4
|
import { SneatApiService } from '@sneat/api';
|
|
4
5
|
import { debtDirectionToApiDirection, apiDirectionToDebtDirection, DEBTUS_SERVICE } from '@sneat/extension-debtus-contract';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
// ===========================================================================
|
|
8
|
-
// Fable: prototype demo data
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// These fixtures back the debtus web-UI flows that do NOT yet have a wired,
|
|
11
|
-
// authenticated Go HTTP endpoint (balances summary, contacts list, per-contact
|
|
12
|
-
// balance, transfer history). They are shaped exactly like the real contract
|
|
13
|
-
// models so that when the backend endpoints are wired (see PR notes:
|
|
14
|
-
// api4unsorted contacts CRUD + a balances endpoint), the internal service can
|
|
15
|
-
// swap `of(demo…)` for a `sneatApiService.get(…)` call with no UI changes.
|
|
16
|
-
//
|
|
17
|
-
// Counterparties are modelled as contactus space contacts (contactID = a
|
|
18
|
-
// contactus contact id) — debtus does NOT own a separate contact list.
|
|
19
|
-
// `bff-friend` is intentionally in a DIFFERENT space to exercise cross-space
|
|
20
|
-
// lending in the UI.
|
|
21
|
-
// ===========================================================================
|
|
22
|
-
const DEMO_CONTACT_BALANCES = [
|
|
23
|
-
{
|
|
24
|
-
contactID: 'contact-alice',
|
|
25
|
-
title: 'Alice Johnson',
|
|
26
|
-
balance: { USD: 120, EUR: 0 },
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
contactID: 'contact-bob',
|
|
30
|
-
title: 'Bob Smith',
|
|
31
|
-
balance: { USD: -45 },
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
contactID: 'contact-carol',
|
|
35
|
-
title: 'Carol Lee',
|
|
36
|
-
balance: { EUR: 30 },
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
// Cross-space counterparty: belongs to their own personal space.
|
|
40
|
-
contactID: 'contact-dave',
|
|
41
|
-
title: 'Dave (family friend)',
|
|
42
|
-
balance: { USD: 200 },
|
|
43
|
-
counterpartySpaceID: 'space-dave-personal',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
contactID: 'contact-erin',
|
|
47
|
-
title: 'Erin Park',
|
|
48
|
-
balance: { EUR: 0, USD: 0 }, // settled up
|
|
49
|
-
},
|
|
50
|
-
];
|
|
51
|
-
function demoTransfersForSpace(spaceID) {
|
|
52
|
-
return [
|
|
53
|
-
{
|
|
54
|
-
id: 'transfer-1001',
|
|
55
|
-
direction: 'lend',
|
|
56
|
-
amount: { currency: 'USD', value: 120 },
|
|
57
|
-
counterpartyContactID: 'contact-alice',
|
|
58
|
-
counterpartyTitle: 'Alice Johnson',
|
|
59
|
-
note: 'Concert tickets',
|
|
60
|
-
created: '2026-06-20T10:15:00Z',
|
|
61
|
-
dueOn: '2026-07-20T00:00:00Z',
|
|
62
|
-
isReturn: false,
|
|
63
|
-
isOutstanding: true,
|
|
64
|
-
creatorSpaceID: spaceID,
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: 'transfer-1002',
|
|
68
|
-
direction: 'borrow',
|
|
69
|
-
amount: { currency: 'USD', value: 45 },
|
|
70
|
-
counterpartyContactID: 'contact-bob',
|
|
71
|
-
counterpartyTitle: 'Bob Smith',
|
|
72
|
-
note: 'Lunch',
|
|
73
|
-
created: '2026-06-22T12:30:00Z',
|
|
74
|
-
isReturn: false,
|
|
75
|
-
isOutstanding: true,
|
|
76
|
-
creatorSpaceID: spaceID,
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: 'transfer-1003',
|
|
80
|
-
direction: 'lend',
|
|
81
|
-
amount: { currency: 'EUR', value: 30 },
|
|
82
|
-
counterpartyContactID: 'contact-carol',
|
|
83
|
-
counterpartyTitle: 'Carol Lee',
|
|
84
|
-
created: '2026-06-25T09:00:00Z',
|
|
85
|
-
isReturn: false,
|
|
86
|
-
isOutstanding: true,
|
|
87
|
-
creatorSpaceID: spaceID,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
id: 'transfer-1004',
|
|
91
|
-
direction: 'lend',
|
|
92
|
-
amount: { currency: 'USD', value: 200 },
|
|
93
|
-
counterpartyContactID: 'contact-dave',
|
|
94
|
-
counterpartyTitle: 'Dave (family friend)',
|
|
95
|
-
note: 'Cross-space loan (different space)',
|
|
96
|
-
created: '2026-06-18T08:00:00Z',
|
|
97
|
-
isReturn: false,
|
|
98
|
-
isOutstanding: true,
|
|
99
|
-
creatorSpaceID: spaceID,
|
|
100
|
-
counterpartySpaceID: 'space-dave-personal',
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: 'transfer-1005',
|
|
104
|
-
direction: 'borrow',
|
|
105
|
-
amount: { currency: 'USD', value: 20 },
|
|
106
|
-
counterpartyContactID: 'contact-erin',
|
|
107
|
-
counterpartyTitle: 'Erin Park',
|
|
108
|
-
note: 'Coffee (already returned)',
|
|
109
|
-
created: '2026-06-10T08:00:00Z',
|
|
110
|
-
isReturn: false,
|
|
111
|
-
isOutstanding: false,
|
|
112
|
-
creatorSpaceID: spaceID,
|
|
113
|
-
},
|
|
114
|
-
];
|
|
115
|
-
}
|
|
6
|
+
import { map } from 'rxjs';
|
|
116
7
|
|
|
117
8
|
class DebtusService {
|
|
118
9
|
sneatApiService = inject(SneatApiService);
|
|
119
|
-
// ----- REAL endpoint: legacy thin create (kept, backwards compatible) -----
|
|
120
10
|
createDebtRecord(request) {
|
|
121
11
|
return this.sneatApiService.post('debtus/create_debt_record', request);
|
|
122
12
|
}
|
|
123
|
-
// ===========================================================================
|
|
124
|
-
// Fable: prototype demo data
|
|
125
|
-
// No wired, authenticated Go HTTP endpoint exists yet for balances / contacts
|
|
126
|
-
// / history (api4unsorted contacts CRUD is defined but not mounted in
|
|
127
|
-
// backend/debtus/module.go; there is no balances endpoint). These read from
|
|
128
|
-
// fixtures. SWAP POINT: replace each `of(...)` with a `sneatApiService.get`
|
|
129
|
-
// once the endpoints are wired — the return types already match.
|
|
130
|
-
// ===========================================================================
|
|
131
13
|
getContactBalances(spaceID) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
14
|
+
const params = new HttpParams().set('spaceID', spaceID);
|
|
15
|
+
return this.sneatApiService
|
|
16
|
+
.get('api4debtus/space/contact-balances', params)
|
|
17
|
+
.pipe(map((contacts) => contacts.map((contact) => ({
|
|
18
|
+
contactID: contact.contactID,
|
|
19
|
+
title: contact.title,
|
|
20
|
+
balance: centsBalanceToMajor(contact.balance),
|
|
21
|
+
counterpartySpaceID: contact.counterpartySpaceID,
|
|
22
|
+
}))));
|
|
135
23
|
}
|
|
136
24
|
getContactBalance(spaceID, contactID) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
};
|
|
144
|
-
return of({ ...found });
|
|
25
|
+
return this.getContactBalances(spaceID).pipe(map((contacts) => contacts.find((c) => c.contactID === contactID)), map((contact) => {
|
|
26
|
+
if (!contact) {
|
|
27
|
+
throw new Error(`Debtus contact "${contactID}" was not found`);
|
|
28
|
+
}
|
|
29
|
+
return contact;
|
|
30
|
+
}));
|
|
145
31
|
}
|
|
146
32
|
getTransfers(spaceID, contactID) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
|
|
33
|
+
let params = new HttpParams().set('spaceID', spaceID);
|
|
34
|
+
if (contactID) {
|
|
35
|
+
params = params.set('contactID', contactID);
|
|
36
|
+
}
|
|
37
|
+
return this.sneatApiService
|
|
38
|
+
.get('api4debtus/space/transfers', params)
|
|
39
|
+
.pipe(map((dto) => dto.transfers.map(mapSpaceTransferDto)));
|
|
154
40
|
}
|
|
155
|
-
// ===========================================================================
|
|
156
|
-
// REAL endpoints below.
|
|
157
|
-
// ===========================================================================
|
|
158
|
-
/** Reads from demo fixtures; unknown ids error (no fabricated receipts). */
|
|
159
41
|
getTransfer(spaceID, transferID) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// .get<IApiTransferDto>('api4debtus/transfer', params)
|
|
167
|
-
// .pipe(map((dto) => this.mapTransferDto(dto, spaceID)));
|
|
168
|
-
const found = demoTransfersForSpace(spaceID).find((t) => t.id === transferID);
|
|
169
|
-
// Fable refactoring: a transfer that is not in the fixtures (i.e. any
|
|
170
|
-
// REAL transfer just created via POST create-transfer) must be an error,
|
|
171
|
-
// not a fabricated "Unknown / 0.00 USD / Outstanding" receipt — that was
|
|
172
|
-
// presenting fiction as a financial record. The create/settle pages now
|
|
173
|
-
// hand the created transfer to the details page via router state, so this
|
|
174
|
-
// path is only hit on cold loads of unknown ids. The old synthesized
|
|
175
|
-
// fallback is kept below (commented out) per the no-delete policy:
|
|
176
|
-
// return of(
|
|
177
|
-
// found ?? {
|
|
178
|
-
// id: transferID,
|
|
179
|
-
// direction: 'lend',
|
|
180
|
-
// amount: { currency: 'USD', value: 0 },
|
|
181
|
-
// counterpartyContactID: '',
|
|
182
|
-
// counterpartyTitle: 'Unknown',
|
|
183
|
-
// created: new Date().toISOString(),
|
|
184
|
-
// isReturn: false,
|
|
185
|
-
// isOutstanding: true,
|
|
186
|
-
// creatorSpaceID: spaceID,
|
|
187
|
-
// },
|
|
188
|
-
// );
|
|
189
|
-
return found
|
|
190
|
-
? of(found)
|
|
191
|
-
: throwError(() => new Error(`Transfer "${transferID}" was not found (transfer reads are not wired to the live backend yet).`));
|
|
42
|
+
const params = new HttpParams()
|
|
43
|
+
.set('spaceID', spaceID)
|
|
44
|
+
.set('id', transferID);
|
|
45
|
+
return this.sneatApiService
|
|
46
|
+
.get('api4debtus/space/transfer', params)
|
|
47
|
+
.pipe(map(mapSpaceTransferDto));
|
|
192
48
|
}
|
|
193
|
-
/** REAL: POST /api4debtus/create-transfer (Firebase-authenticated). */
|
|
194
49
|
createTransfer(request) {
|
|
195
50
|
const apiDirection = debtDirectionToApiDirection(request.direction);
|
|
196
51
|
const body = {
|
|
@@ -198,16 +53,10 @@ class DebtusService {
|
|
|
198
53
|
direction: apiDirection,
|
|
199
54
|
amount: {
|
|
200
55
|
currency: request.amount.currency,
|
|
201
|
-
value: request.amount.value,
|
|
56
|
+
value: majorToCents(request.amount.value),
|
|
202
57
|
},
|
|
203
|
-
// For u2c (lend) the counterparty is the recipient (toContactID); for
|
|
204
|
-
// c2u (borrow) the counterparty is the source (fromContactID).
|
|
205
58
|
toContactID: apiDirection === 'u2c' ? request.contactID : undefined,
|
|
206
59
|
fromContactID: apiDirection === 'c2u' ? request.contactID : undefined,
|
|
207
|
-
// The backend CreateTransferRequest accepts both `note` and
|
|
208
|
-
// `counterpartySpaceID` (facade4debtus/transfers_create_transfer_dto.go);
|
|
209
|
-
// omitting them silently discarded the user's typed note and the
|
|
210
|
-
// cross-space marker on a financial write.
|
|
211
60
|
note: request.note,
|
|
212
61
|
counterpartySpaceID: request.counterpartySpaceID || undefined,
|
|
213
62
|
isReturn: request.isReturn ?? false,
|
|
@@ -218,24 +67,10 @@ class DebtusService {
|
|
|
218
67
|
.post('api4debtus/create-transfer', body)
|
|
219
68
|
.pipe(map((resp) => this.mapCreateResponse(resp, request)));
|
|
220
69
|
}
|
|
221
|
-
/** Settle-up = a reverse-direction return transfer (mirrors the bot). */
|
|
222
70
|
settleUp(request) {
|
|
223
|
-
// Fable refactoring: the direction now comes from the request — the page
|
|
224
|
-
// that shows the balance derives it via `settleDirectionForBalance` and is
|
|
225
|
-
// the source of truth. Previously it was inferred from DEMO_CONTACT_BALANCES
|
|
226
|
-
// fixtures, so any contact NOT in the fixtures got `'borrow'`
|
|
227
|
-
// unconditionally and settling a debt the user owed recorded the WRONG
|
|
228
|
-
// direction, increasing the imbalance. Old fixture-based inference kept
|
|
229
|
-
// below per the no-delete policy:
|
|
230
|
-
// const contact = DEMO_CONTACT_BALANCES.find(
|
|
231
|
-
// (c) => c.contactID === request.contactID,
|
|
232
|
-
// );
|
|
233
|
-
// const currentValue = contact?.balance[request.amount.currency] ?? 0;
|
|
234
|
-
// const direction = settleDirectionForBalance(currentValue || 1);
|
|
235
|
-
const direction = request.direction;
|
|
236
71
|
return this.createTransfer({
|
|
237
72
|
spaceID: request.spaceID,
|
|
238
|
-
direction,
|
|
73
|
+
direction: request.direction,
|
|
239
74
|
amount: request.amount,
|
|
240
75
|
contactID: request.contactID,
|
|
241
76
|
contactTitle: request.contactTitle,
|
|
@@ -243,56 +78,37 @@ class DebtusService {
|
|
|
243
78
|
counterpartySpaceID: request.counterpartySpaceID,
|
|
244
79
|
});
|
|
245
80
|
}
|
|
246
|
-
// ----- mapping helpers -----
|
|
247
81
|
mapCreateResponse(resp, request) {
|
|
248
82
|
if (resp.Error) {
|
|
249
83
|
throw new Error(resp.Error);
|
|
250
84
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
85
|
+
if (!resp.Transfer) {
|
|
86
|
+
throw new Error('Debtus create-transfer response omitted the transfer');
|
|
87
|
+
}
|
|
88
|
+
const counterparty = resp.Transfer.To ?? resp.Transfer.From;
|
|
89
|
+
if (!counterparty) {
|
|
90
|
+
throw new Error('Debtus create-transfer response omitted the counterparty');
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
transfer: {
|
|
94
|
+
id: resp.Transfer.Id,
|
|
257
95
|
direction: request.direction,
|
|
258
|
-
amount:
|
|
259
|
-
|
|
260
|
-
|
|
96
|
+
amount: {
|
|
97
|
+
currency: request.amount.currency,
|
|
98
|
+
value: centsToMajor(resp.Transfer.Amount.value),
|
|
99
|
+
},
|
|
100
|
+
counterpartyContactID: counterparty.ID,
|
|
101
|
+
counterpartyTitle: counterparty.Name,
|
|
261
102
|
note: request.note,
|
|
262
|
-
created:
|
|
263
|
-
dueOn:
|
|
264
|
-
isReturn:
|
|
265
|
-
isOutstanding:
|
|
103
|
+
created: resp.Transfer.Created,
|
|
104
|
+
dueOn: nonZeroDate(resp.Transfer.Due),
|
|
105
|
+
isReturn: resp.Transfer.IsReturn ?? false,
|
|
106
|
+
isOutstanding: resp.Transfer.IsOutstanding ?? !(resp.Transfer.IsReturn ?? false),
|
|
266
107
|
creatorSpaceID: request.spaceID,
|
|
267
108
|
counterpartySpaceID: request.counterpartySpaceID,
|
|
268
|
-
};
|
|
269
|
-
return {
|
|
270
|
-
transfer,
|
|
271
|
-
userBalance: (resp.UserBalance ?? {}),
|
|
272
|
-
counterpartyBalance: (resp.CounterpartyBalance ??
|
|
273
|
-
{}),
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
mapTransferDto(dto, spaceID) {
|
|
277
|
-
const counterparty = dto.To ?? dto.From;
|
|
278
|
-
const direction = dto.Direction
|
|
279
|
-
? apiDirectionToDebtDirection(dto.Direction)
|
|
280
|
-
: 'lend';
|
|
281
|
-
return {
|
|
282
|
-
id: dto.Id,
|
|
283
|
-
direction,
|
|
284
|
-
amount: {
|
|
285
|
-
currency: (dto.Amount?.currency ?? 'USD'),
|
|
286
|
-
value: dto.Amount?.value ?? 0,
|
|
287
109
|
},
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
note: dto.Comment,
|
|
291
|
-
created: dto.Created ?? new Date().toISOString(),
|
|
292
|
-
dueOn: dto.Due,
|
|
293
|
-
isReturn: dto.IsReturn ?? false,
|
|
294
|
-
isOutstanding: dto.IsOutstanding ?? true,
|
|
295
|
-
creatorSpaceID: spaceID,
|
|
110
|
+
userBalance: centsBalanceToMajor(resp.UserBalance ?? {}),
|
|
111
|
+
counterpartyBalance: centsBalanceToMajor(resp.CounterpartyBalance ?? {}),
|
|
296
112
|
};
|
|
297
113
|
}
|
|
298
114
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -301,6 +117,31 @@ class DebtusService {
|
|
|
301
117
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DebtusService, decorators: [{
|
|
302
118
|
type: Injectable
|
|
303
119
|
}] });
|
|
120
|
+
function mapSpaceTransferDto(dto) {
|
|
121
|
+
return {
|
|
122
|
+
...dto,
|
|
123
|
+
direction: apiDirectionToDebtDirection(dto.direction),
|
|
124
|
+
amount: {
|
|
125
|
+
currency: dto.amount.currency,
|
|
126
|
+
value: centsToMajor(dto.amount.value),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function centsBalanceToMajor(balance) {
|
|
131
|
+
return Object.fromEntries(Object.entries(balance).map(([currency, cents]) => [
|
|
132
|
+
currency,
|
|
133
|
+
centsToMajor(cents),
|
|
134
|
+
]));
|
|
135
|
+
}
|
|
136
|
+
function majorToCents(value) {
|
|
137
|
+
return Math.round(value * 100);
|
|
138
|
+
}
|
|
139
|
+
function centsToMajor(value) {
|
|
140
|
+
return value / 100;
|
|
141
|
+
}
|
|
142
|
+
function nonZeroDate(value) {
|
|
143
|
+
return !value || value.startsWith('0001-01-01') ? undefined : value;
|
|
144
|
+
}
|
|
304
145
|
|
|
305
146
|
// Registers the concrete DebtusService and binds it to the DEBTUS_SERVICE token so
|
|
306
147
|
// consumers depend only on the IDebtusService contract. Wired in at app
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sneat-extension-debtus.mjs","sources":["../../../../../../libs/extensions/debtus/runtime/src/lib/services/demo-data.ts","../../../../../../libs/extensions/debtus/runtime/src/lib/services/debtus.service.ts","../../../../../../libs/extensions/debtus/runtime/src/lib/provide-debtus.ts","../../../../../../libs/extensions/debtus/runtime/src/sneat-extension-debtus.ts"],"sourcesContent":["import {\n IContactBalance,\n IDebtusTransfer,\n} from '@sneat/extension-debtus-contract';\n\n// ===========================================================================\n// Fable: prototype demo data\n// ---------------------------------------------------------------------------\n// These fixtures back the debtus web-UI flows that do NOT yet have a wired,\n// authenticated Go HTTP endpoint (balances summary, contacts list, per-contact\n// balance, transfer history). They are shaped exactly like the real contract\n// models so that when the backend endpoints are wired (see PR notes:\n// api4unsorted contacts CRUD + a balances endpoint), the internal service can\n// swap `of(demo…)` for a `sneatApiService.get(…)` call with no UI changes.\n//\n// Counterparties are modelled as contactus space contacts (contactID = a\n// contactus contact id) — debtus does NOT own a separate contact list.\n// `bff-friend` is intentionally in a DIFFERENT space to exercise cross-space\n// lending in the UI.\n// ===========================================================================\n\nexport const DEMO_CONTACT_BALANCES: readonly IContactBalance[] = [\n {\n contactID: 'contact-alice',\n title: 'Alice Johnson',\n balance: { USD: 120, EUR: 0 },\n },\n {\n contactID: 'contact-bob',\n title: 'Bob Smith',\n balance: { USD: -45 },\n },\n {\n contactID: 'contact-carol',\n title: 'Carol Lee',\n balance: { EUR: 30 },\n },\n {\n // Cross-space counterparty: belongs to their own personal space.\n contactID: 'contact-dave',\n title: 'Dave (family friend)',\n balance: { USD: 200 },\n counterpartySpaceID: 'space-dave-personal',\n },\n {\n contactID: 'contact-erin',\n title: 'Erin Park',\n balance: { EUR: 0, USD: 0 }, // settled up\n },\n];\n\nexport function demoTransfersForSpace(spaceID: string): IDebtusTransfer[] {\n return [\n {\n id: 'transfer-1001',\n direction: 'lend',\n amount: { currency: 'USD', value: 120 },\n counterpartyContactID: 'contact-alice',\n counterpartyTitle: 'Alice Johnson',\n note: 'Concert tickets',\n created: '2026-06-20T10:15:00Z',\n dueOn: '2026-07-20T00:00:00Z',\n isReturn: false,\n isOutstanding: true,\n creatorSpaceID: spaceID,\n },\n {\n id: 'transfer-1002',\n direction: 'borrow',\n amount: { currency: 'USD', value: 45 },\n counterpartyContactID: 'contact-bob',\n counterpartyTitle: 'Bob Smith',\n note: 'Lunch',\n created: '2026-06-22T12:30:00Z',\n isReturn: false,\n isOutstanding: true,\n creatorSpaceID: spaceID,\n },\n {\n id: 'transfer-1003',\n direction: 'lend',\n amount: { currency: 'EUR', value: 30 },\n counterpartyContactID: 'contact-carol',\n counterpartyTitle: 'Carol Lee',\n created: '2026-06-25T09:00:00Z',\n isReturn: false,\n isOutstanding: true,\n creatorSpaceID: spaceID,\n },\n {\n id: 'transfer-1004',\n direction: 'lend',\n amount: { currency: 'USD', value: 200 },\n counterpartyContactID: 'contact-dave',\n counterpartyTitle: 'Dave (family friend)',\n note: 'Cross-space loan (different space)',\n created: '2026-06-18T08:00:00Z',\n isReturn: false,\n isOutstanding: true,\n creatorSpaceID: spaceID,\n counterpartySpaceID: 'space-dave-personal',\n },\n {\n id: 'transfer-1005',\n direction: 'borrow',\n amount: { currency: 'USD', value: 20 },\n counterpartyContactID: 'contact-erin',\n counterpartyTitle: 'Erin Park',\n note: 'Coffee (already returned)',\n created: '2026-06-10T08:00:00Z',\n isReturn: false,\n isOutstanding: false,\n creatorSpaceID: spaceID,\n },\n ];\n}\n","import { Injectable, inject } from '@angular/core';\nimport { SneatApiService } from '@sneat/api';\nimport {\n IContactBalance,\n ICreateDebtRecordRequest,\n ICreateTransferRequest,\n ICreateTransferResponse,\n IDebtusService,\n IDebtusTransfer,\n ISettleUpRequest,\n apiDirectionToDebtDirection,\n debtDirectionToApiDirection,\n} from '@sneat/extension-debtus-contract';\nimport { Observable, map, of, throwError } from 'rxjs';\nimport { DEMO_CONTACT_BALANCES, demoTransfersForSpace } from './demo-data';\n\n// Backend DTO shapes (facade4debtus/dto4debtus). Only the fields the UI reads\n// are declared here.\ninterface IApiContactDto {\n readonly ID: string;\n readonly UserID?: string;\n readonly Name: string;\n readonly Comment?: string;\n}\n\ninterface IApiTransferDto {\n readonly Id: string;\n readonly Created: string;\n readonly Amount: { readonly currency: string; readonly value: number };\n readonly IsReturn?: boolean;\n readonly CreatorUserID?: string;\n readonly From?: IApiContactDto;\n readonly To?: IApiContactDto;\n readonly Due?: string;\n readonly Direction?: 'u2c' | 'c2u' | '3d-party';\n readonly IsOutstanding?: boolean;\n readonly Comment?: string;\n}\n\ninterface IApiCreateTransferResponse {\n readonly Error?: string;\n readonly Transfer?: IApiTransferDto;\n readonly UserBalance?: Record<string, number>;\n readonly CounterpartyBalance?: Record<string, number>;\n}\n\n@Injectable()\nexport class DebtusService implements IDebtusService {\n private readonly sneatApiService = inject(SneatApiService);\n\n // ----- REAL endpoint: legacy thin create (kept, backwards compatible) -----\n public createDebtRecord(\n request: ICreateDebtRecordRequest,\n ): Observable<string> {\n return this.sneatApiService.post('debtus/create_debt_record', request);\n }\n\n // ===========================================================================\n // Fable: prototype demo data\n // No wired, authenticated Go HTTP endpoint exists yet for balances / contacts\n // / history (api4unsorted contacts CRUD is defined but not mounted in\n // backend/debtus/module.go; there is no balances endpoint). These read from\n // fixtures. SWAP POINT: replace each `of(...)` with a `sneatApiService.get`\n // once the endpoints are wired — the return types already match.\n // ===========================================================================\n\n public getContactBalances(spaceID: string): Observable<IContactBalance[]> {\n // SWAP: this.sneatApiService.get<IContactBalance[]>('api4debtus/user/contacts', new HttpParams().set('spaceID', spaceID))\n void spaceID;\n return of(DEMO_CONTACT_BALANCES.map((c) => ({ ...c })));\n }\n\n public getContactBalance(\n spaceID: string,\n contactID: string,\n ): Observable<IContactBalance> {\n void spaceID;\n const found =\n DEMO_CONTACT_BALANCES.find((c) => c.contactID === contactID) ??\n ({\n contactID,\n title: contactID,\n balance: {},\n } as IContactBalance);\n return of({ ...found });\n }\n\n public getTransfers(\n spaceID: string,\n contactID?: string,\n ): Observable<IDebtusTransfer[]> {\n // NOTE: GET /api4debtus/user/api4transfers exists but is currently broken\n // server-side (auth bug queries an empty userID). Using fixtures until the\n // backend fix lands. SWAP: this.sneatApiService.get('api4debtus/user/api4transfers', params).\n const all = demoTransfersForSpace(spaceID);\n return of(\n contactID\n ? all.filter((t) => t.counterpartyContactID === contactID)\n : all,\n );\n }\n\n // ===========================================================================\n // REAL endpoints below.\n // ===========================================================================\n\n /** Reads from demo fixtures; unknown ids error (no fabricated receipts). */\n public getTransfer(\n spaceID: string,\n transferID: string,\n ): Observable<IDebtusTransfer> {\n // The live GET transfer endpoint returns a perspective-resolved TransferDto.\n // For the prototype we resolve from fixtures so the receipt screen renders\n // without a live backend; SWAP to the real GET when running against a\n // deployed server:\n // const params = new HttpParams().set('id', transferID);\n // return this.sneatApiService\n // .get<IApiTransferDto>('api4debtus/transfer', params)\n // .pipe(map((dto) => this.mapTransferDto(dto, spaceID)));\n const found = demoTransfersForSpace(spaceID).find(\n (t) => t.id === transferID,\n );\n // Fable refactoring: a transfer that is not in the fixtures (i.e. any\n // REAL transfer just created via POST create-transfer) must be an error,\n // not a fabricated \"Unknown / 0.00 USD / Outstanding\" receipt — that was\n // presenting fiction as a financial record. The create/settle pages now\n // hand the created transfer to the details page via router state, so this\n // path is only hit on cold loads of unknown ids. The old synthesized\n // fallback is kept below (commented out) per the no-delete policy:\n // return of(\n // found ?? {\n // id: transferID,\n // direction: 'lend',\n // amount: { currency: 'USD', value: 0 },\n // counterpartyContactID: '',\n // counterpartyTitle: 'Unknown',\n // created: new Date().toISOString(),\n // isReturn: false,\n // isOutstanding: true,\n // creatorSpaceID: spaceID,\n // },\n // );\n return found\n ? of(found)\n : throwError(\n () =>\n new Error(\n `Transfer \"${transferID}\" was not found (transfer reads are not wired to the live backend yet).`,\n ),\n );\n }\n\n /** REAL: POST /api4debtus/create-transfer (Firebase-authenticated). */\n public createTransfer(\n request: ICreateTransferRequest,\n ): Observable<ICreateTransferResponse> {\n const apiDirection = debtDirectionToApiDirection(request.direction);\n const body = {\n spaceID: request.spaceID,\n direction: apiDirection,\n amount: {\n currency: request.amount.currency,\n value: request.amount.value,\n },\n // For u2c (lend) the counterparty is the recipient (toContactID); for\n // c2u (borrow) the counterparty is the source (fromContactID).\n toContactID: apiDirection === 'u2c' ? request.contactID : undefined,\n fromContactID: apiDirection === 'c2u' ? request.contactID : undefined,\n // The backend CreateTransferRequest accepts both `note` and\n // `counterpartySpaceID` (facade4debtus/transfers_create_transfer_dto.go);\n // omitting them silently discarded the user's typed note and the\n // cross-space marker on a financial write.\n note: request.note,\n counterpartySpaceID: request.counterpartySpaceID || undefined,\n isReturn: request.isReturn ?? false,\n returnToTransferID: request.returnToTransferID,\n dueOn: request.dueOn,\n };\n return this.sneatApiService\n .post<IApiCreateTransferResponse>('api4debtus/create-transfer', body)\n .pipe(\n map((resp) => this.mapCreateResponse(resp, request)),\n );\n }\n\n /** Settle-up = a reverse-direction return transfer (mirrors the bot). */\n public settleUp(\n request: ISettleUpRequest,\n ): Observable<ICreateTransferResponse> {\n // Fable refactoring: the direction now comes from the request — the page\n // that shows the balance derives it via `settleDirectionForBalance` and is\n // the source of truth. Previously it was inferred from DEMO_CONTACT_BALANCES\n // fixtures, so any contact NOT in the fixtures got `'borrow'`\n // unconditionally and settling a debt the user owed recorded the WRONG\n // direction, increasing the imbalance. Old fixture-based inference kept\n // below per the no-delete policy:\n // const contact = DEMO_CONTACT_BALANCES.find(\n // (c) => c.contactID === request.contactID,\n // );\n // const currentValue = contact?.balance[request.amount.currency] ?? 0;\n // const direction = settleDirectionForBalance(currentValue || 1);\n const direction = request.direction;\n return this.createTransfer({\n spaceID: request.spaceID,\n direction,\n amount: request.amount,\n contactID: request.contactID,\n contactTitle: request.contactTitle,\n isReturn: true,\n counterpartySpaceID: request.counterpartySpaceID,\n });\n }\n\n // ----- mapping helpers -----\n\n private mapCreateResponse(\n resp: IApiCreateTransferResponse,\n request: ICreateTransferRequest,\n ): ICreateTransferResponse {\n if (resp.Error) {\n throw new Error(resp.Error);\n }\n const transfer: IDebtusTransfer = resp.Transfer\n ? this.mapTransferDto(resp.Transfer, request.spaceID)\n : {\n // If the backend omits the transfer echo, synthesize from the request\n // so the UI can still navigate to a detail screen.\n id: `pending-${Date.now()}`,\n direction: request.direction,\n amount: request.amount,\n counterpartyContactID: request.contactID,\n counterpartyTitle: request.contactTitle ?? request.contactID,\n note: request.note,\n created: new Date().toISOString(),\n dueOn: request.dueOn,\n isReturn: request.isReturn ?? false,\n isOutstanding: true,\n creatorSpaceID: request.spaceID,\n counterpartySpaceID: request.counterpartySpaceID,\n };\n return {\n transfer,\n userBalance: (resp.UserBalance ?? {}) as ICreateTransferResponse['userBalance'],\n counterpartyBalance: (resp.CounterpartyBalance ??\n {}) as ICreateTransferResponse['counterpartyBalance'],\n };\n }\n\n private mapTransferDto(\n dto: IApiTransferDto,\n spaceID: string,\n ): IDebtusTransfer {\n const counterparty = dto.To ?? dto.From;\n const direction = dto.Direction\n ? apiDirectionToDebtDirection(dto.Direction)\n : 'lend';\n return {\n id: dto.Id,\n direction,\n amount: {\n currency: (dto.Amount?.currency ?? 'USD') as\n | 'USD'\n | 'EUR',\n value: dto.Amount?.value ?? 0,\n },\n counterpartyContactID: counterparty?.ID ?? '',\n counterpartyTitle: counterparty?.Name ?? 'Unknown',\n note: dto.Comment,\n created: dto.Created ?? new Date().toISOString(),\n dueOn: dto.Due,\n isReturn: dto.IsReturn ?? false,\n isOutstanding: dto.IsOutstanding ?? true,\n creatorSpaceID: spaceID,\n };\n }\n}\n","import { Provider } from '@angular/core';\nimport { DEBTUS_SERVICE } from '@sneat/extension-debtus-contract';\nimport { DebtusService } from './services';\n\n// Registers the concrete DebtusService and binds it to the DEBTUS_SERVICE token so\n// consumers depend only on the IDebtusService contract. Wired in at app\n// bootstrap (consumers do not import this factory directly).\nexport function provideDebtus(): Provider[] {\n return [DebtusService, { provide: DEBTUS_SERVICE, useExisting: DebtusService }];\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEO,MAAM,qBAAqB,GAA+B;AAC/D,IAAA;AACE,QAAA,SAAS,EAAE,eAAe;AAC1B,QAAA,KAAK,EAAE,eAAe;QACtB,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE;AAC9B,KAAA;AACD,IAAA;AACE,QAAA,SAAS,EAAE,aAAa;AACxB,QAAA,KAAK,EAAE,WAAW;AAClB,QAAA,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE;AACtB,KAAA;AACD,IAAA;AACE,QAAA,SAAS,EAAE,eAAe;AAC1B,QAAA,KAAK,EAAE,WAAW;AAClB,QAAA,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;AACrB,KAAA;AACD,IAAA;;AAEE,QAAA,SAAS,EAAE,cAAc;AACzB,QAAA,KAAK,EAAE,sBAAsB;AAC7B,QAAA,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;AACrB,QAAA,mBAAmB,EAAE,qBAAqB;AAC3C,KAAA;AACD,IAAA;AACE,QAAA,SAAS,EAAE,cAAc;AACzB,QAAA,KAAK,EAAE,WAAW;QAClB,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;AAC5B,KAAA;CACF;AAEK,SAAU,qBAAqB,CAAC,OAAe,EAAA;IACnD,OAAO;AACL,QAAA;AACE,YAAA,EAAE,EAAE,eAAe;AACnB,YAAA,SAAS,EAAE,MAAM;YACjB,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE;AACvC,YAAA,qBAAqB,EAAE,eAAe;AACtC,YAAA,iBAAiB,EAAE,eAAe;AAClC,YAAA,IAAI,EAAE,iBAAiB;AACvB,YAAA,OAAO,EAAE,sBAAsB;AAC/B,YAAA,KAAK,EAAE,sBAAsB;AAC7B,YAAA,QAAQ,EAAE,KAAK;AACf,YAAA,aAAa,EAAE,IAAI;AACnB,YAAA,cAAc,EAAE,OAAO;AACxB,SAAA;AACD,QAAA;AACE,YAAA,EAAE,EAAE,eAAe;AACnB,YAAA,SAAS,EAAE,QAAQ;YACnB,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;AACtC,YAAA,qBAAqB,EAAE,aAAa;AACpC,YAAA,iBAAiB,EAAE,WAAW;AAC9B,YAAA,IAAI,EAAE,OAAO;AACb,YAAA,OAAO,EAAE,sBAAsB;AAC/B,YAAA,QAAQ,EAAE,KAAK;AACf,YAAA,aAAa,EAAE,IAAI;AACnB,YAAA,cAAc,EAAE,OAAO;AACxB,SAAA;AACD,QAAA;AACE,YAAA,EAAE,EAAE,eAAe;AACnB,YAAA,SAAS,EAAE,MAAM;YACjB,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;AACtC,YAAA,qBAAqB,EAAE,eAAe;AACtC,YAAA,iBAAiB,EAAE,WAAW;AAC9B,YAAA,OAAO,EAAE,sBAAsB;AAC/B,YAAA,QAAQ,EAAE,KAAK;AACf,YAAA,aAAa,EAAE,IAAI;AACnB,YAAA,cAAc,EAAE,OAAO;AACxB,SAAA;AACD,QAAA;AACE,YAAA,EAAE,EAAE,eAAe;AACnB,YAAA,SAAS,EAAE,MAAM;YACjB,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE;AACvC,YAAA,qBAAqB,EAAE,cAAc;AACrC,YAAA,iBAAiB,EAAE,sBAAsB;AACzC,YAAA,IAAI,EAAE,oCAAoC;AAC1C,YAAA,OAAO,EAAE,sBAAsB;AAC/B,YAAA,QAAQ,EAAE,KAAK;AACf,YAAA,aAAa,EAAE,IAAI;AACnB,YAAA,cAAc,EAAE,OAAO;AACvB,YAAA,mBAAmB,EAAE,qBAAqB;AAC3C,SAAA;AACD,QAAA;AACE,YAAA,EAAE,EAAE,eAAe;AACnB,YAAA,SAAS,EAAE,QAAQ;YACnB,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;AACtC,YAAA,qBAAqB,EAAE,cAAc;AACrC,YAAA,iBAAiB,EAAE,WAAW;AAC9B,YAAA,IAAI,EAAE,2BAA2B;AACjC,YAAA,OAAO,EAAE,sBAAsB;AAC/B,YAAA,QAAQ,EAAE,KAAK;AACf,YAAA,aAAa,EAAE,KAAK;AACpB,YAAA,cAAc,EAAE,OAAO;AACxB,SAAA;KACF;AACH;;MCpEa,aAAa,CAAA;AACP,IAAA,eAAe,GAAG,MAAM,CAAC,eAAe,CAAC;;AAGnD,IAAA,gBAAgB,CACrB,OAAiC,EAAA;QAEjC,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,2BAA2B,EAAE,OAAO,CAAC;IACxE;;;;;;;;;AAWO,IAAA,kBAAkB,CAAC,OAAe,EAAA;;AAEvC,QAAA,KAAK,OAAO;AACZ,QAAA,OAAO,EAAE,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACzD;IAEO,iBAAiB,CACtB,OAAe,EACf,SAAiB,EAAA;AAEjB,QAAA,KAAK,OAAO;AACZ,QAAA,MAAM,KAAK,GACT,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC;AAC3D,YAAA;gBACC,SAAS;AACT,gBAAA,KAAK,EAAE,SAAS;AAChB,gBAAA,OAAO,EAAE,EAAE;aACQ;AACvB,QAAA,OAAO,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;IACzB;IAEO,YAAY,CACjB,OAAe,EACf,SAAkB,EAAA;;;;AAKlB,QAAA,MAAM,GAAG,GAAG,qBAAqB,CAAC,OAAO,CAAC;QAC1C,OAAO,EAAE,CACP;AACE,cAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,qBAAqB,KAAK,SAAS;cACvD,GAAG,CACR;IACH;;;;;IAOO,WAAW,CAChB,OAAe,EACf,UAAkB,EAAA;;;;;;;;;QAUlB,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC,IAAI,CAC/C,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,UAAU,CAC3B;;;;;;;;;;;;;;;;;;;;;AAqBD,QAAA,OAAO;AACL,cAAE,EAAE,CAAC,KAAK;AACV,cAAE,UAAU,CACR,MACE,IAAI,KAAK,CACP,CAAA,UAAA,EAAa,UAAU,CAAA,uEAAA,CAAyE,CACjG,CACJ;IACP;;AAGO,IAAA,cAAc,CACnB,OAA+B,EAAA;QAE/B,MAAM,YAAY,GAAG,2BAA2B,CAAC,OAAO,CAAC,SAAS,CAAC;AACnE,QAAA,MAAM,IAAI,GAAG;YACX,OAAO,EAAE,OAAO,CAAC,OAAO;AACxB,YAAA,SAAS,EAAE,YAAY;AACvB,YAAA,MAAM,EAAE;AACN,gBAAA,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ;AACjC,gBAAA,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK;AAC5B,aAAA;;;AAGD,YAAA,WAAW,EAAE,YAAY,KAAK,KAAK,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS;AACnE,YAAA,aAAa,EAAE,YAAY,KAAK,KAAK,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS;;;;;YAKrE,IAAI,EAAE,OAAO,CAAC,IAAI;AAClB,YAAA,mBAAmB,EAAE,OAAO,CAAC,mBAAmB,IAAI,SAAS;AAC7D,YAAA,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;YACnC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,KAAK,EAAE,OAAO,CAAC,KAAK;SACrB;QACD,OAAO,IAAI,CAAC;AACT,aAAA,IAAI,CAA6B,4BAA4B,EAAE,IAAI;AACnE,aAAA,IAAI,CACH,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CACrD;IACL;;AAGO,IAAA,QAAQ,CACb,OAAyB,EAAA;;;;;;;;;;;;;AAczB,QAAA,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS;QACnC,OAAO,IAAI,CAAC,cAAc,CAAC;YACzB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,SAAS;YACT,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,YAAY,EAAE,OAAO,CAAC,YAAY;AAClC,YAAA,QAAQ,EAAE,IAAI;YACd,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;AACjD,SAAA,CAAC;IACJ;;IAIQ,iBAAiB,CACvB,IAAgC,EAChC,OAA+B,EAAA;AAE/B,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE;AACd,YAAA,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QAC7B;AACA,QAAA,MAAM,QAAQ,GAAoB,IAAI,CAAC;AACrC,cAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO;AACpD,cAAE;;;AAGE,gBAAA,EAAE,EAAE,CAAA,QAAA,EAAW,IAAI,CAAC,GAAG,EAAE,CAAA,CAAE;gBAC3B,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,qBAAqB,EAAE,OAAO,CAAC,SAAS;AACxC,gBAAA,iBAAiB,EAAE,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,SAAS;gBAC5D,IAAI,EAAE,OAAO,CAAC,IAAI;AAClB,gBAAA,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACjC,KAAK,EAAE,OAAO,CAAC,KAAK;AACpB,gBAAA,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;AACnC,gBAAA,aAAa,EAAE,IAAI;gBACnB,cAAc,EAAE,OAAO,CAAC,OAAO;gBAC/B,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;aACjD;QACL,OAAO;YACL,QAAQ;AACR,YAAA,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAA2C;AAC/E,YAAA,mBAAmB,GAAG,IAAI,CAAC,mBAAmB;AAC5C,gBAAA,EAAE,CAAmD;SACxD;IACH;IAEQ,cAAc,CACpB,GAAoB,EACpB,OAAe,EAAA;QAEf,MAAM,YAAY,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI;AACvC,QAAA,MAAM,SAAS,GAAG,GAAG,CAAC;AACpB,cAAE,2BAA2B,CAAC,GAAG,CAAC,SAAS;cACzC,MAAM;QACV,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,SAAS;AACT,YAAA,MAAM,EAAE;gBACN,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,IAAI,KAAK,CAE/B;AACT,gBAAA,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC;AAC9B,aAAA;AACD,YAAA,qBAAqB,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE;AAC7C,YAAA,iBAAiB,EAAE,YAAY,EAAE,IAAI,IAAI,SAAS;YAClD,IAAI,EAAE,GAAG,CAAC,OAAO;YACjB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAChD,KAAK,EAAE,GAAG,CAAC,GAAG;AACd,YAAA,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,KAAK;AAC/B,YAAA,aAAa,EAAE,GAAG,CAAC,aAAa,IAAI,IAAI;AACxC,YAAA,cAAc,EAAE,OAAO;SACxB;IACH;uGAnOW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;2GAAb,aAAa,EAAA,CAAA;;2FAAb,aAAa,EAAA,UAAA,EAAA,CAAA;kBADzB;;;AC1CD;AACA;AACA;SACgB,aAAa,GAAA;AAC3B,IAAA,OAAO,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC;AACjF;;ACTA;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"sneat-extension-debtus.mjs","sources":["../../../../../../libs/extensions/debtus/runtime/src/lib/services/debtus.service.ts","../../../../../../libs/extensions/debtus/runtime/src/lib/provide-debtus.ts","../../../../../../libs/extensions/debtus/runtime/src/sneat-extension-debtus.ts"],"sourcesContent":["import { HttpParams } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { SneatApiService } from '@sneat/api';\nimport {\n IContactBalance,\n ICreateDebtRecordRequest,\n ICreateTransferRequest,\n ICreateTransferResponse,\n IDebtusService,\n IDebtusTransfer,\n ISettleUpRequest,\n apiDirectionToDebtDirection,\n debtDirectionToApiDirection,\n} from '@sneat/extension-debtus-contract';\nimport { Observable, map } from 'rxjs';\n\ninterface IApiContactBalanceDto {\n readonly contactID: string;\n readonly title: string;\n readonly balance: Record<string, number>;\n readonly counterpartySpaceID?: string;\n}\n\ninterface IApiSpaceTransferDto {\n readonly id: string;\n readonly direction: 'u2c' | 'c2u';\n readonly amount: { readonly currency: string; readonly value: number };\n readonly counterpartyContactID: string;\n readonly counterpartyTitle: string;\n readonly note?: string;\n readonly created: string;\n readonly dueOn?: string;\n readonly isReturn: boolean;\n readonly isOutstanding: boolean;\n readonly creatorSpaceID: string;\n readonly counterpartySpaceID?: string;\n}\n\ninterface IApiSpaceTransfersDto {\n readonly transfers: IApiSpaceTransferDto[];\n}\n\ninterface IApiContactDto {\n readonly ID: string;\n readonly UserID?: string;\n readonly Name: string;\n}\n\ninterface IApiCreatedTransferDto {\n readonly Id: string;\n readonly Created: string;\n readonly Amount: { readonly currency: string; readonly value: number };\n readonly IsReturn?: boolean;\n readonly IsOutstanding?: boolean;\n readonly From?: IApiContactDto;\n readonly To?: IApiContactDto;\n readonly Due?: string;\n}\n\ninterface IApiCreateTransferResponse {\n readonly Error?: string;\n readonly Transfer?: IApiCreatedTransferDto;\n readonly UserBalance?: Record<string, number>;\n readonly CounterpartyBalance?: Record<string, number>;\n}\n\n@Injectable()\nexport class DebtusService implements IDebtusService {\n private readonly sneatApiService = inject(SneatApiService);\n\n public createDebtRecord(\n request: ICreateDebtRecordRequest,\n ): Observable<string> {\n return this.sneatApiService.post('debtus/create_debt_record', request);\n }\n\n public getContactBalances(spaceID: string): Observable<IContactBalance[]> {\n const params = new HttpParams().set('spaceID', spaceID);\n return this.sneatApiService\n .get<IApiContactBalanceDto[]>(\n 'api4debtus/space/contact-balances',\n params,\n )\n .pipe(\n map((contacts) =>\n contacts.map((contact) => ({\n contactID: contact.contactID,\n title: contact.title,\n balance: centsBalanceToMajor(contact.balance),\n counterpartySpaceID: contact.counterpartySpaceID,\n })),\n ),\n );\n }\n\n public getContactBalance(\n spaceID: string,\n contactID: string,\n ): Observable<IContactBalance> {\n return this.getContactBalances(spaceID).pipe(\n map((contacts) => contacts.find((c) => c.contactID === contactID)),\n map((contact) => {\n if (!contact) {\n throw new Error(`Debtus contact \"${contactID}\" was not found`);\n }\n return contact;\n }),\n );\n }\n\n public getTransfers(\n spaceID: string,\n contactID?: string,\n ): Observable<IDebtusTransfer[]> {\n let params = new HttpParams().set('spaceID', spaceID);\n if (contactID) {\n params = params.set('contactID', contactID);\n }\n return this.sneatApiService\n .get<IApiSpaceTransfersDto>('api4debtus/space/transfers', params)\n .pipe(map((dto) => dto.transfers.map(mapSpaceTransferDto)));\n }\n\n public getTransfer(\n spaceID: string,\n transferID: string,\n ): Observable<IDebtusTransfer> {\n const params = new HttpParams()\n .set('spaceID', spaceID)\n .set('id', transferID);\n return this.sneatApiService\n .get<IApiSpaceTransferDto>('api4debtus/space/transfer', params)\n .pipe(map(mapSpaceTransferDto));\n }\n\n public createTransfer(\n request: ICreateTransferRequest,\n ): Observable<ICreateTransferResponse> {\n const apiDirection = debtDirectionToApiDirection(request.direction);\n const body = {\n spaceID: request.spaceID,\n direction: apiDirection,\n amount: {\n currency: request.amount.currency,\n value: majorToCents(request.amount.value),\n },\n toContactID: apiDirection === 'u2c' ? request.contactID : undefined,\n fromContactID: apiDirection === 'c2u' ? request.contactID : undefined,\n note: request.note,\n counterpartySpaceID: request.counterpartySpaceID || undefined,\n isReturn: request.isReturn ?? false,\n returnToTransferID: request.returnToTransferID,\n dueOn: request.dueOn,\n };\n return this.sneatApiService\n .post<IApiCreateTransferResponse>('api4debtus/create-transfer', body)\n .pipe(map((resp) => this.mapCreateResponse(resp, request)));\n }\n\n public settleUp(\n request: ISettleUpRequest,\n ): Observable<ICreateTransferResponse> {\n return this.createTransfer({\n spaceID: request.spaceID,\n direction: request.direction,\n amount: request.amount,\n contactID: request.contactID,\n contactTitle: request.contactTitle,\n isReturn: true,\n counterpartySpaceID: request.counterpartySpaceID,\n });\n }\n\n private mapCreateResponse(\n resp: IApiCreateTransferResponse,\n request: ICreateTransferRequest,\n ): ICreateTransferResponse {\n if (resp.Error) {\n throw new Error(resp.Error);\n }\n if (!resp.Transfer) {\n throw new Error('Debtus create-transfer response omitted the transfer');\n }\n const counterparty = resp.Transfer.To ?? resp.Transfer.From;\n if (!counterparty) {\n throw new Error('Debtus create-transfer response omitted the counterparty');\n }\n return {\n transfer: {\n id: resp.Transfer.Id,\n direction: request.direction,\n amount: {\n currency: request.amount.currency,\n value: centsToMajor(resp.Transfer.Amount.value),\n },\n counterpartyContactID: counterparty.ID,\n counterpartyTitle: counterparty.Name,\n note: request.note,\n created: resp.Transfer.Created,\n dueOn: nonZeroDate(resp.Transfer.Due),\n isReturn: resp.Transfer.IsReturn ?? false,\n isOutstanding:\n resp.Transfer.IsOutstanding ?? !(resp.Transfer.IsReturn ?? false),\n creatorSpaceID: request.spaceID,\n counterpartySpaceID: request.counterpartySpaceID,\n },\n userBalance: centsBalanceToMajor(resp.UserBalance ?? {}),\n counterpartyBalance: centsBalanceToMajor(\n resp.CounterpartyBalance ?? {},\n ),\n };\n }\n}\n\nfunction mapSpaceTransferDto(dto: IApiSpaceTransferDto): IDebtusTransfer {\n return {\n ...dto,\n direction: apiDirectionToDebtDirection(dto.direction),\n amount: {\n currency: dto.amount.currency as IDebtusTransfer['amount']['currency'],\n value: centsToMajor(dto.amount.value),\n },\n };\n}\n\nfunction centsBalanceToMajor(\n balance: Record<string, number>,\n): IContactBalance['balance'] {\n return Object.fromEntries(\n Object.entries(balance).map(([currency, cents]) => [\n currency,\n centsToMajor(cents),\n ]),\n ) as IContactBalance['balance'];\n}\n\nfunction majorToCents(value: number): number {\n return Math.round(value * 100);\n}\n\nfunction centsToMajor(value: number): number {\n return value / 100;\n}\n\nfunction nonZeroDate(value?: string): string | undefined {\n return !value || value.startsWith('0001-01-01') ? undefined : value;\n}\n","import { Provider } from '@angular/core';\nimport { DEBTUS_SERVICE } from '@sneat/extension-debtus-contract';\nimport { DebtusService } from './services';\n\n// Registers the concrete DebtusService and binds it to the DEBTUS_SERVICE token so\n// consumers depend only on the IDebtusService contract. Wired in at app\n// bootstrap (consumers do not import this factory directly).\nexport function provideDebtus(): Provider[] {\n return [DebtusService, { provide: DEBTUS_SERVICE, useExisting: DebtusService }];\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -9,14 +9,10 @@ declare class DebtusService implements IDebtusService {
|
|
|
9
9
|
getContactBalances(spaceID: string): Observable<IContactBalance[]>;
|
|
10
10
|
getContactBalance(spaceID: string, contactID: string): Observable<IContactBalance>;
|
|
11
11
|
getTransfers(spaceID: string, contactID?: string): Observable<IDebtusTransfer[]>;
|
|
12
|
-
/** Reads from demo fixtures; unknown ids error (no fabricated receipts). */
|
|
13
12
|
getTransfer(spaceID: string, transferID: string): Observable<IDebtusTransfer>;
|
|
14
|
-
/** REAL: POST /api4debtus/create-transfer (Firebase-authenticated). */
|
|
15
13
|
createTransfer(request: ICreateTransferRequest): Observable<ICreateTransferResponse>;
|
|
16
|
-
/** Settle-up = a reverse-direction return transfer (mirrors the bot). */
|
|
17
14
|
settleUp(request: ISettleUpRequest): Observable<ICreateTransferResponse>;
|
|
18
15
|
private mapCreateResponse;
|
|
19
|
-
private mapTransferDto;
|
|
20
16
|
static ɵfac: i0.ɵɵFactoryDeclaration<DebtusService, never>;
|
|
21
17
|
static ɵprov: i0.ɵɵInjectableDeclaration<DebtusService>;
|
|
22
18
|
}
|