11from graphviz import Digraph
2+ import os
3+
4+ # Create docs folder if not exists
5+ os .makedirs ("docs" , exist_ok = True )
26
3- # Criar diagrama ER
47dot = Digraph ("ERD_PortalEmprego" , format = "png" )
5- dot .attr (rankdir = "LR" , size = "8" )
8+ dot .attr (rankdir = "TB" , fontsize = "12" , fontname = "Arial" )
9+
10+ # Helper: add table-style nodes with optional FK cardinalities
11+ def add_table (name , title , fields , color = "lightgray" ):
12+ label = f'''<
13+ <TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0">
14+ <TR><TD BGCOLOR="{ color } " COLSPAN="2"><B>{ title } </B></TD></TR>'''
15+ for f in fields :
16+ label += f'\n <TR><TD ALIGN="LEFT">{ f } </TD></TR>'
17+ label += "\n </TABLE>>"
18+ dot .node (name , label = label , shape = "none" )
619
7- # Definir estilo
8- node_attr = {"shape" : "record" , "fontname" : "Arial" , "fontsize" : "10" }
20+ # Tables with FK cardinalities
21+ add_table ("user_profiles" , "user_profiles" , [
22+ "id (PK)" ,
23+ "email (UNIQUE)" ,
24+ "nome_completo" ,
25+ "perfil" ,
26+ "telefone" ,
27+ "criado_em" ,
28+ "atualizado_em"
29+ ], color = "lightblue" )
930
10- # Tabelas principais
11- dot . node ( "user_profiles" , """{
12- user_profiles |
13- + id (PK) \\ l
14- + email (UNIQUE) \\ l
15- + nome_completo \\ l
16- + perfil \\ l
17- + telefone \\ l
18- + criado_em \\ l
19- + atualizado_em \\ l
20- }""" , ** node_attr )
31+ add_table ( "candidate_profiles" , "candidate_profiles" , [
32+ "id (PK)" ,
33+ "id_utilizador (FK → user_profiles.id) 1:1" ,
34+ "curriculo_url" ,
35+ "competencias" ,
36+ "anos_experiencia" ,
37+ "formacao" ,
38+ "salario_min/max" ,
39+ "localizacoes_preferidas" ,
40+ "linkedin/github/portfolio"
41+ ], color = "lightyellow" )
2142
22- dot .node ("candidate_profiles" , """{
23- candidate_profiles |
24- + id (PK) \\ l
25- + id_utilizador (FK) \\ l
26- + curriculo_url \\ l
27- + competencias \\ l
28- + anos_experiencia \\ l
29- + formacao \\ l
30- + salario_min/max \\ l
31- + localizacoes_preferidas \\ l
32- + linkedin/github/portfolio \\ l
33- }""" , ** node_attr )
43+ add_table ("company_profiles" , "company_profiles" , [
44+ "id (PK)" ,
45+ "id_utilizador (FK → user_profiles.id) 1:1" ,
46+ "nome_empresa" ,
47+ "descricao_empresa" ,
48+ "sector_atividade" ,
49+ "dimensao_empresa" ,
50+ "numero_contribuinte" ,
51+ "morada/cidade/pais faturacao" ,
52+ "stripe_customer_id"
53+ ], color = "lightpink" )
3454
35- dot . node ( "company_profiles " , """{
36- company_profiles |
37- + id (PK) \\ l
38- + id_utilizador (FK) \\ l
39- + nome_empresa \\ l
40- + descricao_empresa \\ l
41- + sector_atividade \\ l
42- + dimensao_empresa \\ l
43- + numero_contribuinte \\ l
44- + morada/cidade/pais faturacao \\ l
45- + stripe_customer_id \\ l
46- }""" , ** node_attr )
55+ add_table ( "jobs " , "jobs" , [
56+ "id (PK)" ,
57+ "id_empresa (FK → company_profiles.id) 1:N" ,
58+ "titulo" ,
59+ "descricao" ,
60+ "salario_min/max" ,
61+ "localizacao" ,
62+ "tipo_contrato" ,
63+ "estado" ,
64+ "experiencia_requerida" ,
65+ "categoria"
66+ ], color = "lightgreen" )
4767
48- dot .node ("jobs" , """{
49- jobs |
50- + id (PK) \\ l
51- + id_empresa (FK) \\ l
52- + titulo \\ l
53- + descricao \\ l
54- + salario_min/max \\ l
55- + localizacao \\ l
56- + tipo_contrato \\ l
57- + estado \\ l
58- + experiencia_requerida \\ l
59- + categoria \\ l
60- }""" , ** node_attr )
68+ add_table ("job_applications" , "job_applications" , [
69+ "id (PK)" ,
70+ "id_oferta (FK → jobs.id) 1:N" ,
71+ "id_candidato (FK → candidate_profiles.id) 1:N" ,
72+ "carta_apresentacao_url" ,
73+ "curriculo_url" ,
74+ "estado"
75+ ], color = "orange" )
6176
62- dot .node ("job_applications" , """{
63- job_applications |
64- + id (PK) \\ l
65- + id_oferta (FK) \\ l
66- + id_candidato (FK) \\ l
67- + carta_apresentacao_url \\ l
68- + curriculo_url \\ l
69- + estado \\ l
70- }""" , ** node_attr )
77+ add_table ("payment_history" , "payment_history" , [
78+ "id (PK)" ,
79+ "id_empresa (FK → company_profiles.id) 1:N" ,
80+ "stripe_payment_intent_id" ,
81+ "valor" ,
82+ "moeda" ,
83+ "estado" ,
84+ "metodo_pagamento" ,
85+ "iva_percentual" ,
86+ "valor_com_iva"
87+ ], color = "lightgray" )
7188
72- dot .node ("payment_history" , """{
73- payment_history |
74- + id (PK) \\ l
75- + id_empresa (FK) \\ l
76- + stripe_payment_intent_id \\ l
77- + valor \\ l
78- + moeda \\ l
79- + estado \\ l
80- + metodo_pagamento \\ l
81- + iva_percentual \\ l
82- + valor_com_iva \\ l
83- }""" , ** node_attr )
89+ # Simple edge labels for readability (no crow foots)
90+ dot .edge ("user_profiles" , "candidate_profiles" , label = "1 → 1" )
91+ dot .edge ("user_profiles" , "company_profiles" , label = "1 → 1" )
92+ dot .edge ("company_profiles" , "jobs" , label = "1 → *" )
93+ dot .edge ("jobs" , "job_applications" , label = "1 → *" )
94+ dot .edge ("candidate_profiles" , "job_applications" , label = "1 → *" )
95+ dot .edge ("company_profiles" , "payment_history" , label = "1 → *" )
8496
85- # Relacionamentos
86- dot .edge ("user_profiles" , "candidate_profiles" , label = "1 - n" )
87- dot .edge ("user_profiles" , "company_profiles" , label = "1 - n" )
88- dot .edge ("company_profiles" , "jobs" , label = "1 - n" )
89- dot .edge ("jobs" , "job_applications" , label = "1 - n" )
90- dot .edge ("candidate_profiles" , "job_applications" , label = "1 - n" )
91- dot .edge ("company_profiles" , "payment_history" , label = "1 - n" )
97+ # Export
98+ output_path = "./images/ERD_PortalEmprego"
99+ dot .render (output_path , cleanup = True )
92100
93- # Exportar
94- output_path = "/mnt/data/ERD_PortalEmprego.png"
95- dot .render (output_path , cleanup = True )
101+ print (f"ERD saved to { output_path } .png" )
0 commit comments