ofcoop-credit-shared-core 0.1.0-alpha.0
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.
- package/README.md +17 -0
- package/dist/OfcoopCreditCore.d.ts +60 -0
- package/dist/OfcoopCreditCore.js +273 -0
- package/dist/contracts/CreditPolicyContract.d.ts +37 -0
- package/dist/contracts/CreditPolicyContract.js +2 -0
- package/dist/contracts/InstallmentContract.d.ts +39 -0
- package/dist/contracts/InstallmentContract.js +2 -0
- package/dist/contracts/LoanBoardContract.d.ts +24 -0
- package/dist/contracts/LoanBoardContract.js +2 -0
- package/dist/contracts/LoanContract.d.ts +36 -0
- package/dist/contracts/LoanContract.js +2 -0
- package/dist/contracts/LoanDueWindowContract.d.ts +25 -0
- package/dist/contracts/LoanDueWindowContract.js +2 -0
- package/dist/data/applyPendingMigrations.d.ts +2 -0
- package/dist/data/applyPendingMigrations.js +30 -0
- package/dist/data/migrations.d.ts +2 -0
- package/dist/data/migrations.js +20 -0
- package/dist/data/repositories.d.ts +39 -0
- package/dist/data/repositories.js +167 -0
- package/dist/data/schemas.d.ts +8 -0
- package/dist/data/schemas.js +70 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +34 -0
- package/dist/services/CreditOrchestrationService.d.ts +32 -0
- package/dist/services/CreditOrchestrationService.js +27 -0
- package/dist/services/CreditPolicyService.d.ts +4 -0
- package/dist/services/CreditPolicyService.js +14 -0
- package/dist/services/DailyCreditOpsService.d.ts +2 -0
- package/dist/services/DailyCreditOpsService.js +7 -0
- package/dist/services/InstallmentService.d.ts +4 -0
- package/dist/services/InstallmentService.js +6 -0
- package/dist/services/LoanBoardService.d.ts +4 -0
- package/dist/services/LoanBoardService.js +6 -0
- package/dist/services/LoanDueWindowService.d.ts +4 -0
- package/dist/services/LoanDueWindowService.js +6 -0
- package/dist/services/LoanService.d.ts +4 -0
- package/dist/services/LoanService.js +16 -0
- package/dist/services/createDbAdapterOfcoopCreditServices.d.ts +16 -0
- package/dist/services/createDbAdapterOfcoopCreditServices.js +19 -0
- package/dist/services/errors.d.ts +8 -0
- package/dist/services/errors.js +23 -0
- package/dist/services/impl/DbAdapterCreditPolicyService.d.ts +12 -0
- package/dist/services/impl/DbAdapterCreditPolicyService.js +61 -0
- package/dist/services/impl/DbAdapterInstallmentService.d.ts +16 -0
- package/dist/services/impl/DbAdapterInstallmentService.js +129 -0
- package/dist/services/impl/DbAdapterLoanBoardService.d.ts +7 -0
- package/dist/services/impl/DbAdapterLoanBoardService.js +44 -0
- package/dist/services/impl/DbAdapterLoanDueWindowService.d.ts +7 -0
- package/dist/services/impl/DbAdapterLoanDueWindowService.js +48 -0
- package/dist/services/impl/DbAdapterLoanService.d.ts +13 -0
- package/dist/services/impl/DbAdapterLoanService.js +74 -0
- package/dist/services/impl/runtimeSupport.d.ts +18 -0
- package/dist/services/impl/runtimeSupport.js +29 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# ofcoop-credit-shared-core
|
|
2
|
+
|
|
3
|
+
Shared-core domain kredit koperasi (loan, installment, credit policy) di atas primitive `ofcore`.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
- `npm run typecheck`
|
|
7
|
+
- `npm run build`
|
|
8
|
+
- `npm run verify:contract`
|
|
9
|
+
- `npm run verify:surface`
|
|
10
|
+
- `npm run verify:logic`
|
|
11
|
+
- `npm run test`
|
|
12
|
+
- `npm run ci:check`
|
|
13
|
+
|
|
14
|
+
## Publish
|
|
15
|
+
1. `npm run ci:check`
|
|
16
|
+
2. `npm pack --dry-run`
|
|
17
|
+
3. `npm publish --access public`
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { CoreAdapterRegistry, CoreRuntime, CoreRuntimeStartOptions } from 'ofcore';
|
|
2
|
+
import type { CoreRuntimeHooks } from 'ofcore';
|
|
3
|
+
import type { DbAdapter } from 'ofcore';
|
|
4
|
+
import type { HttpAdapter } from 'ofcore';
|
|
5
|
+
import type { LoggerAdapter } from 'ofcore';
|
|
6
|
+
import type { PlatformAdapter } from 'ofcore';
|
|
7
|
+
import type { SocketAdapter } from 'ofcore';
|
|
8
|
+
import type { ActivitySink } from 'ofcore';
|
|
9
|
+
import type { ReadonlyStore, Store } from 'ofcore';
|
|
10
|
+
import type { OfcoopCreditDomainEvent } from './services/createDbAdapterOfcoopCreditServices';
|
|
11
|
+
import type { OfcoopCreditDomainServices } from './services/createDbAdapterOfcoopCreditServices';
|
|
12
|
+
export type OfcoopCreditDomainServicesOverride = Partial<OfcoopCreditDomainServices>;
|
|
13
|
+
export type OfcoopCreditDomainServicesFactory = () => Promise<OfcoopCreditDomainServices> | OfcoopCreditDomainServices;
|
|
14
|
+
export type OfcoopCreditRuntimeHooks = Pick<CoreRuntimeHooks<OfcoopCreditDomainServices, never>, 'runMigrations' | 'runSeed' | 'onInit' | 'onStop'>;
|
|
15
|
+
export interface OfcoopCreditRuntimeState {
|
|
16
|
+
phase: 'idle' | 'starting' | 'started' | 'stopping' | 'stopped' | 'error';
|
|
17
|
+
started: boolean;
|
|
18
|
+
startCount: number;
|
|
19
|
+
stopCount: number;
|
|
20
|
+
lastError: string | null;
|
|
21
|
+
lastTransitionAt: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class OfcoopCreditCore {
|
|
24
|
+
private readonly runtime;
|
|
25
|
+
private readonly runtimeStateStore;
|
|
26
|
+
domainServices?: OfcoopCreditDomainServices;
|
|
27
|
+
readonly runtimeStore: ReadonlyStore<OfcoopCreditRuntimeState>;
|
|
28
|
+
constructor(runtime: CoreRuntime<OfcoopCreditDomainServices, never>, runtimeStateStore: Store<OfcoopCreditRuntimeState>);
|
|
29
|
+
static builder(): OfcoopCreditCoreBuilder;
|
|
30
|
+
get registry(): CoreAdapterRegistry | undefined;
|
|
31
|
+
start(options?: CoreRuntimeStartOptions): Promise<void>;
|
|
32
|
+
init(options?: CoreRuntimeStartOptions): Promise<void>;
|
|
33
|
+
stop(): Promise<void>;
|
|
34
|
+
restart(): Promise<void>;
|
|
35
|
+
isStarted(): boolean;
|
|
36
|
+
createCorrelationId(prefix?: string): string;
|
|
37
|
+
use(name: string, init: (core: OfcoopCreditCore) => Promise<void>, deps?: string[]): this;
|
|
38
|
+
}
|
|
39
|
+
export declare class OfcoopCreditCoreBuilder {
|
|
40
|
+
private readonly runtimeBuilder;
|
|
41
|
+
private domainServicesFactory?;
|
|
42
|
+
private domainServiceOverrides;
|
|
43
|
+
private runtimeHooks;
|
|
44
|
+
private domainEventSink?;
|
|
45
|
+
constructor();
|
|
46
|
+
withPlatformAdapter(factory: (core: CoreRuntime<any, any>) => PlatformAdapter): this;
|
|
47
|
+
withDbAdapter(factory: (core: CoreRuntime<any, any>) => DbAdapter): this;
|
|
48
|
+
withHttpAdapter(factory: (core: CoreRuntime<any, any>) => HttpAdapter): this;
|
|
49
|
+
withSocketAdapter(factory: (core: CoreRuntime<any, any>) => SocketAdapter): this;
|
|
50
|
+
withLoggerAdapter(factory: (core: CoreRuntime<any, any>) => LoggerAdapter): this;
|
|
51
|
+
withActivitySink(factory: (core: CoreRuntime<any, any>) => ActivitySink): this;
|
|
52
|
+
withExtension(name: string, factory: (core: CoreRuntime<any, any>) => unknown): this;
|
|
53
|
+
withDomainServicesFactory(factory: OfcoopCreditDomainServicesFactory): this;
|
|
54
|
+
overrideDomainServices(overrides: OfcoopCreditDomainServicesOverride): this;
|
|
55
|
+
withRuntimeHooks(hooks: Partial<OfcoopCreditRuntimeHooks>): this;
|
|
56
|
+
withMigrationRunner(runner: NonNullable<OfcoopCreditRuntimeHooks['runMigrations']>): this;
|
|
57
|
+
withSeedRunner(runner: NonNullable<OfcoopCreditRuntimeHooks['runSeed']>): this;
|
|
58
|
+
withDomainEventSink(sink: (event: OfcoopCreditDomainEvent) => Promise<void> | void): this;
|
|
59
|
+
build(): OfcoopCreditCore;
|
|
60
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OfcoopCreditCoreBuilder = exports.OfcoopCreditCore = void 0;
|
|
4
|
+
const ofcore_1 = require("ofcore");
|
|
5
|
+
const ofcore_2 = require("ofcore");
|
|
6
|
+
const ofcore_3 = require("ofcore");
|
|
7
|
+
const applyPendingMigrations_1 = require("./data/applyPendingMigrations");
|
|
8
|
+
const createDbAdapterOfcoopCreditServices_1 = require("./services/createDbAdapterOfcoopCreditServices");
|
|
9
|
+
class OfcoopCreditCore {
|
|
10
|
+
constructor(runtime, runtimeStateStore) {
|
|
11
|
+
this.runtime = runtime;
|
|
12
|
+
this.runtimeStateStore = runtimeStateStore;
|
|
13
|
+
this.runtimeStore = (0, ofcore_3.asReadonlyStore)(this.runtimeStateStore);
|
|
14
|
+
}
|
|
15
|
+
static builder() {
|
|
16
|
+
return new OfcoopCreditCoreBuilder();
|
|
17
|
+
}
|
|
18
|
+
get registry() {
|
|
19
|
+
return this.runtime.registry;
|
|
20
|
+
}
|
|
21
|
+
async start(options = {}) {
|
|
22
|
+
this.runtimeStateStore.setState({
|
|
23
|
+
phase: 'starting',
|
|
24
|
+
started: false,
|
|
25
|
+
lastError: null,
|
|
26
|
+
lastTransitionAt: new Date().toISOString(),
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
await this.runtime.start(options);
|
|
30
|
+
this.domainServices = this.runtime.domainServices;
|
|
31
|
+
this.runtimeStateStore.setState((state) => ({
|
|
32
|
+
...state,
|
|
33
|
+
phase: 'started',
|
|
34
|
+
started: true,
|
|
35
|
+
startCount: state.startCount + 1,
|
|
36
|
+
lastError: null,
|
|
37
|
+
lastTransitionAt: new Date().toISOString(),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.runtimeStateStore.setState({
|
|
42
|
+
phase: 'error',
|
|
43
|
+
started: false,
|
|
44
|
+
lastError: error instanceof Error ? error.message : String(error),
|
|
45
|
+
lastTransitionAt: new Date().toISOString(),
|
|
46
|
+
});
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async init(options = {}) {
|
|
51
|
+
this.runtimeStateStore.setState({
|
|
52
|
+
phase: 'starting',
|
|
53
|
+
started: false,
|
|
54
|
+
lastError: null,
|
|
55
|
+
lastTransitionAt: new Date().toISOString(),
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
await this.runtime.init(options);
|
|
59
|
+
this.domainServices = this.runtime.domainServices;
|
|
60
|
+
this.runtimeStateStore.setState((state) => ({
|
|
61
|
+
...state,
|
|
62
|
+
phase: 'started',
|
|
63
|
+
started: true,
|
|
64
|
+
startCount: state.startCount + 1,
|
|
65
|
+
lastError: null,
|
|
66
|
+
lastTransitionAt: new Date().toISOString(),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
this.runtimeStateStore.setState({
|
|
71
|
+
phase: 'error',
|
|
72
|
+
started: false,
|
|
73
|
+
lastError: error instanceof Error ? error.message : String(error),
|
|
74
|
+
lastTransitionAt: new Date().toISOString(),
|
|
75
|
+
});
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async stop() {
|
|
80
|
+
this.runtimeStateStore.setState({
|
|
81
|
+
phase: 'stopping',
|
|
82
|
+
started: this.runtime.isStarted(),
|
|
83
|
+
lastError: null,
|
|
84
|
+
lastTransitionAt: new Date().toISOString(),
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
await this.runtime.stop();
|
|
88
|
+
this.runtimeStateStore.setState((state) => ({
|
|
89
|
+
...state,
|
|
90
|
+
phase: 'stopped',
|
|
91
|
+
started: false,
|
|
92
|
+
stopCount: state.stopCount + 1,
|
|
93
|
+
lastError: null,
|
|
94
|
+
lastTransitionAt: new Date().toISOString(),
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
this.runtimeStateStore.setState({
|
|
99
|
+
phase: 'error',
|
|
100
|
+
started: this.runtime.isStarted(),
|
|
101
|
+
lastError: error instanceof Error ? error.message : String(error),
|
|
102
|
+
lastTransitionAt: new Date().toISOString(),
|
|
103
|
+
});
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async restart() {
|
|
108
|
+
this.runtimeStateStore.setState({
|
|
109
|
+
phase: 'starting',
|
|
110
|
+
started: this.runtime.isStarted(),
|
|
111
|
+
lastError: null,
|
|
112
|
+
lastTransitionAt: new Date().toISOString(),
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
await this.runtime.restart();
|
|
116
|
+
this.domainServices = this.runtime.domainServices;
|
|
117
|
+
this.runtimeStateStore.setState((state) => ({
|
|
118
|
+
...state,
|
|
119
|
+
phase: 'started',
|
|
120
|
+
started: true,
|
|
121
|
+
startCount: state.startCount + 1,
|
|
122
|
+
lastError: null,
|
|
123
|
+
lastTransitionAt: new Date().toISOString(),
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
this.runtimeStateStore.setState({
|
|
128
|
+
phase: 'error',
|
|
129
|
+
started: this.runtime.isStarted(),
|
|
130
|
+
lastError: error instanceof Error ? error.message : String(error),
|
|
131
|
+
lastTransitionAt: new Date().toISOString(),
|
|
132
|
+
});
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
isStarted() {
|
|
137
|
+
return this.runtime.isStarted();
|
|
138
|
+
}
|
|
139
|
+
createCorrelationId(prefix = 'ofcoop-credit') {
|
|
140
|
+
return this.runtime.createCorrelationId(prefix);
|
|
141
|
+
}
|
|
142
|
+
use(name, init, deps = []) {
|
|
143
|
+
this.runtime.use(name, async () => init(this), deps);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.OfcoopCreditCore = OfcoopCreditCore;
|
|
148
|
+
class OfcoopCreditCoreBuilder {
|
|
149
|
+
constructor() {
|
|
150
|
+
this.runtimeBuilder = ofcore_1.CoreRuntime.builder();
|
|
151
|
+
this.domainServiceOverrides = {};
|
|
152
|
+
this.runtimeHooks = {};
|
|
153
|
+
this.runtimeBuilder.withDbAdapter(() => new ofcore_2.InMemoryDbAdapter());
|
|
154
|
+
}
|
|
155
|
+
withPlatformAdapter(factory) {
|
|
156
|
+
this.runtimeBuilder.withPlatformAdapter(factory);
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
withDbAdapter(factory) {
|
|
160
|
+
this.runtimeBuilder.withDbAdapter(factory);
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
withHttpAdapter(factory) {
|
|
164
|
+
this.runtimeBuilder.withHttpAdapter(factory);
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
withSocketAdapter(factory) {
|
|
168
|
+
this.runtimeBuilder.withSocketAdapter(factory);
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
withLoggerAdapter(factory) {
|
|
172
|
+
this.runtimeBuilder.withLoggerAdapter(factory);
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
withActivitySink(factory) {
|
|
176
|
+
this.runtimeBuilder.withActivitySink(factory);
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
withExtension(name, factory) {
|
|
180
|
+
this.runtimeBuilder.withExtension(name, factory);
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
withDomainServicesFactory(factory) {
|
|
184
|
+
this.domainServicesFactory = factory;
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
overrideDomainServices(overrides) {
|
|
188
|
+
this.domainServiceOverrides = { ...this.domainServiceOverrides, ...overrides };
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
withRuntimeHooks(hooks) {
|
|
192
|
+
this.runtimeHooks = { ...this.runtimeHooks, ...hooks };
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
withMigrationRunner(runner) {
|
|
196
|
+
return this.withRuntimeHooks({ runMigrations: runner });
|
|
197
|
+
}
|
|
198
|
+
withSeedRunner(runner) {
|
|
199
|
+
return this.withRuntimeHooks({ runSeed: runner });
|
|
200
|
+
}
|
|
201
|
+
withDomainEventSink(sink) {
|
|
202
|
+
this.domainEventSink = sink;
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
build() {
|
|
206
|
+
const runtimeStateStore = (0, ofcore_3.createStore)({
|
|
207
|
+
phase: 'idle',
|
|
208
|
+
started: false,
|
|
209
|
+
startCount: 0,
|
|
210
|
+
stopCount: 0,
|
|
211
|
+
lastError: null,
|
|
212
|
+
lastTransitionAt: new Date().toISOString(),
|
|
213
|
+
});
|
|
214
|
+
const defaultRunMigrations = async (runtime) => {
|
|
215
|
+
const dbAdapter = runtime.registry?.dbAdapter;
|
|
216
|
+
if (!dbAdapter)
|
|
217
|
+
return;
|
|
218
|
+
await (0, applyPendingMigrations_1.applyPendingMigrations)(dbAdapter, runtime.registry?.loggerAdapter);
|
|
219
|
+
};
|
|
220
|
+
this.runtimeBuilder.withHooks({
|
|
221
|
+
...this.runtimeHooks,
|
|
222
|
+
runMigrations: this.runtimeHooks.runMigrations ?? defaultRunMigrations,
|
|
223
|
+
createDomainServices: async (runtime) => {
|
|
224
|
+
const emitEvent = async (event) => {
|
|
225
|
+
const errors = [];
|
|
226
|
+
if (this.domainEventSink) {
|
|
227
|
+
try {
|
|
228
|
+
await this.domainEventSink(event);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
errors.push(error);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (runtime.registry?.activitySink) {
|
|
235
|
+
try {
|
|
236
|
+
await runtime.registry.activitySink.publish({
|
|
237
|
+
activityId: runtime.createCorrelationId('ofcoop-credit-event'),
|
|
238
|
+
activityType: `ofcoop-credit.${event.name}`,
|
|
239
|
+
occurredAt: event.occurredAt,
|
|
240
|
+
scopeRef: {
|
|
241
|
+
domain: 'ofcoop-credit',
|
|
242
|
+
...(event.tenantId ? { tenantId: event.tenantId } : {}),
|
|
243
|
+
...(event.branchId ? { branchId: event.branchId } : {}),
|
|
244
|
+
},
|
|
245
|
+
payload: {
|
|
246
|
+
entityId: event.entityId ?? null,
|
|
247
|
+
...(event.payload ?? {}),
|
|
248
|
+
},
|
|
249
|
+
correlationId: runtime.createCorrelationId('ofcoop-credit-corr'),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
errors.push(error);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (errors.length > 0) {
|
|
257
|
+
throw errors[0];
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
const base = this.domainServicesFactory
|
|
261
|
+
? await this.domainServicesFactory()
|
|
262
|
+
: await (0, createDbAdapterOfcoopCreditServices_1.createDbAdapterOfcoopCreditServices)(runtime.registry?.dbAdapter ?? new ofcore_2.InMemoryDbAdapter(), {
|
|
263
|
+
logger: runtime.registry?.loggerAdapter,
|
|
264
|
+
emitEvent,
|
|
265
|
+
});
|
|
266
|
+
return { ...base, ...this.domainServiceOverrides };
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
const runtime = this.runtimeBuilder.build();
|
|
270
|
+
return new OfcoopCreditCore(runtime, runtimeStateStore);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
exports.OfcoopCreditCoreBuilder = OfcoopCreditCoreBuilder;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface CreditPolicyContract {
|
|
2
|
+
id: string;
|
|
3
|
+
policyCode: string;
|
|
4
|
+
penaltyRatePerDay: number;
|
|
5
|
+
gracePeriodDays: number;
|
|
6
|
+
maxTenorMonths: number;
|
|
7
|
+
isActive: boolean;
|
|
8
|
+
branchId?: string | null;
|
|
9
|
+
tenantId?: string | null;
|
|
10
|
+
version: number;
|
|
11
|
+
lastModified: string;
|
|
12
|
+
deleted: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface CreateCreditPolicyInput {
|
|
15
|
+
policyCode: string;
|
|
16
|
+
penaltyRatePerDay: number;
|
|
17
|
+
gracePeriodDays: number;
|
|
18
|
+
maxTenorMonths: number;
|
|
19
|
+
isActive?: boolean;
|
|
20
|
+
branchId?: string | null;
|
|
21
|
+
tenantId?: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface UpdateCreditPolicyInput {
|
|
24
|
+
penaltyRatePerDay?: number;
|
|
25
|
+
gracePeriodDays?: number;
|
|
26
|
+
maxTenorMonths?: number;
|
|
27
|
+
isActive?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface CreditPolicyScope {
|
|
30
|
+
tenantId: string;
|
|
31
|
+
branchId?: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface CreditPolicyServiceContract {
|
|
34
|
+
createCreditPolicy(input: CreateCreditPolicyInput): Promise<CreditPolicyContract>;
|
|
35
|
+
updateCreditPolicy(id: string, updates: UpdateCreditPolicyInput): Promise<CreditPolicyContract>;
|
|
36
|
+
getActiveCreditPolicy(scope: CreditPolicyScope): Promise<CreditPolicyContract | null>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type InstallmentStatus = 'scheduled' | 'partial' | 'paid' | 'overdue';
|
|
2
|
+
export interface InstallmentContract {
|
|
3
|
+
id: string;
|
|
4
|
+
loanId: string;
|
|
5
|
+
installmentNo: number;
|
|
6
|
+
dueDate: string;
|
|
7
|
+
principalDue: number;
|
|
8
|
+
serviceDue: number;
|
|
9
|
+
penaltyDue: number;
|
|
10
|
+
paidAmount: number;
|
|
11
|
+
status: InstallmentStatus;
|
|
12
|
+
branchId?: string | null;
|
|
13
|
+
tenantId?: string | null;
|
|
14
|
+
version: number;
|
|
15
|
+
lastModified: string;
|
|
16
|
+
deleted: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface RecordInstallmentPaymentInput {
|
|
19
|
+
loanId: string;
|
|
20
|
+
installmentNo: number;
|
|
21
|
+
paidAmount: number;
|
|
22
|
+
paidAt: string;
|
|
23
|
+
referenceId?: string | null;
|
|
24
|
+
notes?: string | null;
|
|
25
|
+
branchId?: string | null;
|
|
26
|
+
tenantId?: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface InstallmentQueryOptions {
|
|
29
|
+
includeDeleted?: boolean;
|
|
30
|
+
status?: InstallmentStatus;
|
|
31
|
+
dueDateFrom?: string;
|
|
32
|
+
dueDateTo?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface InstallmentServiceContract {
|
|
35
|
+
generateInstallmentSchedule(loanId: string): Promise<InstallmentContract[]>;
|
|
36
|
+
recordInstallmentPayment(input: RecordInstallmentPaymentInput): Promise<InstallmentContract>;
|
|
37
|
+
listInstallmentsByLoan(loanId: string, options?: InstallmentQueryOptions): Promise<InstallmentContract[]>;
|
|
38
|
+
listOverdueInstallments(options?: InstallmentQueryOptions): Promise<InstallmentContract[]>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LoanStatus } from './LoanContract';
|
|
2
|
+
export interface LoanBoardItemContract {
|
|
3
|
+
loanId: string;
|
|
4
|
+
memberId: string;
|
|
5
|
+
tenantId?: string | null;
|
|
6
|
+
branchId?: string | null;
|
|
7
|
+
status: LoanStatus;
|
|
8
|
+
principalAmount: number;
|
|
9
|
+
outstandingAmount: number;
|
|
10
|
+
overdueInstallmentCount: number;
|
|
11
|
+
nextDueDate?: string | null;
|
|
12
|
+
lastPaymentDate?: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface LoanBoardScope {
|
|
15
|
+
tenantId: string;
|
|
16
|
+
branchId?: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface LoanBoardQueryOptions {
|
|
19
|
+
memberId?: string;
|
|
20
|
+
status?: LoanStatus;
|
|
21
|
+
}
|
|
22
|
+
export interface LoanBoardServiceContract {
|
|
23
|
+
listLoanBoard(scope: LoanBoardScope, options?: LoanBoardQueryOptions): Promise<LoanBoardItemContract[]>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type LoanStatus = 'active' | 'closed' | 'defaulted';
|
|
2
|
+
export interface LoanContract {
|
|
3
|
+
id: string;
|
|
4
|
+
memberId: string;
|
|
5
|
+
principalAmount: number;
|
|
6
|
+
tenorMonths: number;
|
|
7
|
+
serviceRate: number;
|
|
8
|
+
startDate: string;
|
|
9
|
+
status: LoanStatus;
|
|
10
|
+
branchId?: string | null;
|
|
11
|
+
tenantId?: string | null;
|
|
12
|
+
version: number;
|
|
13
|
+
lastModified: string;
|
|
14
|
+
deleted: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface CreateLoanInput {
|
|
17
|
+
memberId: string;
|
|
18
|
+
principalAmount: number;
|
|
19
|
+
tenorMonths: number;
|
|
20
|
+
serviceRate: number;
|
|
21
|
+
startDate: string;
|
|
22
|
+
branchId?: string | null;
|
|
23
|
+
tenantId?: string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface ListLoanOptions {
|
|
26
|
+
includeDeleted?: boolean;
|
|
27
|
+
status?: LoanStatus;
|
|
28
|
+
tenantId?: string | null;
|
|
29
|
+
branchId?: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface LoanServiceContract {
|
|
32
|
+
createLoan(input: CreateLoanInput): Promise<LoanContract>;
|
|
33
|
+
getLoanById(id: string): Promise<LoanContract | null>;
|
|
34
|
+
listLoansByMember(memberId: string, options?: ListLoanOptions): Promise<LoanContract[]>;
|
|
35
|
+
closeLoan(loanId: string): Promise<LoanContract>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { InstallmentStatus } from './InstallmentContract';
|
|
2
|
+
export interface LoanDueWindowItemContract {
|
|
3
|
+
loanId: string;
|
|
4
|
+
memberId: string;
|
|
5
|
+
installmentNo: number;
|
|
6
|
+
dueDate: string;
|
|
7
|
+
installmentStatus: InstallmentStatus;
|
|
8
|
+
dueAmount: number;
|
|
9
|
+
paidAmount: number;
|
|
10
|
+
outstandingAmount: number;
|
|
11
|
+
tenantId?: string | null;
|
|
12
|
+
branchId?: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface LoanDueWindowScope {
|
|
15
|
+
tenantId: string;
|
|
16
|
+
branchId?: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface LoanDueWindowQueryInput {
|
|
19
|
+
dateFrom: string;
|
|
20
|
+
dateTo: string;
|
|
21
|
+
memberId?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface LoanDueWindowServiceContract {
|
|
24
|
+
listLoansDueInWindow(scope: LoanDueWindowScope, input: LoanDueWindowQueryInput): Promise<LoanDueWindowItemContract[]>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyPendingMigrations = applyPendingMigrations;
|
|
4
|
+
const migrations_1 = require("./migrations");
|
|
5
|
+
const noopLogger = {
|
|
6
|
+
logInfo() { },
|
|
7
|
+
logWarn() { },
|
|
8
|
+
logError() { },
|
|
9
|
+
};
|
|
10
|
+
async function applyPendingMigrations(adapter, logger = noopLogger) {
|
|
11
|
+
logger.logInfo('[ofcoop-credit] starting database migration process');
|
|
12
|
+
const sorted = [...migrations_1.migrations].sort((a, b) => a.toVersion - b.toVersion);
|
|
13
|
+
const targetVersion = sorted.length > 0 ? sorted[sorted.length - 1].toVersion : 0;
|
|
14
|
+
let currentVersion = await adapter.getSchemaVersion();
|
|
15
|
+
if (currentVersion == null || currentVersion < 0)
|
|
16
|
+
currentVersion = 0;
|
|
17
|
+
if (currentVersion >= targetVersion) {
|
|
18
|
+
logger.logInfo(`[ofcoop-credit] database already up-to-date at v${currentVersion}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
for (const migration of sorted) {
|
|
22
|
+
if (migration.toVersion > currentVersion) {
|
|
23
|
+
logger.logInfo(`[ofcoop-credit] applying migration v${migration.toVersion}`);
|
|
24
|
+
await migration.up(adapter);
|
|
25
|
+
await adapter.setSchemaVersion(migration.toVersion);
|
|
26
|
+
currentVersion = migration.toVersion;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
logger.logInfo(`[ofcoop-credit] migration completed at v${currentVersion}`);
|
|
30
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrations = void 0;
|
|
4
|
+
const schemas_1 = require("./schemas");
|
|
5
|
+
exports.migrations = [
|
|
6
|
+
{
|
|
7
|
+
toVersion: 1,
|
|
8
|
+
up: async (adapter) => {
|
|
9
|
+
const tables = (0, schemas_1.getOfcoopCreditTableSchemas)();
|
|
10
|
+
for (const table of tables) {
|
|
11
|
+
try {
|
|
12
|
+
await adapter.addTable(table);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// Adapter may throw when table already exists; keep migration idempotent.
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DbAdapter } from 'ofcore';
|
|
2
|
+
import type { CreditPolicyContract, CreditPolicyScope } from '../contracts/CreditPolicyContract';
|
|
3
|
+
import type { InstallmentContract, InstallmentQueryOptions } from '../contracts/InstallmentContract';
|
|
4
|
+
import type { ListLoanOptions, LoanContract } from '../contracts/LoanContract';
|
|
5
|
+
export declare class LoanRepository {
|
|
6
|
+
private readonly db;
|
|
7
|
+
constructor(db: DbAdapter);
|
|
8
|
+
create(row: LoanContract): Promise<LoanContract>;
|
|
9
|
+
update(id: string, updates: Partial<LoanContract>): Promise<LoanContract>;
|
|
10
|
+
getById(id: string): Promise<LoanContract | null>;
|
|
11
|
+
listByMember(memberId: string, options?: ListLoanOptions): Promise<LoanContract[]>;
|
|
12
|
+
list(options?: ListLoanOptions): Promise<LoanContract[]>;
|
|
13
|
+
}
|
|
14
|
+
export declare class InstallmentRepository {
|
|
15
|
+
private readonly db;
|
|
16
|
+
constructor(db: DbAdapter);
|
|
17
|
+
createMany(rows: InstallmentContract[]): Promise<InstallmentContract[]>;
|
|
18
|
+
update(id: string, updates: Partial<InstallmentContract>): Promise<InstallmentContract>;
|
|
19
|
+
listByLoan(loanId: string, options?: InstallmentQueryOptions): Promise<InstallmentContract[]>;
|
|
20
|
+
list(options?: InstallmentQueryOptions & {
|
|
21
|
+
loanId?: string;
|
|
22
|
+
}): Promise<InstallmentContract[]>;
|
|
23
|
+
getByLoanAndInstallmentNo(loanId: string, installmentNo: number): Promise<InstallmentContract | null>;
|
|
24
|
+
listOverdue(options?: InstallmentQueryOptions): Promise<InstallmentContract[]>;
|
|
25
|
+
}
|
|
26
|
+
export declare class CreditPolicyRepository {
|
|
27
|
+
private readonly db;
|
|
28
|
+
constructor(db: DbAdapter);
|
|
29
|
+
create(row: CreditPolicyContract): Promise<CreditPolicyContract>;
|
|
30
|
+
update(id: string, updates: Partial<CreditPolicyContract>): Promise<CreditPolicyContract>;
|
|
31
|
+
getById(id: string): Promise<CreditPolicyContract | null>;
|
|
32
|
+
getActive(scope: CreditPolicyScope): Promise<CreditPolicyContract | null>;
|
|
33
|
+
}
|
|
34
|
+
export interface OfcoopCreditRepositories {
|
|
35
|
+
loanRepository: LoanRepository;
|
|
36
|
+
installmentRepository: InstallmentRepository;
|
|
37
|
+
creditPolicyRepository: CreditPolicyRepository;
|
|
38
|
+
}
|
|
39
|
+
export declare function createOfcoopCreditRepositories(db: DbAdapter): OfcoopCreditRepositories;
|