@hed-hog/finance 0.0.251 → 0.0.252
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/finance.service.d.ts +1 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +205 -137
- package/dist/finance.service.js.map +1 -1
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +176 -70
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +100 -5
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +19 -20
- package/hedhog/frontend/app/page.tsx.ejs +11 -4
- package/hedhog/frontend/messages/en.json +44 -0
- package/hedhog/frontend/messages/pt.json +44 -0
- package/hedhog/query/constraints.sql +3 -1
- package/package.json +5 -5
- package/src/finance.service.ts +98 -14
|
@@ -221,8 +221,12 @@ function Alertas({
|
|
|
221
221
|
(e) => e.statusConciliacao === 'pendente'
|
|
222
222
|
).length;
|
|
223
223
|
|
|
224
|
-
const periodoBase = periodoAberto?.inicio
|
|
225
|
-
|
|
224
|
+
const periodoBase = periodoAberto?.inicio
|
|
225
|
+
? new Date(periodoAberto.inicio)
|
|
226
|
+
: new Date();
|
|
227
|
+
const mes = new Intl.DateTimeFormat(locale, { month: 'long' }).format(
|
|
228
|
+
periodoBase
|
|
229
|
+
);
|
|
226
230
|
const periodoAtual = `${mes.charAt(0).toUpperCase()}${mes.slice(1)}/${periodoBase.getFullYear()}`;
|
|
227
231
|
|
|
228
232
|
return (
|
|
@@ -281,6 +285,9 @@ export default function DashboardPage() {
|
|
|
281
285
|
} = data;
|
|
282
286
|
|
|
283
287
|
const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
|
|
288
|
+
const titulosPagarAprovados = titulosPagar.filter(
|
|
289
|
+
(titulo) => titulo.status !== 'rascunho' && titulo.status !== 'cancelado'
|
|
290
|
+
);
|
|
284
291
|
|
|
285
292
|
return (
|
|
286
293
|
<Page>
|
|
@@ -335,7 +342,7 @@ export default function DashboardPage() {
|
|
|
335
342
|
</CardContent>
|
|
336
343
|
</Card>
|
|
337
344
|
<Alertas
|
|
338
|
-
titulosPagar={
|
|
345
|
+
titulosPagar={titulosPagarAprovados}
|
|
339
346
|
extratos={extratos}
|
|
340
347
|
periodoAberto={periodoAberto}
|
|
341
348
|
locale={locale}
|
|
@@ -344,7 +351,7 @@ export default function DashboardPage() {
|
|
|
344
351
|
</div>
|
|
345
352
|
|
|
346
353
|
<ProximosVencimentos
|
|
347
|
-
titulosPagar={
|
|
354
|
+
titulosPagar={titulosPagarAprovados}
|
|
348
355
|
titulosReceber={titulosReceber}
|
|
349
356
|
getPessoaById={getPessoaById}
|
|
350
357
|
t={t}
|
|
@@ -198,6 +198,44 @@
|
|
|
198
198
|
"reverse": "Reverse",
|
|
199
199
|
"cancel": "Cancel"
|
|
200
200
|
},
|
|
201
|
+
"validation": {
|
|
202
|
+
"installmentRequired": "Installment is required",
|
|
203
|
+
"amountGreaterThanZero": "Amount must be greater than zero"
|
|
204
|
+
},
|
|
205
|
+
"dialogs": {
|
|
206
|
+
"cancel": {
|
|
207
|
+
"title": "Confirm cancellation",
|
|
208
|
+
"description": "This action changes the title status to canceled and does not remove audit records.",
|
|
209
|
+
"cancel": "Cancel",
|
|
210
|
+
"confirm": "Confirm cancellation"
|
|
211
|
+
},
|
|
212
|
+
"reverse": {
|
|
213
|
+
"title": "Confirm reversal",
|
|
214
|
+
"description": "This action reverses the selected settlement, recalculates installment balances, and updates the title status.",
|
|
215
|
+
"cancel": "Cancel",
|
|
216
|
+
"confirm": "Confirm reversal"
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"settleSheet": {
|
|
220
|
+
"title": "Register settlement",
|
|
221
|
+
"description": "Provide the installment and settlement amount. The backend validates status and amount limits automatically.",
|
|
222
|
+
"installmentLabel": "Installment",
|
|
223
|
+
"installmentPlaceholder": "Select",
|
|
224
|
+
"installmentOption": "Installment {number} - open amount: {amount}",
|
|
225
|
+
"amountLabel": "Amount",
|
|
226
|
+
"descriptionLabel": "Description (optional)",
|
|
227
|
+
"confirm": "Confirm settlement"
|
|
228
|
+
},
|
|
229
|
+
"messages": {
|
|
230
|
+
"approveSuccess": "Title approved successfully",
|
|
231
|
+
"approveError": "Could not approve the title",
|
|
232
|
+
"settleSuccess": "Settlement registered successfully",
|
|
233
|
+
"settleError": "Could not register settlement",
|
|
234
|
+
"reverseSuccess": "Reversal completed successfully",
|
|
235
|
+
"reverseError": "Could not reverse the settlement",
|
|
236
|
+
"cancelSuccess": "Title canceled successfully",
|
|
237
|
+
"cancelError": "Could not cancel the title"
|
|
238
|
+
},
|
|
201
239
|
"documentData": {
|
|
202
240
|
"title": "Document Data",
|
|
203
241
|
"supplier": "Supplier",
|
|
@@ -254,6 +292,12 @@
|
|
|
254
292
|
"fine": "Fine",
|
|
255
293
|
"account": "Account",
|
|
256
294
|
"method": "Method",
|
|
295
|
+
"actions": "Actions",
|
|
296
|
+
"reverseButton": "Reverse",
|
|
297
|
+
"reverseDialogTitle": "Confirm reversal",
|
|
298
|
+
"reverseDialogDescription": "This action creates a reversal for the settlement and recalculates balances and status.",
|
|
299
|
+
"reverseDialogCancel": "Cancel",
|
|
300
|
+
"reverseDialogConfirm": "Confirm reversal",
|
|
257
301
|
"none": "No settlement recorded"
|
|
258
302
|
},
|
|
259
303
|
"audit": { "none": "No audit event" }
|
|
@@ -198,6 +198,44 @@
|
|
|
198
198
|
"reverse": "Estornar",
|
|
199
199
|
"cancel": "Cancelar"
|
|
200
200
|
},
|
|
201
|
+
"validation": {
|
|
202
|
+
"installmentRequired": "Parcela obrigatória",
|
|
203
|
+
"amountGreaterThanZero": "Valor deve ser maior que zero"
|
|
204
|
+
},
|
|
205
|
+
"dialogs": {
|
|
206
|
+
"cancel": {
|
|
207
|
+
"title": "Confirmar cancelamento",
|
|
208
|
+
"description": "Essa ação altera o título para cancelado e não remove os registros de auditoria.",
|
|
209
|
+
"cancel": "Cancelar",
|
|
210
|
+
"confirm": "Confirmar cancelamento"
|
|
211
|
+
},
|
|
212
|
+
"reverse": {
|
|
213
|
+
"title": "Confirmar estorno",
|
|
214
|
+
"description": "Esta ação estorna a liquidação selecionada, recalcula os saldos das parcelas e atualiza o status do título.",
|
|
215
|
+
"cancel": "Cancelar",
|
|
216
|
+
"confirm": "Confirmar estorno"
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"settleSheet": {
|
|
220
|
+
"title": "Registrar baixa",
|
|
221
|
+
"description": "Informe a parcela e o valor da baixa. O backend valida os estados e limites de valor automaticamente.",
|
|
222
|
+
"installmentLabel": "Parcela",
|
|
223
|
+
"installmentPlaceholder": "Selecione",
|
|
224
|
+
"installmentOption": "Parcela {number} - em aberto: {amount}",
|
|
225
|
+
"amountLabel": "Valor",
|
|
226
|
+
"descriptionLabel": "Descrição (opcional)",
|
|
227
|
+
"confirm": "Confirmar baixa"
|
|
228
|
+
},
|
|
229
|
+
"messages": {
|
|
230
|
+
"approveSuccess": "Título aprovado com sucesso",
|
|
231
|
+
"approveError": "Não foi possível aprovar o título",
|
|
232
|
+
"settleSuccess": "Baixa registrada com sucesso",
|
|
233
|
+
"settleError": "Não foi possível registrar a baixa",
|
|
234
|
+
"reverseSuccess": "Estorno realizado com sucesso",
|
|
235
|
+
"reverseError": "Não foi possível estornar a liquidação",
|
|
236
|
+
"cancelSuccess": "Título cancelado com sucesso",
|
|
237
|
+
"cancelError": "Não foi possível cancelar o título"
|
|
238
|
+
},
|
|
201
239
|
"documentData": {
|
|
202
240
|
"title": "Dados do Documento",
|
|
203
241
|
"supplier": "Fornecedor",
|
|
@@ -254,6 +292,12 @@
|
|
|
254
292
|
"fine": "Multa",
|
|
255
293
|
"account": "Conta",
|
|
256
294
|
"method": "Método",
|
|
295
|
+
"actions": "Ações",
|
|
296
|
+
"reverseButton": "Estornar",
|
|
297
|
+
"reverseDialogTitle": "Confirmar estorno",
|
|
298
|
+
"reverseDialogDescription": "Esta ação cria o estorno da liquidação e recalcula saldos e status.",
|
|
299
|
+
"reverseDialogCancel": "Cancelar",
|
|
300
|
+
"reverseDialogConfirm": "Confirmar estorno",
|
|
257
301
|
"none": "Nenhuma liquidação registrada"
|
|
258
302
|
},
|
|
259
303
|
"audit": { "none": "Nenhum evento de auditoria" }
|
|
@@ -102,7 +102,9 @@ BEGIN
|
|
|
102
102
|
SELECT COALESCE(SUM(sa.allocated_amount_cents), 0)
|
|
103
103
|
INTO allocated_total_cents
|
|
104
104
|
FROM settlement_allocation sa
|
|
105
|
-
|
|
105
|
+
INNER JOIN settlement s ON s.id = sa.settlement_id
|
|
106
|
+
WHERE sa.installment_id = target_installment_id
|
|
107
|
+
AND s.status <> 'reversed';
|
|
106
108
|
|
|
107
109
|
IF allocated_total_cents > installment_amount_cents THEN
|
|
108
110
|
RAISE EXCEPTION
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/finance",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.252",
|
|
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-pagination": "0.0.5",
|
|
13
|
-
"@hed-hog/contact": "0.0.251",
|
|
14
12
|
"@hed-hog/api-prisma": "0.0.4",
|
|
13
|
+
"@hed-hog/contact": "0.0.251",
|
|
14
|
+
"@hed-hog/api-pagination": "0.0.5",
|
|
15
15
|
"@hed-hog/tag": "0.0.251",
|
|
16
16
|
"@hed-hog/api-locale": "0.0.11",
|
|
17
17
|
"@hed-hog/api-types": "0.0.1",
|
|
18
|
-
"@hed-hog/
|
|
19
|
-
"@hed-hog/
|
|
18
|
+
"@hed-hog/core": "0.0.251",
|
|
19
|
+
"@hed-hog/api": "0.0.3"
|
|
20
20
|
},
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
package/src/finance.service.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { PrismaService } from '@hed-hog/api-prisma';
|
|
|
4
4
|
import { AiService, FileService } from '@hed-hog/core';
|
|
5
5
|
import {
|
|
6
6
|
BadRequestException,
|
|
7
|
+
ConflictException,
|
|
7
8
|
forwardRef,
|
|
8
9
|
Inject,
|
|
9
10
|
Injectable,
|
|
@@ -833,7 +834,9 @@ export class FinanceService {
|
|
|
833
834
|
const day7 = this.addDays(today, 7);
|
|
834
835
|
const day30 = this.addDays(today, 30);
|
|
835
836
|
|
|
836
|
-
const payableInstallments = this.extractOpenInstallments(
|
|
837
|
+
const payableInstallments = this.extractOpenInstallments(
|
|
838
|
+
(payables || []).filter((title) => this.isPayableTitleApproved(title)),
|
|
839
|
+
);
|
|
837
840
|
const receivableInstallments = this.extractOpenInstallments(receivables);
|
|
838
841
|
|
|
839
842
|
const saldoCaixa = (bankAccounts || [])
|
|
@@ -887,6 +890,11 @@ export class FinanceService {
|
|
|
887
890
|
);
|
|
888
891
|
}
|
|
889
892
|
|
|
893
|
+
private isPayableTitleApproved(title: any) {
|
|
894
|
+
const status = String(title?.status || '').toLowerCase();
|
|
895
|
+
return status !== 'rascunho' && status !== 'cancelado';
|
|
896
|
+
}
|
|
897
|
+
|
|
890
898
|
private sumInstallmentsDueBetween(
|
|
891
899
|
installments: any[],
|
|
892
900
|
startDate: Date,
|
|
@@ -2906,12 +2914,46 @@ export class FinanceService {
|
|
|
2906
2914
|
throw new BadRequestException('Title cannot be canceled in current status');
|
|
2907
2915
|
}
|
|
2908
2916
|
|
|
2917
|
+
const hasActiveSettlements = await tx.settlement_allocation.findFirst({
|
|
2918
|
+
where: {
|
|
2919
|
+
financial_installment: {
|
|
2920
|
+
title_id: title.id,
|
|
2921
|
+
},
|
|
2922
|
+
settlement: {
|
|
2923
|
+
status: {
|
|
2924
|
+
not: 'reversed',
|
|
2925
|
+
},
|
|
2926
|
+
},
|
|
2927
|
+
},
|
|
2928
|
+
select: {
|
|
2929
|
+
id: true,
|
|
2930
|
+
},
|
|
2931
|
+
});
|
|
2932
|
+
|
|
2933
|
+
if (hasActiveSettlements) {
|
|
2934
|
+
throw new ConflictException(
|
|
2935
|
+
'Não é possível cancelar enquanto houver liquidações ativas. Estorne primeiro.',
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2909
2939
|
await this.assertDateNotInClosedPeriod(
|
|
2910
2940
|
tx,
|
|
2911
2941
|
title.competence_date,
|
|
2912
2942
|
'cancel title',
|
|
2913
2943
|
);
|
|
2914
2944
|
|
|
2945
|
+
await tx.financial_installment.updateMany({
|
|
2946
|
+
where: {
|
|
2947
|
+
title_id: title.id,
|
|
2948
|
+
status: {
|
|
2949
|
+
not: 'canceled',
|
|
2950
|
+
},
|
|
2951
|
+
},
|
|
2952
|
+
data: {
|
|
2953
|
+
status: 'canceled',
|
|
2954
|
+
},
|
|
2955
|
+
});
|
|
2956
|
+
|
|
2915
2957
|
await tx.financial_title.update({
|
|
2916
2958
|
where: { id: title.id },
|
|
2917
2959
|
data: {
|
|
@@ -2966,8 +3008,9 @@ export class FinanceService {
|
|
|
2966
3008
|
throw new BadRequestException('Invalid settlement date');
|
|
2967
3009
|
}
|
|
2968
3010
|
|
|
2969
|
-
|
|
2970
|
-
const
|
|
3011
|
+
try {
|
|
3012
|
+
const result = await this.prisma.$transaction(async (tx) => {
|
|
3013
|
+
const title = await tx.financial_title.findFirst({
|
|
2971
3014
|
where: {
|
|
2972
3015
|
id: titleId,
|
|
2973
3016
|
title_type: titleType,
|
|
@@ -2991,6 +3034,10 @@ export class FinanceService {
|
|
|
2991
3034
|
}
|
|
2992
3035
|
|
|
2993
3036
|
if (!['open', 'partial'].includes(title.status)) {
|
|
3037
|
+
if (title.status === 'canceled') {
|
|
3038
|
+
throw new ConflictException('Título cancelado não pode receber baixa');
|
|
3039
|
+
}
|
|
3040
|
+
|
|
2994
3041
|
throw new BadRequestException(
|
|
2995
3042
|
'Only open/partial titles can be settled',
|
|
2996
3043
|
);
|
|
@@ -2998,7 +3045,7 @@ export class FinanceService {
|
|
|
2998
3045
|
|
|
2999
3046
|
await this.assertDateNotInClosedPeriod(
|
|
3000
3047
|
tx,
|
|
3001
|
-
|
|
3048
|
+
settledAt,
|
|
3002
3049
|
'settle installment',
|
|
3003
3050
|
);
|
|
3004
3051
|
|
|
@@ -3022,11 +3069,15 @@ export class FinanceService {
|
|
|
3022
3069
|
}
|
|
3023
3070
|
|
|
3024
3071
|
if (installment.status === 'settled' || installment.status === 'canceled') {
|
|
3025
|
-
|
|
3072
|
+
if (installment.status === 'settled') {
|
|
3073
|
+
throw new ConflictException('Parcela já liquidada');
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
throw new ConflictException('Parcela cancelada não pode receber baixa');
|
|
3026
3077
|
}
|
|
3027
3078
|
|
|
3028
3079
|
if (amountCents > installment.open_amount_cents) {
|
|
3029
|
-
throw new
|
|
3080
|
+
throw new ConflictException('Settlement amount exceeds open amount');
|
|
3030
3081
|
}
|
|
3031
3082
|
|
|
3032
3083
|
const paymentMethodId = await this.resolvePaymentMethodId(
|
|
@@ -3145,16 +3196,27 @@ export class FinanceService {
|
|
|
3145
3196
|
throw new NotFoundException('Financial title not found');
|
|
3146
3197
|
}
|
|
3147
3198
|
|
|
3199
|
+
return {
|
|
3200
|
+
title: updatedTitle,
|
|
3201
|
+
settlementId: settlement.id,
|
|
3202
|
+
};
|
|
3203
|
+
});
|
|
3204
|
+
|
|
3148
3205
|
return {
|
|
3149
|
-
title
|
|
3150
|
-
settlementId:
|
|
3206
|
+
...this.mapTitleToFront(result.title),
|
|
3207
|
+
settlementId: String(result.settlementId),
|
|
3151
3208
|
};
|
|
3152
|
-
})
|
|
3209
|
+
} catch (error: any) {
|
|
3210
|
+
const message = String(error?.message || '');
|
|
3153
3211
|
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3212
|
+
if (message.includes('Soma de settlement_allocation')) {
|
|
3213
|
+
throw new ConflictException(
|
|
3214
|
+
'Não foi possível registrar a baixa. Existe alocação ativa acima do limite da parcela.',
|
|
3215
|
+
);
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
throw error;
|
|
3219
|
+
}
|
|
3158
3220
|
}
|
|
3159
3221
|
|
|
3160
3222
|
private async reverseTitleSettlement(
|
|
@@ -3228,7 +3290,7 @@ export class FinanceService {
|
|
|
3228
3290
|
}
|
|
3229
3291
|
|
|
3230
3292
|
if (settlement.status === 'reversed') {
|
|
3231
|
-
throw new
|
|
3293
|
+
throw new ConflictException('Liquidação já estornada.');
|
|
3232
3294
|
}
|
|
3233
3295
|
|
|
3234
3296
|
for (const allocation of settlement.settlement_allocation) {
|
|
@@ -3279,6 +3341,28 @@ export class FinanceService {
|
|
|
3279
3341
|
},
|
|
3280
3342
|
});
|
|
3281
3343
|
|
|
3344
|
+
await tx.bank_reconciliation.updateMany({
|
|
3345
|
+
where: {
|
|
3346
|
+
settlement_id: settlement.id,
|
|
3347
|
+
status: 'pending',
|
|
3348
|
+
},
|
|
3349
|
+
data: {
|
|
3350
|
+
status: 'reversed',
|
|
3351
|
+
},
|
|
3352
|
+
});
|
|
3353
|
+
|
|
3354
|
+
await tx.bank_reconciliation.updateMany({
|
|
3355
|
+
where: {
|
|
3356
|
+
settlement_id: settlement.id,
|
|
3357
|
+
status: {
|
|
3358
|
+
in: ['reconciled', 'adjusted'],
|
|
3359
|
+
},
|
|
3360
|
+
},
|
|
3361
|
+
data: {
|
|
3362
|
+
status: 'adjusted',
|
|
3363
|
+
},
|
|
3364
|
+
});
|
|
3365
|
+
|
|
3282
3366
|
const previousTitleStatus = title.status;
|
|
3283
3367
|
const nextTitleStatus = await this.recalculateTitleStatus(tx, title.id);
|
|
3284
3368
|
|