@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 { of, throwError, map } from 'rxjs';
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
- // SWAP: this.sneatApiService.get<IContactBalance[]>('api4debtus/user/contacts', new HttpParams().set('spaceID', spaceID))
133
- void spaceID;
134
- return of(DEMO_CONTACT_BALANCES.map((c) => ({ ...c })));
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
- void spaceID;
138
- const found = DEMO_CONTACT_BALANCES.find((c) => c.contactID === contactID) ??
139
- {
140
- contactID,
141
- title: contactID,
142
- balance: {},
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
- // NOTE: GET /api4debtus/user/api4transfers exists but is currently broken
148
- // server-side (auth bug queries an empty userID). Using fixtures until the
149
- // backend fix lands. SWAP: this.sneatApiService.get('api4debtus/user/api4transfers', params).
150
- const all = demoTransfersForSpace(spaceID);
151
- return of(contactID
152
- ? all.filter((t) => t.counterpartyContactID === contactID)
153
- : all);
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
- // The live GET transfer endpoint returns a perspective-resolved TransferDto.
161
- // For the prototype we resolve from fixtures so the receipt screen renders
162
- // without a live backend; SWAP to the real GET when running against a
163
- // deployed server:
164
- // const params = new HttpParams().set('id', transferID);
165
- // return this.sneatApiService
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
- const transfer = resp.Transfer
252
- ? this.mapTransferDto(resp.Transfer, request.spaceID)
253
- : {
254
- // If the backend omits the transfer echo, synthesize from the request
255
- // so the UI can still navigate to a detail screen.
256
- id: `pending-${Date.now()}`,
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: request.amount,
259
- counterpartyContactID: request.contactID,
260
- counterpartyTitle: request.contactTitle ?? request.contactID,
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: new Date().toISOString(),
263
- dueOn: request.dueOn,
264
- isReturn: request.isReturn ?? false,
265
- isOutstanding: true,
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
- counterpartyContactID: counterparty?.ID ?? '',
289
- counterpartyTitle: counterparty?.Name ?? 'Unknown',
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":"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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sneat/extension-debtus",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "peerDependencies": {
5
5
  "@angular/core": ">=22.0.0 <23.0.0",
6
6
  "rxjs": "^7.0.0",
@@ -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
  }