@hed-hog/finance 0.0.235 → 0.0.237
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/create-cost-center.dto.d.ts +4 -0
- package/dist/dto/create-cost-center.dto.d.ts.map +1 -0
- package/dist/dto/create-cost-center.dto.js +24 -0
- package/dist/dto/create-cost-center.dto.js.map +1 -0
- package/dist/dto/create-finance-category.dto.d.ts +6 -0
- package/dist/dto/create-finance-category.dto.d.ts.map +1 -0
- package/dist/dto/create-finance-category.dto.js +37 -0
- package/dist/dto/create-finance-category.dto.js.map +1 -0
- package/dist/dto/create-period-close.dto.d.ts +7 -0
- package/dist/dto/create-period-close.dto.d.ts.map +1 -0
- package/dist/dto/create-period-close.dto.js +44 -0
- package/dist/dto/create-period-close.dto.js.map +1 -0
- package/dist/dto/move-finance-category.dto.d.ts +5 -0
- package/dist/dto/move-finance-category.dto.d.ts.map +1 -0
- package/dist/dto/move-finance-category.dto.js +32 -0
- package/dist/dto/move-finance-category.dto.js.map +1 -0
- package/dist/dto/update-cost-center.dto.d.ts +5 -0
- package/dist/dto/update-cost-center.dto.d.ts.map +1 -0
- package/dist/dto/update-cost-center.dto.js +32 -0
- package/dist/dto/update-cost-center.dto.js.map +1 -0
- package/dist/dto/update-finance-category.dto.d.ts +7 -0
- package/dist/dto/update-finance-category.dto.d.ts.map +1 -0
- package/dist/dto/update-finance-category.dto.js +46 -0
- package/dist/dto/update-finance-category.dto.js.map +1 -0
- package/dist/finance-audit-logs.controller.d.ts +13 -0
- package/dist/finance-audit-logs.controller.d.ts.map +1 -0
- package/dist/finance-audit-logs.controller.js +54 -0
- package/dist/finance-audit-logs.controller.js.map +1 -0
- package/dist/finance-categories.controller.d.ts +42 -0
- package/dist/finance-categories.controller.d.ts.map +1 -0
- package/dist/finance-categories.controller.js +84 -0
- package/dist/finance-categories.controller.js.map +1 -0
- package/dist/finance-cost-centers.controller.d.ts +32 -0
- package/dist/finance-cost-centers.controller.d.ts.map +1 -0
- package/dist/finance-cost-centers.controller.js +72 -0
- package/dist/finance-cost-centers.controller.js.map +1 -0
- package/dist/finance-period-close.controller.d.ts +27 -0
- package/dist/finance-period-close.controller.d.ts.map +1 -0
- package/dist/finance-period-close.controller.js +64 -0
- package/dist/finance-period-close.controller.js.map +1 -0
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +8 -0
- package/dist/finance.module.js.map +1 -1
- package/dist/finance.service.d.ts +111 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +446 -17
- package/dist/finance.service.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/hedhog/data/route.yaml +108 -0
- package/hedhog/frontend/app/_components/person-field-with-create.tsx.ejs +627 -0
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +865 -883
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +838 -861
- package/hedhog/frontend/app/administration/audit-logs/page.tsx.ejs +309 -0
- package/hedhog/frontend/app/administration/categories/page.tsx.ejs +725 -0
- package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +378 -0
- package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +502 -0
- package/hedhog/frontend/messages/en.json +225 -0
- package/hedhog/frontend/messages/pt.json +225 -0
- package/package.json +5 -5
- package/src/dto/create-cost-center.dto.ts +9 -0
- package/src/dto/create-finance-category.dto.ts +21 -0
- package/src/dto/create-period-close.dto.ts +34 -0
- package/src/dto/move-finance-category.dto.ts +18 -0
- package/src/dto/update-cost-center.dto.ts +17 -0
- package/src/dto/update-finance-category.dto.ts +30 -0
- package/src/finance-audit-logs.controller.ts +30 -0
- package/src/finance-categories.controller.ts +52 -0
- package/src/finance-cost-centers.controller.ts +43 -0
- package/src/finance-period-close.controller.ts +34 -0
- package/src/finance.module.ts +8 -0
- package/src/finance.service.ts +578 -9
- package/src/index.ts +4 -0
package/src/finance.service.ts
CHANGED
|
@@ -9,8 +9,14 @@ import {
|
|
|
9
9
|
NotFoundException
|
|
10
10
|
} from '@nestjs/common';
|
|
11
11
|
import { CreateBankAccountDto } from './dto/create-bank-account.dto';
|
|
12
|
+
import { CreateCostCenterDto } from './dto/create-cost-center.dto';
|
|
13
|
+
import { CreateFinanceCategoryDto } from './dto/create-finance-category.dto';
|
|
12
14
|
import { CreateFinancialTitleDto } from './dto/create-financial-title.dto';
|
|
15
|
+
import { CreatePeriodCloseDto } from './dto/create-period-close.dto';
|
|
16
|
+
import { MoveFinanceCategoryDto } from './dto/move-finance-category.dto';
|
|
13
17
|
import { UpdateBankAccountDto } from './dto/update-bank-account.dto';
|
|
18
|
+
import { UpdateCostCenterDto } from './dto/update-cost-center.dto';
|
|
19
|
+
import { UpdateFinanceCategoryDto } from './dto/update-finance-category.dto';
|
|
14
20
|
|
|
15
21
|
type TitleType = 'payable' | 'receivable';
|
|
16
22
|
|
|
@@ -797,6 +803,221 @@ export class FinanceService {
|
|
|
797
803
|
return bankAccounts.map((bankAccount) => this.mapBankAccountToFront(bankAccount));
|
|
798
804
|
}
|
|
799
805
|
|
|
806
|
+
async listCostCenters() {
|
|
807
|
+
const costCenters = await this.prisma.cost_center.findMany({
|
|
808
|
+
orderBy: [{ code: 'asc' }, { name: 'asc' }],
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
return costCenters.map((costCenter) => this.mapCostCenterToFront(costCenter));
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async listAuditLogs(
|
|
815
|
+
paginationParams,
|
|
816
|
+
filters?: {
|
|
817
|
+
search?: string;
|
|
818
|
+
action?: string;
|
|
819
|
+
entity_table?: string;
|
|
820
|
+
actor_user_id?: string;
|
|
821
|
+
from?: string;
|
|
822
|
+
to?: string;
|
|
823
|
+
},
|
|
824
|
+
) {
|
|
825
|
+
const actorUserId = filters?.actor_user_id
|
|
826
|
+
? Number.parseInt(filters.actor_user_id, 10)
|
|
827
|
+
: undefined;
|
|
828
|
+
|
|
829
|
+
const fromDate = filters?.from ? new Date(filters.from) : undefined;
|
|
830
|
+
const toDate = filters?.to ? new Date(filters.to) : undefined;
|
|
831
|
+
|
|
832
|
+
const where: any = {
|
|
833
|
+
...(filters?.action ? { action: filters.action } : {}),
|
|
834
|
+
...(filters?.entity_table ? { entity_table: filters.entity_table } : {}),
|
|
835
|
+
...(Number.isNaN(actorUserId) || !actorUserId
|
|
836
|
+
? {}
|
|
837
|
+
: { actor_user_id: actorUserId }),
|
|
838
|
+
...((fromDate || toDate) &&
|
|
839
|
+
!(fromDate && Number.isNaN(fromDate.getTime())) &&
|
|
840
|
+
!(toDate && Number.isNaN(toDate.getTime()))
|
|
841
|
+
? {
|
|
842
|
+
created_at: {
|
|
843
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
844
|
+
...(toDate ? { lte: toDate } : {}),
|
|
845
|
+
},
|
|
846
|
+
}
|
|
847
|
+
: {}),
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const search = filters?.search?.trim();
|
|
851
|
+
if (search) {
|
|
852
|
+
where.OR = [
|
|
853
|
+
{ action: { contains: search, mode: 'insensitive' } },
|
|
854
|
+
{ entity_table: { contains: search, mode: 'insensitive' } },
|
|
855
|
+
{ entity_id: { contains: search, mode: 'insensitive' } },
|
|
856
|
+
{ summary: { contains: search, mode: 'insensitive' } },
|
|
857
|
+
{ ip_address: { contains: search, mode: 'insensitive' } },
|
|
858
|
+
];
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const paginated = await this.paginationService.paginatePrismaModel(
|
|
862
|
+
this.prisma.audit_log,
|
|
863
|
+
{
|
|
864
|
+
page: paginationParams?.page,
|
|
865
|
+
pageSize: paginationParams?.pageSize,
|
|
866
|
+
sortField: paginationParams?.sortField || 'created_at',
|
|
867
|
+
sortOrder: paginationParams?.sortOrder || 'desc',
|
|
868
|
+
validSortFields: [
|
|
869
|
+
'id',
|
|
870
|
+
'created_at',
|
|
871
|
+
'action',
|
|
872
|
+
'entity_table',
|
|
873
|
+
'entity_id',
|
|
874
|
+
],
|
|
875
|
+
where,
|
|
876
|
+
include: {
|
|
877
|
+
user: {
|
|
878
|
+
select: {
|
|
879
|
+
id: true,
|
|
880
|
+
name: true,
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
return {
|
|
888
|
+
...paginated,
|
|
889
|
+
data: (paginated.data || []).map((log) => ({
|
|
890
|
+
id: String(log.id),
|
|
891
|
+
actorUserId: log.actor_user_id ? String(log.actor_user_id) : null,
|
|
892
|
+
actorName: log.user?.name || '-',
|
|
893
|
+
actorEmail: '',
|
|
894
|
+
action: log.action,
|
|
895
|
+
entityTable: log.entity_table,
|
|
896
|
+
entityId: log.entity_id,
|
|
897
|
+
summary: log.summary || '',
|
|
898
|
+
ipAddress: log.ip_address || '',
|
|
899
|
+
beforeData: log.before_data || '',
|
|
900
|
+
afterData: log.after_data || '',
|
|
901
|
+
createdAt: log.created_at?.toISOString?.() || null,
|
|
902
|
+
})),
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async listFinanceCategories() {
|
|
907
|
+
const categories = await this.prisma.finance_category.findMany({
|
|
908
|
+
orderBy: [{ parent_id: 'asc' }, { updated_at: 'asc' }, { name: 'asc' }],
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
return categories.map((category) => this.mapFinanceCategoryToFront(category));
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
async listPeriodClose(
|
|
915
|
+
paginationParams: PaginationDTO,
|
|
916
|
+
filters?: {
|
|
917
|
+
search?: string;
|
|
918
|
+
status?: string;
|
|
919
|
+
user?: string;
|
|
920
|
+
from?: string;
|
|
921
|
+
to?: string;
|
|
922
|
+
},
|
|
923
|
+
) {
|
|
924
|
+
const fromDate = filters?.from ? new Date(filters.from) : undefined;
|
|
925
|
+
const toDate = filters?.to ? new Date(filters.to) : undefined;
|
|
926
|
+
const userNumericId = filters?.user
|
|
927
|
+
? Number.parseInt(filters.user, 10)
|
|
928
|
+
: undefined;
|
|
929
|
+
|
|
930
|
+
const andConditions: any[] = [];
|
|
931
|
+
|
|
932
|
+
if (filters?.status && filters.status !== 'all') {
|
|
933
|
+
andConditions.push({ status: filters.status });
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (
|
|
937
|
+
(fromDate && !Number.isNaN(fromDate.getTime())) ||
|
|
938
|
+
(toDate && !Number.isNaN(toDate.getTime()))
|
|
939
|
+
) {
|
|
940
|
+
andConditions.push({
|
|
941
|
+
period_start: {
|
|
942
|
+
...(fromDate && !Number.isNaN(fromDate.getTime())
|
|
943
|
+
? { gte: fromDate }
|
|
944
|
+
: {}),
|
|
945
|
+
},
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
andConditions.push({
|
|
949
|
+
period_end: {
|
|
950
|
+
...(toDate && !Number.isNaN(toDate.getTime()) ? { lte: toDate } : {}),
|
|
951
|
+
},
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const search = filters?.search?.trim();
|
|
956
|
+
if (search) {
|
|
957
|
+
andConditions.push({
|
|
958
|
+
OR: [
|
|
959
|
+
{ notes: { contains: search, mode: 'insensitive' } },
|
|
960
|
+
{ user: { is: { name: { contains: search, mode: 'insensitive' } } } },
|
|
961
|
+
{
|
|
962
|
+
user: { is: { email: { contains: search, mode: 'insensitive' } } },
|
|
963
|
+
},
|
|
964
|
+
],
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (filters?.user?.trim()) {
|
|
969
|
+
andConditions.push({
|
|
970
|
+
OR: [
|
|
971
|
+
...(Number.isNaN(userNumericId) || !userNumericId
|
|
972
|
+
? []
|
|
973
|
+
: [{ closed_by_user_id: userNumericId }]),
|
|
974
|
+
{ user: { is: { name: { contains: filters.user, mode: 'insensitive' } } } },
|
|
975
|
+
{
|
|
976
|
+
user: {
|
|
977
|
+
is: { email: { contains: filters.user, mode: 'insensitive' } },
|
|
978
|
+
},
|
|
979
|
+
},
|
|
980
|
+
],
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const where = andConditions.length > 0 ? { AND: andConditions } : {};
|
|
985
|
+
|
|
986
|
+
const paginated = await this.paginationService.paginatePrismaModel(
|
|
987
|
+
this.prisma.period_close,
|
|
988
|
+
{
|
|
989
|
+
page: paginationParams?.page,
|
|
990
|
+
pageSize: paginationParams?.pageSize,
|
|
991
|
+
sortField: paginationParams?.sortField || 'period_start',
|
|
992
|
+
sortOrder: paginationParams?.sortOrder || 'desc',
|
|
993
|
+
validSortFields: [
|
|
994
|
+
'id',
|
|
995
|
+
'period_start',
|
|
996
|
+
'period_end',
|
|
997
|
+
'status',
|
|
998
|
+
'closed_at',
|
|
999
|
+
'created_at',
|
|
1000
|
+
],
|
|
1001
|
+
where,
|
|
1002
|
+
include: {
|
|
1003
|
+
user: {
|
|
1004
|
+
select: {
|
|
1005
|
+
id: true,
|
|
1006
|
+
name: true,
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
},
|
|
1010
|
+
},
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
return {
|
|
1014
|
+
...paginated,
|
|
1015
|
+
data: (paginated.data || []).map((period) =>
|
|
1016
|
+
this.mapPeriodCloseToFront(period),
|
|
1017
|
+
),
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
800
1021
|
async listBankStatements(bankAccountId?: number) {
|
|
801
1022
|
const statements = await this.prisma.bank_statement_line.findMany({
|
|
802
1023
|
where: {
|
|
@@ -879,6 +1100,89 @@ export class FinanceService {
|
|
|
879
1100
|
return this.mapBankAccountToFront(account);
|
|
880
1101
|
}
|
|
881
1102
|
|
|
1103
|
+
async createCostCenter(data: CreateCostCenterDto) {
|
|
1104
|
+
const code = await this.generateCostCenterCode(data.name);
|
|
1105
|
+
|
|
1106
|
+
const created = await this.prisma.cost_center.create({
|
|
1107
|
+
data: {
|
|
1108
|
+
code,
|
|
1109
|
+
name: data.name.trim(),
|
|
1110
|
+
status: 'active',
|
|
1111
|
+
},
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
return this.mapCostCenterToFront(created);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async createFinanceCategory(data: CreateFinanceCategoryDto) {
|
|
1118
|
+
if (data.parent_id) {
|
|
1119
|
+
await this.ensureFinanceCategoryExists(data.parent_id);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const created = await this.prisma.finance_category.create({
|
|
1123
|
+
data: {
|
|
1124
|
+
code: await this.generateFinanceCategoryCode(),
|
|
1125
|
+
name: data.name.trim(),
|
|
1126
|
+
kind: this.mapCategoryKindFromPt(data.kind),
|
|
1127
|
+
status: 'active',
|
|
1128
|
+
parent_id: data.parent_id || null,
|
|
1129
|
+
},
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
return this.mapFinanceCategoryToFront(created);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
async createPeriodClose(data: CreatePeriodCloseDto, userId?: number) {
|
|
1136
|
+
const periodStart = new Date(data.period_start);
|
|
1137
|
+
const periodEnd = new Date(data.period_end);
|
|
1138
|
+
|
|
1139
|
+
if (
|
|
1140
|
+
Number.isNaN(periodStart.getTime()) ||
|
|
1141
|
+
Number.isNaN(periodEnd.getTime())
|
|
1142
|
+
) {
|
|
1143
|
+
throw new BadRequestException('Invalid period dates');
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (periodStart > periodEnd) {
|
|
1147
|
+
throw new BadRequestException('period_start must be before period_end');
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
const overlapped = await this.prisma.period_close.findFirst({
|
|
1151
|
+
where: {
|
|
1152
|
+
period_start: { lte: periodEnd },
|
|
1153
|
+
period_end: { gte: periodStart },
|
|
1154
|
+
},
|
|
1155
|
+
select: { id: true },
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
if (overlapped) {
|
|
1159
|
+
throw new BadRequestException('There is already a period in this range');
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const status = data.status || 'closed';
|
|
1163
|
+
|
|
1164
|
+
const created = await this.prisma.period_close.create({
|
|
1165
|
+
data: {
|
|
1166
|
+
period_start: periodStart,
|
|
1167
|
+
period_end: periodEnd,
|
|
1168
|
+
status,
|
|
1169
|
+
closed_at: status === 'closed' ? new Date() : null,
|
|
1170
|
+
closed_by_user_id: status === 'closed' ? userId || null : null,
|
|
1171
|
+
notes: data.notes?.trim() || null,
|
|
1172
|
+
},
|
|
1173
|
+
include: {
|
|
1174
|
+
user: {
|
|
1175
|
+
select: {
|
|
1176
|
+
id: true,
|
|
1177
|
+
name: true,
|
|
1178
|
+
},
|
|
1179
|
+
},
|
|
1180
|
+
},
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
return this.mapPeriodCloseToFront(created);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
882
1186
|
async updateBankAccount(id: number, data: UpdateBankAccountDto) {
|
|
883
1187
|
const current = await this.prisma.bank_account.findUnique({
|
|
884
1188
|
where: { id },
|
|
@@ -912,6 +1216,104 @@ export class FinanceService {
|
|
|
912
1216
|
return this.mapBankAccountToFront(updated);
|
|
913
1217
|
}
|
|
914
1218
|
|
|
1219
|
+
async updateCostCenter(id: number, data: UpdateCostCenterDto) {
|
|
1220
|
+
const current = await this.prisma.cost_center.findUnique({
|
|
1221
|
+
where: { id },
|
|
1222
|
+
select: { id: true },
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
if (!current) {
|
|
1226
|
+
throw new NotFoundException('Cost center not found');
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const updated = await this.prisma.cost_center.update({
|
|
1230
|
+
where: { id },
|
|
1231
|
+
data: {
|
|
1232
|
+
name: data.name?.trim(),
|
|
1233
|
+
status: data.status,
|
|
1234
|
+
},
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
return this.mapCostCenterToFront(updated);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
async updateFinanceCategory(id: number, data: UpdateFinanceCategoryDto) {
|
|
1241
|
+
const current = await this.prisma.finance_category.findUnique({
|
|
1242
|
+
where: { id },
|
|
1243
|
+
select: { id: true },
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
if (!current) {
|
|
1247
|
+
throw new NotFoundException('Finance category not found');
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (data.parent_id) {
|
|
1251
|
+
await this.ensureFinanceCategoryExists(data.parent_id);
|
|
1252
|
+
await this.ensureNoFinanceCategoryCycle(id, data.parent_id);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const updated = await this.prisma.finance_category.update({
|
|
1256
|
+
where: { id },
|
|
1257
|
+
data: {
|
|
1258
|
+
name: data.name?.trim(),
|
|
1259
|
+
kind: data.kind ? this.mapCategoryKindFromPt(data.kind) : undefined,
|
|
1260
|
+
parent_id: data.parent_id,
|
|
1261
|
+
status: data.status,
|
|
1262
|
+
},
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
return this.mapFinanceCategoryToFront(updated);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
async moveFinanceCategory(id: number, data: MoveFinanceCategoryDto) {
|
|
1269
|
+
const current = await this.prisma.finance_category.findUnique({
|
|
1270
|
+
where: { id },
|
|
1271
|
+
select: { id: true, parent_id: true },
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
if (!current) {
|
|
1275
|
+
throw new NotFoundException('Finance category not found');
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
const targetParentId = data.parent_id || null;
|
|
1279
|
+
|
|
1280
|
+
if (targetParentId) {
|
|
1281
|
+
await this.ensureFinanceCategoryExists(targetParentId);
|
|
1282
|
+
await this.ensureNoFinanceCategoryCycle(id, targetParentId);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
await this.prisma.finance_category.update({
|
|
1286
|
+
where: { id },
|
|
1287
|
+
data: { parent_id: targetParentId },
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
const siblings = await this.prisma.finance_category.findMany({
|
|
1291
|
+
where: { parent_id: targetParentId },
|
|
1292
|
+
select: { id: true },
|
|
1293
|
+
orderBy: [{ updated_at: 'asc' }, { name: 'asc' }],
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
const siblingIds = siblings.map((item) => item.id).filter((item) => item !== id);
|
|
1297
|
+
const targetPosition = Math.max(
|
|
1298
|
+
0,
|
|
1299
|
+
Math.min(data.position ?? siblingIds.length, siblingIds.length),
|
|
1300
|
+
);
|
|
1301
|
+
siblingIds.splice(targetPosition, 0, id);
|
|
1302
|
+
|
|
1303
|
+
await this.prisma.$transaction(
|
|
1304
|
+
siblingIds.map((categoryId) =>
|
|
1305
|
+
this.prisma.finance_category.update({
|
|
1306
|
+
where: { id: categoryId },
|
|
1307
|
+
data: {
|
|
1308
|
+
parent_id: targetParentId,
|
|
1309
|
+
},
|
|
1310
|
+
}),
|
|
1311
|
+
),
|
|
1312
|
+
);
|
|
1313
|
+
|
|
1314
|
+
return { success: true };
|
|
1315
|
+
}
|
|
1316
|
+
|
|
915
1317
|
async deleteBankAccount(id: number) {
|
|
916
1318
|
const current = await this.prisma.bank_account.findUnique({
|
|
917
1319
|
where: { id },
|
|
@@ -932,6 +1334,46 @@ export class FinanceService {
|
|
|
932
1334
|
return { success: true };
|
|
933
1335
|
}
|
|
934
1336
|
|
|
1337
|
+
async deleteCostCenter(id: number) {
|
|
1338
|
+
const current = await this.prisma.cost_center.findUnique({
|
|
1339
|
+
where: { id },
|
|
1340
|
+
select: { id: true },
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
if (!current) {
|
|
1344
|
+
throw new NotFoundException('Cost center not found');
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
await this.prisma.cost_center.update({
|
|
1348
|
+
where: { id },
|
|
1349
|
+
data: {
|
|
1350
|
+
status: 'inactive',
|
|
1351
|
+
},
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
return { success: true };
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
async deleteFinanceCategory(id: number) {
|
|
1358
|
+
const current = await this.prisma.finance_category.findUnique({
|
|
1359
|
+
where: { id },
|
|
1360
|
+
select: { id: true },
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
if (!current) {
|
|
1364
|
+
throw new NotFoundException('Finance category not found');
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
await this.prisma.finance_category.update({
|
|
1368
|
+
where: { id },
|
|
1369
|
+
data: {
|
|
1370
|
+
status: 'inactive',
|
|
1371
|
+
},
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
return { success: true };
|
|
1375
|
+
}
|
|
1376
|
+
|
|
935
1377
|
private async listTitles(
|
|
936
1378
|
titleType: TitleType,
|
|
937
1379
|
paginationParams: PaginationDTO,
|
|
@@ -1096,14 +1538,6 @@ export class FinanceService {
|
|
|
1096
1538
|
|
|
1097
1539
|
private async loadPeople() {
|
|
1098
1540
|
const people = await this.prisma.person.findMany({
|
|
1099
|
-
include: {
|
|
1100
|
-
document: {
|
|
1101
|
-
select: {
|
|
1102
|
-
value: true,
|
|
1103
|
-
},
|
|
1104
|
-
take: 1,
|
|
1105
|
-
},
|
|
1106
|
-
},
|
|
1107
1541
|
orderBy: { name: 'asc' },
|
|
1108
1542
|
});
|
|
1109
1543
|
|
|
@@ -1111,7 +1545,7 @@ export class FinanceService {
|
|
|
1111
1545
|
id: String(person.id),
|
|
1112
1546
|
nome: person.name,
|
|
1113
1547
|
tipo: 'ambos',
|
|
1114
|
-
documento:
|
|
1548
|
+
documento: '',
|
|
1115
1549
|
}));
|
|
1116
1550
|
}
|
|
1117
1551
|
|
|
@@ -1394,6 +1828,76 @@ export class FinanceService {
|
|
|
1394
1828
|
};
|
|
1395
1829
|
}
|
|
1396
1830
|
|
|
1831
|
+
private mapCostCenterToFront(costCenter: any) {
|
|
1832
|
+
return {
|
|
1833
|
+
id: String(costCenter.id),
|
|
1834
|
+
codigo: costCenter.code,
|
|
1835
|
+
nome: costCenter.name,
|
|
1836
|
+
status: costCenter.status,
|
|
1837
|
+
ativo: costCenter.status === 'active',
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
private mapCategoryKindToPt(kind?: string | null) {
|
|
1842
|
+
const kindMap = {
|
|
1843
|
+
revenue: 'receita',
|
|
1844
|
+
expense: 'despesa',
|
|
1845
|
+
transfer: 'transferencia',
|
|
1846
|
+
adjustment: 'ajuste',
|
|
1847
|
+
other: 'outro',
|
|
1848
|
+
};
|
|
1849
|
+
|
|
1850
|
+
return kindMap[kind] || 'outro';
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
private mapCategoryKindFromPt(kind?: string | null) {
|
|
1854
|
+
const normalized = (kind || '').toLowerCase();
|
|
1855
|
+
const kindMap = {
|
|
1856
|
+
receita: 'revenue',
|
|
1857
|
+
revenue: 'revenue',
|
|
1858
|
+
despesa: 'expense',
|
|
1859
|
+
expense: 'expense',
|
|
1860
|
+
transferencia: 'transfer',
|
|
1861
|
+
transferência: 'transfer',
|
|
1862
|
+
transfer: 'transfer',
|
|
1863
|
+
ajuste: 'adjustment',
|
|
1864
|
+
adjustment: 'adjustment',
|
|
1865
|
+
outro: 'other',
|
|
1866
|
+
other: 'other',
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1869
|
+
return kindMap[normalized] || 'other';
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
private mapFinanceCategoryToFront(category: any) {
|
|
1873
|
+
return {
|
|
1874
|
+
id: String(category.id),
|
|
1875
|
+
codigo: category.code,
|
|
1876
|
+
nome: category.name,
|
|
1877
|
+
parentId: category.parent_id ? String(category.parent_id) : null,
|
|
1878
|
+
natureza: this.mapCategoryKindToPt(category.kind),
|
|
1879
|
+
status: category.status,
|
|
1880
|
+
ativo: category.status === 'active',
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
private mapPeriodCloseToFront(period: any) {
|
|
1885
|
+
return {
|
|
1886
|
+
id: String(period.id),
|
|
1887
|
+
periodStart: period.period_start?.toISOString?.() || null,
|
|
1888
|
+
periodEnd: period.period_end?.toISOString?.() || null,
|
|
1889
|
+
status: period.status,
|
|
1890
|
+
closedAt: period.closed_at?.toISOString?.() || null,
|
|
1891
|
+
closedByUserId: period.closed_by_user_id
|
|
1892
|
+
? String(period.closed_by_user_id)
|
|
1893
|
+
: null,
|
|
1894
|
+
closedByName: period.user?.name || '-',
|
|
1895
|
+
closedByEmail: '',
|
|
1896
|
+
notes: period.notes || '',
|
|
1897
|
+
createdAt: period.created_at?.toISOString?.() || null,
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1397
1901
|
private mapStatementStatusToPt(status?: string | null) {
|
|
1398
1902
|
const statusMap = {
|
|
1399
1903
|
pending: 'pendente',
|
|
@@ -1419,6 +1923,71 @@ export class FinanceService {
|
|
|
1419
1923
|
return `${bankPrefix || 'ACC'}-${accountSuffix}`;
|
|
1420
1924
|
}
|
|
1421
1925
|
|
|
1926
|
+
private async generateCostCenterCode(name?: string) {
|
|
1927
|
+
const sanitizedName = (name || 'CENTRO')
|
|
1928
|
+
.trim()
|
|
1929
|
+
.replace(/\s+/g, '-')
|
|
1930
|
+
.replace(/[^A-Za-z0-9-]/g, '')
|
|
1931
|
+
.toUpperCase();
|
|
1932
|
+
|
|
1933
|
+
const basePrefix = sanitizedName.slice(0, 8) || 'CENTRO';
|
|
1934
|
+
|
|
1935
|
+
const lastCostCenter = await this.prisma.cost_center.findFirst({
|
|
1936
|
+
orderBy: { id: 'desc' },
|
|
1937
|
+
select: { id: true },
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
const suffix = String((lastCostCenter?.id || 0) + 1).padStart(4, '0');
|
|
1941
|
+
|
|
1942
|
+
return `${basePrefix}-${suffix}`;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
private async generateFinanceCategoryCode() {
|
|
1946
|
+
const lastCategory = await this.prisma.finance_category.findFirst({
|
|
1947
|
+
orderBy: { id: 'desc' },
|
|
1948
|
+
select: { id: true },
|
|
1949
|
+
});
|
|
1950
|
+
|
|
1951
|
+
const suffix = String((lastCategory?.id || 0) + 1).padStart(4, '0');
|
|
1952
|
+
|
|
1953
|
+
return `CAT-${suffix}`;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
private async ensureFinanceCategoryExists(id: number) {
|
|
1957
|
+
const category = await this.prisma.finance_category.findUnique({
|
|
1958
|
+
where: { id },
|
|
1959
|
+
select: { id: true },
|
|
1960
|
+
});
|
|
1961
|
+
|
|
1962
|
+
if (!category) {
|
|
1963
|
+
throw new NotFoundException('Finance category not found');
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
private async ensureNoFinanceCategoryCycle(
|
|
1968
|
+
categoryId: number,
|
|
1969
|
+
targetParentId: number,
|
|
1970
|
+
) {
|
|
1971
|
+
if (categoryId === targetParentId) {
|
|
1972
|
+
throw new BadRequestException('Category cannot be parent of itself');
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
let currentParentId: number | null = targetParentId;
|
|
1976
|
+
|
|
1977
|
+
while (currentParentId) {
|
|
1978
|
+
if (currentParentId === categoryId) {
|
|
1979
|
+
throw new BadRequestException('Invalid parent category hierarchy');
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
const parent = await this.prisma.finance_category.findUnique({
|
|
1983
|
+
where: { id: currentParentId },
|
|
1984
|
+
select: { parent_id: true },
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
currentParentId = parent?.parent_id || null;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1422
1991
|
private toCents(value: number) {
|
|
1423
1992
|
return Math.round(value * 100);
|
|
1424
1993
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export * from './dto/create-financial-title.dto';
|
|
2
|
+
export * from './finance-audit-logs.controller';
|
|
2
3
|
export * from './finance-bank-accounts.controller';
|
|
4
|
+
export * from './finance-categories.controller';
|
|
5
|
+
export * from './finance-cost-centers.controller';
|
|
3
6
|
export * from './finance-data.controller';
|
|
4
7
|
export * from './finance-installments.controller';
|
|
8
|
+
export * from './finance-period-close.controller';
|
|
5
9
|
export * from './finance-statements.controller';
|
|
6
10
|
export * from './finance.module';
|
|
7
11
|
export * from './finance.service';
|