tin-spa 20.14.99 → 20.14.113

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 (51) hide show
  1. package/core/index.d.ts +3033 -0
  2. package/fesm2022/tin-spa-accounting.module-Cg4vWaJE.mjs +2231 -0
  3. package/fesm2022/tin-spa-accounting.module-Cg4vWaJE.mjs.map +1 -0
  4. package/fesm2022/tin-spa-agent-page.component-DJvMPjCQ.mjs +39 -0
  5. package/fesm2022/tin-spa-agent-page.component-DJvMPjCQ.mjs.map +1 -0
  6. package/fesm2022/tin-spa-agent.component-D0CGGnGZ.mjs +1379 -0
  7. package/fesm2022/tin-spa-agent.component-D0CGGnGZ.mjs.map +1 -0
  8. package/fesm2022/tin-spa-assets.module-qIWNwJ2E.mjs +167 -0
  9. package/fesm2022/tin-spa-assets.module-qIWNwJ2E.mjs.map +1 -0
  10. package/fesm2022/tin-spa-core.mjs +13628 -0
  11. package/fesm2022/tin-spa-core.mjs.map +1 -0
  12. package/fesm2022/tin-spa-general-erp.module-CoYxceLj.mjs +138 -0
  13. package/fesm2022/tin-spa-general-erp.module-CoYxceLj.mjs.map +1 -0
  14. package/fesm2022/tin-spa-hr.module-CKPZ9ZOj.mjs +134 -0
  15. package/fesm2022/tin-spa-hr.module-CKPZ9ZOj.mjs.map +1 -0
  16. package/fesm2022/tin-spa-http.mjs +117 -0
  17. package/fesm2022/tin-spa-http.mjs.map +1 -0
  18. package/fesm2022/tin-spa-inventory.module-DRKAcNau.mjs +338 -0
  19. package/fesm2022/tin-spa-inventory.module-DRKAcNau.mjs.map +1 -0
  20. package/fesm2022/tin-spa-keys.mjs +14 -0
  21. package/fesm2022/tin-spa-keys.mjs.map +1 -0
  22. package/fesm2022/tin-spa-landing.mjs +189 -0
  23. package/fesm2022/tin-spa-landing.mjs.map +1 -0
  24. package/fesm2022/tin-spa-loans.module-BTDmgrWo.mjs +115 -0
  25. package/fesm2022/tin-spa-loans.module-BTDmgrWo.mjs.map +1 -0
  26. package/fesm2022/tin-spa-login.mjs +719 -0
  27. package/fesm2022/tin-spa-login.mjs.map +1 -0
  28. package/fesm2022/tin-spa-manufacturing.module-CHta6oV_.mjs +96 -0
  29. package/fesm2022/tin-spa-manufacturing.module-CHta6oV_.mjs.map +1 -0
  30. package/fesm2022/tin-spa-map.mjs +750 -0
  31. package/fesm2022/tin-spa-map.mjs.map +1 -0
  32. package/fesm2022/tin-spa-payroll.module-CZ_GO1jF.mjs +847 -0
  33. package/fesm2022/tin-spa-payroll.module-CZ_GO1jF.mjs.map +1 -0
  34. package/fesm2022/tin-spa-purchasing.module-NCdwquaF.mjs +161 -0
  35. package/fesm2022/tin-spa-purchasing.module-NCdwquaF.mjs.map +1 -0
  36. package/fesm2022/tin-spa-sales.module-D0uEGJW7.mjs +1346 -0
  37. package/fesm2022/tin-spa-sales.module-D0uEGJW7.mjs.map +1 -0
  38. package/fesm2022/tin-spa-tenancy-erp.module-GwIv8Wbc.mjs +932 -0
  39. package/fesm2022/tin-spa-tenancy-erp.module-GwIv8Wbc.mjs.map +1 -0
  40. package/fesm2022/tin-spa-tin-spa-Cqe5hYTr.mjs +22576 -0
  41. package/fesm2022/tin-spa-tin-spa-Cqe5hYTr.mjs.map +1 -0
  42. package/fesm2022/tin-spa.mjs +2 -42079
  43. package/fesm2022/tin-spa.mjs.map +1 -1
  44. package/http/index.d.ts +16 -0
  45. package/index.d.ts +588 -4687
  46. package/keys/index.d.ts +4 -0
  47. package/landing/index.d.ts +175 -0
  48. package/login/index.d.ts +141 -0
  49. package/map/index.d.ts +148 -0
  50. package/package.json +25 -1
  51. package/src/tin-styles.css +81 -2
@@ -0,0 +1,2231 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, inject, ViewChild, NgModule } from '@angular/core';
3
+ import * as i1$2 from '@angular/router';
4
+ import { RouterModule } from '@angular/router';
5
+ import * as i1 from 'tin-spa/core';
6
+ import { AccountingService, MessageService, ApiErrorService, PurchasingService, HttpService, DataServiceLib, ReceiptDialogComponent } from 'tin-spa/core';
7
+ import { T as TilesComponent, a as TabsComponent, P as PageComponent, D as DateComponent, b as TableComponent, S as SelectLiteComponent, c as TinSpaModule, C as ChartsComponent, d as TenantTransferService, e as SpaAdminModule } from './tin-spa-tin-spa-Cqe5hYTr.mjs';
8
+ import * as i1$1 from '@angular/common';
9
+ import { CommonModule } from '@angular/common';
10
+ import * as i3 from '@angular/material/button';
11
+ import * as i3$1 from '@angular/material/icon';
12
+ import { HttpClient } from '@angular/common/http';
13
+ import * as i3$2 from '@angular/material/tabs';
14
+ import * as i2 from '@angular/forms';
15
+ import * as i7 from '@angular/material/menu';
16
+ import * as i3$3 from '@angular/material/form-field';
17
+ import * as i4 from '@angular/material/input';
18
+ import * as i6 from '@angular/material/card';
19
+ import * as i4$1 from '@angular/material/dialog';
20
+ import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
21
+ import * as i7$1 from '@angular/material/select';
22
+ import * as i4$2 from '@angular/material/checkbox';
23
+
24
+ class AccountsComponent {
25
+ constructor(dataServiceLib, accountingService) {
26
+ this.dataServiceLib = dataServiceLib;
27
+ this.accountingService = accountingService;
28
+ // Changed: Tile config with Cash, Bank, Income MTD, and Expenses MTD
29
+ this.accountTileConfig = {
30
+ tiles: [
31
+ { name: 'cashBalance', alias: 'Cash', color: '#4CAF50', icon: 'payments', style: 'icon', info: 'Total balance of all cash accounts' },
32
+ { name: 'bankBalance', alias: 'Bank', color: '#2196F3', icon: 'account_balance', style: 'icon', info: 'Total balance of all bank accounts' },
33
+ { name: 'mtdIncome', alias: 'Income (MTD)', color: '#9C27B0', icon: 'trending_up', style: 'icon', info: 'Month-to-date income from revenue accounts' },
34
+ { name: 'mtdExpenses', alias: 'Expenses (MTD)', color: '#F44336', icon: 'trending_down', style: 'icon', info: 'Month-to-date expenses' },
35
+ ],
36
+ loadAction: { url: 'accounts/summary/tiles' },
37
+ loadInit: true
38
+ };
39
+ this.accountTabConfigs = [
40
+ {
41
+ ...this.accountingService.accountsTableConfig,
42
+ tabTitle: 'Assets',
43
+ loadAction: { url: 'accounts/type/asset' },
44
+ countAction: { url: 'accounts/count/asset' }
45
+ },
46
+ {
47
+ ...this.accountingService.accountsTableConfig,
48
+ tabTitle: 'Liabilities',
49
+ loadAction: { url: 'accounts/type/liability' },
50
+ countAction: { url: 'accounts/count/liability' }
51
+ },
52
+ {
53
+ ...this.accountingService.accountsTableConfig,
54
+ tabTitle: 'Income',
55
+ loadAction: { url: 'accounts/type/income' },
56
+ countAction: { url: 'accounts/count/income' }
57
+ },
58
+ {
59
+ ...this.accountingService.accountsTableConfig,
60
+ tabTitle: 'Expenses',
61
+ loadAction: { url: 'accounts/type/expense' },
62
+ countAction: { url: 'accounts/count/expense' }
63
+ },
64
+ {
65
+ ...this.accountingService.accountsTableConfig,
66
+ tabTitle: 'Equity',
67
+ loadAction: { url: 'accounts/type/equity' },
68
+ countAction: { url: 'accounts/count/equity' }
69
+ },
70
+ // Removed: Aggregate tab - no longer used
71
+ ];
72
+ }
73
+ ngOnInit() {
74
+ }
75
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountsComponent, deps: [{ token: i1.DataServiceLib }, { token: i1.AccountingService }], target: i0.ɵɵFactoryTarget.Component }); }
76
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AccountsComponent, isStandalone: false, selector: "spa-accounts", ngImport: i0, template: `
77
+ <h4>Accounts</h4>
78
+ <hr>
79
+
80
+ <spa-tiles [config]="accountTileConfig"></spa-tiles>
81
+
82
+ <spa-tabs [tableConfigs]="accountTabConfigs"></spa-tabs>
83
+ `, isInline: true, dependencies: [{ kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
84
+ }
85
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountsComponent, decorators: [{
86
+ type: Component,
87
+ args: [{
88
+ selector: 'spa-accounts',
89
+ template: `
90
+ <h4>Accounts</h4>
91
+ <hr>
92
+
93
+ <spa-tiles [config]="accountTileConfig"></spa-tiles>
94
+
95
+ <spa-tabs [tableConfigs]="accountTabConfigs"></spa-tabs>
96
+ `,
97
+ standalone: false
98
+ }]
99
+ }], ctorParameters: () => [{ type: i1.DataServiceLib }, { type: i1.AccountingService }] });
100
+
101
+ // Accounting Aggregates component with monthly aggregated financial data
102
+ class AggregatesComponent {
103
+ constructor(dataServiceLib, accountingService) {
104
+ this.dataServiceLib = dataServiceLib;
105
+ this.accountingService = accountingService;
106
+ this.aggregateTabConfigs = [
107
+ {
108
+ ...this.accountingService.aggregatesTableConfig,
109
+ tabTitle: 'Monthly Summary',
110
+ loadAction: { url: 'accounts/aggregates/monthly' },
111
+ },
112
+ {
113
+ ...this.accountingService.cashFlowAggregatesTableConfig,
114
+ tabTitle: 'Cash Flow',
115
+ loadAction: { url: 'accounts/aggregates/cashflow' },
116
+ },
117
+ {
118
+ ...this.accountingService.cumulativeAggregatesTableConfig,
119
+ tabTitle: 'Cumulative Balances',
120
+ loadAction: { url: 'accounts/aggregates/cumulative' },
121
+ },
122
+ {
123
+ ...this.accountingService.balanceSheetAggregatesTableConfig,
124
+ tabTitle: 'Balance Sheet',
125
+ loadAction: { url: 'accounts/aggregates/balancesheet' },
126
+ }
127
+ ];
128
+ }
129
+ ngOnInit() {
130
+ }
131
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AggregatesComponent, deps: [{ token: i1.DataServiceLib }, { token: i1.AccountingService }], target: i0.ɵɵFactoryTarget.Component }); }
132
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AggregatesComponent, isStandalone: false, selector: "spa-aggregates", ngImport: i0, template: `
133
+ <h4>Aggregates</h4>
134
+ <hr>
135
+
136
+ <spa-tabs [tableConfigs]="aggregateTabConfigs"></spa-tabs>
137
+ `, isInline: true, dependencies: [{ kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
138
+ }
139
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AggregatesComponent, decorators: [{
140
+ type: Component,
141
+ args: [{
142
+ selector: 'spa-aggregates',
143
+ template: `
144
+ <h4>Aggregates</h4>
145
+ <hr>
146
+
147
+ <spa-tabs [tableConfigs]="aggregateTabConfigs"></spa-tabs>
148
+ `,
149
+ standalone: false
150
+ }]
151
+ }], ctorParameters: () => [{ type: i1.DataServiceLib }, { type: i1.AccountingService }] });
152
+
153
+ // Aging report component for accounts receivable tracking
154
+ class AgingComponent {
155
+ ngOnInit() {
156
+ this.loadSummaryData();
157
+ }
158
+ loadSummaryData() {
159
+ this.dataServiceLib.CallApi({ url: 'invoices/aging-summary/x' }, '').subscribe((apiResponse) => {
160
+ if (apiResponse.success) {
161
+ this.summaryData = apiResponse.data;
162
+ }
163
+ else {
164
+ this.apiErrorService.presentAppFailure(apiResponse, 'load', 'invoices/aging-summary/x'); // Added: was silent. summaryData stayed undefined, the *ngIf dropped the whole tile strip, and an accounts-receivable page with NO aging totals reads as "you are owed nothing" rather than "we could not work it out"
165
+ }
166
+ });
167
+ }
168
+ // Changed: Post IFRS 9 bad-debt provision — confirms, calls invoices provision custom action, shows returned summary
169
+ postProvision() {
170
+ this.messageService.confirm('Post the bad-debt provision? This posts the allowance delta per the aging matrix as a GL journal.').subscribe((result) => {
171
+ if (result == 'yes') {
172
+ this.dataServiceLib.CallApi({ url: 'invoices?action=provision', method: 'post' }, {}).subscribe((apiResponse) => {
173
+ // Changed: REPLACES the old `'Error: ' + apiResponse.message` toast rather than layering on it.
174
+ // That string prefixed the server's raw words with a second word for "error" and pushed the useful
175
+ // part right; a leaked SQL fragment reached the user unchanged. 'action' rather than 'submit' —
176
+ // this is a confirmed page button, not a form, so the 'submit' copy would describe a screen the
177
+ // user is not looking at.
178
+ if (apiResponse.success) {
179
+ this.messageService.toast(apiResponse.message);
180
+ this.loadSummaryData();
181
+ }
182
+ else
183
+ this.apiErrorService.presentAppFailure(apiResponse, 'action', 'invoices?action=provision');
184
+ });
185
+ }
186
+ });
187
+ }
188
+ constructor(dataServiceLib) {
189
+ this.dataServiceLib = dataServiceLib;
190
+ this.accountingService = inject(AccountingService);
191
+ this.messageService = inject(MessageService); // Changed: Injected for provision confirm/toast
192
+ this.apiErrorService = inject(ApiErrorService); // Changed: Injected for the summary load and the provision failure
193
+ this.tileConfig = this.accountingService.agingTileConfig;
194
+ // this.tileConfig = this.accountingService.agingTileConfig;
195
+ this.agingTableConfigs = [
196
+ {
197
+ ...this.accountingService.agingBaseTableConfig,
198
+ tabTitle: 'Current',
199
+ loadAction: { url: 'invoices/aging-current/x' },
200
+ countAction: { url: 'invoices/count/aging-current' }
201
+ },
202
+ {
203
+ ...this.accountingService.agingBaseTableConfig,
204
+ tabTitle: '1-30 Days',
205
+ loadAction: { url: 'invoices/aging-30/x' },
206
+ countAction: { url: 'invoices/count/aging-30' }
207
+ },
208
+ {
209
+ ...this.accountingService.agingBaseTableConfig,
210
+ tabTitle: '31-60 Days',
211
+ loadAction: { url: 'invoices/aging-60/x' },
212
+ countAction: { url: 'invoices/count/aging-60' }
213
+ },
214
+ {
215
+ ...this.accountingService.agingBaseTableConfig,
216
+ tabTitle: '61-90 Days',
217
+ loadAction: { url: 'invoices/aging-90/x' },
218
+ countAction: { url: 'invoices/count/aging-90' }
219
+ },
220
+ {
221
+ ...this.accountingService.agingBaseTableConfig,
222
+ tabTitle: '90+ Days',
223
+ loadAction: { url: 'invoices/aging-90plus/x' },
224
+ countAction: { url: 'invoices/count/aging-90plus' }
225
+ }
226
+ ];
227
+ }
228
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgingComponent, deps: [{ token: i1.DataServiceLib }], target: i0.ɵɵFactoryTarget.Component }); }
229
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AgingComponent, isStandalone: false, selector: "spa-aging", ngImport: i0, template: `
230
+ <div style="display: flex; align-items: center; justify-content: space-between;">
231
+ <h4>Accounts Receivable Aging</h4>
232
+ <!-- Changed: Post IFRS 9 bad-debt provision delta from the aging matrix -->
233
+ <button mat-stroked-button color="warn" (click)="postProvision()"><mat-icon>account_balance</mat-icon> Post Bad-Debt Provision</button>
234
+ </div>
235
+ <hr>
236
+ <div *ngIf="summaryData" class="mt-3 mb-3">
237
+ <spa-tiles [config]="tileConfig" [data]="summaryData"></spa-tiles>
238
+ </div>
239
+
240
+ <spa-tabs [tableConfigs]="agingTableConfigs"></spa-tabs>
241
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
242
+ }
243
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgingComponent, decorators: [{
244
+ type: Component,
245
+ args: [{
246
+ selector: 'spa-aging',
247
+ template: `
248
+ <div style="display: flex; align-items: center; justify-content: space-between;">
249
+ <h4>Accounts Receivable Aging</h4>
250
+ <!-- Changed: Post IFRS 9 bad-debt provision delta from the aging matrix -->
251
+ <button mat-stroked-button color="warn" (click)="postProvision()"><mat-icon>account_balance</mat-icon> Post Bad-Debt Provision</button>
252
+ </div>
253
+ <hr>
254
+ <div *ngIf="summaryData" class="mt-3 mb-3">
255
+ <spa-tiles [config]="tileConfig" [data]="summaryData"></spa-tiles>
256
+ </div>
257
+
258
+ <spa-tabs [tableConfigs]="agingTableConfigs"></spa-tabs>
259
+ `,
260
+ standalone: false
261
+ }]
262
+ }], ctorParameters: () => [{ type: i1.DataServiceLib }] });
263
+
264
+ // AP Aging report component for accounts payable tracking — mirrors AR AgingComponent pattern
265
+ class SupplierAgingComponent {
266
+ ngOnInit() {
267
+ this.loadSummaryData();
268
+ }
269
+ loadSummaryData() {
270
+ this.dataServiceLib.CallApi({ url: 'purchases/aging-summary/x' }, '').subscribe((apiResponse) => {
271
+ if (apiResponse.success) {
272
+ this.summaryData = apiResponse.data;
273
+ }
274
+ else {
275
+ this.apiErrorService.presentAppFailure(apiResponse, 'load', 'purchases/aging-summary/x'); // Added: was silent. The tile strip vanished behind its *ngIf, so an accounts-payable page that could not compute its totals looked like a company that owes nothing
276
+ }
277
+ });
278
+ }
279
+ constructor(dataServiceLib) {
280
+ this.dataServiceLib = dataServiceLib;
281
+ this.purchasingService = inject(PurchasingService);
282
+ this.apiErrorService = inject(ApiErrorService); // Changed: Injected so a failed summary load says so
283
+ this.tileConfig = this.purchasingService.apAgingTileConfig;
284
+ // Changed: Build 5-tab aging table layout using AP aging base config from PurchasingService
285
+ this.agingTableConfigs = [
286
+ {
287
+ ...this.purchasingService.apAgingBaseTableConfig,
288
+ tabTitle: 'Current',
289
+ loadAction: { url: 'purchases/aging-current/x' },
290
+ countAction: { url: 'purchases/count/aging-current' }
291
+ },
292
+ {
293
+ ...this.purchasingService.apAgingBaseTableConfig,
294
+ tabTitle: '1-30 Days',
295
+ loadAction: { url: 'purchases/aging-30/x' },
296
+ countAction: { url: 'purchases/count/aging-30' }
297
+ },
298
+ {
299
+ ...this.purchasingService.apAgingBaseTableConfig,
300
+ tabTitle: '31-60 Days',
301
+ loadAction: { url: 'purchases/aging-60/x' },
302
+ countAction: { url: 'purchases/count/aging-60' }
303
+ },
304
+ {
305
+ ...this.purchasingService.apAgingBaseTableConfig,
306
+ tabTitle: '61-90 Days',
307
+ loadAction: { url: 'purchases/aging-90/x' },
308
+ countAction: { url: 'purchases/count/aging-90' }
309
+ },
310
+ {
311
+ ...this.purchasingService.apAgingBaseTableConfig,
312
+ tabTitle: '90+ Days',
313
+ loadAction: { url: 'purchases/aging-90plus/x' },
314
+ countAction: { url: 'purchases/count/aging-90plus' }
315
+ }
316
+ ];
317
+ }
318
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SupplierAgingComponent, deps: [{ token: i1.DataServiceLib }], target: i0.ɵɵFactoryTarget.Component }); }
319
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SupplierAgingComponent, isStandalone: false, selector: "spa-supplier-aging", ngImport: i0, template: `
320
+ <h4>Accounts Payable Aging</h4>
321
+ <hr>
322
+ <div *ngIf="summaryData" class="mt-3 mb-3">
323
+ <spa-tiles [config]="tileConfig" [data]="summaryData"></spa-tiles>
324
+ </div>
325
+
326
+ <spa-tabs [tableConfigs]="agingTableConfigs"></spa-tabs>
327
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
328
+ }
329
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SupplierAgingComponent, decorators: [{
330
+ type: Component,
331
+ args: [{
332
+ selector: 'spa-supplier-aging',
333
+ template: `
334
+ <h4>Accounts Payable Aging</h4>
335
+ <hr>
336
+ <div *ngIf="summaryData" class="mt-3 mb-3">
337
+ <spa-tiles [config]="tileConfig" [data]="summaryData"></spa-tiles>
338
+ </div>
339
+
340
+ <spa-tabs [tableConfigs]="agingTableConfigs"></spa-tabs>
341
+ `,
342
+ standalone: false
343
+ }]
344
+ }], ctorParameters: () => [{ type: i1.DataServiceLib }] });
345
+
346
+ class TransactionTypesComponent {
347
+ constructor() {
348
+ this.accountingService = inject(AccountingService);
349
+ this.pageConfig = {
350
+ title: 'Transaction Types',
351
+ tableConfig: this.accountingService.transactionTypesTableConfig
352
+ };
353
+ }
354
+ ngOnInit() {
355
+ }
356
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TransactionTypesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
357
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TransactionTypesComponent, isStandalone: false, selector: "spa-transaction-types", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
358
+ }
359
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TransactionTypesComponent, decorators: [{
360
+ type: Component,
361
+ args: [{
362
+ selector: 'spa-transaction-types',
363
+ template: '<spa-page [config]="pageConfig"></spa-page>',
364
+ standalone: false
365
+ }]
366
+ }] });
367
+
368
+ // Transactions page
369
+ // Changed (journal-dialog-conform): the "New Journal" button used to be drawn HERE, in a flex row above <spa-page>,
370
+ // which floated it on the app shell outside the page. It is now journalCreateButton on transactionsTableConfig with
371
+ // inHeader: true, so the table header renders it in line with Create Transaction. The hand-rolled
372
+ // JournalEntryDialogComponent it opened is gone — the button now carries the house details dialog config.
373
+ class TransactionsComponent {
374
+ constructor() {
375
+ this.accountingService = inject(AccountingService);
376
+ this.pageConfig = {
377
+ title: 'Transactions',
378
+ tableConfig: this.accountingService.transactionsTableConfig,
379
+ searchTableConfig: this.accountingService.transactionSearchTableConfig // Changed: Enable search mode with date range, description, account filters
380
+ };
381
+ }
382
+ ngOnInit() {
383
+ }
384
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TransactionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
385
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TransactionsComponent, isStandalone: false, selector: "spa-transactions", ngImport: i0, template: `<spa-page [config]="pageConfig"></spa-page>`, isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
386
+ }
387
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TransactionsComponent, decorators: [{
388
+ type: Component,
389
+ args: [{
390
+ selector: 'spa-transactions',
391
+ template: `<spa-page [config]="pageConfig"></spa-page>`,
392
+ standalone: false
393
+ }]
394
+ }] });
395
+
396
+ // Invoice management component for tin-spa library
397
+ class InvoicesComponent {
398
+ constructor() {
399
+ this.accountingService = inject(AccountingService);
400
+ this.httpService = inject(HttpService);
401
+ this.http = inject(HttpClient);
402
+ this.messageService = inject(MessageService);
403
+ this.pageConfig = {
404
+ title: 'Invoices',
405
+ tableConfig: this.accountingService.invoicesTableConfig
406
+ };
407
+ }
408
+ ngOnInit() {
409
+ }
410
+ // Handle action click events from the table
411
+ actionClicked(event) {
412
+ if (event.name === 'pdf') {
413
+ this.messageService.confirm(`Download Invoice ?`).subscribe((result) => {
414
+ if (result == "yes") {
415
+ this.download(event.data);
416
+ }
417
+ });
418
+ }
419
+ }
420
+ // Download invoice as PDF
421
+ download(invoice) {
422
+ this.http.post(`${this.httpService.apiUrl}invoices/pdf?action=x`, invoice, { responseType: 'blob' })
423
+ .subscribe((response) => {
424
+ const blob = new Blob([response], { type: 'application/pdf' });
425
+ const url = window.URL.createObjectURL(blob);
426
+ const link = document.createElement('a');
427
+ link.href = url;
428
+ link.download = `Invoice_${invoice.invoiceID}.pdf`;
429
+ link.click();
430
+ window.URL.revokeObjectURL(url);
431
+ });
432
+ }
433
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: InvoicesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
434
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: InvoicesComponent, isStandalone: false, selector: "spa-invoices", ngImport: i0, template: '<spa-page [config]="pageConfig" (actionClick)="actionClicked($event)"></spa-page>', isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
435
+ }
436
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: InvoicesComponent, decorators: [{
437
+ type: Component,
438
+ args: [{
439
+ selector: 'spa-invoices',
440
+ template: '<spa-page [config]="pageConfig" (actionClick)="actionClicked($event)"></spa-page>',
441
+ standalone: false
442
+ }]
443
+ }] });
444
+
445
+ // Statement page (B4) — invoice history plus open-item customer/supplier statements with Excel/PDF export
446
+ // Changed: the two open-item tabs were hand-written markup (a flex row of totals and a raw <table>). They are
447
+ // now the library's own spa-tiles + spa-table, driven by config in AccountingService, so the page picks up
448
+ // every table behaviour centrally: responsive minColumns, the filter box, money formatting and empty states.
449
+ // The PDF and Excel downloads are untouched — both are produced SERVER-SIDE and nothing here contributes to them.
450
+ class StatementComponent {
451
+ constructor() {
452
+ this.accountingService = inject(AccountingService);
453
+ this.httpService = inject(HttpService);
454
+ this.http = inject(HttpClient);
455
+ this.pageConfig = {
456
+ title: 'Statement',
457
+ tableConfig: this.accountingService.statementTableConfig,
458
+ searchTableConfig: this.accountingService.statementSearchTableConfig
459
+ };
460
+ // Per-kind UI state for the customer/supplier open-item tabs.
461
+ // Changed: each kind carries its OWN cloned table config — two grids are on screen at once and a TableConfig
462
+ // is mutated by the table it belongs to, so sharing one object between the tabs would cross their state.
463
+ this.state = {
464
+ customer: {
465
+ entityLabel: 'Customer', entityId: null, from: null, to: null, data: null, lines: [],
466
+ listAction: { url: 'customers/list/x' },
467
+ tileConfig: this.accountingService.customerStatementTileConfig,
468
+ tableConfig: { ...this.accountingService.statementLinesTableConfig }
469
+ },
470
+ supplier: {
471
+ entityLabel: 'Supplier', entityId: null, from: null, to: null, data: null, lines: [],
472
+ listAction: { url: 'suppliers/list/x' },
473
+ tileConfig: this.accountingService.supplierStatementTileConfig,
474
+ tableConfig: { ...this.accountingService.statementLinesTableConfig }
475
+ }
476
+ };
477
+ }
478
+ // Changed: the customer/supplier option lists are loaded by spa-select-lite's own loadAction now, so there is
479
+ // no hand-rolled options fetch left here. ngOnInit is kept for the interface and for future per-kind defaults.
480
+ ngOnInit() { }
481
+ query(kind) {
482
+ const s = this.state[kind];
483
+ const params = [];
484
+ if (s.from)
485
+ params.push(`from=${s.from}`);
486
+ if (s.to)
487
+ params.push(`to=${s.to}`);
488
+ return params.length ? `?${params.join('&')}` : '';
489
+ }
490
+ load(kind) {
491
+ const s = this.state[kind];
492
+ if (!s.entityId)
493
+ return; // Added: the header refresh icon routes here too, and it must not fire before an entity is chosen
494
+ this.http.get(`${this.httpService.apiUrl}reports/${kind}-statement/${s.entityId}${this.query(kind)}`)
495
+ .subscribe(res => {
496
+ s.data = res?.data ?? res?.Data ?? null;
497
+ s.lines = s.data?.lines ?? []; // Changed: held as a stable reference — binding `data.lines || []` inline would hand spa-table a new array on every change-detection pass
498
+ });
499
+ }
500
+ // C8: open-item customer statement as PDF — generated entirely server-side (reports/pdf/customer-statement);
501
+ // this method only asks for the blob and saves it. Unchanged by the page rewrite.
502
+ exportPdf(kind) {
503
+ const s = this.state[kind];
504
+ this.http.get(`${this.httpService.apiUrl}reports/pdf/${kind}-statement${this.query(kind) ? this.query(kind) + '&' : '?'}${kind}Id=${s.entityId}`, { responseType: 'blob' })
505
+ .subscribe((response) => {
506
+ const url = window.URL.createObjectURL(new Blob([response], { type: 'application/pdf' }));
507
+ const link = document.createElement('a');
508
+ link.href = url;
509
+ link.download = `${kind}-statement-${s.entityId}.pdf`;
510
+ link.click();
511
+ window.URL.revokeObjectURL(url);
512
+ });
513
+ }
514
+ export(kind) {
515
+ const s = this.state[kind];
516
+ this.http.get(`${this.httpService.apiUrl}reports/export/${kind}-statement${this.query(kind) ? this.query(kind) + '&' : '?'}${kind}Id=${s.entityId}`, { responseType: 'blob' })
517
+ .subscribe((response) => {
518
+ const url = window.URL.createObjectURL(new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }));
519
+ const link = document.createElement('a');
520
+ link.href = url;
521
+ link.download = `${kind}-statement-${s.entityId}.xlsx`;
522
+ link.click();
523
+ window.URL.revokeObjectURL(url);
524
+ });
525
+ }
526
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: StatementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
527
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: StatementComponent, isStandalone: false, selector: "spa-statement", ngImport: i0, template: `
528
+ <mat-tab-group>
529
+ <mat-tab label="Invoice History">
530
+ <div style="padding-top: 8px;">
531
+ <spa-page [config]="pageConfig"></spa-page>
532
+ </div>
533
+ </mat-tab>
534
+
535
+ <mat-tab label="Customer Statement">
536
+ <ng-container *ngTemplateOutlet="statementTab; context: { kind: 'customer' }"></ng-container>
537
+ </mat-tab>
538
+
539
+ <mat-tab label="Supplier Statement">
540
+ <ng-container *ngTemplateOutlet="statementTab; context: { kind: 'supplier' }"></ng-container>
541
+ </mat-tab>
542
+ </mat-tab-group>
543
+
544
+ <ng-template #statementTab let-kind="kind">
545
+ <div style="padding: 16px 0;">
546
+
547
+ <!-- Changed: library inputs instead of raw mat-form-field markup — same three filters, same Run/Export/PDF rules -->
548
+ <div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 12px;">
549
+ <spa-select-lite [display]="state[kind].entityLabel" [loadAction]="state[kind].listAction" optionDisplay="name" optionValue="value" [required]="false" [(value)]="state[kind].entityId" width="240px"></spa-select-lite><!-- optionDisplay/optionValue are NOT defaulted by spa-select-common; without them every option binds undefined and nothing can be selected -->
550
+ <spa-date display="From" width="170px" [required]="false" [value]="state[kind].from" (valueChange)="state[kind].from = $event"></spa-date>
551
+ <spa-date display="To" width="170px" [required]="false" [value]="state[kind].to" (valueChange)="state[kind].to = $event"></spa-date>
552
+ <button mat-flat-button color="primary" (click)="load(kind)" [disabled]="!state[kind].entityId">Run</button>
553
+ <button mat-stroked-button (click)="export(kind)" [disabled]="!state[kind].entityId">
554
+ <mat-icon>download</mat-icon> Export to Excel
555
+ </button>
556
+ <button mat-stroked-button *ngIf="kind === 'customer'" (click)="exportPdf(kind)" [disabled]="!state[kind].entityId">
557
+ <mat-icon>picture_as_pdf</mat-icon> Download PDF
558
+ </button>
559
+ </div>
560
+
561
+ <!-- Changed: totals are spa-tiles, lines are spa-table. Nothing renders until a statement has been run,
562
+ which is exactly what the old *ngIf on the results card did. -->
563
+ <ng-container *ngIf="state[kind].data">
564
+ <spa-tiles [config]="state[kind].tileConfig" [data]="state[kind].data"></spa-tiles>
565
+ <div style="margin-top: 12px;">
566
+ <spa-table [config]="state[kind].tableConfig" [data]="state[kind].lines" (refreshClick)="load(kind)"></spa-table>
567
+ </div>
568
+ </ng-container>
569
+
570
+ </div>
571
+ </ng-template>
572
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i3$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: DateComponent, selector: "spa-date", inputs: ["required", "min", "max", "boundContext", "readonly", "hint", "value", "display", "placeholder", "width", "suffix", "infoMessage", "copyContent", "clearContent"], outputs: ["valueChange"] }, { kind: "component", type: TableComponent, selector: "spa-table", inputs: ["data", "tileData", "config", "localMode", "parentDetails", "reload", "activeTab", "inTab", "nestingLevel"], outputs: ["dataLoad", "totalChange", "actionSuccess", "refreshClick", "searchClick", "createClick", "actionClick", "inputChange", "actionResponse"] }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }] }); }
573
+ }
574
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: StatementComponent, decorators: [{
575
+ type: Component,
576
+ args: [{
577
+ selector: 'spa-statement',
578
+ template: `
579
+ <mat-tab-group>
580
+ <mat-tab label="Invoice History">
581
+ <div style="padding-top: 8px;">
582
+ <spa-page [config]="pageConfig"></spa-page>
583
+ </div>
584
+ </mat-tab>
585
+
586
+ <mat-tab label="Customer Statement">
587
+ <ng-container *ngTemplateOutlet="statementTab; context: { kind: 'customer' }"></ng-container>
588
+ </mat-tab>
589
+
590
+ <mat-tab label="Supplier Statement">
591
+ <ng-container *ngTemplateOutlet="statementTab; context: { kind: 'supplier' }"></ng-container>
592
+ </mat-tab>
593
+ </mat-tab-group>
594
+
595
+ <ng-template #statementTab let-kind="kind">
596
+ <div style="padding: 16px 0;">
597
+
598
+ <!-- Changed: library inputs instead of raw mat-form-field markup — same three filters, same Run/Export/PDF rules -->
599
+ <div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 12px;">
600
+ <spa-select-lite [display]="state[kind].entityLabel" [loadAction]="state[kind].listAction" optionDisplay="name" optionValue="value" [required]="false" [(value)]="state[kind].entityId" width="240px"></spa-select-lite><!-- optionDisplay/optionValue are NOT defaulted by spa-select-common; without them every option binds undefined and nothing can be selected -->
601
+ <spa-date display="From" width="170px" [required]="false" [value]="state[kind].from" (valueChange)="state[kind].from = $event"></spa-date>
602
+ <spa-date display="To" width="170px" [required]="false" [value]="state[kind].to" (valueChange)="state[kind].to = $event"></spa-date>
603
+ <button mat-flat-button color="primary" (click)="load(kind)" [disabled]="!state[kind].entityId">Run</button>
604
+ <button mat-stroked-button (click)="export(kind)" [disabled]="!state[kind].entityId">
605
+ <mat-icon>download</mat-icon> Export to Excel
606
+ </button>
607
+ <button mat-stroked-button *ngIf="kind === 'customer'" (click)="exportPdf(kind)" [disabled]="!state[kind].entityId">
608
+ <mat-icon>picture_as_pdf</mat-icon> Download PDF
609
+ </button>
610
+ </div>
611
+
612
+ <!-- Changed: totals are spa-tiles, lines are spa-table. Nothing renders until a statement has been run,
613
+ which is exactly what the old *ngIf on the results card did. -->
614
+ <ng-container *ngIf="state[kind].data">
615
+ <spa-tiles [config]="state[kind].tileConfig" [data]="state[kind].data"></spa-tiles>
616
+ <div style="margin-top: 12px;">
617
+ <spa-table [config]="state[kind].tableConfig" [data]="state[kind].lines" (refreshClick)="load(kind)"></spa-table>
618
+ </div>
619
+ </ng-container>
620
+
621
+ </div>
622
+ </ng-template>
623
+ `,
624
+ standalone: false
625
+ }]
626
+ }] });
627
+
628
+ // Financial Reports component with Trial Balance, P&L, Balance Sheet, General Ledger tabs + Excel exports (B5/B7) and from/to date range (Phase 1)
629
+ class ReportsComponent {
630
+ constructor(dataServiceLib, accountingService) {
631
+ this.dataServiceLib = dataServiceLib;
632
+ this.accountingService = accountingService;
633
+ this.httpService = inject(HttpService); // Changed: For export downloads
634
+ this.http = inject(HttpClient); // Changed: For export downloads
635
+ this.from = ''; // Changed: Optional report period start
636
+ this.to = ''; // Changed: Optional report period end
637
+ this.reportTabConfigs = [];
638
+ this.buildTabs(); // Changed: Tabs built via method so Run can rebuild with date params
639
+ }
640
+ ngOnInit() {
641
+ }
642
+ // Changed: Append from/to query params when set — backends that ignore them are unaffected
643
+ withRange(url) {
644
+ const params = [];
645
+ if (this.from)
646
+ params.push(`from=${this.from}`);
647
+ if (this.to)
648
+ params.push(`to=${this.to}`);
649
+ return params.length ? `${url}?${params.join('&')}` : url;
650
+ }
651
+ // Changed: Rebuild tab configs with the current date range applied to every report URL
652
+ buildTabs() {
653
+ this.reportTabConfigs = [
654
+ {
655
+ ...this.accountingService.trialBalanceTableConfig,
656
+ tabTitle: 'Trial Balance',
657
+ loadAction: { url: this.withRange('accounts/reports/trialbalance') },
658
+ },
659
+ {
660
+ ...this.accountingService.pnlTableConfig,
661
+ tabTitle: 'Profit & Loss',
662
+ loadAction: { url: this.withRange('accounts/reports/pnl') },
663
+ },
664
+ {
665
+ ...this.accountingService.balanceSheetReportTableConfig,
666
+ tabTitle: 'Balance Sheet',
667
+ loadAction: { url: this.withRange('accounts/reports/balancesheet') },
668
+ },
669
+ {
670
+ ...this.accountingService.generalLedgerTableConfig,
671
+ tabTitle: 'General Ledger',
672
+ loadAction: { url: this.withRange('reports/gl') },
673
+ },
674
+ {
675
+ ...this.accountingService.cashFlowTableConfig,
676
+ tabTitle: 'Cash Flow',
677
+ loadAction: { url: this.withRange('reports/cash-flow') },
678
+ },
679
+ {
680
+ ...this.accountingService.equityChangesTableConfig,
681
+ tabTitle: 'Changes in Equity',
682
+ loadAction: { url: this.withRange('reports/equity-changes') },
683
+ }
684
+ ];
685
+ }
686
+ // Changed: Download an xlsx export from the shared reports endpoint (B7) with the selected date range applied
687
+ export(report) {
688
+ this.http.get(`${this.httpService.apiUrl}${this.withRange(`reports/export/${report}`)}`, { responseType: 'blob' })
689
+ .subscribe((response) => {
690
+ const url = window.URL.createObjectURL(new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }));
691
+ const link = document.createElement('a');
692
+ link.href = url;
693
+ link.download = `${report}.xlsx`;
694
+ link.click();
695
+ window.URL.revokeObjectURL(url);
696
+ });
697
+ }
698
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ReportsComponent, deps: [{ token: i1.DataServiceLib }, { token: i1.AccountingService }], target: i0.ɵɵFactoryTarget.Component }); }
699
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ReportsComponent, isStandalone: false, selector: "spa-reports", ngImport: i0, template: `
700
+ <div style="display: flex; align-items: center; justify-content: space-between;">
701
+ <h4>Financial Reports</h4>
702
+ <button mat-stroked-button [matMenuTriggerFor]="exportMenu">
703
+ <mat-icon>download</mat-icon> Export to Excel
704
+ </button>
705
+ <mat-menu #exportMenu="matMenu">
706
+ <button mat-menu-item (click)="export('trial-balance')">Trial Balance</button>
707
+ <button mat-menu-item (click)="export('pnl')">Profit &amp; Loss</button>
708
+ <button mat-menu-item (click)="export('balance-sheet')">Balance Sheet</button>
709
+ <button mat-menu-item (click)="export('gl')">General Ledger</button>
710
+ <button mat-menu-item (click)="export('vat-return')">VAT Return (current month)</button>
711
+ <button mat-menu-item (click)="export('cash-flow')">Cash Flow (current month)</button>
712
+ <button mat-menu-item (click)="export('equity-changes')">Changes in Equity (current month)</button>
713
+ </mat-menu>
714
+ </div>
715
+ <hr>
716
+
717
+ <!-- Changed: From/To date range controls applied to all report tabs (pattern copied from VatReturnComponent) -->
718
+ <div style="display: flex; gap: 16px; align-items: center; margin-bottom: 16px;">
719
+ <mat-form-field>
720
+ <mat-label>From</mat-label>
721
+ <input matInput type="date" [(ngModel)]="from">
722
+ </mat-form-field>
723
+ <mat-form-field>
724
+ <mat-label>To</mat-label>
725
+ <input matInput type="date" [(ngModel)]="to">
726
+ </mat-form-field>
727
+ <button mat-flat-button color="primary" (click)="buildTabs()">Run</button>
728
+ </div>
729
+
730
+ <spa-tabs [tableConfigs]="reportTabConfigs"></spa-tabs>
731
+ `, isInline: true, dependencies: [{ kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i3$3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }] }); }
732
+ }
733
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ReportsComponent, decorators: [{
734
+ type: Component,
735
+ args: [{
736
+ selector: 'spa-reports',
737
+ template: `
738
+ <div style="display: flex; align-items: center; justify-content: space-between;">
739
+ <h4>Financial Reports</h4>
740
+ <button mat-stroked-button [matMenuTriggerFor]="exportMenu">
741
+ <mat-icon>download</mat-icon> Export to Excel
742
+ </button>
743
+ <mat-menu #exportMenu="matMenu">
744
+ <button mat-menu-item (click)="export('trial-balance')">Trial Balance</button>
745
+ <button mat-menu-item (click)="export('pnl')">Profit &amp; Loss</button>
746
+ <button mat-menu-item (click)="export('balance-sheet')">Balance Sheet</button>
747
+ <button mat-menu-item (click)="export('gl')">General Ledger</button>
748
+ <button mat-menu-item (click)="export('vat-return')">VAT Return (current month)</button>
749
+ <button mat-menu-item (click)="export('cash-flow')">Cash Flow (current month)</button>
750
+ <button mat-menu-item (click)="export('equity-changes')">Changes in Equity (current month)</button>
751
+ </mat-menu>
752
+ </div>
753
+ <hr>
754
+
755
+ <!-- Changed: From/To date range controls applied to all report tabs (pattern copied from VatReturnComponent) -->
756
+ <div style="display: flex; gap: 16px; align-items: center; margin-bottom: 16px;">
757
+ <mat-form-field>
758
+ <mat-label>From</mat-label>
759
+ <input matInput type="date" [(ngModel)]="from">
760
+ </mat-form-field>
761
+ <mat-form-field>
762
+ <mat-label>To</mat-label>
763
+ <input matInput type="date" [(ngModel)]="to">
764
+ </mat-form-field>
765
+ <button mat-flat-button color="primary" (click)="buildTabs()">Run</button>
766
+ </div>
767
+
768
+ <spa-tabs [tableConfigs]="reportTabConfigs"></spa-tabs>
769
+ `,
770
+ standalone: false
771
+ }]
772
+ }], ctorParameters: () => [{ type: i1.DataServiceLib }, { type: i1.AccountingService }] });
773
+
774
+ // Tax Rates management page - CRUD for VAT tax rates
775
+ class TaxRatesComponent {
776
+ constructor() {
777
+ this.accountingService = inject(AccountingService);
778
+ this.pageConfig = {
779
+ title: 'Tax Rates',
780
+ tableConfig: this.accountingService.taxRatesTableConfig
781
+ };
782
+ }
783
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TaxRatesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
784
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TaxRatesComponent, isStandalone: true, selector: "spa-tax-rates", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "ngmodule", type: TinSpaModule }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
785
+ }
786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TaxRatesComponent, decorators: [{
787
+ type: Component,
788
+ args: [{
789
+ selector: 'spa-tax-rates',
790
+ standalone: true,
791
+ imports: [TinSpaModule],
792
+ template: '<spa-page [config]="pageConfig"></spa-page>'
793
+ }]
794
+ }] });
795
+
796
+ // Standing Orders management page — CRUD for recurring scheduled transactions
797
+ class StandingOrdersComponent {
798
+ constructor() {
799
+ this.accountingService = inject(AccountingService);
800
+ this.pageConfig = {
801
+ title: 'Standing Orders',
802
+ tableConfig: this.accountingService.standingOrdersTableConfig
803
+ };
804
+ }
805
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: StandingOrdersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
806
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: StandingOrdersComponent, isStandalone: true, selector: "spa-standing-orders", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "ngmodule", type: TinSpaModule }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
807
+ }
808
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: StandingOrdersComponent, decorators: [{
809
+ type: Component,
810
+ args: [{
811
+ selector: 'spa-standing-orders',
812
+ standalone: true,
813
+ imports: [TinSpaModule],
814
+ template: '<spa-page [config]="pageConfig"></spa-page>'
815
+ }]
816
+ }] });
817
+
818
+ // Currency management page - CRUD for multi-currency support
819
+ class CurrenciesComponent {
820
+ constructor() {
821
+ this.accountingService = inject(AccountingService);
822
+ this.pageConfig = {
823
+ title: 'Currencies',
824
+ tableConfig: this.accountingService.currenciesTableConfig
825
+ };
826
+ }
827
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CurrenciesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
828
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: CurrenciesComponent, isStandalone: true, selector: "spa-currencies", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "ngmodule", type: TinSpaModule }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
829
+ }
830
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CurrenciesComponent, decorators: [{
831
+ type: Component,
832
+ args: [{
833
+ selector: 'spa-currencies',
834
+ standalone: true,
835
+ imports: [TinSpaModule],
836
+ template: '<spa-page [config]="pageConfig"></spa-page>'
837
+ }]
838
+ }] });
839
+
840
+ // Budget management component for CRUD operations
841
+ class BudgetsComponent {
842
+ constructor() {
843
+ this.accountingService = inject(AccountingService);
844
+ this.pageConfig = {
845
+ title: 'Budgets',
846
+ tableConfig: this.accountingService.budgetsTableConfig
847
+ };
848
+ }
849
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: BudgetsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
850
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: BudgetsComponent, isStandalone: false, selector: "spa-budgets", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
851
+ }
852
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: BudgetsComponent, decorators: [{
853
+ type: Component,
854
+ args: [{
855
+ selector: 'spa-budgets',
856
+ template: '<spa-page [config]="pageConfig"></spa-page>',
857
+ standalone: false
858
+ }]
859
+ }] });
860
+
861
+ // Budget vs Actual report component with budget selector, tiles, and tabbed tables
862
+ class BudgetVsActualComponent {
863
+ constructor() {
864
+ this.accountingService = inject(AccountingService);
865
+ this.httpService = inject(HttpService);
866
+ this.selectedBudgetID = null;
867
+ this.reportTabConfigs = [];
868
+ this.summaryTileConfig = { ...this.accountingService.budgetSummaryTileConfig };
869
+ }
870
+ // Load report data when a budget is selected
871
+ onBudgetSelected() {
872
+ if (!this.selectedBudgetID) {
873
+ this.reportTabConfigs = [];
874
+ return;
875
+ }
876
+ // Update tile config to load summary for selected budget
877
+ this.summaryTileConfig = {
878
+ ...this.accountingService.budgetSummaryTileConfig,
879
+ loadAction: { url: `budgetreport/summary/${this.selectedBudgetID}` }
880
+ };
881
+ // Build tab configs with dynamic URLs for selected budget
882
+ this.reportTabConfigs = [
883
+ {
884
+ ...this.accountingService.budgetVsActualByPeriodTableConfig,
885
+ loadAction: { url: `budgetreport/vsactual/${this.selectedBudgetID}` }
886
+ },
887
+ {
888
+ ...this.accountingService.budgetVsActualByAccountTableConfig,
889
+ loadAction: { url: `budgetreport/summary/${this.selectedBudgetID}` }
890
+ }
891
+ ];
892
+ }
893
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: BudgetVsActualComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
894
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: BudgetVsActualComponent, isStandalone: false, selector: "spa-budget-vs-actual", ngImport: i0, template: `
895
+ <h4>Budget vs Actual</h4>
896
+ <hr>
897
+
898
+ <div style="max-width: 300px; margin-bottom: 16px;">
899
+ <spa-select-lite display="Select Budget" [loadAction]="{ url: 'budgets/list/x' }" optionDisplay="name" optionValue="value" [(value)]="selectedBudgetID" (valueChange)="onBudgetSelected()"></spa-select-lite><!-- Changed: optionDisplay/optionValue are NOT defaulted by spa-select-common (both default to ""), so every option rendered blank and bound undefined — no budget could be picked. 'name'/'value' match BaseController.GetList's ListOption shape, same as the statement page -->
900
+ </div>
901
+
902
+ <spa-tiles *ngIf="summaryTileConfig.loadAction" [config]="summaryTileConfig"></spa-tiles>
903
+
904
+ <spa-tabs *ngIf="reportTabConfigs.length > 0" [tableConfigs]="reportTabConfigs"></spa-tabs>
905
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: TabsComponent, selector: "spa-tabs", inputs: ["tableConfigs", "reload", "reloadTab", "parentDetails", "localMode", "nestingLevel"], outputs: ["formRefresh", "actionSuccess"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }] }); }
906
+ }
907
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: BudgetVsActualComponent, decorators: [{
908
+ type: Component,
909
+ args: [{
910
+ selector: 'spa-budget-vs-actual',
911
+ template: `
912
+ <h4>Budget vs Actual</h4>
913
+ <hr>
914
+
915
+ <div style="max-width: 300px; margin-bottom: 16px;">
916
+ <spa-select-lite display="Select Budget" [loadAction]="{ url: 'budgets/list/x' }" optionDisplay="name" optionValue="value" [(value)]="selectedBudgetID" (valueChange)="onBudgetSelected()"></spa-select-lite><!-- Changed: optionDisplay/optionValue are NOT defaulted by spa-select-common (both default to ""), so every option rendered blank and bound undefined — no budget could be picked. 'name'/'value' match BaseController.GetList's ListOption shape, same as the statement page -->
917
+ </div>
918
+
919
+ <spa-tiles *ngIf="summaryTileConfig.loadAction" [config]="summaryTileConfig"></spa-tiles>
920
+
921
+ <spa-tabs *ngIf="reportTabConfigs.length > 0" [tableConfigs]="reportTabConfigs"></spa-tabs>
922
+ `,
923
+ standalone: false
924
+ }]
925
+ }] });
926
+
927
+ // Accounting module dashboard — pure financial/account performance (invoice-specific moved to invoicing)
928
+ class AccountingDashboardComponent {
929
+ constructor() {
930
+ this.summaryTiles = {
931
+ tiles: [
932
+ { name: 'inflows', alias: 'Inflows (MTD)', color: '#4caf50', icon: 'arrow_downward', style: 'icon', info: 'MTD cash inflows' }, // Changed: Cash inflows tile
933
+ { name: 'outflows', alias: 'Outflows (MTD)', color: '#f44336', icon: 'arrow_upward', style: 'icon', info: 'MTD cash outflows' }, // Changed: Cash outflows tile
934
+ { name: 'netCashFlow', alias: 'Net Cash Flow', color: '#2196f3', icon: 'swap_vert', style: 'icon', info: 'Net cash movement' }, // Changed: Net cash flow tile
935
+ { name: 'revenue', alias: 'Revenue (MTD)', color: '#4caf50', icon: 'trending_up', style: 'icon', info: 'MTD revenue' },
936
+ { name: 'expenses', alias: 'Expenses (MTD)', color: '#f44336', icon: 'trending_down', style: 'icon', info: 'MTD expenses' },
937
+ { name: 'cashBalance', alias: 'Cash & Bank', color: '#ff9800', icon: 'account_balance_wallet', style: 'icon', info: 'Cash + bank total' },
938
+ ],
939
+ loadAction: { url: 'accounts/dashboard-summary/x' },
940
+ loadInit: true
941
+ };
942
+ // Changed: Replaced collectionRate/invoiceStatusSplit with profitMargin/expenseBreakdown
943
+ this.chartTiles = {
944
+ tiles: [
945
+ { name: 'profitMargin', alias: 'Profit Margin', color: '#4caf50', chart: { type: 'doughnut', gaugeColor: '#4caf50', height: 130, dataField: 'profitMargin' }, footer: 'Net profit / revenue', footerIcon: 'pie_chart' }, // Changed: Replaced collectionRate
946
+ { name: 'revenueTrendSpark', alias: 'Revenue Trend', color: '#2196f3', chart: { type: 'line', color: '#2196f3', height: 100, dataField: 'revenueTrendSpark' }, footer: 'Last 12 months', footerIcon: 'show_chart' },
947
+ { name: 'expenseBreakdown', alias: 'Expense Breakdown', color: '#ff9800', chart: { type: 'pie', colors: ['#f44336', '#ff5722', '#ff9800', '#ffc107', '#ffeb3b', '#9e9e9e'], height: 130, dataField: 'expenseBreakdown' }, footer: 'Top 5 + Other', footerIcon: 'donut_large' }, // Changed: Replaced invoiceStatusSplit
948
+ { name: 'expenseTrendSpark', alias: 'Expense Trend', color: '#f44336', chart: { type: 'bar', colors: ['#f44336', '#ef5350', '#e57373', '#ef9a9a', '#ffcdd2', '#f44336'], height: 100, dataField: 'expenseTrendSpark' }, footer: 'Last 6 months', footerIcon: 'trending_down' },
949
+ ],
950
+ loadAction: { url: 'accounts/dashboard-chart-tiles/x' },
951
+ loadInit: true
952
+ };
953
+ // Changed: Replaced arAging/invoiceStatus with cashFlow/transactionVolume
954
+ this.chartConfig = {
955
+ charts: [
956
+ { name: 'revenueTrend', title: 'Revenue vs Expenses Trend', type: 'line', height: '300px', showPoints: true, tension: 0.4, colors: ['#4caf50', '#f44336'], showLegend: true },
957
+ { name: 'cashFlow', title: 'Monthly Cash Flow', type: 'bar', height: '300px', colors: ['#4caf50', '#f44336'], showLegend: true }, // Changed: Replaced arAging
958
+ { name: 'accountBalances', title: 'Account Balances by Type', type: 'bar', height: '300px', colors: ['#2196f3', '#f44336', '#4caf50', '#ff9800', '#9c27b0'] },
959
+ { name: 'transactionVolume', title: 'Monthly Transaction Volume', type: 'bar', height: '300px', colors: ['#7c4dff'] }, // Changed: Replaced invoiceStatus
960
+ ],
961
+ loadAction: { url: 'accounts/dashboard-charts/x' },
962
+ loadInit: true,
963
+ columns: 2
964
+ };
965
+ // Changed: New cash/bank running balance line chart with separate data source
966
+ this.cashBankChartConfig = {
967
+ charts: [
968
+ { name: 'cashBankBalances', title: 'Cash & Bank Balances (30 Days)', type: 'line', height: '500px', showPoints: true, tension: 0.4, showLegend: true,
969
+ options: { scales: { y: { grid: { color: '#f0f0f0', drawBorder: false }, ticks: { font: { size: 11 }, color: '#999', maxTicksLimit: 12 }, border: { display: false } } } } }, // Changed: More granular Y-axis ticks for better readability
970
+ ],
971
+ loadAction: { url: 'accounts/dashboard-cash-balances/x' },
972
+ loadInit: true,
973
+ columns: 1
974
+ };
975
+ }
976
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
977
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AccountingDashboardComponent, isStandalone: false, selector: "spa-accounting-dashboard", ngImport: i0, template: `
978
+ <div class="dashboard-container">
979
+ <h4 class="dashboard-title"><mat-icon>account_balance</mat-icon> Accounting Dashboard</h4>
980
+ <spa-tiles [config]="summaryTiles"></spa-tiles>
981
+ <div style="margin-top: 16px;"></div>
982
+ <spa-tiles [config]="chartTiles"></spa-tiles>
983
+ <spa-charts [config]="chartConfig"></spa-charts>
984
+ <!-- Changed: Separate cash/bank balance chart with own data source -->
985
+ <spa-charts [config]="cashBankChartConfig"></spa-charts>
986
+ </div>
987
+ `, isInline: true, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}\n"], dependencies: [{ kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: ChartsComponent, selector: "spa-charts", inputs: ["config", "data", "reload"] }] }); }
988
+ }
989
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingDashboardComponent, decorators: [{
990
+ type: Component,
991
+ args: [{ selector: 'spa-accounting-dashboard', template: `
992
+ <div class="dashboard-container">
993
+ <h4 class="dashboard-title"><mat-icon>account_balance</mat-icon> Accounting Dashboard</h4>
994
+ <spa-tiles [config]="summaryTiles"></spa-tiles>
995
+ <div style="margin-top: 16px;"></div>
996
+ <spa-tiles [config]="chartTiles"></spa-tiles>
997
+ <spa-charts [config]="chartConfig"></spa-charts>
998
+ <!-- Changed: Separate cash/bank balance chart with own data source -->
999
+ <spa-charts [config]="cashBankChartConfig"></spa-charts>
1000
+ </div>
1001
+ `, standalone: false, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}\n"] }]
1002
+ }] });
1003
+
1004
+ // Invoice module dashboard with pipeline visibility, aging, and collection KPIs
1005
+ class InvoiceDashboardComponent {
1006
+ constructor(dataService) {
1007
+ this.dataService = dataService;
1008
+ this.attentionItems = [];
1009
+ // Changed: Summary tiles — invoice pipeline KPIs
1010
+ this.summaryTiles = {
1011
+ tiles: [
1012
+ { name: 'totalInvoices', alias: 'Total Invoices', color: '#1565c0', icon: 'receipt_long', style: 'icon', info: 'All active invoices' },
1013
+ { name: 'drafts', alias: 'Drafts', color: '#9e9e9e', icon: 'edit_note', style: 'icon', info: 'Invoices awaiting submission' },
1014
+ { name: 'outstandingAmount', alias: 'Outstanding', color: '#ff9800', icon: 'pending', style: 'icon', prefix: '$', info: 'Total unpaid amount' },
1015
+ { name: 'overdueCount', alias: 'Overdue', color: '#f44336', icon: 'warning', style: 'icon', info: 'Invoices past due date' },
1016
+ { name: 'paidThisMonth', alias: 'Paid (Month)', color: '#4caf50', icon: 'check_circle', style: 'icon', info: 'Fully paid this month' },
1017
+ { name: 'avgDaysToPay', alias: 'Avg Days to Pay', color: '#7b1fa2', icon: 'schedule', style: 'icon', suffix: ' days', info: 'Average payment turnaround' },
1018
+ ],
1019
+ loadAction: { url: 'invoices/dashboard-summary/x' },
1020
+ loadInit: true
1021
+ };
1022
+ // Changed: Chart tiles — collection gauge, trend sparkline, status pie, aging bar
1023
+ this.chartTiles = {
1024
+ tiles: [
1025
+ { name: 'collectionRate', alias: 'Collection Rate', color: '#4caf50', chart: { type: 'doughnut', gaugeColor: '#4caf50', height: 130, dataField: 'collectionRate' }, footer: 'Amount collected vs invoiced', footerIcon: 'check_circle' },
1026
+ { name: 'invoiceTrend', alias: 'Invoice Trend', color: '#2196f3', chart: { type: 'line', color: '#2196f3', height: 100, dataField: 'invoiceTrend' }, footer: 'Last 12 months', footerIcon: 'timeline' },
1027
+ { name: 'statusDistribution', alias: 'Status Split', color: '#ff9800', chart: { type: 'pie', colors: ['#9e9e9e', '#2196f3', '#ff9800', '#4caf50', '#f44336'], height: 130, dataField: 'statusDistribution' }, footer: 'Current distribution', footerIcon: 'pie_chart' },
1028
+ { name: 'agingSnapshot', alias: 'Aging Snapshot', color: '#f44336', chart: { type: 'bar', colors: ['#4caf50', '#ff9800', '#ff5722', '#f44336', '#b71c1c'], height: 100, dataField: 'agingSnapshot' }, footer: 'Outstanding by age', footerIcon: 'bar_chart' },
1029
+ ],
1030
+ loadAction: { url: 'invoices/dashboard-chart-tiles/x' },
1031
+ loadInit: true
1032
+ };
1033
+ // Changed: Full charts — revenue pipeline, aging breakdown, top customers, payment trend
1034
+ this.chartConfig = {
1035
+ charts: [
1036
+ { name: 'revenuePipeline', title: 'Invoiced vs Collected', type: 'bar', height: '300px', showLegend: true, colors: ['#2196f3', '#4caf50'] },
1037
+ { name: 'agingBreakdown', title: 'AR Aging Breakdown', type: 'bar', height: '300px', colors: ['#4caf50', '#ff9800', '#ff5722', '#f44336', '#b71c1c'] },
1038
+ { name: 'topCustomers', title: 'Top Customers by Outstanding', type: 'bar', height: '300px', colors: ['#1565c0', '#1976d2', '#1e88e5', '#2196f3', '#42a5f5'] },
1039
+ { name: 'paymentTrend', title: 'Payment Collection Trend', type: 'line', height: '300px', showPoints: true, tension: 0.4, colors: ['#4caf50'], showLegend: true },
1040
+ { name: 'collectionPerformance', title: 'Collection Performance (12 Months)', type: 'line', height: '300px', showPoints: true, tension: 0.4, colors: ['#f57c00', '#4caf50'], showLegend: true }, // Changed: Avg days to pay + on-time rate trend
1041
+ ],
1042
+ loadAction: { url: 'invoices/dashboard-charts/x' },
1043
+ loadInit: true,
1044
+ columns: 2
1045
+ };
1046
+ }
1047
+ ngOnInit() {
1048
+ // Load attention items — top overdue invoices needing action
1049
+ this.dataService.CallApi({ url: 'invoices/dashboard-attention/x' }).subscribe((res) => {
1050
+ if (res.success)
1051
+ this.attentionItems = res.data || [];
1052
+ });
1053
+ }
1054
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: InvoiceDashboardComponent, deps: [{ token: i1.DataServiceLib }], target: i0.ɵɵFactoryTarget.Component }); }
1055
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: InvoiceDashboardComponent, isStandalone: false, selector: "spa-invoice-dashboard", ngImport: i0, template: `
1056
+ <div class="dashboard-container">
1057
+ <h4 class="dashboard-title"><mat-icon>receipt_long</mat-icon> Invoice Dashboard</h4>
1058
+ <spa-tiles [config]="summaryTiles"></spa-tiles>
1059
+ <div style="margin-top: 16px;"></div>
1060
+ <spa-tiles [config]="chartTiles"></spa-tiles>
1061
+
1062
+ <!-- Attention Required sub-table -->
1063
+ <div *ngIf="attentionItems.length > 0" class="attention-section">
1064
+ <h5 class="section-title">
1065
+ <mat-icon style="vertical-align: middle; margin-right: 4px; color: #f44336;">warning</mat-icon>
1066
+ Needs Attention
1067
+ </h5>
1068
+ <table class="kpi-table">
1069
+ <thead>
1070
+ <tr>
1071
+ <th>Invoice</th>
1072
+ <th>Customer</th>
1073
+ <th>Due Date</th>
1074
+ <th>Days Overdue</th>
1075
+ <th>Outstanding</th>
1076
+ </tr>
1077
+ </thead>
1078
+ <tbody>
1079
+ <tr *ngFor="let item of attentionItems">
1080
+ <td>{{ item.invoiceNumber }}</td>
1081
+ <td>{{ item.customerName }}</td>
1082
+ <td>{{ item.dueDate | date:'mediumDate' }}</td>
1083
+ <td style="color: #f44336; font-weight: 500;">{{ item.daysOverdue }}</td>
1084
+ <td>{{ item.outstandingAmount | currency }}</td>
1085
+ </tr>
1086
+ </tbody>
1087
+ </table>
1088
+ </div>
1089
+
1090
+ <spa-charts [config]="chartConfig"></spa-charts>
1091
+ </div>
1092
+ `, isInline: true, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}.attention-section{margin-top:16px}.section-title{display:flex;align-items:center;margin-bottom:8px;color:#333;font-weight:500}.kpi-table{width:100%;border-collapse:collapse;font-size:13px}.kpi-table th{text-align:left;padding:8px 12px;background:#f5f5f5;border-bottom:2px solid #e0e0e0;font-weight:500}.kpi-table td{padding:8px 12px;border-bottom:1px solid #eee}.kpi-table tr:hover{background:#fafafa}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }, { kind: "component", type: ChartsComponent, selector: "spa-charts", inputs: ["config", "data", "reload"] }, { kind: "pipe", type: i1$1.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] }); }
1093
+ }
1094
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: InvoiceDashboardComponent, decorators: [{
1095
+ type: Component,
1096
+ args: [{ selector: 'spa-invoice-dashboard', template: `
1097
+ <div class="dashboard-container">
1098
+ <h4 class="dashboard-title"><mat-icon>receipt_long</mat-icon> Invoice Dashboard</h4>
1099
+ <spa-tiles [config]="summaryTiles"></spa-tiles>
1100
+ <div style="margin-top: 16px;"></div>
1101
+ <spa-tiles [config]="chartTiles"></spa-tiles>
1102
+
1103
+ <!-- Attention Required sub-table -->
1104
+ <div *ngIf="attentionItems.length > 0" class="attention-section">
1105
+ <h5 class="section-title">
1106
+ <mat-icon style="vertical-align: middle; margin-right: 4px; color: #f44336;">warning</mat-icon>
1107
+ Needs Attention
1108
+ </h5>
1109
+ <table class="kpi-table">
1110
+ <thead>
1111
+ <tr>
1112
+ <th>Invoice</th>
1113
+ <th>Customer</th>
1114
+ <th>Due Date</th>
1115
+ <th>Days Overdue</th>
1116
+ <th>Outstanding</th>
1117
+ </tr>
1118
+ </thead>
1119
+ <tbody>
1120
+ <tr *ngFor="let item of attentionItems">
1121
+ <td>{{ item.invoiceNumber }}</td>
1122
+ <td>{{ item.customerName }}</td>
1123
+ <td>{{ item.dueDate | date:'mediumDate' }}</td>
1124
+ <td style="color: #f44336; font-weight: 500;">{{ item.daysOverdue }}</td>
1125
+ <td>{{ item.outstandingAmount | currency }}</td>
1126
+ </tr>
1127
+ </tbody>
1128
+ </table>
1129
+ </div>
1130
+
1131
+ <spa-charts [config]="chartConfig"></spa-charts>
1132
+ </div>
1133
+ `, standalone: false, styles: [".dashboard-container{padding:16px}.dashboard-title{display:flex;align-items:center;gap:8px;margin-bottom:16px;color:#333;font-weight:500}.attention-section{margin-top:16px}.section-title{display:flex;align-items:center;margin-bottom:8px;color:#333;font-weight:500}.kpi-table{width:100%;border-collapse:collapse;font-size:13px}.kpi-table th{text-align:left;padding:8px 12px;background:#f5f5f5;border-bottom:2px solid #e0e0e0;font-weight:500}.kpi-table td{padding:8px 12px;border-bottom:1px solid #eee}.kpi-table tr:hover{background:#fafafa}\n"] }]
1134
+ }], ctorParameters: () => [{ type: i1.DataServiceLib }] });
1135
+
1136
+ // Credit Notes page (B6) — invoice corrections/returns with GL reversal posting
1137
+ class CreditNotesComponent {
1138
+ constructor() {
1139
+ this.accountingService = inject(AccountingService);
1140
+ this.pageConfig = {
1141
+ title: 'Credit Notes',
1142
+ tableConfig: this.accountingService.creditNotesTableConfig
1143
+ };
1144
+ }
1145
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CreditNotesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1146
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: CreditNotesComponent, isStandalone: false, selector: "spa-credit-notes", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
1147
+ }
1148
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: CreditNotesComponent, decorators: [{
1149
+ type: Component,
1150
+ args: [{
1151
+ selector: 'spa-credit-notes',
1152
+ template: '<spa-page [config]="pageConfig"></spa-page>',
1153
+ standalone: false
1154
+ }]
1155
+ }] });
1156
+
1157
+ // VAT Return page (B2) — ZIMRA VAT7-style summary for a selected tax period
1158
+ // Phase 2 compliance: sales splits (standard/zero-rated/exempt), per-currency breakdown, GL reconciliation, WHT remittance tab
1159
+ class VatReturnComponent {
1160
+ constructor() {
1161
+ this.httpService = inject(HttpService);
1162
+ this.http = inject(HttpClient);
1163
+ this.from = '';
1164
+ this.to = '';
1165
+ this.data = null;
1166
+ this.whtData = []; // Added: WHT remittance rows for the selected period
1167
+ }
1168
+ ngOnInit() {
1169
+ // Default period: current calendar month
1170
+ const now = new Date();
1171
+ this.from = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().substring(0, 10);
1172
+ this.to = new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString().substring(0, 10);
1173
+ this.load();
1174
+ }
1175
+ load() {
1176
+ this.http.get(`${this.httpService.apiUrl}reports/vat-return?from=${this.from}&to=${this.to}`)
1177
+ .subscribe(res => this.data = res?.data ?? res?.Data ?? null);
1178
+ // Added: load WHT remittance rows for the same period (SupplierPayments criteria, value = "from,to")
1179
+ this.http.get(`${this.httpService.apiUrl}supplierpayments/wht-remittance/${this.from},${this.to}`) // Changed: aligned with backend SupplierPaymentsController.CustomCriteria route shape
1180
+ .subscribe(res => this.whtData = res?.data ?? res?.Data ?? []);
1181
+ }
1182
+ export() {
1183
+ this.http.get(`${this.httpService.apiUrl}reports/export/vat-return?from=${this.from}&to=${this.to}`, { responseType: 'blob' })
1184
+ .subscribe((response) => {
1185
+ const url = window.URL.createObjectURL(new Blob([response]));
1186
+ const link = document.createElement('a');
1187
+ link.href = url;
1188
+ link.download = `vat-return-${this.from}-to-${this.to}.xlsx`;
1189
+ link.click();
1190
+ window.URL.revokeObjectURL(url);
1191
+ });
1192
+ }
1193
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: VatReturnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1194
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: VatReturnComponent, isStandalone: false, selector: "spa-vat-return", ngImport: i0, template: `
1195
+ <div style="display: flex; align-items: center; justify-content: space-between;">
1196
+ <h4>VAT Return</h4>
1197
+ <button mat-stroked-button (click)="export()" [disabled]="!data">
1198
+ <mat-icon>download</mat-icon> Export to Excel
1199
+ </button>
1200
+ </div>
1201
+ <hr>
1202
+
1203
+ <div style="display: flex; gap: 16px; align-items: center; margin-bottom: 16px;">
1204
+ <mat-form-field>
1205
+ <mat-label>From</mat-label>
1206
+ <input matInput type="date" [(ngModel)]="from">
1207
+ </mat-form-field>
1208
+ <mat-form-field>
1209
+ <mat-label>To</mat-label>
1210
+ <input matInput type="date" [(ngModel)]="to">
1211
+ </mat-form-field>
1212
+ <button mat-flat-button color="primary" (click)="load()">Run</button>
1213
+ </div>
1214
+
1215
+ <mat-tab-group>
1216
+ <mat-tab label="VAT Return">
1217
+ <mat-card *ngIf="data" style="max-width: 640px; padding: 16px; margin-top: 16px;">
1218
+ <table style="width: 100%; border-collapse: collapse;">
1219
+ <tr><td style="padding: 6px 0;">Standard Rated Sales (excl. VAT)</td><td style="text-align: right;">{{ data.standardRatedSales | number:'1.2-2' }}</td></tr>
1220
+ <tr><td style="padding: 6px 0;">Zero Rated Sales</td><td style="text-align: right;">{{ data.zeroRatedSales | number:'1.2-2' }}</td></tr>
1221
+ <tr *ngIf="data.exemptSales != null"><td style="padding: 6px 0;">Exempt Sales</td><td style="text-align: right;">{{ data.exemptSales | number:'1.2-2' }}</td></tr>
1222
+ <tr style="border-top: 1px solid #ddd;"><td style="padding: 6px 0;"><b>Total Sales</b></td><td style="text-align: right;"><b>{{ data.totalSales | number:'1.2-2' }}</b></td></tr>
1223
+ <tr><td style="padding: 6px 0;">Output VAT</td><td style="text-align: right;">{{ data.outputVat | number:'1.2-2' }}</td></tr>
1224
+ <tr><td style="padding: 6px 0;">Total Purchases (excl. VAT)</td><td style="text-align: right;">{{ data.totalPurchases | number:'1.2-2' }}</td></tr>
1225
+ <tr><td style="padding: 6px 0;">Input VAT</td><td style="text-align: right;">{{ data.inputVat | number:'1.2-2' }}</td></tr>
1226
+ <tr style="border-top: 2px solid #333;">
1227
+ <td style="padding: 8px 0;"><b>{{ data.isPayable ? 'VAT Payable to ZIMRA' : 'VAT Refundable' }}</b></td>
1228
+ <td style="text-align: right;"><b [style.color]="data.isPayable ? 'red' : 'green'">{{ data.netVat | number:'1.2-2' }}</b></td>
1229
+ </tr>
1230
+ </table>
1231
+ </mat-card>
1232
+
1233
+ <!-- Added: per-currency breakdown from byCurrency -->
1234
+ <mat-card *ngIf="data?.byCurrency?.length" style="max-width: 900px; padding: 16px; margin-top: 16px;">
1235
+ <h5 style="margin-top: 0;">Breakdown by Currency</h5>
1236
+ <table style="width: 100%; border-collapse: collapse;">
1237
+ <tr style="border-bottom: 1px solid #ddd;">
1238
+ <th style="text-align: left; padding: 6px 0;">Currency</th>
1239
+ <th style="text-align: right;">Standard Sales</th>
1240
+ <th style="text-align: right;">Zero Rated</th>
1241
+ <th style="text-align: right;">Exempt</th>
1242
+ <th style="text-align: right;">Output VAT</th>
1243
+ <th style="text-align: right;">Input VAT</th>
1244
+ <th style="text-align: right;">Net VAT</th>
1245
+ </tr>
1246
+ <tr *ngFor="let c of data.byCurrency">
1247
+ <td style="padding: 6px 0;">{{ c.currencyCode }}</td>
1248
+ <td style="text-align: right;">{{ c.standardRatedSales | number:'1.2-2' }}</td>
1249
+ <td style="text-align: right;">{{ c.zeroRatedSales | number:'1.2-2' }}</td>
1250
+ <td style="text-align: right;">{{ c.exemptSales | number:'1.2-2' }}</td>
1251
+ <td style="text-align: right;">{{ c.outputVat | number:'1.2-2' }}</td>
1252
+ <td style="text-align: right;">{{ c.inputVat | number:'1.2-2' }}</td>
1253
+ <td style="text-align: right;"><b>{{ c.netVat | number:'1.2-2' }}</b></td>
1254
+ </tr>
1255
+ </table>
1256
+ </mat-card>
1257
+
1258
+ <!-- Added: GL reconciliation — output VAT per GL vs per documents -->
1259
+ <mat-card *ngIf="data?.glReconciliation" style="max-width: 640px; padding: 16px; margin-top: 16px;">
1260
+ <h5 style="margin-top: 0;">GL Reconciliation (Output VAT)</h5>
1261
+ <table style="width: 100%; border-collapse: collapse;">
1262
+ <tr><td style="padding: 6px 0;">Output VAT per GL</td><td style="text-align: right;">{{ data.glReconciliation.outputVatGL | number:'1.2-2' }}</td></tr>
1263
+ <tr><td style="padding: 6px 0;">Output VAT per Documents</td><td style="text-align: right;">{{ data.glReconciliation.outputVatFromDocuments | number:'1.2-2' }}</td></tr>
1264
+ <tr style="border-top: 1px solid #ddd;">
1265
+ <td style="padding: 6px 0;"><b>Difference</b></td>
1266
+ <td style="text-align: right;"><b [style.color]="(data.glReconciliation.difference | number:'1.2-2') !== '0.00' ? '#e65100' : 'inherit'">{{ data.glReconciliation.difference | number:'1.2-2' }}</b></td>
1267
+ </tr>
1268
+ </table>
1269
+ <p *ngIf="(data.glReconciliation.difference | number:'1.2-2') !== '0.00'" style="color: #e65100; margin: 8px 0 0;">
1270
+ <mat-icon style="vertical-align: middle; font-size: 18px;">warning</mat-icon>
1271
+ GL does not agree with document totals — investigate before filing.
1272
+ </p>
1273
+ </mat-card>
1274
+ </mat-tab>
1275
+
1276
+ <!-- Added: WHT remittance report — 30% withholding on payments to suppliers without valid ITF263 -->
1277
+ <mat-tab label="Withholding Tax">
1278
+ <mat-card style="max-width: 900px; padding: 16px; margin-top: 16px;">
1279
+ <h5 style="margin-top: 0;">Withholding Tax Remittance (30% — no valid ITF263)</h5>
1280
+ <table *ngIf="whtData?.length" style="width: 100%; border-collapse: collapse;">
1281
+ <tr style="border-bottom: 1px solid #ddd;">
1282
+ <th style="text-align: left; padding: 6px 0;">Supplier</th>
1283
+ <th style="text-align: left;">Date</th>
1284
+ <th style="text-align: right;">Gross</th>
1285
+ <th style="text-align: right;">WHT</th>
1286
+ <th style="text-align: right;">Net Paid</th>
1287
+ </tr>
1288
+ <tr *ngFor="let w of whtData">
1289
+ <td style="padding: 6px 0;">{{ w.supplierName || w.supplier }}</td>
1290
+ <td>{{ (w.paymentDate || w.date) | date:'mediumDate' }}</td>
1291
+ <td style="text-align: right;">{{ (w.grossAmount ?? w.gross) | number:'1.2-2' }}</td>
1292
+ <td style="text-align: right;">{{ (w.whtAmount ?? w.wht) | number:'1.2-2' }}</td>
1293
+ <td style="text-align: right;">{{ (w.netAmount ?? w.net) | number:'1.2-2' }}</td>
1294
+ </tr>
1295
+ </table>
1296
+ <p *ngIf="!whtData?.length" style="color: #757575;">No withheld supplier payments in this period.</p>
1297
+ </mat-card>
1298
+ </mat-tab>
1299
+ </mat-tab-group>
1300
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i3$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i6.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] }); }
1301
+ }
1302
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: VatReturnComponent, decorators: [{
1303
+ type: Component,
1304
+ args: [{
1305
+ selector: 'spa-vat-return',
1306
+ template: `
1307
+ <div style="display: flex; align-items: center; justify-content: space-between;">
1308
+ <h4>VAT Return</h4>
1309
+ <button mat-stroked-button (click)="export()" [disabled]="!data">
1310
+ <mat-icon>download</mat-icon> Export to Excel
1311
+ </button>
1312
+ </div>
1313
+ <hr>
1314
+
1315
+ <div style="display: flex; gap: 16px; align-items: center; margin-bottom: 16px;">
1316
+ <mat-form-field>
1317
+ <mat-label>From</mat-label>
1318
+ <input matInput type="date" [(ngModel)]="from">
1319
+ </mat-form-field>
1320
+ <mat-form-field>
1321
+ <mat-label>To</mat-label>
1322
+ <input matInput type="date" [(ngModel)]="to">
1323
+ </mat-form-field>
1324
+ <button mat-flat-button color="primary" (click)="load()">Run</button>
1325
+ </div>
1326
+
1327
+ <mat-tab-group>
1328
+ <mat-tab label="VAT Return">
1329
+ <mat-card *ngIf="data" style="max-width: 640px; padding: 16px; margin-top: 16px;">
1330
+ <table style="width: 100%; border-collapse: collapse;">
1331
+ <tr><td style="padding: 6px 0;">Standard Rated Sales (excl. VAT)</td><td style="text-align: right;">{{ data.standardRatedSales | number:'1.2-2' }}</td></tr>
1332
+ <tr><td style="padding: 6px 0;">Zero Rated Sales</td><td style="text-align: right;">{{ data.zeroRatedSales | number:'1.2-2' }}</td></tr>
1333
+ <tr *ngIf="data.exemptSales != null"><td style="padding: 6px 0;">Exempt Sales</td><td style="text-align: right;">{{ data.exemptSales | number:'1.2-2' }}</td></tr>
1334
+ <tr style="border-top: 1px solid #ddd;"><td style="padding: 6px 0;"><b>Total Sales</b></td><td style="text-align: right;"><b>{{ data.totalSales | number:'1.2-2' }}</b></td></tr>
1335
+ <tr><td style="padding: 6px 0;">Output VAT</td><td style="text-align: right;">{{ data.outputVat | number:'1.2-2' }}</td></tr>
1336
+ <tr><td style="padding: 6px 0;">Total Purchases (excl. VAT)</td><td style="text-align: right;">{{ data.totalPurchases | number:'1.2-2' }}</td></tr>
1337
+ <tr><td style="padding: 6px 0;">Input VAT</td><td style="text-align: right;">{{ data.inputVat | number:'1.2-2' }}</td></tr>
1338
+ <tr style="border-top: 2px solid #333;">
1339
+ <td style="padding: 8px 0;"><b>{{ data.isPayable ? 'VAT Payable to ZIMRA' : 'VAT Refundable' }}</b></td>
1340
+ <td style="text-align: right;"><b [style.color]="data.isPayable ? 'red' : 'green'">{{ data.netVat | number:'1.2-2' }}</b></td>
1341
+ </tr>
1342
+ </table>
1343
+ </mat-card>
1344
+
1345
+ <!-- Added: per-currency breakdown from byCurrency -->
1346
+ <mat-card *ngIf="data?.byCurrency?.length" style="max-width: 900px; padding: 16px; margin-top: 16px;">
1347
+ <h5 style="margin-top: 0;">Breakdown by Currency</h5>
1348
+ <table style="width: 100%; border-collapse: collapse;">
1349
+ <tr style="border-bottom: 1px solid #ddd;">
1350
+ <th style="text-align: left; padding: 6px 0;">Currency</th>
1351
+ <th style="text-align: right;">Standard Sales</th>
1352
+ <th style="text-align: right;">Zero Rated</th>
1353
+ <th style="text-align: right;">Exempt</th>
1354
+ <th style="text-align: right;">Output VAT</th>
1355
+ <th style="text-align: right;">Input VAT</th>
1356
+ <th style="text-align: right;">Net VAT</th>
1357
+ </tr>
1358
+ <tr *ngFor="let c of data.byCurrency">
1359
+ <td style="padding: 6px 0;">{{ c.currencyCode }}</td>
1360
+ <td style="text-align: right;">{{ c.standardRatedSales | number:'1.2-2' }}</td>
1361
+ <td style="text-align: right;">{{ c.zeroRatedSales | number:'1.2-2' }}</td>
1362
+ <td style="text-align: right;">{{ c.exemptSales | number:'1.2-2' }}</td>
1363
+ <td style="text-align: right;">{{ c.outputVat | number:'1.2-2' }}</td>
1364
+ <td style="text-align: right;">{{ c.inputVat | number:'1.2-2' }}</td>
1365
+ <td style="text-align: right;"><b>{{ c.netVat | number:'1.2-2' }}</b></td>
1366
+ </tr>
1367
+ </table>
1368
+ </mat-card>
1369
+
1370
+ <!-- Added: GL reconciliation — output VAT per GL vs per documents -->
1371
+ <mat-card *ngIf="data?.glReconciliation" style="max-width: 640px; padding: 16px; margin-top: 16px;">
1372
+ <h5 style="margin-top: 0;">GL Reconciliation (Output VAT)</h5>
1373
+ <table style="width: 100%; border-collapse: collapse;">
1374
+ <tr><td style="padding: 6px 0;">Output VAT per GL</td><td style="text-align: right;">{{ data.glReconciliation.outputVatGL | number:'1.2-2' }}</td></tr>
1375
+ <tr><td style="padding: 6px 0;">Output VAT per Documents</td><td style="text-align: right;">{{ data.glReconciliation.outputVatFromDocuments | number:'1.2-2' }}</td></tr>
1376
+ <tr style="border-top: 1px solid #ddd;">
1377
+ <td style="padding: 6px 0;"><b>Difference</b></td>
1378
+ <td style="text-align: right;"><b [style.color]="(data.glReconciliation.difference | number:'1.2-2') !== '0.00' ? '#e65100' : 'inherit'">{{ data.glReconciliation.difference | number:'1.2-2' }}</b></td>
1379
+ </tr>
1380
+ </table>
1381
+ <p *ngIf="(data.glReconciliation.difference | number:'1.2-2') !== '0.00'" style="color: #e65100; margin: 8px 0 0;">
1382
+ <mat-icon style="vertical-align: middle; font-size: 18px;">warning</mat-icon>
1383
+ GL does not agree with document totals — investigate before filing.
1384
+ </p>
1385
+ </mat-card>
1386
+ </mat-tab>
1387
+
1388
+ <!-- Added: WHT remittance report — 30% withholding on payments to suppliers without valid ITF263 -->
1389
+ <mat-tab label="Withholding Tax">
1390
+ <mat-card style="max-width: 900px; padding: 16px; margin-top: 16px;">
1391
+ <h5 style="margin-top: 0;">Withholding Tax Remittance (30% — no valid ITF263)</h5>
1392
+ <table *ngIf="whtData?.length" style="width: 100%; border-collapse: collapse;">
1393
+ <tr style="border-bottom: 1px solid #ddd;">
1394
+ <th style="text-align: left; padding: 6px 0;">Supplier</th>
1395
+ <th style="text-align: left;">Date</th>
1396
+ <th style="text-align: right;">Gross</th>
1397
+ <th style="text-align: right;">WHT</th>
1398
+ <th style="text-align: right;">Net Paid</th>
1399
+ </tr>
1400
+ <tr *ngFor="let w of whtData">
1401
+ <td style="padding: 6px 0;">{{ w.supplierName || w.supplier }}</td>
1402
+ <td>{{ (w.paymentDate || w.date) | date:'mediumDate' }}</td>
1403
+ <td style="text-align: right;">{{ (w.grossAmount ?? w.gross) | number:'1.2-2' }}</td>
1404
+ <td style="text-align: right;">{{ (w.whtAmount ?? w.wht) | number:'1.2-2' }}</td>
1405
+ <td style="text-align: right;">{{ (w.netAmount ?? w.net) | number:'1.2-2' }}</td>
1406
+ </tr>
1407
+ </table>
1408
+ <p *ngIf="!whtData?.length" style="color: #757575;">No withheld supplier payments in this period.</p>
1409
+ </mat-card>
1410
+ </mat-tab>
1411
+ </mat-tab-group>
1412
+ `,
1413
+ standalone: false
1414
+ }]
1415
+ }] });
1416
+
1417
+ // Fiscal Periods page (C2) — period create/close/reopen/lock management
1418
+ class FiscalPeriodsComponent {
1419
+ constructor() {
1420
+ this.accountingService = inject(AccountingService);
1421
+ this.pageConfig = {
1422
+ title: 'Fiscal Periods',
1423
+ tableConfig: this.accountingService.fiscalPeriodsTableConfig
1424
+ };
1425
+ }
1426
+ ngOnInit() {
1427
+ }
1428
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FiscalPeriodsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1429
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: FiscalPeriodsComponent, isStandalone: false, selector: "spa-fiscal-periods", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
1430
+ }
1431
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: FiscalPeriodsComponent, decorators: [{
1432
+ type: Component,
1433
+ args: [{
1434
+ selector: 'spa-fiscal-periods',
1435
+ template: '<spa-page [config]="pageConfig"></spa-page>',
1436
+ standalone: false
1437
+ }]
1438
+ }] });
1439
+
1440
+ // Customer Receipts page (C3) — receipts list with new-receipt and allocate actions
1441
+ class ReceiptsComponent {
1442
+ constructor() {
1443
+ this.accountingService = inject(AccountingService);
1444
+ this.dataService = inject(DataServiceLib); // Added: supplies dialogPanelClass so this dialog honours appConfig.dialogStyle
1445
+ this.dialog = inject(MatDialog);
1446
+ this.pageConfig = {
1447
+ title: 'Customer Receipts',
1448
+ tableConfig: this.accountingService.customerReceiptsTableConfig
1449
+ };
1450
+ // Changed: the allocate wiring is GONE from here. AccountingService now owns receiptAllocateButton.onClick,
1451
+ // so the button works in every consumer instead of only on this page — this constructor was the single
1452
+ // reason shift's and grip's Day Book allocate lanes could not use it. Re-assigning it here would just
1453
+ // narrow it back to this page's dialog, and the service's handler is the same dialog with the same data.
1454
+ // Changed: the PDF wiring is GONE from here too — AccountingService now owns receiptPdfButton.onClick via
1455
+ // downloadReceiptPdf, so the button works in every consumer. That was the last library backlog row of the
1456
+ // pair. This constructor is now empty of wiring and exists only to satisfy the class.
1457
+ }
1458
+ ngOnInit() {
1459
+ }
1460
+ // Changed: this is now the NEW RECEIPT path only — allocation moved to AccountingService.openReceiptAllocation.
1461
+ // The optional row parameter is kept so the signature stays compatible for any consumer holding a reference.
1462
+ openDialog(row) {
1463
+ this.dialog.open(ReceiptDialogComponent, { width: '860px', maxWidth: '95vw', data: row ?? null, panelClass: this.dataService?.dialogPanelClass ?? [] }) // Changed: was missing panelClass, so this dialog never picked up the modern surface
1464
+ .afterClosed().subscribe(saved => { if (saved)
1465
+ this.page?.tableReload.next(true); });
1466
+ }
1467
+ // Changed: the blob download itself moved to AccountingService.downloadReceiptPdf. This method is kept as a
1468
+ // thin delegate so the signature stays available to any consumer holding a reference to this component,
1469
+ // exactly as openDialog's optional row parameter was kept in 3a681d6.
1470
+ downloadPdf(receipt) {
1471
+ this.accountingService.downloadReceiptPdf(receipt);
1472
+ }
1473
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ReceiptsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1474
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ReceiptsComponent, isStandalone: false, selector: "spa-receipts", viewQueries: [{ propertyName: "page", first: true, predicate: PageComponent, descendants: true }], ngImport: i0, template: `
1475
+ <div style="display: flex; justify-content: flex-end;">
1476
+ <button mat-stroked-button (click)="openDialog()">
1477
+ <mat-icon>receipt</mat-icon> New Receipt
1478
+ </button>
1479
+ </div>
1480
+ <spa-page [config]="pageConfig"></spa-page>
1481
+ `, isInline: true, dependencies: [{ kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
1482
+ }
1483
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ReceiptsComponent, decorators: [{
1484
+ type: Component,
1485
+ args: [{
1486
+ selector: 'spa-receipts',
1487
+ template: `
1488
+ <div style="display: flex; justify-content: flex-end;">
1489
+ <button mat-stroked-button (click)="openDialog()">
1490
+ <mat-icon>receipt</mat-icon> New Receipt
1491
+ </button>
1492
+ </div>
1493
+ <spa-page [config]="pageConfig"></spa-page>
1494
+ `,
1495
+ standalone: false
1496
+ }]
1497
+ }], ctorParameters: () => [], propDecorators: { page: [{
1498
+ type: ViewChild,
1499
+ args: [PageComponent]
1500
+ }] } });
1501
+
1502
+ // Bank statement import dialog (C4) — paste CSV lines (date, description, reference, amount) for a GL cash/bank account
1503
+ class StatementImportDialogComponent {
1504
+ constructor() {
1505
+ this.dialogRef = inject((MatDialogRef));
1506
+ this.httpService = inject(HttpService);
1507
+ this.http = inject(HttpClient);
1508
+ this.messageService = inject(MessageService);
1509
+ this.accountID = null;
1510
+ this.statementFrom = '';
1511
+ this.statementTo = '';
1512
+ this.closingBalance = 0;
1513
+ this.csv = '';
1514
+ this.parseError = '';
1515
+ this.accounts = [];
1516
+ // Changed: materialized — parsedLines/netMovement were getters read 4x per change-detection cycle
1517
+ // (twice in the template, plus netMovement and canSave), each call re-parsing the whole CSV, allocating a
1518
+ // new array AND writing this.parseError from inside a getter (a side effect during change detection).
1519
+ this.parsedLines = [];
1520
+ this.netMovement = 0;
1521
+ }
1522
+ ngOnInit() {
1523
+ this.http.get(`${this.httpService.apiUrl}accounts/list/x`)
1524
+ .subscribe(res => this.accounts = res?.data ?? []);
1525
+ const now = new Date();
1526
+ this.statementFrom = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().substring(0, 10);
1527
+ this.statementTo = new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString().substring(0, 10);
1528
+ }
1529
+ // Added: recomputed only when the CSV text actually changes (bound via (ngModelChange))
1530
+ parseCsv() {
1531
+ this.parseError = '';
1532
+ const lines = [];
1533
+ for (const raw of this.csv.split('\n').map(l => l.trim()).filter(l => l)) {
1534
+ const parts = raw.split(',').map(p => p.trim());
1535
+ if (parts.length < 2) {
1536
+ this.parseError = `Cannot parse: "${raw}"`;
1537
+ this.parsedLines = [];
1538
+ this.netMovement = 0;
1539
+ return;
1540
+ }
1541
+ const amount = parseFloat(parts[parts.length - 1]);
1542
+ if (isNaN(amount)) {
1543
+ this.parseError = `Invalid amount in: "${raw}"`;
1544
+ this.parsedLines = [];
1545
+ this.netMovement = 0;
1546
+ return;
1547
+ }
1548
+ lines.push({ date: parts[0], description: parts[1] ?? '', reference: parts.length > 3 ? parts[2] : '', amount });
1549
+ }
1550
+ this.parsedLines = lines;
1551
+ this.netMovement = lines.reduce((sum, line) => sum + line.amount, 0);
1552
+ }
1553
+ get canSave() { return !!this.accountID && this.parsedLines.length > 0 && !this.parseError; } // Returns a primitive — safe to leave as a getter
1554
+ save() {
1555
+ const dto = {
1556
+ accountID: this.accountID,
1557
+ statementFrom: this.statementFrom,
1558
+ statementTo: this.statementTo,
1559
+ closingBalance: this.closingBalance,
1560
+ lines: this.parsedLines
1561
+ };
1562
+ this.http.post(`${this.httpService.apiUrl}bankstatementimports/dto?action=create`, dto)
1563
+ .subscribe(res => {
1564
+ this.messageService.toast(res?.message ?? (res?.success ? 'Statement imported' : 'Import failed'));
1565
+ if (res?.success)
1566
+ this.dialogRef.close(true);
1567
+ });
1568
+ }
1569
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: StatementImportDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1570
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: StatementImportDialogComponent, isStandalone: false, selector: "spa-statement-import-dialog", ngImport: i0, template: `
1571
+ <h2 mat-dialog-title>Import Bank Statement</h2>
1572
+ <mat-dialog-content>
1573
+ <div style="display: flex; gap: 16px; flex-wrap: wrap;">
1574
+ <mat-form-field style="min-width: 220px;">
1575
+ <mat-label>Account</mat-label>
1576
+ <mat-select [(ngModel)]="accountID">
1577
+ <mat-option *ngFor="let option of accounts" [value]="option.value">{{ option.name }}</mat-option>
1578
+ </mat-select>
1579
+ </mat-form-field>
1580
+ <mat-form-field>
1581
+ <mat-label>From</mat-label>
1582
+ <input matInput type="date" [(ngModel)]="statementFrom">
1583
+ </mat-form-field>
1584
+ <mat-form-field>
1585
+ <mat-label>To</mat-label>
1586
+ <input matInput type="date" [(ngModel)]="statementTo">
1587
+ </mat-form-field>
1588
+ <mat-form-field>
1589
+ <mat-label>Closing Balance</mat-label>
1590
+ <input matInput type="number" [(ngModel)]="closingBalance">
1591
+ </mat-form-field>
1592
+ </div>
1593
+ <mat-form-field style="width: 100%;">
1594
+ <mat-label>Statement lines (CSV: date, description, reference, amount — money out negative)</mat-label>
1595
+ <textarea matInput rows="10" [(ngModel)]="csv" (ngModelChange)="parseCsv()" placeholder="2026-07-01, Deposit ABC, REF001, 1500.00&#10;2026-07-03, Bank charges, , -25.50"></textarea>
1596
+ </mat-form-field>
1597
+ <p *ngIf="parseError" style="color: red;"><em>{{ parseError }}</em></p>
1598
+ <p *ngIf="parsedLines.length"><em>{{ parsedLines.length }} line(s) parsed, net movement {{ netMovement | number:'1.2-2' }}</em></p>
1599
+ </mat-dialog-content>
1600
+ <mat-dialog-actions align="end">
1601
+ <button mat-button (click)="dialogRef.close(false)">Cancel</button>
1602
+ <button mat-flat-button color="primary" (click)="save()" [disabled]="!canSave">Import</button>
1603
+ </mat-dialog-actions>
1604
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: i4$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i3$3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i7$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }] }); }
1605
+ }
1606
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: StatementImportDialogComponent, decorators: [{
1607
+ type: Component,
1608
+ args: [{
1609
+ selector: 'spa-statement-import-dialog',
1610
+ template: `
1611
+ <h2 mat-dialog-title>Import Bank Statement</h2>
1612
+ <mat-dialog-content>
1613
+ <div style="display: flex; gap: 16px; flex-wrap: wrap;">
1614
+ <mat-form-field style="min-width: 220px;">
1615
+ <mat-label>Account</mat-label>
1616
+ <mat-select [(ngModel)]="accountID">
1617
+ <mat-option *ngFor="let option of accounts" [value]="option.value">{{ option.name }}</mat-option>
1618
+ </mat-select>
1619
+ </mat-form-field>
1620
+ <mat-form-field>
1621
+ <mat-label>From</mat-label>
1622
+ <input matInput type="date" [(ngModel)]="statementFrom">
1623
+ </mat-form-field>
1624
+ <mat-form-field>
1625
+ <mat-label>To</mat-label>
1626
+ <input matInput type="date" [(ngModel)]="statementTo">
1627
+ </mat-form-field>
1628
+ <mat-form-field>
1629
+ <mat-label>Closing Balance</mat-label>
1630
+ <input matInput type="number" [(ngModel)]="closingBalance">
1631
+ </mat-form-field>
1632
+ </div>
1633
+ <mat-form-field style="width: 100%;">
1634
+ <mat-label>Statement lines (CSV: date, description, reference, amount — money out negative)</mat-label>
1635
+ <textarea matInput rows="10" [(ngModel)]="csv" (ngModelChange)="parseCsv()" placeholder="2026-07-01, Deposit ABC, REF001, 1500.00&#10;2026-07-03, Bank charges, , -25.50"></textarea>
1636
+ </mat-form-field>
1637
+ <p *ngIf="parseError" style="color: red;"><em>{{ parseError }}</em></p>
1638
+ <p *ngIf="parsedLines.length"><em>{{ parsedLines.length }} line(s) parsed, net movement {{ netMovement | number:'1.2-2' }}</em></p>
1639
+ </mat-dialog-content>
1640
+ <mat-dialog-actions align="end">
1641
+ <button mat-button (click)="dialogRef.close(false)">Cancel</button>
1642
+ <button mat-flat-button color="primary" (click)="save()" [disabled]="!canSave">Import</button>
1643
+ </mat-dialog-actions>
1644
+ `,
1645
+ standalone: false
1646
+ }]
1647
+ }] });
1648
+
1649
+ // Bank reconciliation match screen (C4) — statement lines vs unreconciled book entries; auto/manual match, unmatch, complete
1650
+ class ReconcileDialogComponent {
1651
+ constructor() {
1652
+ this.dialogRef = inject((MatDialogRef));
1653
+ this.data = inject(MAT_DIALOG_DATA);
1654
+ this.httpService = inject(HttpService);
1655
+ this.http = inject(HttpClient);
1656
+ this.messageService = inject(MessageService);
1657
+ this.rec = null;
1658
+ this.selectedLine = null;
1659
+ this.selectedEntry = null;
1660
+ this.changed = false;
1661
+ }
1662
+ get difference() { return (this.rec?.bookBalance ?? 0) - (this.rec?.statementBalance ?? 0); }
1663
+ ngOnInit() { this.load(); }
1664
+ load() {
1665
+ this.selectedLine = null;
1666
+ this.selectedEntry = null;
1667
+ this.http.get(`${this.httpService.apiUrl}bankstatementimports/reconciliation/${this.data.bankStatementImportID}`)
1668
+ .subscribe(res => this.rec = res?.data ?? null);
1669
+ }
1670
+ autoMatch() {
1671
+ this.http.post(`${this.httpService.apiUrl}bankstatementimports/dto?action=auto-match`, { bankStatementImportID: this.data.bankStatementImportID })
1672
+ .subscribe(res => { this.messageService.toast(res?.message ?? ''); this.changed = true; this.load(); });
1673
+ }
1674
+ match() {
1675
+ this.http.post(`${this.httpService.apiUrl}bankstatementimports/match?action=match`, { bankStatementLineID: this.selectedLine.bankStatementLineID, transactionEntryID: this.selectedEntry.transactionEntryID })
1676
+ .subscribe(res => { this.messageService.toast(res?.message ?? ''); this.changed = true; this.load(); });
1677
+ }
1678
+ unmatch(line) {
1679
+ this.http.post(`${this.httpService.apiUrl}bankstatementimports/match?action=unmatch`, { bankStatementLineID: line.bankStatementLineID, transactionEntryID: 0 })
1680
+ .subscribe(res => { this.messageService.toast(res?.message ?? ''); this.changed = true; this.load(); });
1681
+ }
1682
+ complete() {
1683
+ this.http.post(`${this.httpService.apiUrl}bankstatementimports/dto?action=complete`, { bankStatementImportID: this.data.bankStatementImportID })
1684
+ .subscribe(res => {
1685
+ this.messageService.toast(res?.message ?? '');
1686
+ if (res?.success)
1687
+ this.dialogRef.close(true);
1688
+ });
1689
+ }
1690
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ReconcileDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1691
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ReconcileDialogComponent, isStandalone: false, selector: "spa-reconcile-dialog", ngImport: i0, template: `
1692
+ <h2 mat-dialog-title>Reconcile: {{ rec?.name }}</h2>
1693
+ <mat-dialog-content>
1694
+ <div *ngIf="rec" style="display: flex; gap: 32px; flex-wrap: wrap; margin-bottom: 12px;">
1695
+ <div><small>Book Balance</small><br><b>{{ rec.bookBalance | number:'1.2-2' }}</b></div>
1696
+ <div><small>Statement Balance</small><br><b>{{ rec.statementBalance | number:'1.2-2' }}</b></div>
1697
+ <div><small>Difference</small><br><b [style.color]="difference ? 'red' : 'green'">{{ difference | number:'1.2-2' }}</b></div>
1698
+ <div><small>Matched</small><br><b>{{ rec.matchedCount }} / {{ rec.lineCount }}</b></div>
1699
+ </div>
1700
+
1701
+ <div style="display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap;" *ngIf="rec">
1702
+ <div style="flex: 1; min-width: 380px;">
1703
+ <h4>Statement Lines</h4>
1704
+ <table style="width: 100%; border-collapse: collapse;">
1705
+ <tr style="text-align: left; border-bottom: 1px solid #ccc;">
1706
+ <th style="padding: 4px;"></th><th style="padding: 4px;">Date</th><th style="padding: 4px;">Description</th>
1707
+ <th style="padding: 4px; text-align: right;">Amount</th><th style="padding: 4px;">Status</th>
1708
+ </tr>
1709
+ <tr *ngFor="let line of rec.lines" style="border-bottom: 1px solid #eee;"
1710
+ [style.background]="selectedLine === line ? '#E3F2FD' : line.isMatched ? '#F1F8E9' : ''">
1711
+ <td style="padding: 4px;">
1712
+ <mat-checkbox *ngIf="!line.isMatched" [checked]="selectedLine === line" (change)="selectedLine = $event.checked ? line : null"></mat-checkbox>
1713
+ </td>
1714
+ <td style="padding: 4px;">{{ line.date }}</td>
1715
+ <td style="padding: 4px;">{{ line.description }}</td>
1716
+ <td style="padding: 4px; text-align: right;">{{ line.amount | number:'1.2-2' }}</td>
1717
+ <td style="padding: 4px;">
1718
+ <span *ngIf="line.isMatched" title="{{ line.matchedDescription }}">✔</span>
1719
+ <button mat-button *ngIf="line.isMatched" (click)="unmatch(line)">Unmatch</button>
1720
+ </td>
1721
+ </tr>
1722
+ </table>
1723
+ </div>
1724
+
1725
+ <div style="flex: 1; min-width: 380px;">
1726
+ <h4>Unreconciled Book Entries</h4>
1727
+ <table style="width: 100%; border-collapse: collapse;">
1728
+ <tr style="text-align: left; border-bottom: 1px solid #ccc;">
1729
+ <th style="padding: 4px;"></th><th style="padding: 4px;">Date</th><th style="padding: 4px;">Description</th>
1730
+ <th style="padding: 4px; text-align: right;">Amount</th>
1731
+ </tr>
1732
+ <tr *ngFor="let entry of rec.unreconciledEntries" style="border-bottom: 1px solid #eee;"
1733
+ [style.background]="selectedEntry === entry ? '#E3F2FD' : ''">
1734
+ <td style="padding: 4px;">
1735
+ <mat-checkbox [checked]="selectedEntry === entry" (change)="selectedEntry = $event.checked ? entry : null"></mat-checkbox>
1736
+ </td>
1737
+ <td style="padding: 4px;">{{ entry.date }}</td>
1738
+ <td style="padding: 4px;">{{ entry.description }}</td>
1739
+ <td style="padding: 4px; text-align: right;">{{ entry.amount | number:'1.2-2' }}</td>
1740
+ </tr>
1741
+ </table>
1742
+ </div>
1743
+ </div>
1744
+ </mat-dialog-content>
1745
+ <mat-dialog-actions align="end">
1746
+ <button mat-button (click)="dialogRef.close(changed)">Close</button>
1747
+ <button mat-stroked-button (click)="autoMatch()">Auto Match</button>
1748
+ <button mat-stroked-button (click)="match()" [disabled]="!selectedLine || !selectedEntry">Match Selected</button>
1749
+ <button mat-flat-button color="primary" (click)="complete()" [disabled]="!rec || rec.matchedCount != rec.lineCount">Complete Reconciliation</button>
1750
+ </mat-dialog-actions>
1751
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4$2.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: i4$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }] }); }
1752
+ }
1753
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ReconcileDialogComponent, decorators: [{
1754
+ type: Component,
1755
+ args: [{
1756
+ selector: 'spa-reconcile-dialog',
1757
+ template: `
1758
+ <h2 mat-dialog-title>Reconcile: {{ rec?.name }}</h2>
1759
+ <mat-dialog-content>
1760
+ <div *ngIf="rec" style="display: flex; gap: 32px; flex-wrap: wrap; margin-bottom: 12px;">
1761
+ <div><small>Book Balance</small><br><b>{{ rec.bookBalance | number:'1.2-2' }}</b></div>
1762
+ <div><small>Statement Balance</small><br><b>{{ rec.statementBalance | number:'1.2-2' }}</b></div>
1763
+ <div><small>Difference</small><br><b [style.color]="difference ? 'red' : 'green'">{{ difference | number:'1.2-2' }}</b></div>
1764
+ <div><small>Matched</small><br><b>{{ rec.matchedCount }} / {{ rec.lineCount }}</b></div>
1765
+ </div>
1766
+
1767
+ <div style="display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap;" *ngIf="rec">
1768
+ <div style="flex: 1; min-width: 380px;">
1769
+ <h4>Statement Lines</h4>
1770
+ <table style="width: 100%; border-collapse: collapse;">
1771
+ <tr style="text-align: left; border-bottom: 1px solid #ccc;">
1772
+ <th style="padding: 4px;"></th><th style="padding: 4px;">Date</th><th style="padding: 4px;">Description</th>
1773
+ <th style="padding: 4px; text-align: right;">Amount</th><th style="padding: 4px;">Status</th>
1774
+ </tr>
1775
+ <tr *ngFor="let line of rec.lines" style="border-bottom: 1px solid #eee;"
1776
+ [style.background]="selectedLine === line ? '#E3F2FD' : line.isMatched ? '#F1F8E9' : ''">
1777
+ <td style="padding: 4px;">
1778
+ <mat-checkbox *ngIf="!line.isMatched" [checked]="selectedLine === line" (change)="selectedLine = $event.checked ? line : null"></mat-checkbox>
1779
+ </td>
1780
+ <td style="padding: 4px;">{{ line.date }}</td>
1781
+ <td style="padding: 4px;">{{ line.description }}</td>
1782
+ <td style="padding: 4px; text-align: right;">{{ line.amount | number:'1.2-2' }}</td>
1783
+ <td style="padding: 4px;">
1784
+ <span *ngIf="line.isMatched" title="{{ line.matchedDescription }}">✔</span>
1785
+ <button mat-button *ngIf="line.isMatched" (click)="unmatch(line)">Unmatch</button>
1786
+ </td>
1787
+ </tr>
1788
+ </table>
1789
+ </div>
1790
+
1791
+ <div style="flex: 1; min-width: 380px;">
1792
+ <h4>Unreconciled Book Entries</h4>
1793
+ <table style="width: 100%; border-collapse: collapse;">
1794
+ <tr style="text-align: left; border-bottom: 1px solid #ccc;">
1795
+ <th style="padding: 4px;"></th><th style="padding: 4px;">Date</th><th style="padding: 4px;">Description</th>
1796
+ <th style="padding: 4px; text-align: right;">Amount</th>
1797
+ </tr>
1798
+ <tr *ngFor="let entry of rec.unreconciledEntries" style="border-bottom: 1px solid #eee;"
1799
+ [style.background]="selectedEntry === entry ? '#E3F2FD' : ''">
1800
+ <td style="padding: 4px;">
1801
+ <mat-checkbox [checked]="selectedEntry === entry" (change)="selectedEntry = $event.checked ? entry : null"></mat-checkbox>
1802
+ </td>
1803
+ <td style="padding: 4px;">{{ entry.date }}</td>
1804
+ <td style="padding: 4px;">{{ entry.description }}</td>
1805
+ <td style="padding: 4px; text-align: right;">{{ entry.amount | number:'1.2-2' }}</td>
1806
+ </tr>
1807
+ </table>
1808
+ </div>
1809
+ </div>
1810
+ </mat-dialog-content>
1811
+ <mat-dialog-actions align="end">
1812
+ <button mat-button (click)="dialogRef.close(changed)">Close</button>
1813
+ <button mat-stroked-button (click)="autoMatch()">Auto Match</button>
1814
+ <button mat-stroked-button (click)="match()" [disabled]="!selectedLine || !selectedEntry">Match Selected</button>
1815
+ <button mat-flat-button color="primary" (click)="complete()" [disabled]="!rec || rec.matchedCount != rec.lineCount">Complete Reconciliation</button>
1816
+ </mat-dialog-actions>
1817
+ `,
1818
+ standalone: false
1819
+ }]
1820
+ }] });
1821
+
1822
+ // Bank Reconciliation page (C4) — statement imports list + import/reconcile dialogs
1823
+ class BankReconciliationComponent {
1824
+ constructor() {
1825
+ this.accountingService = inject(AccountingService);
1826
+ this.dialog = inject(MatDialog);
1827
+ this.dataService = inject(DataServiceLib); // Added: supplies dialogPanelClass so these dialogs honour appConfig.dialogStyle
1828
+ this.pageConfig = {
1829
+ title: 'Bank Reconciliation',
1830
+ tableConfig: this.accountingService.bankStatementImportsTableConfig
1831
+ };
1832
+ // Reconcile row button opens the match screen (configs live in the service, dialogs in the component)
1833
+ this.accountingService.bankReconcileButton.onClick = (row) => this.dialog.open(ReconcileDialogComponent, { width: '1100px', maxWidth: '95vw', data: row, panelClass: this.dataService?.dialogPanelClass ?? [] }) // Changed: was missing panelClass, so this dialog never picked up the modern surface
1834
+ .afterClosed().subscribe(changed => { if (changed)
1835
+ this.page?.tableReload.next(true); });
1836
+ }
1837
+ ngOnInit() {
1838
+ }
1839
+ newImport() {
1840
+ this.dialog.open(StatementImportDialogComponent, { width: '860px', maxWidth: '95vw', panelClass: this.dataService?.dialogPanelClass ?? [] }) // Changed: was missing panelClass, so this dialog never picked up the modern surface
1841
+ .afterClosed().subscribe(saved => { if (saved)
1842
+ this.page?.tableReload.next(true); });
1843
+ }
1844
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: BankReconciliationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1845
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: BankReconciliationComponent, isStandalone: false, selector: "spa-bank-reconciliation", viewQueries: [{ propertyName: "page", first: true, predicate: PageComponent, descendants: true }], ngImport: i0, template: `
1846
+ <div style="display: flex; justify-content: flex-end;">
1847
+ <button mat-stroked-button (click)="newImport()">
1848
+ <mat-icon>upload_file</mat-icon> Import Statement
1849
+ </button>
1850
+ </div>
1851
+ <spa-page [config]="pageConfig"></spa-page>
1852
+ `, isInline: true, dependencies: [{ kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
1853
+ }
1854
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: BankReconciliationComponent, decorators: [{
1855
+ type: Component,
1856
+ args: [{
1857
+ selector: 'spa-bank-reconciliation',
1858
+ template: `
1859
+ <div style="display: flex; justify-content: flex-end;">
1860
+ <button mat-stroked-button (click)="newImport()">
1861
+ <mat-icon>upload_file</mat-icon> Import Statement
1862
+ </button>
1863
+ </div>
1864
+ <spa-page [config]="pageConfig"></spa-page>
1865
+ `,
1866
+ standalone: false
1867
+ }]
1868
+ }], ctorParameters: () => [], propDecorators: { page: [{
1869
+ type: ViewChild,
1870
+ args: [PageComponent]
1871
+ }] } });
1872
+
1873
+ // Moved (N11) from grip-spa into tin-spa, under Accounting (owner directive 2026-08-17). The design notes
1874
+ // from the Grip original hold: the two tables are one story, so an action on the arrangements table
1875
+ // refreshes the transfers list beneath it via the service's shared Subject.
1876
+ class TenantTransfersComponent {
1877
+ constructor(tenantTransferService) {
1878
+ this.tenantTransferService = tenantTransferService;
1879
+ }
1880
+ // Any successful action on an arrangement can change the transfers list — sending money adds a row, and
1881
+ // revoking is worth re-reading the list after too. Refreshing unconditionally is correct and cheap.
1882
+ arrangementActioned() {
1883
+ this.tenantTransferService.transfersReload.next(true);
1884
+ }
1885
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TenantTransfersComponent, deps: [{ token: TenantTransferService }], target: i0.ɵɵFactoryTarget.Component }); }
1886
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TenantTransfersComponent, isStandalone: false, selector: "spa-tenant-transfers", ngImport: i0, template: "<!-- Organisation Transfers \u2014 the handshake and the money that flows over it. Two sections in one page\n deliberately: an arrangement is meaningless without the transfers it permits, and a transfer is\n unreadable without the arrangement that authorised it. -->\n<h4>Organisation Transfers</h4>\n<hr>\n\n<div class=\"mt-3 tenant-transfers\" style=\"font-size: 14px;\">\n\n <p class=\"tt-lead\">\n Money can only move between two organisations that have <strong>agreed an arrangement</strong> first.\n One side proposes it, the other accepts it, and only then can the proposing side send.\n </p>\n\n <!-- (actionSuccess) \u2014 Send Money is a button on THIS table, so without this wire the Transfers list below\n still shows the previous rows after money has already moved. -->\n <spa-table [config]=\"tenantTransferService.linksTableConfig\" (actionSuccess)=\"arrangementActioned()\"></spa-table>\n\n <div class=\"tt-gap\"></div>\n\n <spa-table [config]=\"tenantTransferService.transfersTableConfig\" [reload]=\"tenantTransferService.transfersReload\"></spa-table>\n\n</div>\n", styles: [".tenant-transfers{max-width:100%;min-width:0}.tenant-transfers .tt-lead{max-width:74ch;margin:0 0 20px;line-height:1.55;opacity:.85;overflow-wrap:break-word}.tenant-transfers spa-table{display:block;max-width:100%;min-width:0;overflow-x:auto}.tenant-transfers .tt-gap{height:28px}\n"], dependencies: [{ kind: "component", type: TableComponent, selector: "spa-table", inputs: ["data", "tileData", "config", "localMode", "parentDetails", "reload", "activeTab", "inTab", "nestingLevel"], outputs: ["dataLoad", "totalChange", "actionSuccess", "refreshClick", "searchClick", "createClick", "actionClick", "inputChange", "actionResponse"] }] }); }
1887
+ }
1888
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TenantTransfersComponent, decorators: [{
1889
+ type: Component,
1890
+ args: [{ selector: 'spa-tenant-transfers', standalone: false, template: "<!-- Organisation Transfers \u2014 the handshake and the money that flows over it. Two sections in one page\n deliberately: an arrangement is meaningless without the transfers it permits, and a transfer is\n unreadable without the arrangement that authorised it. -->\n<h4>Organisation Transfers</h4>\n<hr>\n\n<div class=\"mt-3 tenant-transfers\" style=\"font-size: 14px;\">\n\n <p class=\"tt-lead\">\n Money can only move between two organisations that have <strong>agreed an arrangement</strong> first.\n One side proposes it, the other accepts it, and only then can the proposing side send.\n </p>\n\n <!-- (actionSuccess) \u2014 Send Money is a button on THIS table, so without this wire the Transfers list below\n still shows the previous rows after money has already moved. -->\n <spa-table [config]=\"tenantTransferService.linksTableConfig\" (actionSuccess)=\"arrangementActioned()\"></spa-table>\n\n <div class=\"tt-gap\"></div>\n\n <spa-table [config]=\"tenantTransferService.transfersTableConfig\" [reload]=\"tenantTransferService.transfersReload\"></spa-table>\n\n</div>\n", styles: [".tenant-transfers{max-width:100%;min-width:0}.tenant-transfers .tt-lead{max-width:74ch;margin:0 0 20px;line-height:1.55;opacity:.85;overflow-wrap:break-word}.tenant-transfers spa-table{display:block;max-width:100%;min-width:0;overflow-x:auto}.tenant-transfers .tt-gap{height:28px}\n"] }]
1891
+ }], ctorParameters: () => [{ type: TenantTransferService }] });
1892
+
1893
+ // New revenue schedule dialog (C7) — defer a submitted invoice's net revenue over a date range
1894
+ class RevenueScheduleDialogComponent {
1895
+ constructor() {
1896
+ this.dialogRef = inject((MatDialogRef));
1897
+ this.httpService = inject(HttpService);
1898
+ this.http = inject(HttpClient);
1899
+ this.messageService = inject(MessageService);
1900
+ this.invoiceID = null;
1901
+ this.startDate = '';
1902
+ this.endDate = '';
1903
+ this.invoices = [];
1904
+ }
1905
+ ngOnInit() {
1906
+ this.http.get(`${this.httpService.apiUrl}revenueschedules/eligible-invoices/x`)
1907
+ .subscribe(res => this.invoices = res?.data ?? []);
1908
+ }
1909
+ save() {
1910
+ const dto = { invoiceID: this.invoiceID, startDate: this.startDate, endDate: this.endDate };
1911
+ this.http.post(`${this.httpService.apiUrl}revenueschedules/dto?action=create`, dto)
1912
+ .subscribe(res => {
1913
+ this.messageService.toast(res?.message ?? (res?.success ? 'Schedule created' : 'Failed'));
1914
+ if (res?.success)
1915
+ this.dialogRef.close(true);
1916
+ });
1917
+ }
1918
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RevenueScheduleDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1919
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: RevenueScheduleDialogComponent, isStandalone: false, selector: "spa-revenue-schedule-dialog", ngImport: i0, template: `
1920
+ <h2 mat-dialog-title>Defer Invoice Revenue</h2>
1921
+ <mat-dialog-content>
1922
+ <div style="display: flex; gap: 16px; flex-wrap: wrap;">
1923
+ <mat-form-field style="min-width: 280px;">
1924
+ <mat-label>Invoice</mat-label>
1925
+ <mat-select [(ngModel)]="invoiceID">
1926
+ <mat-option *ngFor="let option of invoices" [value]="option.value">{{ option.name }}</mat-option>
1927
+ </mat-select>
1928
+ </mat-form-field>
1929
+ <mat-form-field>
1930
+ <mat-label>Service Period Start</mat-label>
1931
+ <input matInput type="date" [(ngModel)]="startDate">
1932
+ </mat-form-field>
1933
+ <mat-form-field>
1934
+ <mat-label>Service Period End</mat-label>
1935
+ <input matInput type="date" [(ngModel)]="endDate">
1936
+ </mat-form-field>
1937
+ </div>
1938
+ <p><em>The invoice's net revenue (excl. VAT) moves to Deferred Revenue and is recognized monthly, pro-rata by days, as each period ends.</em></p>
1939
+ </mat-dialog-content>
1940
+ <mat-dialog-actions align="end">
1941
+ <button mat-button (click)="dialogRef.close(false)">Cancel</button>
1942
+ <button mat-flat-button color="primary" (click)="save()" [disabled]="!invoiceID || !startDate || !endDate || endDate <= startDate">Defer Revenue</button>
1943
+ </mat-dialog-actions>
1944
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: i4$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i3$3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i7$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] }); }
1945
+ }
1946
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RevenueScheduleDialogComponent, decorators: [{
1947
+ type: Component,
1948
+ args: [{
1949
+ selector: 'spa-revenue-schedule-dialog',
1950
+ template: `
1951
+ <h2 mat-dialog-title>Defer Invoice Revenue</h2>
1952
+ <mat-dialog-content>
1953
+ <div style="display: flex; gap: 16px; flex-wrap: wrap;">
1954
+ <mat-form-field style="min-width: 280px;">
1955
+ <mat-label>Invoice</mat-label>
1956
+ <mat-select [(ngModel)]="invoiceID">
1957
+ <mat-option *ngFor="let option of invoices" [value]="option.value">{{ option.name }}</mat-option>
1958
+ </mat-select>
1959
+ </mat-form-field>
1960
+ <mat-form-field>
1961
+ <mat-label>Service Period Start</mat-label>
1962
+ <input matInput type="date" [(ngModel)]="startDate">
1963
+ </mat-form-field>
1964
+ <mat-form-field>
1965
+ <mat-label>Service Period End</mat-label>
1966
+ <input matInput type="date" [(ngModel)]="endDate">
1967
+ </mat-form-field>
1968
+ </div>
1969
+ <p><em>The invoice's net revenue (excl. VAT) moves to Deferred Revenue and is recognized monthly, pro-rata by days, as each period ends.</em></p>
1970
+ </mat-dialog-content>
1971
+ <mat-dialog-actions align="end">
1972
+ <button mat-button (click)="dialogRef.close(false)">Cancel</button>
1973
+ <button mat-flat-button color="primary" (click)="save()" [disabled]="!invoiceID || !startDate || !endDate || endDate <= startDate">Defer Revenue</button>
1974
+ </mat-dialog-actions>
1975
+ `,
1976
+ standalone: false
1977
+ }]
1978
+ }] });
1979
+
1980
+ // Revenue Schedules page (C7) — deferred revenue schedules with recognize-due sweep and cancel
1981
+ class RevenueSchedulesComponent {
1982
+ constructor() {
1983
+ this.accountingService = inject(AccountingService);
1984
+ this.dataService = inject(DataServiceLib); // Added: supplies dialogPanelClass so this dialog honours appConfig.dialogStyle
1985
+ this.dialog = inject(MatDialog);
1986
+ this.pageConfig = {
1987
+ title: 'Revenue Schedules',
1988
+ tableConfig: this.accountingService.revenueSchedulesTableConfig
1989
+ };
1990
+ }
1991
+ ngOnInit() {
1992
+ }
1993
+ newSchedule() {
1994
+ this.dialog.open(RevenueScheduleDialogComponent, { width: '760px', maxWidth: '95vw', panelClass: this.dataService?.dialogPanelClass ?? [] }) // Changed: was missing panelClass, so this dialog never picked up the modern surface
1995
+ .afterClosed().subscribe(saved => { if (saved)
1996
+ this.page?.tableReload.next(true); });
1997
+ }
1998
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RevenueSchedulesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1999
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: RevenueSchedulesComponent, isStandalone: false, selector: "spa-revenue-schedules", viewQueries: [{ propertyName: "page", first: true, predicate: PageComponent, descendants: true }], ngImport: i0, template: `
2000
+ <div style="display: flex; justify-content: flex-end;">
2001
+ <button mat-stroked-button (click)="newSchedule()">
2002
+ <mat-icon>schedule_send</mat-icon> Defer Invoice Revenue
2003
+ </button>
2004
+ </div>
2005
+ <spa-page [config]="pageConfig"></spa-page>
2006
+ `, isInline: true, dependencies: [{ kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
2007
+ }
2008
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RevenueSchedulesComponent, decorators: [{
2009
+ type: Component,
2010
+ args: [{
2011
+ selector: 'spa-revenue-schedules',
2012
+ template: `
2013
+ <div style="display: flex; justify-content: flex-end;">
2014
+ <button mat-stroked-button (click)="newSchedule()">
2015
+ <mat-icon>schedule_send</mat-icon> Defer Invoice Revenue
2016
+ </button>
2017
+ </div>
2018
+ <spa-page [config]="pageConfig"></spa-page>
2019
+ `,
2020
+ standalone: false
2021
+ }]
2022
+ }], propDecorators: { page: [{
2023
+ type: ViewChild,
2024
+ args: [PageComponent]
2025
+ }] } });
2026
+
2027
+ // Quotations page (C10) — two-step create pattern with send/accept/reject/convert lifecycle + PDF download (C8)
2028
+ class QuotesComponent {
2029
+ constructor() {
2030
+ this.accountingService = inject(AccountingService);
2031
+ this.httpService = inject(HttpService);
2032
+ this.http = inject(HttpClient);
2033
+ this.pageConfig = {
2034
+ title: 'Quotations',
2035
+ tableConfig: this.accountingService.quotesTableConfig
2036
+ };
2037
+ }
2038
+ ngOnInit() {
2039
+ }
2040
+ // PDF button emits actionClick; download the blob (mirrors invoice PDF flow)
2041
+ actionClicked(event) {
2042
+ if (event.name === 'pdf')
2043
+ this.download(event.data);
2044
+ }
2045
+ download(quote) {
2046
+ this.http.post(`${this.httpService.apiUrl}quotes/pdf?action=x`, quote, { responseType: 'blob' })
2047
+ .subscribe((response) => {
2048
+ const blob = new Blob([response], { type: 'application/pdf' });
2049
+ const url = window.URL.createObjectURL(blob);
2050
+ const link = document.createElement('a');
2051
+ link.href = url;
2052
+ link.download = `Quote_${quote.quoteID}.pdf`;
2053
+ link.click();
2054
+ window.URL.revokeObjectURL(url);
2055
+ });
2056
+ }
2057
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: QuotesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2058
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: QuotesComponent, isStandalone: false, selector: "spa-quotes", ngImport: i0, template: '<spa-page [config]="pageConfig" (actionClick)="actionClicked($event)"></spa-page>', isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
2059
+ }
2060
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: QuotesComponent, decorators: [{
2061
+ type: Component,
2062
+ args: [{
2063
+ selector: 'spa-quotes',
2064
+ template: '<spa-page [config]="pageConfig" (actionClick)="actionClicked($event)"></spa-page>',
2065
+ standalone: false
2066
+ }]
2067
+ }] });
2068
+
2069
+ // Quotation rate card admin page (quotations-suite) — the quotable services a quotation is priced from, with
2070
+ // their variant rates on a nested tab. Two-step create: the service header is created first, then the details
2071
+ // dialog opens on its Rates tab. Every config lives on AccountingService beside the quotations configs, so
2072
+ // this page is the same nine lines the Quotations page is.
2073
+ class RateCardComponent {
2074
+ constructor() {
2075
+ this.accountingService = inject(AccountingService);
2076
+ this.pageConfig = {
2077
+ title: 'Quotation Rate Card',
2078
+ tableConfig: this.accountingService.quotableServicesTableConfig
2079
+ };
2080
+ }
2081
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RateCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2082
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: RateCardComponent, isStandalone: false, selector: "spa-rate-card", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
2083
+ }
2084
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RateCardComponent, decorators: [{
2085
+ type: Component,
2086
+ args: [{
2087
+ selector: 'spa-rate-card',
2088
+ template: '<spa-page [config]="pageConfig"></spa-page>',
2089
+ standalone: false
2090
+ }]
2091
+ }] });
2092
+
2093
+ // Changed: Exported routes array for use by SpaHomeRoutingModule
2094
+ //
2095
+ // Added (F3): leaf-level data.moduleKey. INERT metadata — nothing in this library reads it and no guard is
2096
+ // attached here; a consumer opts in from its own home route. The path says "accounting" but three separate
2097
+ // ModuleCatalog modules have pages under it, so the leaves that do NOT belong to Accounting say so explicitly
2098
+ // (deepest data wins in the resolver, so a leaf overrides the area). Keys are transcribed from the CapItem
2099
+ // tree in datalib.service.ts — the same source the nav menu already gates on — not invented:
2100
+ // invoicing -> capInvoicing.capSubItems (datalib.service.ts:372) — dashboard, quotes, invoices,
2101
+ // credit notes, receipts, revenue schedules, aging, statements
2102
+ // purchasing -> capSupplierAging (:520) is a child of capPurchasingModule (:456) despite its accounting path
2103
+ // Everything left bare here is a capAccounting sub-item (:267) and correctly inherits the area's "accounting".
2104
+ const ACCOUNTING_ROUTES = [
2105
+ { path: "accounts", component: AccountsComponent },
2106
+ { path: "aggregates", component: AggregatesComponent },
2107
+ { path: "transaction-types", component: TransactionTypesComponent },
2108
+ { path: "transactions", component: TransactionsComponent },
2109
+ { path: "invoices", component: InvoicesComponent, data: { moduleKey: 'invoicing' } }, // Added (F3): Invoicing, not Accounting
2110
+ { path: "aging", component: AgingComponent, data: { moduleKey: 'invoicing', capName: 'cap58' } }, // Added (F3): capAging is a capInvoicing sub-item // Changed: added INERT capName — nothing in this library reads it; cap58 is capAging, whose menu link is exactly "home/accounting/aging" (datalib.service.ts:489,491). A consumer that attaches capabilityGuard() in its OWN route table refuses the deep link; an app that never adds that line is unaffected
2111
+ { path: "supplier-aging", component: SupplierAgingComponent, data: { moduleKey: 'purchasing', capName: 'cap63' } }, // Added (F3): AP Aging belongs to Purchasing // Changed: added INERT capName — cap63 is capSupplierAging, menu link exactly "home/accounting/supplier-aging" (datalib.service.ts:597,599)
2112
+ { path: "statements", component: StatementComponent, data: { moduleKey: 'invoicing' } }, // Added (F3): customer statement is an Invoicing page
2113
+ { path: "reports", component: ReportsComponent },
2114
+ { path: "tax-rates", component: TaxRatesComponent },
2115
+ { path: "standing-orders", component: StandingOrdersComponent },
2116
+ { path: "currencies", component: CurrenciesComponent }, // Changed: Removed Fixed Assets routes — moved to Assets module
2117
+ { path: "budgets", component: BudgetsComponent },
2118
+ { path: "budget-vs-actual", component: BudgetVsActualComponent },
2119
+ { path: "dashboard", component: AccountingDashboardComponent },
2120
+ { path: "invoice-dashboard", component: InvoiceDashboardComponent, data: { moduleKey: 'invoicing' } }, // Added (F3): Invoicing dashboard
2121
+ { path: "credit-notes", component: CreditNotesComponent, data: { moduleKey: 'invoicing' } }, // Changed: Credit notes route (B6) // Added (F3)
2122
+ { path: "vat-return", component: VatReturnComponent }, // Changed: VAT return route (B2)
2123
+ { path: "fiscal-periods", component: FiscalPeriodsComponent }, // Changed: Fiscal periods route (C2)
2124
+ { path: "receipts", component: ReceiptsComponent, data: { moduleKey: 'invoicing' } }, // Changed: Customer receipts route (C3) // Added (F3)
2125
+ { path: "bank-reconciliation", component: BankReconciliationComponent }, // Changed: Bank reconciliation route (C4)
2126
+ { path: "tenant-transfers", component: TenantTransfersComponent }, // Added (N11): Organisation Transfers
2127
+ { path: "revenue-schedules", component: RevenueSchedulesComponent, data: { moduleKey: 'invoicing' } }, // Changed: Deferred revenue route (C7) // Added (F3)
2128
+ { path: "quotes", component: QuotesComponent, data: { moduleKey: 'invoicing' } }, // Changed: Quotations route (C10) // Added (F3)
2129
+ { path: "rate-card", component: RateCardComponent, data: { moduleKey: 'invoicing' } } // Added (quotations-suite): the rate card the guided quote builder prices from — same module key and same cap53 server gate as Quotations
2130
+ ];
2131
+ class AccountingRoutingModule {
2132
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2133
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: AccountingRoutingModule, imports: [i1$2.RouterModule], exports: [RouterModule] }); }
2134
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingRoutingModule, imports: [RouterModule.forChild(ACCOUNTING_ROUTES), RouterModule] }); }
2135
+ }
2136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingRoutingModule, decorators: [{
2137
+ type: NgModule,
2138
+ args: [{
2139
+ imports: [RouterModule.forChild(ACCOUNTING_ROUTES)],
2140
+ exports: [RouterModule]
2141
+ }]
2142
+ }] });
2143
+
2144
+ class AccountingModule {
2145
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2146
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: AccountingModule, declarations: [AccountsComponent,
2147
+ AggregatesComponent,
2148
+ AgingComponent,
2149
+ SupplierAgingComponent,
2150
+ TransactionTypesComponent,
2151
+ TransactionsComponent,
2152
+ InvoicesComponent,
2153
+ StatementComponent,
2154
+ ReportsComponent,
2155
+ BudgetsComponent,
2156
+ BudgetVsActualComponent,
2157
+ AccountingDashboardComponent,
2158
+ InvoiceDashboardComponent,
2159
+ CreditNotesComponent, // Changed: Credit notes page (B6)
2160
+ VatReturnComponent, // Changed: VAT return page (B2)
2161
+ FiscalPeriodsComponent, // Changed: Fiscal periods page (C2)
2162
+ ReceiptsComponent, // Changed: Customer receipts page (C3)
2163
+ BankReconciliationComponent, // Changed: Bank reconciliation page (C4)
2164
+ TenantTransfersComponent, // Added (N11): Organisation Transfers page
2165
+ StatementImportDialogComponent, // Changed: Statement import dialog (C4)
2166
+ ReconcileDialogComponent, // Changed: Reconcile match screen (C4)
2167
+ RevenueSchedulesComponent, // Changed: Deferred revenue page (C7)
2168
+ RevenueScheduleDialogComponent, // Changed: Defer revenue dialog (C7)
2169
+ QuotesComponent, // Changed: Quotations page (C10)
2170
+ RateCardComponent // Added (quotations-suite): quotation rate card admin page
2171
+ ], imports: [AccountingRoutingModule,
2172
+ CommonModule,
2173
+ SpaAdminModule,
2174
+ // Changed: Standalone components must be imported, not declared
2175
+ TaxRatesComponent,
2176
+ StandingOrdersComponent,
2177
+ CurrenciesComponent // Changed: Removed Fixed Assets — moved to Assets module
2178
+ ] }); }
2179
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingModule, imports: [AccountingRoutingModule,
2180
+ CommonModule,
2181
+ SpaAdminModule,
2182
+ // Changed: Standalone components must be imported, not declared
2183
+ TaxRatesComponent,
2184
+ StandingOrdersComponent,
2185
+ CurrenciesComponent // Changed: Removed Fixed Assets — moved to Assets module
2186
+ ] }); }
2187
+ }
2188
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AccountingModule, decorators: [{
2189
+ type: NgModule,
2190
+ args: [{
2191
+ declarations: [
2192
+ AccountsComponent,
2193
+ AggregatesComponent,
2194
+ AgingComponent,
2195
+ SupplierAgingComponent,
2196
+ TransactionTypesComponent,
2197
+ TransactionsComponent,
2198
+ InvoicesComponent,
2199
+ StatementComponent,
2200
+ ReportsComponent,
2201
+ BudgetsComponent,
2202
+ BudgetVsActualComponent,
2203
+ AccountingDashboardComponent,
2204
+ InvoiceDashboardComponent,
2205
+ CreditNotesComponent, // Changed: Credit notes page (B6)
2206
+ VatReturnComponent, // Changed: VAT return page (B2)
2207
+ FiscalPeriodsComponent, // Changed: Fiscal periods page (C2)
2208
+ ReceiptsComponent, // Changed: Customer receipts page (C3)
2209
+ BankReconciliationComponent, // Changed: Bank reconciliation page (C4)
2210
+ TenantTransfersComponent, // Added (N11): Organisation Transfers page
2211
+ StatementImportDialogComponent, // Changed: Statement import dialog (C4)
2212
+ ReconcileDialogComponent, // Changed: Reconcile match screen (C4)
2213
+ RevenueSchedulesComponent, // Changed: Deferred revenue page (C7)
2214
+ RevenueScheduleDialogComponent, // Changed: Defer revenue dialog (C7)
2215
+ QuotesComponent, // Changed: Quotations page (C10)
2216
+ RateCardComponent // Added (quotations-suite): quotation rate card admin page
2217
+ ],
2218
+ imports: [
2219
+ AccountingRoutingModule,
2220
+ CommonModule,
2221
+ SpaAdminModule,
2222
+ // Changed: Standalone components must be imported, not declared
2223
+ TaxRatesComponent,
2224
+ StandingOrdersComponent,
2225
+ CurrenciesComponent // Changed: Removed Fixed Assets — moved to Assets module
2226
+ ]
2227
+ }]
2228
+ }] });
2229
+
2230
+ export { AccountingModule };
2231
+ //# sourceMappingURL=tin-spa-accounting.module-Cg4vWaJE.mjs.map