@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.
Files changed (34) hide show
  1. package/dist/dto/create-finance-tag.dto.d.ts +5 -0
  2. package/dist/dto/create-finance-tag.dto.d.ts.map +1 -0
  3. package/dist/dto/create-finance-tag.dto.js +29 -0
  4. package/dist/dto/create-finance-tag.dto.js.map +1 -0
  5. package/dist/dto/update-installment-tags.dto.d.ts +4 -0
  6. package/dist/dto/update-installment-tags.dto.d.ts.map +1 -0
  7. package/dist/dto/update-installment-tags.dto.js +27 -0
  8. package/dist/dto/update-installment-tags.dto.js.map +1 -0
  9. package/dist/finance-data.controller.d.ts +4 -0
  10. package/dist/finance-data.controller.d.ts.map +1 -1
  11. package/dist/finance-installments.controller.d.ts +81 -0
  12. package/dist/finance-installments.controller.d.ts.map +1 -1
  13. package/dist/finance-installments.controller.js +36 -0
  14. package/dist/finance-installments.controller.js.map +1 -1
  15. package/dist/finance.service.d.ts +86 -0
  16. package/dist/finance.service.d.ts.map +1 -1
  17. package/dist/finance.service.js +185 -2
  18. package/dist/finance.service.js.map +1 -1
  19. package/hedhog/data/route.yaml +27 -0
  20. package/hedhog/frontend/app/_components/person-field-with-create.tsx.ejs +627 -0
  21. package/hedhog/frontend/app/_lib/use-finance-data.ts.ejs +2 -0
  22. package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +249 -78
  23. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +903 -883
  24. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +202 -20
  25. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +877 -861
  26. package/hedhog/frontend/app/administration/categories/page.tsx.ejs +108 -25
  27. package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +15 -8
  28. package/hedhog/frontend/messages/en.json +38 -0
  29. package/hedhog/frontend/messages/pt.json +38 -0
  30. package/package.json +5 -5
  31. package/src/dto/create-finance-tag.dto.ts +15 -0
  32. package/src/dto/update-installment-tags.dto.ts +12 -0
  33. package/src/finance-installments.controller.ts +43 -9
  34. package/src/finance.service.ts +255 -12
@@ -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
- payables,
701
- receivables,
702
- people,
703
- categories,
704
- costCenters,
705
- bankAccounts,
706
- tags,
707
- auditLogs,
708
- ] = await Promise.all([
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: title.financial_title_attachment.map(
1690
- (attachment) => attachment.file?.filename || attachment.file?.path,
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;