Skip to content

Feat/company custom fields#39

Open
guilhermearaujo-glitch wants to merge 9 commits into
thaleslaray:mainfrom
guilhermearaujo-glitch:feat/company-custom-fields
Open

Feat/company custom fields#39
guilhermearaujo-glitch wants to merge 9 commits into
thaleslaray:mainfrom
guilhermearaujo-glitch:feat/company-custom-fields

Conversation

@guilhermearaujo-glitch
Copy link
Copy Markdown

@guilhermearaujo-glitch guilhermearaujo-glitch commented Apr 27, 2026

Summary by CodeRabbit

  • New Features

    • Added custom company-specific fields to enhance company profiles: employee count, industry classification, applicable regulations, and last health assessment date
    • Meeting activities now trigger external webhook notifications for better team collaboration
  • Improvements

    • Deal items table now supports horizontal scrolling for better visibility of all columns
    • Optimized deal item caching for consistent data synchronization

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 27, 2026

Someone is attempting to deploy a commit to the Thales Laray Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@guilhermearaujo-glitch has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 21 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c301fde-ad19-446a-b41c-ca4eaa39af10

📥 Commits

Reviewing files that changed from the base of the PR and between 6794ce9 and 5c8b7de.

📒 Files selected for processing (2)
  • features/contacts/hooks/useContactsController.ts
  • lib/supabase/contacts.ts
📝 Walkthrough

Walkthrough

The PR introduces company-specific form fields to contact management, implements webhook notifications for meeting activities, optimizes deal item cache updates, improves table overflow handling, adjusts form field validation schema, and modifies the stage-evaluations cron frequency.

Changes

Cohort / File(s) Summary
Company Form & Validation
features/contacts/components/CompanyFormModal.tsx, lib/validations/schemas.ts
Added support for four custom company fields (num_funcionarios, cnae, nrs_aplicaveis, data_ultimo_aso) with form reset logic and validation constraints. Form now seeds and repopulates custom field values when modal opens.
Deal Management
features/boards/components/Modals/DealDetailModal.tsx, lib/query/hooks/useDealsQuery.ts
Updated deal item table to support horizontal overflow with constrained width (min-w-[480px]). Added cache updates for add/remove deal item mutations to immutably append/filter items in DEALS_VIEW_KEY.
Activity Webhook Integration
lib/query/hooks/useActivitiesQuery.ts
useCreateActivity now reads authenticated user and conditionally triggers external HTTP POST webhook for MEETING type activities, constructing webhook payload from activity details and attendee email with error handling.
Infrastructure Configuration
vercel.json
Adjusted cron job frequency for /api/cron/stage-evaluations from every minute to daily at midnight (0 0 * * *).

Sequence Diagram

sequenceDiagram
    actor Client
    participant Activity Service as Activity Service
    participant Cache as Cache Layer
    participant External Webhook as External Webhook
    
    Client->>Activity Service: createActivity(data)
    Activity Service->>Activity Service: Validate activity type
    alt Activity type is MEETING
        Activity Service->>External Webhook: POST webhook<br/>(title, description,<br/>start/end times, attendee)
        External Webhook-->>Activity Service: Response (success or error)
        Activity Service->>Activity Service: Log webhook result
    end
    Activity Service->>Cache: Invalidate queries
    Activity Service->>Cache: Update optimistic state
    Activity Service-->>Client: Return created activity
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • PR #5: Directly modifies useActivitiesQuery.ts with the useCreateActivity flow, including webhook integration logic.
  • PR #14: Directly modifies lib/query/hooks/useDealsQuery.ts for DEALS_VIEW_KEY cache management with item mutations.

Poem

🐰 Tables scroll with grace so wide,
Companies dance with fields inside,
Meetings ping the webhook bell,
Caches sync and crons do dwell,
All the changes fit so well! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Feat/company custom fields' directly aligns with the main change: adding custom/company-specific fields to the CompanyFormModal and related form validation schema.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (4)
lib/query/hooks/useActivitiesQuery.ts (1)

193-197: Add a timeout, idempotency key, and extract to a helper.

A few smaller hardening items on the same call:

  • No timeout / AbortSignal: if the n8n endpoint hangs, the request lingers until the browser/tab decides to abort. Use AbortSignal.timeout(...) (or an AbortController).
  • No idempotency: onSuccess can fire again if the mutation is retried or if the component remounts and replays state in some flows; the downstream calendar will create duplicate events. Including activity.id in the payload (e.g. as external_id) lets n8n dedupe.
  • Inline fetch in onSuccess makes this hook do two unrelated things; extracting to e.g. lib/integrations/meetingWebhook.ts keeps the mutation focused and unit-testable.
♻️ Sketch
-      if (data.type === "MEETING") {
-        const startDate = data.date ? new Date(data.date) : null;
-        if (startDate && !isNaN(startDate.getTime())) {
-          fetch("https://n8n-production-9012a.up.railway.app/webhook/0ebbdfef-a03e-4109-bdce-7d00e70218f0", {
-            method: "POST",
-            headers: { "Content-Type": "application/json" },
-            body: JSON.stringify({ title: data.title, description: data.description || data.dealTitle, start_time: startDate.toISOString(), end_time: new Date(startDate.getTime() + 3600000).toISOString(), attendees: user?.email || "" })
-          }).catch((err) => console.error("[Calendar] Webhook error:", err));
-        }
-      }
+      if (data.type === 'MEETING') {
+        void notifyMeetingCreated(data, user?.email ?? '');
+      }

…with the helper owning the URL lookup, timeout, and external_id:

// lib/integrations/meetingWebhook.ts
export async function notifyMeetingCreated(activity: Activity, attendeeEmail: string) {
  const start = new Date(activity.date);
  if (Number.isNaN(start.getTime())) return;

  try {
    await fetch(/* url from org settings */, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        external_id: activity.id,
        title: activity.title,
        description: activity.description || activity.dealTitle,
        start_time: start.toISOString(),
        end_time: new Date(start.getTime() + 60 * 60_000).toISOString(),
        attendees: attendeeEmail,
      }),
      signal: AbortSignal.timeout(5_000),
    });
  } catch (err) {
    console.error('[Calendar] Webhook error:', err);
  }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/query/hooks/useActivitiesQuery.ts` around lines 193 - 197, The inline
fetch in useActivitiesQuery's onSuccess should be moved to a dedicated helper
(e.g., export async function notifyMeetingCreated(activity, attendeeEmail) in
lib/integrations/meetingWebhook.ts) that reads the webhook URL from org
settings, validates/parses activity.date, includes activity.id as external_id
for idempotency, sets a request timeout using AbortSignal.timeout (or an
AbortController) and logs errors; replace the inline fetch call inside
useActivitiesQuery's onSuccess with a call to notifyMeetingCreated(activity,
user?.email || "") so the mutation remains focused and the webhook logic is
testable and hardened.
lib/validations/schemas.ts (2)

125-125: Use MAX_LENGTHS.MEDIUM_TEXT instead of magic 500.

MAX_LENGTHS.MEDIUM_TEXT already equals 500 (line 26). Using the constant keeps the schema consistent with the rest of the file.

♻️ Proposed fix
-  nrs_aplicaveis: optionalString.pipe(z.string().max(500)),
+  nrs_aplicaveis: optionalString.pipe(z.string().max(MAX_LENGTHS.MEDIUM_TEXT)),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/validations/schemas.ts` at line 125, Replace the magic number 500 in the
schema for nrs_aplicaveis with the existing constant MAX_LENGTHS.MEDIUM_TEXT:
locate the nrs_aplicaveis line that uses
optionalString.pipe(z.string().max(500)) and change the .max argument to
MAX_LENGTHS.MEDIUM_TEXT so it uses the shared constant and stays consistent with
other schema definitions.

126-126: Add ISO date format validation to data_ultimo_aso field.

The field receives YYYY-MM-DD strings from <InputField type="date" /> but only validates string length. Add format validation using z.iso.date() to prevent malformed dates if the input type or population method changes.

Suggested fix
-  data_ultimo_aso: optionalString.pipe(z.string().max(MAX_LENGTHS.SHORT_TEXT)),
+  data_ultimo_aso: optionalString.pipe(
+    z.union([z.literal(''), z.iso.date()])
+  ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/validations/schemas.ts` at line 126, The data_ultimo_aso schema currently
only enforces max length; update the validation for the data_ultimo_aso field
(where optionalString.pipe(...) is used) to also enforce an ISO date
(YYYY-MM-DD) format — replace the current z.string().max(...) validator with a
string-format validator (e.g. a regex that matches /^\d{4}-\d{2}-\d{2}$/ or an
equivalent zod ISO-date check) so the field is rejected if it is not a valid
YYYY-MM-DD date while keeping the optionalString wrapper.
features/contacts/components/CompanyFormModal.tsx (1)

63-68: Redundant re-parse — zodResolver already validated.

handleSubmit(handleFormSubmit) only fires after the resolver has parsed the input through companyFormSchema, so calling companyFormSchema.parse(data) again on Line 64 duplicates the work and will throw on transform-only mismatches if the input/output types ever diverge. Use the resolved data directly:

♻️ Proposed fix
-  const handleFormSubmit = (data: CompanyFormInput) => {
-    const parsed = companyFormSchema.parse(data);
-    onSubmit(parsed);
-    onClose();
-    reset();
-  };
+  const handleFormSubmit = handleSubmit((data) => {
+    onSubmit(data as CompanyFormData);
+    onClose();
+    reset();
+  });

…and pass handleFormSubmit directly to <ModalForm onSubmit={…}>. Or simply type the handler as (data: CompanyFormData) => … since react-hook-form returns the resolver's output type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/contacts/components/CompanyFormModal.tsx` around lines 63 - 68, The
handler handleFormSubmit redundantly re-parses data with companyFormSchema.parse
even though react-hook-form's zodResolver already returns the parsed type;
remove the parse call and accept the resolved type instead (change the handler
signature to (data: CompanyFormData) or the resolver output type), then pass
that handler directly to ModalForm's onSubmit (or to
handleSubmit(handleFormSubmit) without an extra parse) and keep the existing
onSubmit(parsed); onClose(); reset(); sequence unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@features/contacts/components/CompanyFormModal.tsx`:
- Line 26: The code is using an unsafe cast (editingCompany as any) to access
custom_fields; instead extend the Organization interface in your types module
(add optional property custom_fields?: { num_funcionarios?: number; cnae?:
string; nrs_aplicaveis?: string; data_ultimo_aso?: string }) so the
Company/Organization type includes custom_fields, then remove the casts in
CompanyFormModal.tsx and access with optional chaining
(editingCompany?.custom_fields ?? {}); also review any DB schema/queries that
persist crm_companies to ensure custom_fields is persisted or handled
appropriately if it should be stored.

In `@lib/query/hooks/useActivitiesQuery.ts`:
- Line 196: The request body currently hardcodes end_time to start_time plus 1
hour in useActivitiesQuery.ts (where the payload is built for the webhook),
causing incorrect durations; update the payload construction in that function to
compute end_time from a provided Activity field (preferably data.endDate or
data.durationMinutes) and fall back to a per-organization default duration if
those are absent (read the org default from the existing org/config API or pass
it into the hook), and ensure the body uses the computed ISO string for end_time
instead of new Date(startDate.getTime() + 3600000).
- Around line 190-199: The client-side webhook call in useActivitiesQuery.ts
(inside the useActivitiesQuery hook) must be removed and replaced with a
server-side trigger: add per-organization webhook settings to the
organization_settings table and expose them via the existing org-config pattern
(similar to getOrgAIConfig) so each org has an enabled flag and webhook_url;
create a Route Handler/Edge Function endpoint that accepts a minimal meeting
payload (title, description, start_time, end_time, organization_id,
attendee_email) and from server-side reads the organization_settings (filtering
by organization_id) to get and sanitize the webhook_url with sanitizeUrl()
before calling fetch, include any auth header from settings, and log the
organization_id; update useActivitiesQuery to POST to this new internal endpoint
instead of calling the hardcoded external URL and ensure no PII or webhook URLs
remain in the client bundle.

In `@lib/query/hooks/useDealsQuery.ts`:
- Around line 547-554: The current onSuccess handler updates the DEALS_VIEW_KEY
optimistically but uses setQueryData without a generic (leaving old as unknown)
and then onSettled calls invalidateQueries(queryKeys.deals.lists()) which
prefix-matches and immediately refetches, negating the optimization; fix by
changing setQueryData to use the explicit generic setQueryData<DealView[]> (so
old is typed) and update only the specific view cache key pattern
[...queryKeys.deals.lists(), 'view'] (i.e. use
queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, ...) where DEALS_VIEW_KEY
is the view key), and change the onSettled invalidation to invalidate only
[...queryKeys.deals.lists(), 'view'] (or remove the invalidate if you prefer
relying on setQueryData) so there is no cascade invalidation from
queryKeys.deals.lists().
- Around line 520-527: The direct cache update in the onSuccess handler for
adding an item uses queryClient.setQueryData without the <DealView[]> generic
(so old is inferred as unknown) and is immediately undone because the existing
onSettled still calls queryClient.invalidateQueries({ queryKey:
queryKeys.deals.lists() }) which prefix-invalidates DEALS_VIEW_KEY; fix by
adding the explicit generic to setQueryData (setQueryData<DealView[]>) and stop
invalidating the parent lists key in the onSettled for this mutation (either
remove or narrow the invalidateQueries call so it does not target
queryKeys.deals.lists()) so the optimized in-place update to DEALS_VIEW_KEY
persists and remains type-safe.

In `@lib/validations/schemas.ts`:
- Line 123: The num_funcionarios schema silently coerces an empty string to 0
causing cleared numeric inputs to persist as 0; update the num_funcionarios
definition to first preprocess the value (in the schema for num_funcionarios)
converting '' to undefined, then apply z.coerce.number().int().min(0).optional()
so that empty string becomes undefined and respects .optional()—locate the
num_funcionarios symbol in the schema and wrap the existing coercion with a
.preprocess(fn) that returns undefined for '' and the original value otherwise.
- Around line 123-126: The schema is validating fields (num_funcionarios, cnae,
nrs_aplicaveis, data_ultimo_aso) that are never persisted because
handleCompanySubmit only sends { name, industry, website } and
companiesService.create/update and the crm_companies table lack these columns;
either stop validating/displaying unused fields or wire them end-to-end. Fix by
one of two options: (A) Remove these four fields from companyFormSchema and from
CompanyFormModal.tsx so validation and UI match companiesService inputs, or (B)
Add them to the persistence layer: extend the DTO/type used by
handleCompanySubmit to include num_funcionarios, cnae, nrs_aplicaveis,
data_ultimo_aso; update companiesService.create and companiesService.update to
accept and pass these fields through; add corresponding columns to the
crm_companies table (with a DB migration) and update any repository/ORM mappings
so the new fields are saved and returned.

In `@vercel.json`:
- Line 13: The cron entry for the route "/api/cron/stage-evaluations" was
changed to "0 0 * * *", which will run once daily and create a backlog/stale
state; revert or update this schedule to a high-frequency cron (e.g., every
minute or every few minutes like "*/1 * * * *" or "*/5 * * * *") to preserve
current near-real-time processing, or if you must keep a low frequency,
implement a compensating fix in the stage evaluation handler (the code behind
"/api/cron/stage-evaluations") to process much larger batches and add
parallelization and explicit product-level acceptance of delayed updates before
changing the schedule.

---

Nitpick comments:
In `@features/contacts/components/CompanyFormModal.tsx`:
- Around line 63-68: The handler handleFormSubmit redundantly re-parses data
with companyFormSchema.parse even though react-hook-form's zodResolver already
returns the parsed type; remove the parse call and accept the resolved type
instead (change the handler signature to (data: CompanyFormData) or the resolver
output type), then pass that handler directly to ModalForm's onSubmit (or to
handleSubmit(handleFormSubmit) without an extra parse) and keep the existing
onSubmit(parsed); onClose(); reset(); sequence unchanged.

In `@lib/query/hooks/useActivitiesQuery.ts`:
- Around line 193-197: The inline fetch in useActivitiesQuery's onSuccess should
be moved to a dedicated helper (e.g., export async function
notifyMeetingCreated(activity, attendeeEmail) in
lib/integrations/meetingWebhook.ts) that reads the webhook URL from org
settings, validates/parses activity.date, includes activity.id as external_id
for idempotency, sets a request timeout using AbortSignal.timeout (or an
AbortController) and logs errors; replace the inline fetch call inside
useActivitiesQuery's onSuccess with a call to notifyMeetingCreated(activity,
user?.email || "") so the mutation remains focused and the webhook logic is
testable and hardened.

In `@lib/validations/schemas.ts`:
- Line 125: Replace the magic number 500 in the schema for nrs_aplicaveis with
the existing constant MAX_LENGTHS.MEDIUM_TEXT: locate the nrs_aplicaveis line
that uses optionalString.pipe(z.string().max(500)) and change the .max argument
to MAX_LENGTHS.MEDIUM_TEXT so it uses the shared constant and stays consistent
with other schema definitions.
- Line 126: The data_ultimo_aso schema currently only enforces max length;
update the validation for the data_ultimo_aso field (where
optionalString.pipe(...) is used) to also enforce an ISO date (YYYY-MM-DD)
format — replace the current z.string().max(...) validator with a string-format
validator (e.g. a regex that matches /^\d{4}-\d{2}-\d{2}$/ or an equivalent zod
ISO-date check) so the field is rejected if it is not a valid YYYY-MM-DD date
while keeping the optionalString wrapper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aeffeb9e-12c7-439c-b6df-4470beedee01

📥 Commits

Reviewing files that changed from the base of the PR and between bd16185 and 6794ce9.

📒 Files selected for processing (6)
  • features/boards/components/Modals/DealDetailModal.tsx
  • features/contacts/components/CompanyFormModal.tsx
  • lib/query/hooks/useActivitiesQuery.ts
  • lib/query/hooks/useDealsQuery.ts
  • lib/validations/schemas.ts
  • vercel.json

onSubmit,
editingCompany,
}) => {
const customFields = (editingCompany as any)?.custom_fields ?? {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the Company type definition to confirm custom_fields isn't typed.
fd -e ts -e tsx | xargs rg -nP -C3 '\b(interface|type)\s+Company\b'

Repository: thaleslaray/nossocrm

Length of output: 455


🏁 Script executed:

#!/bin/bash
# Find the Organization type definition
rg -n 'interface Organization|type Organization' --type ts --type tsx -B2 -A10

Repository: thaleslaray/nossocrm

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Read the CompanyFormModal.tsx file to see the actual usage
cat -n features/contacts/components/CompanyFormModal.tsx | head -100

Repository: thaleslaray/nossocrm

Length of output: 3716


🏁 Script executed:

#!/bin/bash
# Search for Organization type definition without using --type flag
rg 'interface Organization|type Organization' -B2 -A15 types/

Repository: thaleslaray/nossocrm

Length of output: 1568


🏁 Script executed:

#!/bin/bash
# List and explore types directory structure
ls -la types/
cat -n types/types.ts | head -200

Repository: thaleslaray/nossocrm

Length of output: 7926


🏁 Script executed:

#!/bin/bash
# Find the companyFormSchema definition
fd -name '*schema*' -type f | xargs rg -l 'companyFormSchema' | head -5

Repository: thaleslaray/nossocrm

Length of output: 390


🏁 Script executed:

#!/bin/bash
# Look for any custom_fields references in the codebase
rg 'custom_fields' -n | head -20

Repository: thaleslaray/nossocrm

Length of output: 1341


🏁 Script executed:

#!/bin/bash
# Check the database schema for companies table
rg 'CREATE TABLE.*companies' -A 30 supabase/migrations/

Repository: thaleslaray/nossocrm

Length of output: 2764


🏁 Script executed:

#!/bin/bash
# Read the companyFormSchema to understand expected structure
cat -n lib/validations/schemas.ts | grep -A 30 'companyFormSchema'

Repository: thaleslaray/nossocrm

Length of output: 2165


🏁 Script executed:

#!/bin/bash
# Check where editingCompany comes from and is used in the component
rg -B 5 'editingCompany' features/contacts/components/CompanyFormModal.tsx | head -40

Repository: thaleslaray/nossocrm

Length of output: 1140


🏁 Script executed:

#!/bin/bash
# Search for where CompanyFormModal is used to see how editingCompany is passed
rg 'CompanyFormModal' -B 3 -A 3 --type ts --type tsx 2>/dev/null || rg 'CompanyFormModal' -B 3 -A 3

Repository: thaleslaray/nossocrm

Length of output: 2938


🏁 Script executed:

#!/bin/bash
# Check ContactsPage to see where editingCompany is set for the modal
rg -n 'editingCompany\|setEditingCompany\|handleCompanySubmit' features/contacts/ContactsPage.tsx | head -20

Repository: thaleslaray/nossocrm

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Look for the controller and its company-related methods
rg -n 'controller\.\(editingCompany\|setEditingCompany\|handleCompanySubmit\)' features/contacts/ContactsPage.tsx

Repository: thaleslaray/nossocrm

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Read the ContactsPage file to understand the controller structure
wc -l features/contacts/ContactsPage.tsx

Repository: thaleslaray/nossocrm

Length of output: 103


🏁 Script executed:

#!/bin/bash
# Search for useSettingsController or similar controller hook
rg 'useSettingsController\|useContactsController\|controller.*=' features/contacts/ContactsPage.tsx | head -10

Repository: thaleslaray/nossocrm

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Read the ContactsPage file to understand data flow
cat -n features/contacts/ContactsPage.tsx | head -150

Repository: thaleslaray/nossocrm

Length of output: 7604


🏁 Script executed:

#!/bin/bash
# Continue reading ContactsPage to find CompanyFormModal usage
cat -n features/contacts/ContactsPage.tsx | sed -n '150,220p'

Repository: thaleslaray/nossocrm

Length of output: 3850


🏁 Script executed:

#!/antml/bash
# Search for the useContactsController hook to understand what data it manages
fd -name 'useContactsController*' -type f

Repository: thaleslaray/nossocrm

Length of output: 295


🏁 Script executed:

#!/bin/bash
# Find the useContactsController hook
fd 'useContactsController' -type f

Repository: thaleslaray/nossocrm

Length of output: 235


🏁 Script executed:

#!/bin/bash
# Read the useContactsController hook
cat -n features/contacts/hooks/useContactsController.ts | head -150

Repository: thaleslaray/nossocrm

Length of output: 7169


🏁 Script executed:

#!/bin/bash
# Continue reading useContactsController to find editingCompany state and handleCompanySubmit
cat -n features/contacts/hooks/useContactsController.ts | sed -n '150,300p'

Repository: thaleslaray/nossocrm

Length of output: 6336


🏁 Script executed:

#!/bin/bash
# Search for where custom_fields might be populated/set in the companies data
rg 'custom_fields' -B 3 -A 3 lib/query/hooks/useContactsQuery.ts 2>/dev/null || echo "Not found in useContactsQuery"

Repository: thaleslaray/nossocrm

Length of output: 94


🏁 Script executed:

#!/antml:bash
# Check if there's any data transformation or query that adds custom_fields
fd 'useContactsQuery' -type f

Repository: thaleslaray/nossocrm

Length of output: 235


🏁 Script executed:

#!/bin/bash
# Find the useContactsQuery file
find features/contacts/hooks -name '*useContactsQuery*'

Repository: thaleslaray/nossocrm

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Search for where companies are fetched and if custom_fields are added
rg 'useCompanies' -B 2 -A 10 | head -50

Repository: thaleslaray/nossocrm

Length of output: 3212


🏁 Script executed:

#!/bin/bash
# Find the companiesService to see what getAll() returns
rg 'companiesService' -l

Repository: thaleslaray/nossocrm

Length of output: 232


🏁 Script executed:

#!/an/bash
# Search for where companiesService is defined
rg 'export.*companiesService\|const companiesService' -B 2 -A 5

Repository: thaleslaray/nossocrm

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Look at lib/supabase/contacts.ts for companiesService
cat -n lib/supabase/contacts.ts | head -100

Repository: thaleslaray/nossocrm

Length of output: 3771


🏁 Script executed:

#!/bin/bash
# Search for companiesService definition more broadly
rg 'companiesService.*=' lib/ -B 2 -A 5

Repository: thaleslaray/nossocrm

Length of output: 476


🏁 Script executed:

#!/bin/bash
# Find the getAll method in companiesService
rg -A 30 'companiesService = \{' lib/supabase/contacts.ts | grep -A 25 'getAll'

Repository: thaleslaray/nossocrm

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Read more of the companiesService to find getAll
cat -n lib/supabase/contacts.ts | sed -n '150,300p'

Repository: thaleslaray/nossocrm

Length of output: 6674


Remove as any casts and extend the Company type to include custom_fields.

The as any casts at lines 26 and 50 bypass TypeScript strict mode, removing compile-time safety for the custom_fields property. Per coding guidelines, TypeScript 5.x must run with strict mode enabled. The Organization interface (which Company aliases) does not currently define custom_fields, and the database schema for crm_companies does not include this column either.

To fix this properly:

  1. Add custom_fields?: { num_funcionarios?: number; cnae?: string; nrs_aplicaveis?: string; data_ultimo_aso?: string } to the Organization interface in @/types
  2. Remove the as any casts and use optional chaining directly: editingCompany?.custom_fields ?? {}
  3. Ensure the database schema and queries include this field if custom fields are meant to be persisted
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/contacts/components/CompanyFormModal.tsx` at line 26, The code is
using an unsafe cast (editingCompany as any) to access custom_fields; instead
extend the Organization interface in your types module (add optional property
custom_fields?: { num_funcionarios?: number; cnae?: string; nrs_aplicaveis?:
string; data_ultimo_aso?: string }) so the Company/Organization type includes
custom_fields, then remove the casts in CompanyFormModal.tsx and access with
optional chaining (editingCompany?.custom_fields ?? {}); also review any DB
schema/queries that persist crm_companies to ensure custom_fields is persisted
or handled appropriately if it should be stored.

Comment on lines +190 to +199
if (data.type === "MEETING") {
const startDate = data.date ? new Date(data.date) : null;
if (startDate && !isNaN(startDate.getTime())) {
fetch("https://n8n-production-9012a.up.railway.app/webhook/0ebbdfef-a03e-4109-bdce-7d00e70218f0", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: data.title, description: data.description || data.dealTitle, start_time: startDate.toISOString(), end_time: new Date(startDate.getTime() + 3600000).toISOString(), attendees: user?.email || "" })
}).catch((err) => console.error("[Calendar] Webhook error:", err));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Move webhook URL out of client bundle and per-organization config.

The webhook URL is hardcoded into a client-side hook, so it ships in the browser bundle, fires for every tenant, cannot be disabled per organization, and routes user PII (email, meeting title/description) to a single external endpoint regardless of tenant. This breaks multi-tenant isolation and makes the integration impossible to audit or rotate without a code deploy.

Recommended approach:

  • Store the webhook URL (and an enable flag) in organization_settings, retrieved via the existing org-config pattern (similar to getOrgAIConfig).
  • Trigger the webhook from a server-side route (Route Handler) or Edge Function rather than from the browser, so the URL and any auth header stay off the client and an organization_id can be enforced/logged.
  • Sanitize the URL with sanitizeUrl() from @/lib/utils/sanitize.ts before calling fetch.

As per coding guidelines: "Store AI provider API keys in organization_settings database table, not in environment variables; retrieve via getOrgAIConfig(orgId)" — the same principle applies to per-tenant webhook destinations, and "All database queries must filter by organization_id to ensure multi-tenant security" implies external integrations should also be tenant-scoped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/query/hooks/useActivitiesQuery.ts` around lines 190 - 199, The
client-side webhook call in useActivitiesQuery.ts (inside the useActivitiesQuery
hook) must be removed and replaced with a server-side trigger: add
per-organization webhook settings to the organization_settings table and expose
them via the existing org-config pattern (similar to getOrgAIConfig) so each org
has an enabled flag and webhook_url; create a Route Handler/Edge Function
endpoint that accepts a minimal meeting payload (title, description, start_time,
end_time, organization_id, attendee_email) and from server-side reads the
organization_settings (filtering by organization_id) to get and sanitize the
webhook_url with sanitizeUrl() before calling fetch, include any auth header
from settings, and log the organization_id; update useActivitiesQuery to POST to
this new internal endpoint instead of calling the hardcoded external URL and
ensure no PII or webhook URLs remain in the client bundle.

fetch("https://n8n-production-9012a.up.railway.app/webhook/0ebbdfef-a03e-4109-bdce-7d00e70218f0", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: data.title, description: data.description || data.dealTitle, start_time: startDate.toISOString(), end_time: new Date(startDate.getTime() + 3600000).toISOString(), attendees: user?.email || "" })
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

end_time hardcoded to start + 1 hour.

Every meeting is reported as exactly 60 minutes, regardless of the user’s actual scheduling. Calendar events created from this webhook will have wrong durations for any non‑1h meeting. If the Activity model doesn’t carry an explicit end time, either:

  • Add an endDate/durationMinutes to the create payload and use it here, or
  • Make the default duration configurable per organization rather than baked into the hook.
🛠️ Suggested adjustment (assuming an optional duration is added to the activity payload)
-          body: JSON.stringify({ title: data.title, description: data.description || data.dealTitle, start_time: startDate.toISOString(), end_time: new Date(startDate.getTime() + 3600000).toISOString(), attendees: user?.email || "" })
+          body: JSON.stringify({
+            title: data.title,
+            description: data.description || data.dealTitle,
+            start_time: startDate.toISOString(),
+            end_time: new Date(
+              startDate.getTime() + (data.durationMinutes ?? 60) * 60_000
+            ).toISOString(),
+            attendees: user?.email || '',
+          })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/query/hooks/useActivitiesQuery.ts` at line 196, The request body
currently hardcodes end_time to start_time plus 1 hour in useActivitiesQuery.ts
(where the payload is built for the webhook), causing incorrect durations;
update the payload construction in that function to compute end_time from a
provided Activity field (preferably data.endDate or data.durationMinutes) and
fall back to a per-organization default duration if those are absent (read the
org default from the existing org/config API or pass it into the hook), and
ensure the body uses the computed ISO string for end_time instead of new
Date(startDate.getTime() + 3600000).

Comment on lines +520 to +527
onSuccess: (data, { dealId }) => {
queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
);
});
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

onSettled invalidation cascades to DEALS_VIEW_KEY, undoing this optimization; also missing <DealView[]> generic.

Two concerns with the new direct cache update:

  1. The unchanged onSettled at line 530 calls queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() }). Because DEALS_VIEW_KEY = [...queryKeys.deals.lists(), 'view'], TanStack's prefix matching will invalidate DEALS_VIEW_KEY as well, triggering an immediate refetch right after this setQueryData. That largely defeats the "optimize deal item cache updates" intent, and it diverges from the pattern used everywhere else in this file (e.g. useUpdateDeal, useDeleteDeal) where the explicit comment is "NÃO fazer invalidateQueries para deals - Realtime gerencia a sincronização".

  2. setQueryData is called without the <DealView[]> generic, so old is inferred as unknown and old.map(...) is not type-safe under strict mode. Every other call site in this file passes the generic explicitly (e.g. lines 272, 299, 363, 487).

Based on learnings: "For Deals entity mutations, always use [...queryKeys.deals.lists(), 'view'] cache key pattern" and "Prefer setQueryData over invalidateQueries for instant UI updates".

♻️ Proposed fix
-    onSuccess: (data, { dealId }) => {
-      queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
-        if (!old) return old;
-        return old.map((d) =>
-          d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
-        );
-      });
-    },
-    onSettled: (_data, _error, { dealId }) => {
-      queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
-      queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() });
-    },
+    onSuccess: (data, { dealId }) => {
+      queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, (old) => {
+        if (!old) return old;
+        return old.map((d) =>
+          d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
+        );
+      });
+    },
+    onSettled: (_data, _error, { dealId }) => {
+      // NÃO invalidar queryKeys.deals.lists() — cascateia para DEALS_VIEW_KEY
+      // e desfaz o setQueryData acima. Realtime mantém a sincronização.
+      queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSuccess: (data, { dealId }) => {
queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
);
});
},
onSuccess: (data, { dealId }) => {
queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === dealId ? { ...d, items: [...(d.items ?? []), data.item] } : d
);
});
},
onSettled: (_data, _error, { dealId }) => {
// NÃO invalidar queryKeys.deals.lists() — cascateia para DEALS_VIEW_KEY
// e desfaz o setQueryData acima. Realtime mantém a sincronização.
queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/query/hooks/useDealsQuery.ts` around lines 520 - 527, The direct cache
update in the onSuccess handler for adding an item uses queryClient.setQueryData
without the <DealView[]> generic (so old is inferred as unknown) and is
immediately undone because the existing onSettled still calls
queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() }) which
prefix-invalidates DEALS_VIEW_KEY; fix by adding the explicit generic to
setQueryData (setQueryData<DealView[]>) and stop invalidating the parent lists
key in the onSettled for this mutation (either remove or narrow the
invalidateQueries call so it does not target queryKeys.deals.lists()) so the
optimized in-place update to DEALS_VIEW_KEY persists and remains type-safe.

Comment on lines +547 to +554
onSuccess: (data) => {
queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === data.dealId ? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) } : d
);
});
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same cascade-invalidation and missing-generic issues as useAddDealItem.

The onSettled at line 557 invalidates queryKeys.deals.lists(), which prefix-matches DEALS_VIEW_KEY and triggers a refetch immediately after the optimization here. Also missing <DealView[]> generic on setQueryData, leaving old as unknown.

Based on learnings: "For Deals entity mutations, always use [...queryKeys.deals.lists(), 'view'] cache key pattern" and "Prefer setQueryData over invalidateQueries for instant UI updates".

♻️ Proposed fix
-    onSuccess: (data) => {
-      queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
-        if (!old) return old;
-        return old.map((d) =>
-          d.id === data.dealId ? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) } : d
-        );
-      });
-    },
-    onSettled: (_data, _error, { dealId }) => {
-      queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
-      queryClient.invalidateQueries({ queryKey: queryKeys.deals.lists() });
-    },
+    onSuccess: (data) => {
+      queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, (old) => {
+        if (!old) return old;
+        return old.map((d) =>
+          d.id === data.dealId
+            ? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) }
+            : d
+        );
+      });
+    },
+    onSettled: (_data, _error, { dealId }) => {
+      // Mesmo motivo de useAddDealItem: lists() cascateia em DEALS_VIEW_KEY.
+      queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSuccess: (data) => {
queryClient.setQueryData(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === data.dealId ? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) } : d
);
});
},
onSuccess: (data) => {
queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, (old) => {
if (!old) return old;
return old.map((d) =>
d.id === data.dealId
? { ...d, items: (d.items ?? []).filter((i) => i.id !== data.itemId) }
: d
);
});
},
onSettled: (_data, _error, { dealId }) => {
// Mesmo motivo de useAddDealItem: lists() cascateia em DEALS_VIEW_KEY.
queryClient.invalidateQueries({ queryKey: queryKeys.deals.detail(dealId) });
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/query/hooks/useDealsQuery.ts` around lines 547 - 554, The current
onSuccess handler updates the DEALS_VIEW_KEY optimistically but uses
setQueryData without a generic (leaving old as unknown) and then onSettled calls
invalidateQueries(queryKeys.deals.lists()) which prefix-matches and immediately
refetches, negating the optimization; fix by changing setQueryData to use the
explicit generic setQueryData<DealView[]> (so old is typed) and update only the
specific view cache key pattern [...queryKeys.deals.lists(), 'view'] (i.e. use
queryClient.setQueryData<DealView[]>(DEALS_VIEW_KEY, ...) where DEALS_VIEW_KEY
is the view key), and change the onSettled invalidation to invalidate only
[...queryKeys.deals.lists(), 'view'] (or remove the invalidate if you prefer
relying on setQueryData) so there is no cascade invalidation from
queryKeys.deals.lists().

// ============ COMPANY SCHEMAS ============

export const companyFormSchema = z.object({
num_funcionarios: z.coerce.number().int().min(0).optional(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

In Zod 4.3.6, does z.coerce.number().optional() treat an empty string input as undefined or coerce it to 0?

💡 Result:

In Zod 4.3.6, z.coerce.number.optional coerces an empty string input ("") to 0, not undefined. The coercion step uses JavaScript's Number(""), which returns 0. This 0 is then passed to the inner z.number validation (which accepts 0) and the .optional wrapper. Since 0 is defined and valid, the output is 0 | undefined (from optional), but specifically 0 for empty string input. This behavior is confirmed by: - Official test in Zod 4.3.6 source: expect(schema.parse("")).toEqual(0); for z.coerce.number. - Multiple GitHub issues/discussions reporting z.coerce.number.parse("") returns 0. - Documentation stating z.coerce.number uses Number(input). No changes to this behavior in 4.3.x releases. Users often use preprocess/transform/pipe for custom empty string handling (e.g., to undefined).

Citations:


🏁 Script executed:

git ls-files | grep -E 'schemas\.(ts|js)' | head -20

Repository: thaleslaray/nossocrm

Length of output: 171


🏁 Script executed:

wc -l lib/validations/schemas.ts

Repository: thaleslaray/nossocrm

Length of output: 95


🏁 Script executed:

sed -n '110,135p' lib/validations/schemas.ts

Repository: thaleslaray/nossocrm

Length of output: 1195


Preprocess empty string input to undefined to avoid silent coercion to 0.

<input type="number"> with an empty value yields '' from react-hook-form. The current schema coerces '' to 0 (only undefined bypasses .optional()), so clearing the field persists 0 employees instead of leaving it unspecified. Use .preprocess() to convert empty strings to undefined:

♻️ Proposed fix
-  num_funcionarios: z.coerce.number().int().min(0).optional(),
+  num_funcionarios: z
+    .preprocess(
+      (val) => (val === '' || val === null ? undefined : val),
+      z.coerce.number().int().min(0).optional()
+    ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
num_funcionarios: z.coerce.number().int().min(0).optional(),
num_funcionarios: z
.preprocess(
(val) => (val === '' || val === null ? undefined : val),
z.coerce.number().int().min(0).optional()
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/validations/schemas.ts` at line 123, The num_funcionarios schema silently
coerces an empty string to 0 causing cleared numeric inputs to persist as 0;
update the num_funcionarios definition to first preprocess the value (in the
schema for num_funcionarios) converting '' to undefined, then apply
z.coerce.number().int().min(0).optional() so that empty string becomes undefined
and respects .optional()—locate the num_funcionarios symbol in the schema and
wrap the existing coercion with a .preprocess(fn) that returns undefined for ''
and the original value otherwise.

Comment on lines +123 to +126
num_funcionarios: z.coerce.number().int().min(0).optional(),
cnae: optionalString.pipe(z.string().max(MAX_LENGTHS.SHORT_TEXT)),
nrs_aplicaveis: optionalString.pipe(z.string().max(500)),
data_ultimo_aso: optionalString.pipe(z.string().max(MAX_LENGTHS.SHORT_TEXT)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether a migration adds custom_fields to crm_companies and whether
# the create/update paths and read selects include it.
fd -e sql . supabase/migrations | xargs rg -n -C2 'crm_companies'
echo '--- handleCompanySubmit / mutations ---'
rg -nP -C3 'handleCompanySubmit|crm_companies' --type=ts --type=tsx
echo '--- API routes for companies ---'
fd -t f -e ts . app/api | xargs rg -n -C3 'crm_companies|custom_fields'

Repository: thaleslaray/nossocrm

Length of output: 14680


🏁 Script executed:

sed -n '116,126p' supabase/migrations/20251201000000_schema_init.sql

Repository: thaleslaray/nossocrm

Length of output: 470


🏁 Script executed:

rg -n 'handleCompanySubmit' --type=ts --type=jsx --type=js

Repository: thaleslaray/nossocrm

Length of output: 92


🏁 Script executed:

rg -n 'custom_fields' --type=ts --type=jsx --type=js

Repository: thaleslaray/nossocrm

Length of output: 92


🏁 Script executed:

rg 'handleCompanySubmit' -n

Repository: thaleslaray/nossocrm

Length of output: 389


🏁 Script executed:

rg 'num_funcionarios|cnae|nrs_aplicaveis|data_ultimo_aso' -n

Repository: thaleslaray/nossocrm

Length of output: 2080


🏁 Script executed:

rg 'companyFormSchema|CompanyFormSchema' -n

Repository: thaleslaray/nossocrm

Length of output: 664


🏁 Script executed:

sed -n '200,230p' features/contacts/hooks/useContactsController.ts

Repository: thaleslaray/nossocrm

Length of output: 1265


🏁 Script executed:

sed -n '60,75p' features/contacts/components/CompanyFormModal.tsx

Repository: thaleslaray/nossocrm

Length of output: 412


🏁 Script executed:

rg 'createCompanyMutation|updateCompanyMutation' -A5 -B2 features/contacts/hooks/useContactsController.ts | head -50

Repository: thaleslaray/nossocrm

Length of output: 1982


🏁 Script executed:

sed -n '64,70p' features/contacts/components/CompanyFormModal.tsx

Repository: thaleslaray/nossocrm

Length of output: 181


🏁 Script executed:

rg 'export.*useCreateCompany|export.*useUpdateCompany' -A30 -B2 | head -80

Repository: thaleslaray/nossocrm

Length of output: 3308


🏁 Script executed:

rg 'useCreateCompany|useUpdateCompany' --type=ts lib/hooks features/contacts/hooks --files-with-matches

Repository: thaleslaray/nossocrm

Length of output: 113


🏁 Script executed:

rg 'companiesService' -B2 -A2 lib/query/hooks/useContactsQuery.ts | head -20

Repository: thaleslaray/nossocrm

Length of output: 981


🏁 Script executed:

fd -type f -name '*companies*service*' -o -name '*service*' lib/ | grep -i compan

Repository: thaleslaray/nossocrm

Length of output: 235


🏁 Script executed:

rg 'const companiesService|export.*companiesService' -A5

Repository: thaleslaray/nossocrm

Length of output: 1211


🏁 Script executed:

sed -n '/^export const companiesService/,/^export const [a-zA-Z]/p' lib/supabase/contacts.ts | head -150

Repository: thaleslaray/nossocrm

Length of output: 5117


Form collects fields that are discarded before database persistence.

The form (CompanyFormModal.tsx) renders and validates num_funcionarios, cnae, nrs_aplicaveis, and data_ultimo_aso using companyFormSchema, but handleCompanySubmit accepts only { name, industry, website }. These four fields are parsed by validation but never passed to the mutation, and companiesService.create() and companiesService.update() only handle name, industry, and website. The underlying crm_companies table has no columns to store the new fields. Users will enter data in the form with no indication it won't be saved.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/validations/schemas.ts` around lines 123 - 126, The schema is validating
fields (num_funcionarios, cnae, nrs_aplicaveis, data_ultimo_aso) that are never
persisted because handleCompanySubmit only sends { name, industry, website } and
companiesService.create/update and the crm_companies table lack these columns;
either stop validating/displaying unused fields or wire them end-to-end. Fix by
one of two options: (A) Remove these four fields from companyFormSchema and from
CompanyFormModal.tsx so validation and UI match companiesService inputs, or (B)
Add them to the persistence layer: extend the DTO/type used by
handleCompanySubmit to include num_funcionarios, cnae, nrs_aplicaveis,
data_ultimo_aso; update companiesService.create and companiesService.update to
accept and pass these fields through; add corresponding columns to the
crm_companies table (with a DB migration) and update any repository/ORM mappings
so the new fields are saved and returned.

Comment thread vercel.json
{ "path": "/api/cron/daily-briefing", "schedule": "0 8 * * 1-5" },
{ "path": "/api/cron/template-sync", "schedule": "0 6 * * *" },
{ "path": "/api/cron/stage-evaluations", "schedule": "* * * * *" }
{ "path": "/api/cron/stage-evaluations", "schedule": "0 0 * * *" }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Daily schedule likely breaks stage-evaluation SLA and can create queue backlog.

On Line 13, changing /api/cron/stage-evaluations to 0 0 * * * can delay stage advancement/HITL outcomes by up to 24 hours, even though evaluations are enqueued continuously after AI replies and processed in small batches. This materially changes behavior and risks stale pipeline state.

Please keep this cron high-frequency (e.g., every minute or every few minutes), or pair a lower frequency with a larger batch/parallelization strategy and explicit product acceptance of delayed stage updates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vercel.json` at line 13, The cron entry for the route
"/api/cron/stage-evaluations" was changed to "0 0 * * *", which will run once
daily and create a backlog/stale state; revert or update this schedule to a
high-frequency cron (e.g., every minute or every few minutes like "*/1 * * * *"
or "*/5 * * * *") to preserve current near-real-time processing, or if you must
keep a low frequency, implement a compensating fix in the stage evaluation
handler (the code behind "/api/cron/stage-evaluations") to process much larger
batches and add parallelization and explicit product-level acceptance of delayed
updates before changing the schedule.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant