@hed-hog/finance 0.0.236 → 0.0.238
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-finance-tag.dto.d.ts +5 -0
- package/dist/dto/create-finance-tag.dto.d.ts.map +1 -0
- package/dist/dto/create-finance-tag.dto.js +29 -0
- package/dist/dto/create-finance-tag.dto.js.map +1 -0
- package/dist/dto/update-installment-tags.dto.d.ts +4 -0
- package/dist/dto/update-installment-tags.dto.d.ts.map +1 -0
- package/dist/dto/update-installment-tags.dto.js +27 -0
- package/dist/dto/update-installment-tags.dto.js.map +1 -0
- package/dist/finance-data.controller.d.ts +4 -0
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.d.ts +81 -0
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.js +36 -0
- package/dist/finance-installments.controller.js.map +1 -1
- package/dist/finance.service.d.ts +86 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +185 -2
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/route.yaml +27 -0
- package/hedhog/frontend/app/_components/person-field-with-create.tsx.ejs +627 -0
- package/hedhog/frontend/app/_lib/use-finance-data.ts.ejs +2 -0
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +249 -78
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +903 -883
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +202 -20
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +877 -861
- package/hedhog/frontend/app/administration/categories/page.tsx.ejs +108 -25
- package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +15 -8
- package/hedhog/frontend/messages/en.json +38 -0
- package/hedhog/frontend/messages/pt.json +38 -0
- package/package.json +5 -5
- package/src/dto/create-finance-tag.dto.ts +15 -0
- package/src/dto/update-installment-tags.dto.ts +12 -0
- package/src/finance-installments.controller.ts +43 -9
- package/src/finance.service.ts +255 -12
package/src/finance.service.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { CreateBankAccountDto } from './dto/create-bank-account.dto';
|
|
12
12
|
import { CreateCostCenterDto } from './dto/create-cost-center.dto';
|
|
13
13
|
import { CreateFinanceCategoryDto } from './dto/create-finance-category.dto';
|
|
14
|
+
import { CreateFinanceTagDto } from './dto/create-finance-tag.dto';
|
|
14
15
|
import { CreateFinancialTitleDto } from './dto/create-financial-title.dto';
|
|
15
16
|
import { CreatePeriodCloseDto } from './dto/create-period-close.dto';
|
|
16
17
|
import { MoveFinanceCategoryDto } from './dto/move-finance-category.dto';
|
|
@@ -697,15 +698,15 @@ export class FinanceService {
|
|
|
697
698
|
|
|
698
699
|
async getData() {
|
|
699
700
|
const [
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
] = await Promise.
|
|
701
|
+
payablesResult,
|
|
702
|
+
receivablesResult,
|
|
703
|
+
peopleResult,
|
|
704
|
+
categoriesResult,
|
|
705
|
+
costCentersResult,
|
|
706
|
+
bankAccountsResult,
|
|
707
|
+
tagsResult,
|
|
708
|
+
auditLogsResult,
|
|
709
|
+
] = await Promise.allSettled([
|
|
709
710
|
this.loadTitles('payable'),
|
|
710
711
|
this.loadTitles('receivable'),
|
|
711
712
|
this.loadPeople(),
|
|
@@ -716,6 +717,45 @@ export class FinanceService {
|
|
|
716
717
|
this.loadAuditLogs(),
|
|
717
718
|
]);
|
|
718
719
|
|
|
720
|
+
const payables = payablesResult.status === 'fulfilled' ? payablesResult.value : [];
|
|
721
|
+
const receivables =
|
|
722
|
+
receivablesResult.status === 'fulfilled' ? receivablesResult.value : [];
|
|
723
|
+
const people = peopleResult.status === 'fulfilled' ? peopleResult.value : [];
|
|
724
|
+
const categories =
|
|
725
|
+
categoriesResult.status === 'fulfilled' ? categoriesResult.value : [];
|
|
726
|
+
const costCenters =
|
|
727
|
+
costCentersResult.status === 'fulfilled' ? costCentersResult.value : [];
|
|
728
|
+
const bankAccounts =
|
|
729
|
+
bankAccountsResult.status === 'fulfilled' ? bankAccountsResult.value : [];
|
|
730
|
+
const tags = tagsResult.status === 'fulfilled' ? tagsResult.value : [];
|
|
731
|
+
const auditLogs =
|
|
732
|
+
auditLogsResult.status === 'fulfilled' ? auditLogsResult.value : [];
|
|
733
|
+
|
|
734
|
+
if (payablesResult.status === 'rejected') {
|
|
735
|
+
this.logger.error('Failed to load finance payables', payablesResult.reason);
|
|
736
|
+
}
|
|
737
|
+
if (receivablesResult.status === 'rejected') {
|
|
738
|
+
this.logger.error('Failed to load finance receivables', receivablesResult.reason);
|
|
739
|
+
}
|
|
740
|
+
if (peopleResult.status === 'rejected') {
|
|
741
|
+
this.logger.error('Failed to load finance people', peopleResult.reason);
|
|
742
|
+
}
|
|
743
|
+
if (categoriesResult.status === 'rejected') {
|
|
744
|
+
this.logger.error('Failed to load finance categories', categoriesResult.reason);
|
|
745
|
+
}
|
|
746
|
+
if (costCentersResult.status === 'rejected') {
|
|
747
|
+
this.logger.error('Failed to load finance cost centers', costCentersResult.reason);
|
|
748
|
+
}
|
|
749
|
+
if (bankAccountsResult.status === 'rejected') {
|
|
750
|
+
this.logger.error('Failed to load finance bank accounts', bankAccountsResult.reason);
|
|
751
|
+
}
|
|
752
|
+
if (tagsResult.status === 'rejected') {
|
|
753
|
+
this.logger.error('Failed to load finance tags', tagsResult.reason);
|
|
754
|
+
}
|
|
755
|
+
if (auditLogsResult.status === 'rejected') {
|
|
756
|
+
this.logger.error('Failed to load finance audit logs', auditLogsResult.reason);
|
|
757
|
+
}
|
|
758
|
+
|
|
719
759
|
return {
|
|
720
760
|
kpis: {
|
|
721
761
|
saldoCaixa: 0,
|
|
@@ -787,6 +827,68 @@ export class FinanceService {
|
|
|
787
827
|
return this.createTitle(data, 'receivable', locale, userId);
|
|
788
828
|
}
|
|
789
829
|
|
|
830
|
+
async createTag(data: CreateFinanceTagDto) {
|
|
831
|
+
const slug = this.normalizeTagSlug(data.name);
|
|
832
|
+
|
|
833
|
+
if (!slug) {
|
|
834
|
+
throw new BadRequestException('Tag name is required');
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const existingTag = await this.prisma.tag.findFirst({
|
|
838
|
+
where: {
|
|
839
|
+
slug,
|
|
840
|
+
},
|
|
841
|
+
select: {
|
|
842
|
+
id: true,
|
|
843
|
+
slug: true,
|
|
844
|
+
color: true,
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
if (existingTag) {
|
|
849
|
+
return {
|
|
850
|
+
id: String(existingTag.id),
|
|
851
|
+
nome: existingTag.slug,
|
|
852
|
+
cor: existingTag.color,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const createdTag = await this.prisma.tag.create({
|
|
857
|
+
data: {
|
|
858
|
+
slug,
|
|
859
|
+
color: data.color || '#000000',
|
|
860
|
+
status: 'active',
|
|
861
|
+
},
|
|
862
|
+
select: {
|
|
863
|
+
id: true,
|
|
864
|
+
slug: true,
|
|
865
|
+
color: true,
|
|
866
|
+
},
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
return {
|
|
870
|
+
id: String(createdTag.id),
|
|
871
|
+
nome: createdTag.slug,
|
|
872
|
+
cor: createdTag.color,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
async updateAccountsPayableInstallmentTags(
|
|
877
|
+
id: number,
|
|
878
|
+
tagIds: number[],
|
|
879
|
+
locale: string,
|
|
880
|
+
) {
|
|
881
|
+
return this.updateTitleTags(id, 'payable', tagIds, locale);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
async updateAccountsReceivableInstallmentTags(
|
|
885
|
+
id: number,
|
|
886
|
+
tagIds: number[],
|
|
887
|
+
locale: string,
|
|
888
|
+
) {
|
|
889
|
+
return this.updateTitleTags(id, 'receivable', tagIds, locale);
|
|
890
|
+
}
|
|
891
|
+
|
|
790
892
|
async listBankAccounts() {
|
|
791
893
|
const bankAccounts = await this.prisma.bank_account.findMany({
|
|
792
894
|
include: {
|
|
@@ -1492,6 +1594,40 @@ export class FinanceService {
|
|
|
1492
1594
|
},
|
|
1493
1595
|
});
|
|
1494
1596
|
|
|
1597
|
+
const attachmentFileIds = [
|
|
1598
|
+
...new Set((data.attachment_file_ids || []).filter((fileId) => fileId > 0)),
|
|
1599
|
+
];
|
|
1600
|
+
|
|
1601
|
+
if (attachmentFileIds.length > 0) {
|
|
1602
|
+
const existingFiles = await this.prisma.file.findMany({
|
|
1603
|
+
where: {
|
|
1604
|
+
id: { in: attachmentFileIds },
|
|
1605
|
+
},
|
|
1606
|
+
select: {
|
|
1607
|
+
id: true,
|
|
1608
|
+
},
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
const existingFileIds = new Set(existingFiles.map((file) => file.id));
|
|
1612
|
+
const invalidFileIds = attachmentFileIds.filter(
|
|
1613
|
+
(fileId) => !existingFileIds.has(fileId),
|
|
1614
|
+
);
|
|
1615
|
+
|
|
1616
|
+
if (invalidFileIds.length > 0) {
|
|
1617
|
+
throw new BadRequestException(
|
|
1618
|
+
`Invalid attachment file IDs: ${invalidFileIds.join(', ')}`,
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
await this.prisma.financial_title_attachment.createMany({
|
|
1623
|
+
data: attachmentFileIds.map((fileId) => ({
|
|
1624
|
+
title_id: title.id,
|
|
1625
|
+
file_id: fileId,
|
|
1626
|
+
uploaded_by_user_id: userId,
|
|
1627
|
+
})),
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1495
1631
|
for (let index = 0; index < installments.length; index++) {
|
|
1496
1632
|
const installment = installments[index];
|
|
1497
1633
|
const amountCents = this.toCents(installment.amount);
|
|
@@ -1526,6 +1662,95 @@ export class FinanceService {
|
|
|
1526
1662
|
return this.mapTitleToFront(createdTitle, data.payment_channel);
|
|
1527
1663
|
}
|
|
1528
1664
|
|
|
1665
|
+
private async updateTitleTags(
|
|
1666
|
+
titleId: number,
|
|
1667
|
+
titleType: TitleType,
|
|
1668
|
+
tagIds: number[],
|
|
1669
|
+
locale: string,
|
|
1670
|
+
) {
|
|
1671
|
+
const title = await this.getTitleById(titleId, titleType, locale);
|
|
1672
|
+
|
|
1673
|
+
const installmentIds = (title.financial_installment || []).map(
|
|
1674
|
+
(installment) => installment.id,
|
|
1675
|
+
);
|
|
1676
|
+
|
|
1677
|
+
if (installmentIds.length === 0) {
|
|
1678
|
+
throw new BadRequestException('Financial title has no installments');
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
const normalizedTagIds = [
|
|
1682
|
+
...new Set(
|
|
1683
|
+
(tagIds || [])
|
|
1684
|
+
.map((tagId) => Number(tagId))
|
|
1685
|
+
.filter((tagId) => Number.isInteger(tagId) && tagId > 0),
|
|
1686
|
+
),
|
|
1687
|
+
];
|
|
1688
|
+
|
|
1689
|
+
if (normalizedTagIds.length > 0) {
|
|
1690
|
+
const existingTags = await this.prisma.tag.findMany({
|
|
1691
|
+
where: {
|
|
1692
|
+
id: {
|
|
1693
|
+
in: normalizedTagIds,
|
|
1694
|
+
},
|
|
1695
|
+
},
|
|
1696
|
+
select: {
|
|
1697
|
+
id: true,
|
|
1698
|
+
},
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
const existingTagIds = new Set(existingTags.map((tag) => tag.id));
|
|
1702
|
+
const invalidTagIds = normalizedTagIds.filter(
|
|
1703
|
+
(tagId) => !existingTagIds.has(tagId),
|
|
1704
|
+
);
|
|
1705
|
+
|
|
1706
|
+
if (invalidTagIds.length > 0) {
|
|
1707
|
+
throw new BadRequestException(
|
|
1708
|
+
`Invalid tag IDs: ${invalidTagIds.join(', ')}`,
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
await this.prisma.$transaction(async (tx) => {
|
|
1714
|
+
if (normalizedTagIds.length === 0) {
|
|
1715
|
+
await tx.financial_installment_tag.deleteMany({
|
|
1716
|
+
where: {
|
|
1717
|
+
installment_id: {
|
|
1718
|
+
in: installmentIds,
|
|
1719
|
+
},
|
|
1720
|
+
},
|
|
1721
|
+
});
|
|
1722
|
+
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
await tx.financial_installment_tag.deleteMany({
|
|
1727
|
+
where: {
|
|
1728
|
+
installment_id: {
|
|
1729
|
+
in: installmentIds,
|
|
1730
|
+
},
|
|
1731
|
+
tag_id: {
|
|
1732
|
+
notIn: normalizedTagIds,
|
|
1733
|
+
},
|
|
1734
|
+
},
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
const newRelations = installmentIds.flatMap((installmentId) =>
|
|
1738
|
+
normalizedTagIds.map((tagId) => ({
|
|
1739
|
+
installment_id: installmentId,
|
|
1740
|
+
tag_id: tagId,
|
|
1741
|
+
})),
|
|
1742
|
+
);
|
|
1743
|
+
|
|
1744
|
+
await tx.financial_installment_tag.createMany({
|
|
1745
|
+
data: newRelations,
|
|
1746
|
+
skipDuplicates: true,
|
|
1747
|
+
});
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
const updatedTitle = await this.getTitleById(titleId, titleType, locale);
|
|
1751
|
+
return this.mapTitleToFront(updatedTitle);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1529
1754
|
private async loadTitles(type: TitleType) {
|
|
1530
1755
|
const titles = await this.prisma.financial_title.findMany({
|
|
1531
1756
|
where: { title_type: type },
|
|
@@ -1674,6 +1899,11 @@ export class FinanceService {
|
|
|
1674
1899
|
})),
|
|
1675
1900
|
}));
|
|
1676
1901
|
|
|
1902
|
+
const attachmentDetails = title.financial_title_attachment.map((attachment) => ({
|
|
1903
|
+
id: String(attachment.file_id),
|
|
1904
|
+
nome: attachment.file?.filename || attachment.file?.path,
|
|
1905
|
+
}));
|
|
1906
|
+
|
|
1677
1907
|
return {
|
|
1678
1908
|
id: String(title.id),
|
|
1679
1909
|
documento: title.document_number || `TIT-${title.id}`,
|
|
@@ -1686,9 +1916,8 @@ export class FinanceService {
|
|
|
1686
1916
|
criadoEm: title.created_at.toISOString(),
|
|
1687
1917
|
categoriaId: title.finance_category_id ? String(title.finance_category_id) : null,
|
|
1688
1918
|
centroCustoId: firstCostCenter ? String(firstCostCenter) : null,
|
|
1689
|
-
anexos:
|
|
1690
|
-
|
|
1691
|
-
),
|
|
1919
|
+
anexos: attachmentDetails.map((attachment) => attachment.nome),
|
|
1920
|
+
anexosDetalhes: attachmentDetails,
|
|
1692
1921
|
tags,
|
|
1693
1922
|
parcelas: mappedInstallments,
|
|
1694
1923
|
canal:
|
|
@@ -1744,6 +1973,20 @@ export class FinanceService {
|
|
|
1744
1973
|
return statusMap[status] || 'aberto';
|
|
1745
1974
|
}
|
|
1746
1975
|
|
|
1976
|
+
private normalizeTagSlug(value?: string | null) {
|
|
1977
|
+
if (!value) {
|
|
1978
|
+
return '';
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
return value
|
|
1982
|
+
.normalize('NFD')
|
|
1983
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
1984
|
+
.toLowerCase()
|
|
1985
|
+
.trim()
|
|
1986
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1987
|
+
.replace(/(^-|-$)+/g, '');
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1747
1990
|
private mapStatusFromPt(status?: string) {
|
|
1748
1991
|
if (!status || status === 'all') {
|
|
1749
1992
|
return undefined;
|