Skip to content

Commit d47f870

Browse files
Merge pull request #7 from EluminiIT/devin/1774543164-pj-invite-flow
* feat: implement PJ invite flow with email token-based registration - Add CompanyInvite model and CompanyStatus enum - Make Company fields nullable to support partial initial creation - Add invite CRUD functions (create_company_initial, create_company_invite, etc.) - Add invite token generation/verification utils - Create PJ invite email template in Portuguese - Add invite API routes: send, resend, validate, complete registration - Add Alembic migration for companyinvite table and company changes - Add InvitesService to frontend client SDK - Create public /pj-registration route with token validation - Add invite dialog to companies page for sending invites - Confirmation modal before saving registration data - RAZÃO SOCIAL read-only during PJ registration Co-Authored-By: daniel.resgate <daniel.rider69@gmail.com> * chore: remove unused useNavigate import from pj-registration Co-Authored-By: daniel.resgate <daniel.rider69@gmail.com> * fix: address Devin Review bugs - razao_social in invite, complete_registration guard, VITE_API_URL trailing slash - Bug 1: Add razao_social field to CompanyInviteCreate model, invite form, and create_company_initial so razao_social is set when creating pending companies - Bug 2: Add company.status == completed check in complete_registration to prevent overwriting already-completed registrations - Fix pre-existing VITE_API_URL trailing slash causing double-slash API URLs Co-Authored-By: daniel.resgate <daniel.rider69@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: daniel.resgate <daniel.rider69@gmail.com>
2 parents 84610a4 + 39fda6c commit d47f870

File tree

14 files changed

+1603
-20
lines changed

14 files changed

+1603
-20
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Add company invite table and update company fields
2+
3+
Revision ID: b2c3d4e5f6g7
4+
Revises: a1b2c3d4e5f6
5+
Create Date: 2026-03-26 16:00:00.000000
6+
7+
"""
8+
import sqlalchemy as sa
9+
import sqlmodel.sql.sqltypes
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "b2c3d4e5f6g7"
14+
down_revision = "a1b2c3d4e5f6"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# Add new columns to company table
21+
op.add_column(
22+
"company",
23+
sa.Column("email", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
24+
)
25+
op.add_column(
26+
"company",
27+
sa.Column(
28+
"status",
29+
sqlmodel.sql.sqltypes.AutoString(),
30+
nullable=False,
31+
server_default="completed",
32+
),
33+
)
34+
35+
# Make company fields nullable to support partial initial creation
36+
columns_to_make_nullable = [
37+
"razao_social", "representante_legal", "nome_fantasia", "porte",
38+
"atividade_economica_principal", "atividade_economica_secundaria",
39+
"natureza_juridica", "logradouro", "numero", "complemento", "cep",
40+
"bairro", "municipio", "uf", "endereco_eletronico", "telefone_comercial",
41+
"situacao_cadastral", "cpf_representante_legal",
42+
"identidade_representante_legal", "logradouro_representante_legal",
43+
"numero_representante_legal", "complemento_representante_legal",
44+
"cep_representante_legal", "bairro_representante_legal",
45+
"municipio_representante_legal", "uf_representante_legal",
46+
"endereco_eletronico_representante_legal", "telefones_representante_legal",
47+
"banco_cc_cnpj", "agencia_cc_cnpj",
48+
]
49+
date_columns_to_make_nullable = [
50+
"data_abertura", "data_situacao_cadastral", "data_nascimento_representante_legal",
51+
]
52+
53+
for col_name in columns_to_make_nullable:
54+
op.alter_column("company", col_name, existing_type=sa.String(), nullable=True)
55+
56+
for col_name in date_columns_to_make_nullable:
57+
op.alter_column("company", col_name, existing_type=sa.Date(), nullable=True)
58+
59+
# Create companyinvite table
60+
op.create_table(
61+
"companyinvite",
62+
sa.Column("email", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
63+
sa.Column("id", sa.Uuid(), nullable=False),
64+
sa.Column("company_id", sa.Uuid(), nullable=False),
65+
sa.Column("token", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=False),
66+
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
67+
sa.Column("used", sa.Boolean(), nullable=False, server_default=sa.text("false")),
68+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
69+
sa.ForeignKeyConstraint(["company_id"], ["company.id"], ondelete="CASCADE"),
70+
sa.PrimaryKeyConstraint("id"),
71+
)
72+
op.create_index(op.f("ix_companyinvite_token"), "companyinvite", ["token"], unique=True)
73+
74+
75+
def downgrade():
76+
op.drop_index(op.f("ix_companyinvite_token"), table_name="companyinvite")
77+
op.drop_table("companyinvite")
78+
79+
# Revert company columns to non-nullable
80+
columns_to_make_non_nullable = [
81+
"razao_social", "representante_legal", "nome_fantasia", "porte",
82+
"atividade_economica_principal", "atividade_economica_secundaria",
83+
"natureza_juridica", "logradouro", "numero", "complemento", "cep",
84+
"bairro", "municipio", "uf", "endereco_eletronico", "telefone_comercial",
85+
"situacao_cadastral", "cpf_representante_legal",
86+
"identidade_representante_legal", "logradouro_representante_legal",
87+
"numero_representante_legal", "complemento_representante_legal",
88+
"cep_representante_legal", "bairro_representante_legal",
89+
"municipio_representante_legal", "uf_representante_legal",
90+
"endereco_eletronico_representante_legal", "telefones_representante_legal",
91+
"banco_cc_cnpj", "agencia_cc_cnpj",
92+
]
93+
date_columns_to_make_non_nullable = [
94+
"data_abertura", "data_situacao_cadastral", "data_nascimento_representante_legal",
95+
]
96+
97+
for col_name in columns_to_make_non_nullable:
98+
op.alter_column("company", col_name, existing_type=sa.String(), nullable=False)
99+
100+
for col_name in date_columns_to_make_non_nullable:
101+
op.alter_column("company", col_name, existing_type=sa.Date(), nullable=False)
102+
103+
op.drop_column("company", "status")
104+
op.drop_column("company", "email")

backend/app/api/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from fastapi import APIRouter
22

3-
from app.api.routes import companies, items, login, private, users, utils
3+
from app.api.routes import companies, invites, items, login, private, users, utils
44
from app.core.config import settings
55

66
api_router = APIRouter()
@@ -9,6 +9,7 @@
99
api_router.include_router(utils.router)
1010
api_router.include_router(items.router)
1111
api_router.include_router(companies.router)
12+
api_router.include_router(invites.router)
1213

1314

1415
if settings.ENVIRONMENT == "local":

0 commit comments

Comments
 (0)