@hed-hog/finance 0.0.252 → 0.0.253
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/dist/dto/reverse-settlement.dto.d.ts +1 -0
- package/dist/dto/reverse-settlement.dto.d.ts.map +1 -1
- package/dist/dto/reverse-settlement.dto.js +5 -0
- package/dist/dto/reverse-settlement.dto.js.map +1 -1
- package/dist/finance-installments.controller.d.ts +40 -2
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.js +38 -2
- package/dist/finance-installments.controller.js.map +1 -1
- package/dist/finance.service.d.ts +38 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +307 -118
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/route.yaml +27 -0
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +308 -99
- package/hedhog/query/settlement-auditability.sql +175 -0
- package/hedhog/table/bank_reconciliation.yaml +11 -0
- package/hedhog/table/settlement.yaml +17 -1
- package/hedhog/table/settlement_allocation.yaml +3 -0
- package/package.json +5 -5
- package/src/dto/reverse-settlement.dto.ts +4 -0
- package/src/finance-installments.controller.ts +45 -12
- package/src/finance.service.ts +439 -139
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
-- Settlement auditability, reversal linkage and reconciliation helpers
|
|
2
|
+
-- Idempotent script for PostgreSQL
|
|
3
|
+
|
|
4
|
+
DO $$
|
|
5
|
+
BEGIN
|
|
6
|
+
IF NOT EXISTS (
|
|
7
|
+
SELECT 1
|
|
8
|
+
FROM pg_type
|
|
9
|
+
WHERE typname = 'settlement_entry_type_enum'
|
|
10
|
+
) THEN
|
|
11
|
+
CREATE TYPE settlement_entry_type_enum AS ENUM ('normal', 'reversal');
|
|
12
|
+
END IF;
|
|
13
|
+
END $$;
|
|
14
|
+
|
|
15
|
+
ALTER TABLE settlement
|
|
16
|
+
ADD COLUMN IF NOT EXISTS entry_type settlement_entry_type_enum;
|
|
17
|
+
|
|
18
|
+
UPDATE settlement
|
|
19
|
+
SET entry_type = 'normal'
|
|
20
|
+
WHERE entry_type IS NULL;
|
|
21
|
+
|
|
22
|
+
ALTER TABLE settlement
|
|
23
|
+
ALTER COLUMN entry_type SET DEFAULT 'normal';
|
|
24
|
+
|
|
25
|
+
ALTER TABLE settlement
|
|
26
|
+
ALTER COLUMN entry_type SET NOT NULL;
|
|
27
|
+
|
|
28
|
+
ALTER TABLE settlement
|
|
29
|
+
ADD COLUMN IF NOT EXISTS reverses_settlement_id INTEGER;
|
|
30
|
+
|
|
31
|
+
DO $$
|
|
32
|
+
BEGIN
|
|
33
|
+
IF NOT EXISTS (
|
|
34
|
+
SELECT 1
|
|
35
|
+
FROM pg_constraint
|
|
36
|
+
WHERE conname = 'fk_settlement_reverses_settlement'
|
|
37
|
+
) THEN
|
|
38
|
+
ALTER TABLE settlement
|
|
39
|
+
ADD CONSTRAINT fk_settlement_reverses_settlement
|
|
40
|
+
FOREIGN KEY (reverses_settlement_id)
|
|
41
|
+
REFERENCES settlement(id)
|
|
42
|
+
ON DELETE RESTRICT
|
|
43
|
+
ON UPDATE CASCADE;
|
|
44
|
+
END IF;
|
|
45
|
+
END $$;
|
|
46
|
+
|
|
47
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_settlement_reverses_settlement_id
|
|
48
|
+
ON settlement(reverses_settlement_id)
|
|
49
|
+
WHERE reverses_settlement_id IS NOT NULL;
|
|
50
|
+
|
|
51
|
+
DO $$
|
|
52
|
+
BEGIN
|
|
53
|
+
BEGIN
|
|
54
|
+
ALTER TABLE settlement
|
|
55
|
+
ALTER COLUMN amount_cents TYPE BIGINT USING amount_cents::BIGINT;
|
|
56
|
+
EXCEPTION
|
|
57
|
+
WHEN undefined_column THEN
|
|
58
|
+
NULL;
|
|
59
|
+
END;
|
|
60
|
+
END $$;
|
|
61
|
+
|
|
62
|
+
ALTER TABLE settlement_allocation
|
|
63
|
+
ADD COLUMN IF NOT EXISTS amount_cents BIGINT;
|
|
64
|
+
|
|
65
|
+
UPDATE settlement_allocation
|
|
66
|
+
SET amount_cents = allocated_amount_cents
|
|
67
|
+
WHERE amount_cents IS NULL;
|
|
68
|
+
|
|
69
|
+
ALTER TABLE settlement_allocation
|
|
70
|
+
ALTER COLUMN amount_cents SET NOT NULL;
|
|
71
|
+
|
|
72
|
+
DO $$
|
|
73
|
+
BEGIN
|
|
74
|
+
BEGIN
|
|
75
|
+
ALTER TABLE settlement_allocation
|
|
76
|
+
ALTER COLUMN allocated_amount_cents TYPE BIGINT USING allocated_amount_cents::BIGINT;
|
|
77
|
+
EXCEPTION
|
|
78
|
+
WHEN undefined_column THEN
|
|
79
|
+
NULL;
|
|
80
|
+
END;
|
|
81
|
+
END $$;
|
|
82
|
+
|
|
83
|
+
DO $$
|
|
84
|
+
BEGIN
|
|
85
|
+
IF NOT EXISTS (
|
|
86
|
+
SELECT 1
|
|
87
|
+
FROM pg_constraint
|
|
88
|
+
WHERE conname = 'chk_settlement_allocation_amount_non_zero'
|
|
89
|
+
) THEN
|
|
90
|
+
ALTER TABLE settlement_allocation
|
|
91
|
+
ADD CONSTRAINT chk_settlement_allocation_amount_non_zero
|
|
92
|
+
CHECK (amount_cents <> 0);
|
|
93
|
+
END IF;
|
|
94
|
+
END $$;
|
|
95
|
+
|
|
96
|
+
ALTER TABLE bank_reconciliation
|
|
97
|
+
ADD COLUMN IF NOT EXISTS reconciled_at TIMESTAMPTZ(6);
|
|
98
|
+
|
|
99
|
+
ALTER TABLE bank_reconciliation
|
|
100
|
+
ADD COLUMN IF NOT EXISTS reconciled_by_user_id INTEGER;
|
|
101
|
+
|
|
102
|
+
DO $$
|
|
103
|
+
BEGIN
|
|
104
|
+
IF NOT EXISTS (
|
|
105
|
+
SELECT 1
|
|
106
|
+
FROM pg_constraint
|
|
107
|
+
WHERE conname = 'fk_bank_reconciliation_reconciled_by_user'
|
|
108
|
+
) THEN
|
|
109
|
+
ALTER TABLE bank_reconciliation
|
|
110
|
+
ADD CONSTRAINT fk_bank_reconciliation_reconciled_by_user
|
|
111
|
+
FOREIGN KEY (reconciled_by_user_id)
|
|
112
|
+
REFERENCES "user"(id)
|
|
113
|
+
ON DELETE SET NULL
|
|
114
|
+
ON UPDATE CASCADE;
|
|
115
|
+
END IF;
|
|
116
|
+
END $$;
|
|
117
|
+
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_settlement_settled_at
|
|
119
|
+
ON settlement(settled_at);
|
|
120
|
+
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_settlement_entry_type
|
|
122
|
+
ON settlement(entry_type);
|
|
123
|
+
|
|
124
|
+
CREATE OR REPLACE FUNCTION fn_prevent_settlement_updates()
|
|
125
|
+
RETURNS TRIGGER
|
|
126
|
+
LANGUAGE plpgsql
|
|
127
|
+
AS $$
|
|
128
|
+
BEGIN
|
|
129
|
+
RAISE EXCEPTION 'Settlement is immutable. Create a reversal instead of updating settlement %', NEW.id;
|
|
130
|
+
END;
|
|
131
|
+
$$;
|
|
132
|
+
|
|
133
|
+
DROP TRIGGER IF EXISTS trg_prevent_settlement_updates ON settlement;
|
|
134
|
+
|
|
135
|
+
CREATE TRIGGER trg_prevent_settlement_updates
|
|
136
|
+
BEFORE UPDATE ON settlement
|
|
137
|
+
FOR EACH ROW
|
|
138
|
+
EXECUTE FUNCTION fn_prevent_settlement_updates();
|
|
139
|
+
|
|
140
|
+
CREATE OR REPLACE VIEW v_settlement_history AS
|
|
141
|
+
SELECT
|
|
142
|
+
fi.title_id,
|
|
143
|
+
s.id AS settlement_id,
|
|
144
|
+
s.entry_type,
|
|
145
|
+
s.reverses_settlement_id,
|
|
146
|
+
CASE
|
|
147
|
+
WHEN s.entry_type = 'reversal' THEN s.reverses_settlement_id
|
|
148
|
+
ELSE s.id
|
|
149
|
+
END AS settlement_group_id,
|
|
150
|
+
s.settlement_type,
|
|
151
|
+
s.status,
|
|
152
|
+
s.settled_at,
|
|
153
|
+
s.amount_cents,
|
|
154
|
+
s.payment_method_id,
|
|
155
|
+
s.bank_account_id,
|
|
156
|
+
s.description,
|
|
157
|
+
s.external_reference,
|
|
158
|
+
s.created_by_user_id,
|
|
159
|
+
s.created_at,
|
|
160
|
+
s.updated_at,
|
|
161
|
+
sa.installment_id,
|
|
162
|
+
fi.installment_number,
|
|
163
|
+
sa.amount_cents AS allocation_amount_cents,
|
|
164
|
+
sa.discount_cents,
|
|
165
|
+
sa.interest_cents,
|
|
166
|
+
sa.penalty_cents,
|
|
167
|
+
br.id AS bank_reconciliation_id,
|
|
168
|
+
CASE
|
|
169
|
+
WHEN br.id IS NOT NULL THEN TRUE
|
|
170
|
+
ELSE FALSE
|
|
171
|
+
END AS reconciled
|
|
172
|
+
FROM settlement s
|
|
173
|
+
INNER JOIN settlement_allocation sa ON sa.settlement_id = s.id
|
|
174
|
+
INNER JOIN financial_installment fi ON fi.id = sa.installment_id
|
|
175
|
+
LEFT JOIN bank_reconciliation br ON br.settlement_id = s.id;
|
|
@@ -20,6 +20,9 @@ columns:
|
|
|
20
20
|
- name: matched_at
|
|
21
21
|
type: datetime
|
|
22
22
|
isNullable: true
|
|
23
|
+
- name: reconciled_at
|
|
24
|
+
type: datetime
|
|
25
|
+
isNullable: true
|
|
23
26
|
- name: matched_by_user_id
|
|
24
27
|
type: fk
|
|
25
28
|
isNullable: true
|
|
@@ -28,6 +31,14 @@ columns:
|
|
|
28
31
|
column: id
|
|
29
32
|
onDelete: SET NULL
|
|
30
33
|
onUpdate: CASCADE
|
|
34
|
+
- name: reconciled_by_user_id
|
|
35
|
+
type: fk
|
|
36
|
+
isNullable: true
|
|
37
|
+
references:
|
|
38
|
+
table: user
|
|
39
|
+
column: id
|
|
40
|
+
onDelete: SET NULL
|
|
41
|
+
onUpdate: CASCADE
|
|
31
42
|
- type: created_at
|
|
32
43
|
- type: updated_at
|
|
33
44
|
|
|
@@ -27,6 +27,10 @@ columns:
|
|
|
27
27
|
- name: settlement_type
|
|
28
28
|
type: enum
|
|
29
29
|
values: [payable, receivable, transfer, adjustment]
|
|
30
|
+
- name: entry_type
|
|
31
|
+
type: enum
|
|
32
|
+
values: [normal, reversal]
|
|
33
|
+
default: normal
|
|
30
34
|
- name: status
|
|
31
35
|
type: enum
|
|
32
36
|
values: [pending, confirmed, reversed]
|
|
@@ -41,6 +45,14 @@ columns:
|
|
|
41
45
|
type: varchar
|
|
42
46
|
length: 120
|
|
43
47
|
isNullable: true
|
|
48
|
+
- name: reverses_settlement_id
|
|
49
|
+
type: fk
|
|
50
|
+
isNullable: true
|
|
51
|
+
references:
|
|
52
|
+
table: settlement
|
|
53
|
+
column: id
|
|
54
|
+
onDelete: RESTRICT
|
|
55
|
+
onUpdate: CASCADE
|
|
44
56
|
- name: created_by_user_id
|
|
45
57
|
type: fk
|
|
46
58
|
isNullable: true
|
|
@@ -55,4 +67,8 @@ columns:
|
|
|
55
67
|
indices:
|
|
56
68
|
- columns: [status]
|
|
57
69
|
- columns: [settled_at]
|
|
58
|
-
- columns: [bank_account_id]
|
|
70
|
+
- columns: [bank_account_id]
|
|
71
|
+
- columns: [entry_type]
|
|
72
|
+
- columns: [settlement_type, settled_at]
|
|
73
|
+
- columns: [reverses_settlement_id]
|
|
74
|
+
isUnique: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/finance",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.253",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"@nestjs/core": "^11",
|
|
10
10
|
"@nestjs/jwt": "^11",
|
|
11
11
|
"@nestjs/mapped-types": "*",
|
|
12
|
-
"@hed-hog/api-prisma": "0.0.4",
|
|
13
|
-
"@hed-hog/contact": "0.0.251",
|
|
14
12
|
"@hed-hog/api-pagination": "0.0.5",
|
|
13
|
+
"@hed-hog/contact": "0.0.251",
|
|
15
14
|
"@hed-hog/tag": "0.0.251",
|
|
16
15
|
"@hed-hog/api-locale": "0.0.11",
|
|
16
|
+
"@hed-hog/api-prisma": "0.0.4",
|
|
17
17
|
"@hed-hog/api-types": "0.0.1",
|
|
18
|
-
"@hed-hog/
|
|
19
|
-
"@hed-hog/
|
|
18
|
+
"@hed-hog/api": "0.0.3",
|
|
19
|
+
"@hed-hog/core": "0.0.251"
|
|
20
20
|
},
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
@@ -2,22 +2,24 @@ import { Role, User } from '@hed-hog/api';
|
|
|
2
2
|
import { Locale } from '@hed-hog/api-locale';
|
|
3
3
|
import { Pagination } from '@hed-hog/api-pagination';
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
Body,
|
|
6
|
+
Controller,
|
|
7
|
+
Delete,
|
|
8
|
+
Get,
|
|
9
|
+
Param,
|
|
10
|
+
ParseIntPipe,
|
|
11
|
+
Patch,
|
|
12
|
+
Post,
|
|
13
|
+
Query,
|
|
14
|
+
UploadedFile,
|
|
15
|
+
UseInterceptors,
|
|
15
16
|
} from '@nestjs/common';
|
|
16
17
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
17
18
|
import { CreateFinanceTagDto } from './dto/create-finance-tag.dto';
|
|
18
19
|
import { CreateFinancialTitleDto } from './dto/create-financial-title.dto';
|
|
19
20
|
import { ExtractFinancialTitleFromFileDto } from './dto/extract-financial-title-from-file.dto';
|
|
20
21
|
import { RejectTitleDto } from './dto/reject-title.dto';
|
|
22
|
+
import { ReverseSettlementDto } from './dto/reverse-settlement.dto';
|
|
21
23
|
import { FinanceService } from './finance.service';
|
|
22
24
|
|
|
23
25
|
import { UpdateInstallmentTagsDto } from './dto/update-installment-tags.dto';
|
|
@@ -45,6 +47,14 @@ export class FinanceInstallmentsController {
|
|
|
45
47
|
return this.financeService.getAccountsPayableInstallment(id, locale);
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
@Get('titles/:id/settlements-history')
|
|
51
|
+
async getTitleSettlementsHistory(
|
|
52
|
+
@Param('id', ParseIntPipe) id: number,
|
|
53
|
+
@Locale() locale: string,
|
|
54
|
+
) {
|
|
55
|
+
return this.financeService.getTitleSettlementsHistory(id, locale);
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
@Post('accounts-payable/installments')
|
|
49
59
|
async createAccountsPayableTitle(
|
|
50
60
|
@Body() data: CreateFinancialTitleDto,
|
|
@@ -131,7 +141,7 @@ export class FinanceInstallmentsController {
|
|
|
131
141
|
async reverseAccountsPayableSettlement(
|
|
132
142
|
@Param('id', ParseIntPipe) id: number,
|
|
133
143
|
@Param('settlementId', ParseIntPipe) settlementId: number,
|
|
134
|
-
@Body() data,
|
|
144
|
+
@Body() data: ReverseSettlementDto,
|
|
135
145
|
@Locale() locale: string,
|
|
136
146
|
@User() user,
|
|
137
147
|
) {
|
|
@@ -144,6 +154,29 @@ export class FinanceInstallmentsController {
|
|
|
144
154
|
);
|
|
145
155
|
}
|
|
146
156
|
|
|
157
|
+
@Post('settlements/:id/reverse')
|
|
158
|
+
async reverseSettlement(
|
|
159
|
+
@Param('id', ParseIntPipe) settlementId: number,
|
|
160
|
+
@Body() data: ReverseSettlementDto,
|
|
161
|
+
@Locale() locale: string,
|
|
162
|
+
@User() user,
|
|
163
|
+
) {
|
|
164
|
+
return this.financeService.reverseSettlementById(
|
|
165
|
+
settlementId,
|
|
166
|
+
data,
|
|
167
|
+
locale,
|
|
168
|
+
user?.id,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@Delete('bank-reconciliations/:id')
|
|
173
|
+
async unreconcileBankReconciliation(
|
|
174
|
+
@Param('id', ParseIntPipe) id: number,
|
|
175
|
+
@User() user,
|
|
176
|
+
) {
|
|
177
|
+
return this.financeService.unreconcileBankReconciliation(id, user?.id);
|
|
178
|
+
}
|
|
179
|
+
|
|
147
180
|
@Patch('accounts-payable/installments/:id/tags')
|
|
148
181
|
async updateAccountsPayableInstallmentTags(
|
|
149
182
|
@Param('id', ParseIntPipe) id: number,
|
|
@@ -260,7 +293,7 @@ export class FinanceInstallmentsController {
|
|
|
260
293
|
async reverseAccountsReceivableSettlement(
|
|
261
294
|
@Param('id', ParseIntPipe) id: number,
|
|
262
295
|
@Param('settlementId', ParseIntPipe) settlementId: number,
|
|
263
|
-
@Body() data,
|
|
296
|
+
@Body() data: ReverseSettlementDto,
|
|
264
297
|
@Locale() locale: string,
|
|
265
298
|
@User() user,
|
|
266
299
|
) {
|