Skip to content

feat(DEV-15902): Allow Line Items negative price#802

Open
dinis-monite wants to merge 3 commits intomainfrom
feat/DEV-15902_line-items-negative-price
Open

feat(DEV-15902): Allow Line Items negative price#802
dinis-monite wants to merge 3 commits intomainfrom
feat/DEV-15902_line-items-negative-price

Conversation

@dinis-monite
Copy link
Collaborator

@dinis-monite dinis-monite commented Aug 7, 2025

Scope

Allow Line Items negative price values.

Jira ticket: https://monite.atlassian.net/browse/DEV-15902

Implementation

  • Removed min validation from field lineItems.price in PayableDetailsForm.

Steps to test

  1. Create or edit a Payable.
  2. Add or edit one Line Item with a negative value for the price.
  3. The form should accept the nagetive value.
  4. Save the Payable, should see no errors.

Summary by CodeRabbit

  • New Features

    • Line items now support negative prices (e.g., discounts or credits) in payables.
  • Improvements

    • More precise, field-specific validation messages for required fields (e.g., counterpart, bank account, issue date, currency).
    • Streamlined line item entry with consistent form inputs for name, quantity, price, and tax.
    • Minor layout adjustments for clearer totals and better alignment.
  • Chores

    • Patch release entry added for the SDK React package.

@changeset-bot
Copy link

changeset-bot bot commented Aug 7, 2025

🦋 Changeset detected

Latest commit: b231791

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@monite/sdk-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 7, 2025

Walkthrough

Enables negative line-item prices by removing the non-negative validation constraint. Refactors payable forms to use RHFTextField instead of Controller+TextField for several inputs. Applies minor type/format updates (e.g., MoniteCurrency generic, enum typing). Adds a patch changeset entry; no exported API changes.

Changes

Cohort / File(s) Summary of Changes
Changeset
\.changeset/rare-singers-sniff.md
Adds a patch-level changeset describing support for negative line-item prices; no code changes.
Payable details form
packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
Replaces discount field Controller+TextField with RHFTextField; adjusts import ordering (type-only OptionalFields); updates MoniteCurrency generic type argument quoting.
Payable validation schema
packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts
Removes price .min(0) constraint (allow negatives); restructures base schema refinements into separate calls; reformats line-item and enum schema declarations; updates examples; no public type signature changes.
Payable line items form
packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
Switches line item fields (name, quantity, price, tax) to RHFTextField; keeps useFieldArray/useFormContext for array control; maintains adornments for currency/% and total calculation; removes inline Controller error rendering.

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/DEV-15902_line-items-negative-price

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
Contributor

@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: 0

🧹 Nitpick comments (1)
.changeset/rare-singers-sniff.md (1)

5-5: Consider enriching the changeset summary

The title is clear, but adding context—e.g., the Jira ticket DEV-15902 or phrasing the entry in imperative mood (“Allow line-item prices to be negative (DEV-15902)”)—makes the changelog more informative when scanning release notes.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51d76c0 and f6753c8.

📒 Files selected for processing (2)
  • .changeset/rare-singers-sniff.md (1 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{tsx} : Use optimistic updates in React Query mutations when updating Monite API resources to improve perceived performance
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{ts,tsx} : Use custom TypeScript types (e.g., Invoice, CreateInvoiceInput) imported from '@/types/api' for Monite API data structures
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{tsx} : Implement proper error boundaries and handle errors gracefully in React components using Monite API
📚 Learning: applies to packages/sdk-react/**/*.{tsx} : use optimistic updates in react query mutations when upda...
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{tsx} : Use optimistic updates in React Query mutations when updating Monite API resources to improve perceived performance

Applied to files:

  • .changeset/rare-singers-sniff.md
📚 Learning: applies to packages/sdk-react/**/*.{tsx} : implement proper error boundaries and handle errors grace...
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{tsx} : Implement proper error boundaries and handle errors gracefully in React components using Monite API

Applied to files:

  • .changeset/rare-singers-sniff.md
📚 Learning: applies to packages/sdk-react/**/*.{ts,tsx} : use custom typescript types (e.g., invoice, createinvo...
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{ts,tsx} : Use custom TypeScript types (e.g., Invoice, CreateInvoiceInput) imported from '@/types/api' for Monite API data structures

Applied to files:

  • .changeset/rare-singers-sniff.md
📚 Learning: applies to packages/sdk-react/**/*.{tsx} : use query keys consistently in react query hooks for moni...
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{tsx} : Use query keys consistently in React Query hooks for Monite API calls to ensure correct cache management

Applied to files:

  • .changeset/rare-singers-sniff.md
📚 Learning: applies to packages/sdk-react/**/*.{tsx} : use `api...usemutation` for post, pu...
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{tsx} : Use `api.<resource>.<operation>.useMutation` for POST, PUT, PATCH, and DELETE requests to Monite API endpoints

Applied to files:

  • .changeset/rare-singers-sniff.md
📚 Learning: applies to packages/sdk-react/**/*.{tsx} : handle global and mutation-specific errors in monite api ...
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:11:13.444Z
Learning: Applies to packages/sdk-react/**/*.{tsx} : Handle global and mutation-specific errors in Monite API calls using the `onError` callback in React Query hooks

Applied to files:

  • .changeset/rare-singers-sniff.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Analyze (javascript)

@dinis-monite dinis-monite added the pullpreview Generate a live preview for this pull request label Aug 7, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Aug 7, 2025

🚀 Preview URLs are now available! 🚀

Copy link
Contributor

@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: 5

🧹 Nitpick comments (11)
packages/sdk-demo/src/locales/en/messages.ts (2)

1-1: Avoid blanket ESLint disable; not needed here.

This suppresses all rules repository-wide for this file. Since the only flagged issue is the duplicate import, remove the directive.

-/*eslint-disable*/

5-7: Optional: prefer a typed object (or imported JSON) over JSON.parse + cast.

Using a literal (or importing a JSON file) validated with satisfies Messages gives compile-time checks on keys and values.

Example pattern (illustrative):

export const messages = {
  // ...catalog...
} as const satisfies Messages;
packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (1)

17-20: Avoid id collision with useFieldArray; set keyName and use it for React keys.

Your items also have an id field; RHF uses id internally. Use a custom keyName to prevent confusion.

-  const { fields, append, remove } = useFieldArray({
-    control,
-    name: 'lineItems',
-  });
+  const { fields, append, remove } = useFieldArray({
+    control,
+    name: 'lineItems',
+    keyName: 'key',
+  });
-          key={item.id}
+          key={item.key}

Also applies to: 31-31

packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts (1)

41-46: Reflect negative-price support in examples/meta.

Schema allows negatives now; update examples to set expectations.

-    price: z.number().meta({
+    price: z.number().meta({
       title: t(i18n)`Item price`,
       description: t(i18n)`Price per unit`,
-      examples: [10.99, 25.5, 100.0],
+      examples: [10.99, 25.5, 100.0, -5.0],
     }),
packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx (5)

100-116: Invoice-only gating: OK; minor i18n nit

Gating looks good. Drop the unnecessary interpolation to keep the translation static.

-          description={t(
-            i18n
-          )`You can not create receivable with a type other than “${'invoice'}”`}
+          description={t(i18n)`You can not create receivable with a type other than “invoice”`}

252-253: Load state not considered for contacts; may block reminder validation

Counterpart contacts may still be loading at submit time and trigger a false “no email” error. Capture isLoading and guard submit/disable CTA until ready.

-  const { data: counterpartContacts } =
-    useCounterpartContactList(counterpartId);
+  const { data: counterpartContacts, isLoading: areContactsLoading } =
+    useCounterpartContactList(counterpartId);

Outside this hunk, also disable the submit button until contacts are loaded:

// In the Save button
disabled={createReceivable.isPending || areContactsLoading}

314-319: Boolean intent: coerce to booleans for clarity

Prevent string/undefined leakage into later conditionals.

-    const customerHasRemindersEnabled =
-      counterpart && counterpart?.reminders_enabled;
-    const customerHasDefaultEmail =
-      counterpart &&
-      counterpartContacts?.find((contact) => contact.is_default)?.email;
+    const customerHasRemindersEnabled = Boolean(
+      counterpart?.reminders_enabled
+    );
+    const customerHasDefaultEmail = Boolean(
+      counterpartContacts?.find((contact) => contact.is_default)?.email
+    );

338-378: Deduplicate reminder validation branches

Three nearly identical blocks can be consolidated for readability and maintenance.

-    if (
-      !customerHasRemindersEnabled &&
-      customerHasDefaultEmail &&
-      (values.payment_reminder_id || values.overdue_reminder_id)
-    ) {
-      showErrorToast(
-        new Error(
-          'Payment reminders are disabled for this customer. Please enable them in the customer details or turn them off.'
-        )
-      );
-      return;
-    }
-
-    if (
-      !customerHasDefaultEmail &&
-      customerHasRemindersEnabled &&
-      (values.payment_reminder_id || values.overdue_reminder_id)
-    ) {
-      showErrorToast(
-        new Error(
-          'No email address is added for the selected customer. Please add it to the customer details or turn off the reminders.'
-        )
-      );
-      return;
-    }
-
-    if (
-      !customerHasRemindersEnabled &&
-      !customerHasDefaultEmail &&
-      (values.payment_reminder_id || values.overdue_reminder_id)
-    ) {
-      showErrorToast(
-        new Error(
-          'Reminders are disabled for this customer, and no email address has been added for it. Please update the details or turn off reminders.'
-        )
-      );
-      return;
-    }
+    const remindersSelected = Boolean(
+      values.payment_reminder_id || values.overdue_reminder_id
+    );
+    if (remindersSelected) {
+      if (!customerHasRemindersEnabled && !customerHasDefaultEmail) {
+        showErrorToast(
+          new Error(
+            'Reminders are disabled for this customer, and no email address has been added for it. Please update the details or turn off reminders.'
+          )
+        );
+        return;
+      }
+      if (!customerHasRemindersEnabled) {
+        showErrorToast(
+          new Error(
+            'Payment reminders are disabled for this customer. Please enable them in the customer details or turn them off.'
+          )
+        );
+        return;
+      }
+      if (!customerHasDefaultEmail) {
+        showErrorToast(
+          new Error(
+            'No email address is added for the selected customer. Please add it to the customer details or turn off the reminders.'
+          )
+        );
+        return;
+      }
+    }

1107-1111: Avoid Math.random in render: unstable IDs can cause unnecessary re-mounts

Generate stable IDs; index-based fallback is fine for a non-editable preview.

-            line_items: (lineItems || []).map((item) => ({
+            line_items: (lineItems || []).map((item, idx) => ({
               ...item,
-              id: item.id || `temp-${Math.random().toString(36).substr(2, 9)}`,
+              id: item.id || `temp-${idx}`,
             })) as CreateReceivablesFormBeforeValidationLineItemProps[],
packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.utils.ts (2)

58-71: Fallback label “item” may be misleading

Consider “unit” or an empty string to avoid confusing users when measure unit is unknown.

-  return t(i18n)`item`;
+  return t(i18n)`unit`;

128-132: Country code normalization

If getCountries uses upper-case ISO codes, normalize input to reduce fallbacks.

-export const getCountryName = (i18n: I18n, countryCode?: string) => {
+export const getCountryName = (i18n: I18n, countryCode?: string) => {
   if (!countryCode) return '';
-
-  return getCountries(i18n)[countryCode] ?? countryCode;
+  const code = countryCode.toUpperCase();
+  return getCountries(i18n)[code] ?? code;
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6753c8 and 0b49670.

📒 Files selected for processing (19)
  • packages/sdk-demo/public/config.json (1 hunks)
  • packages/sdk-demo/src/locales/en/messages.ts (1 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx (4 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/helpers.ts (1 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts (1 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (3 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/usePayableDetails.tsx (2 hunks)
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx (10 hunks)
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.types.ts (1 hunks)
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.utils.ts (5 hunks)
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreviewMonite.module.css (2 hunks)
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/Items/ItemSelector.tsx (3 hunks)
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/validation.ts (1 hunks)
  • packages/sdk-react/src/hooks/useA4Dimensions.ts (3 hunks)
  • packages/sdk-react/src/hooks/useAdaptiveScale.ts (5 hunks)
  • packages/sdk-react/src/hooks/useResizeObserver.ts (3 hunks)
  • packages/sdk-react/src/hooks/useWindowResize.ts (3 hunks)
  • packages/sdk-react/src/ui/components/aspect-ratio.tsx (1 hunks)
  • packages/sdk-react/src/utils/dimensions.ts (4 hunks)
✅ Files skipped from review due to trivial changes (12)
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.types.ts
  • packages/sdk-react/src/components/payables/PayableDetails/usePayableDetails.tsx
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/Items/ItemSelector.tsx
  • packages/sdk-react/src/hooks/useWindowResize.ts
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/validation.ts
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/helpers.ts
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreviewMonite.module.css
  • packages/sdk-react/src/utils/dimensions.ts
  • packages/sdk-react/src/hooks/useA4Dimensions.ts
  • packages/sdk-demo/public/config.json
  • packages/sdk-react/src/ui/components/aspect-ratio.tsx
  • packages/sdk-react/src/hooks/useResizeObserver.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
🧰 Additional context used
📓 Path-based instructions (2)
packages/sdk-react/**/*.{ts,tsx}

📄 CodeRabbit inference engine (packages/sdk-react/.cursor/rules/api-calls.mdc)

packages/sdk-react/**/*.{ts,tsx}: Use proper TypeScript types for API data and leverage type inference from the OpenAPI schema
Use custom TypeScript types (e.g., Invoice, CreateInvoiceInput) imported from '@/types/api' for Monite API data structures

Files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/hooks/useAdaptiveScale.ts
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.utils.ts
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx
packages/sdk-react/**/*.tsx

📄 CodeRabbit inference engine (packages/sdk-react/.cursor/rules/ui-components.mdc)

packages/sdk-react/**/*.tsx: Always use shadcn/ui components when creating new UI elements
Import components from '@/ui/components/*' when using shadcn/ui components
All Tailwind classes should have the prefix 'mtw' (e.g., 'mtw:w-full')
Use Tailwind CSS utility classes for all styling
Avoid inline styles or styled-components
Follow Tailwind's mobile-first responsive design approach
Use Tailwind's color palette and spacing scale
Do NOT use Material UI for new components
When modifying files with Material UI: only update to shadcn/ui if the component needs significant changes; otherwise, maintain existing MUI implementation; add TODO comments when MUI components should be migrated
Always prefer shadcn/ui components such as Button, Input, Card, Dialog, etc., from '@/ui/components/[component]'
Use React Hook Form with shadcn/ui form components
Use 'lucide-react' for icons and import icons like 'import { Search, Menu, X } from "lucide-react"'
Always use named imports from the specific ui component file
Use the variant prop for different styles (e.g., variant="outline" for buttons)
Use the size prop when available (e.g., size="sm" for smaller buttons)
Extend components with className prop using Tailwind utilities
When updating Material UI components to shadcn/ui: map MUI component to shadcn/ui equivalent, convert sx props and makeStyles to Tailwind classes, update event handlers if API differs, test thoroughly, remove MUI imports only after confirming all usages are replaced
Use TypeScript for all new components
Leverage component prop types for better IntelliSense

Files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx
🧠 Learnings (10)
📚 Learning: 2025-09-04T16:18:44.198Z
Learnt from: costa-monite
PR: team-monite/monite-sdk#785
File: packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx:6-6
Timestamp: 2025-09-04T16:18:44.198Z
Learning: For forms in monite-sdk, prefer lib-agnostic TypeScript types (e.g., PayableDetailsFormFields) for component generics and contracts; avoid coupling components to Zod-inferred types. Use Zod only at the validation layer (e.g., zodResolver with a schema typed as ZodType<FormValues>).

Applied to files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts
📚 Learning: 2025-09-08T09:09:14.252Z
Learnt from: costa-monite
PR: team-monite/monite-sdk#785
File: packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreviewMonite.tsx:320-336
Timestamp: 2025-09-08T09:09:14.252Z
Learning: In InvoicePreviewMonite component at packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreviewMonite.tsx, the price values from item.product.price.value are already in minor units (as defined in the PriceFloat API schema), so they can be passed directly to formatCurrencyToDisplay without conversion.

Applied to files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.utils.ts
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx
📚 Learning: 2025-07-21T08:10:47.057Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: examples/with-nextjs-and-clerk-auth/.cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-21T08:10:47.057Z
Learning: Applies to examples/with-nextjs-and-clerk-auth/src/components/**/*.tsx : Use React Hook Form with shadcn/ui form components for form handling

Applied to files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx
📚 Learning: 2025-07-21T08:11:43.801Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-21T08:11:43.801Z
Learning: Applies to packages/sdk-react/**/*.tsx : Use React Hook Form with shadcn/ui form components

Applied to files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx
📚 Learning: 2025-09-05T11:02:00.698Z
Learnt from: costa-monite
PR: team-monite/monite-sdk#785
File: packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.tsx:34-39
Timestamp: 2025-09-05T11:02:00.698Z
Learning: In the InvoicePreview component at packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.tsx, the minScale: 1 setting in useAdaptiveScale is intentionally set to prevent downscaling below 100% to maintain invoice readability and quality, even if it requires scrolling on smaller viewports.

Applied to files:

  • packages/sdk-react/src/hooks/useAdaptiveScale.ts
  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx
📚 Learning: 2025-07-21T08:11:43.801Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-21T08:11:43.801Z
Learning: Applies to packages/sdk-react/**/*.tsx : When updating Material UI components to shadcn/ui: map MUI component to shadcn/ui equivalent, convert sx props and makeStyles to Tailwind classes, update event handlers if API differs, test thoroughly, remove MUI imports only after confirming all usages are replaced

Applied to files:

  • packages/sdk-react/src/hooks/useAdaptiveScale.ts
📚 Learning: 2025-07-21T08:11:43.801Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-21T08:11:43.801Z
Learning: Applies to packages/sdk-react/**/*.tsx : Use Tailwind's color palette and spacing scale

Applied to files:

  • packages/sdk-react/src/hooks/useAdaptiveScale.ts
📚 Learning: 2025-07-21T08:11:43.801Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-21T08:11:43.801Z
Learning: Applies to packages/sdk-react/**/*.tsx : When modifying files with Material UI: only update to shadcn/ui if the component needs significant changes; otherwise, maintain existing MUI implementation; add TODO comments when MUI components should be migrated

Applied to files:

  • packages/sdk-react/src/hooks/useAdaptiveScale.ts
📚 Learning: 2025-07-21T08:11:43.801Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-21T08:11:43.801Z
Learning: Applies to packages/sdk-react/**/*.tsx : Use the size prop when available (e.g., size="sm" for smaller buttons)

Applied to files:

  • packages/sdk-react/src/hooks/useAdaptiveScale.ts
📚 Learning: 2025-07-21T08:10:12.113Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: examples/with-nextjs-and-clerk-auth/.cursor/rules/api-calls.mdc:0-0
Timestamp: 2025-07-21T08:10:12.113Z
Learning: Applies to examples/with-nextjs-and-clerk-auth/**/*.tsx : Use React Query hooks (useQuery, useMutation) provided by the API.

Applied to files:

  • packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx
🧬 Code graph analysis (2)
packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (1)
packages/sdk-react/src/ui/RHF/RHFTextField/RHFTextField.tsx (1)
  • RHFTextField (8-78)
packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/CreateReceivables.tsx (1)
packages/sdk-react/src/core/queries/useCounterpart.ts (1)
  • useCounterpartContactList (342-422)
🪛 Biome (2.1.2)
packages/sdk-demo/src/locales/en/messages.ts

[error] 4-4: Shouldn't redeclare 'Messages'. Consider to delete it or rename it.

'Messages' is defined here:

(lint/suspicious/noRedeclare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and Push sdk-playground
  • GitHub Check: Build and Push sdk-demo-with-nextjs-and-clerk-auth
  • GitHub Check: Build and Push sdk-drop-in
  • GitHub Check: Build
🔇 Additional comments (15)
packages/sdk-react/src/hooks/useAdaptiveScale.ts (5)

1-1: Import of useA4Dimensions looks good

Matches the hook’s intent to base defaults on A4 dimensions. No issues.


5-5: Import reordering/readability

Purely stylistic; no functional impact.


39-43: A4-based min dimensions — OK

Using useAdaptiveDimensions ? a4Dimensions : 794x1123 is consistent with the A4 default. This aligns with the prior decision to keep minScale >= 1 for readability.


59-65: Defensive guards — OK

Early-returning to minScale on non-positive dimensions is sensible and matches the “never downscale below 1” requirement.


120-120: No-op change

Trailing newline/brace formatting only.

packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (2)

36-41: LGTM on migrating to RHFTextField for name.

Keeps validation/error UI consistent with the new abstraction.


93-103: LGTM on VAT constraints.

0–100% bounds are enforced at the schema and UI layer.

packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts (3)

14-21: LGTM on whitespace-safe requiredString.

Prevents “all-spaces” values from slipping through.


31-39: LGTM on quantity and tax constraints.

Whole-number quantity > 0 and tax within 0–100 are appropriate.

Also applies to: 47-55


83-90: Verify API contract for optional counterpart_bank_account_id. Empty counterpartBankAccount is mapped to undefined in the request payload and validation/refine logic already handles required cases.

packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreview.utils.ts (5)

1-7: Imports: OK

New utils are properly sourced and typed.


16-30: Payment terms discount: OK

Priority order and minor→major conversion are correct.


35-42: Measure unit lookup: OK

Straightforward and typed; null fallback is sensible.


77-86: Rate value resolution: OK

Delegation to getRateValueForDisplay with sensible defaults.


117-126: Counterpart phone extraction: confirm both schema variants include phone

Ensure in the CounterpartResponse type that both the organization and individual shapes always (or optionally) include phone; adjust your code to handle cases where either variant may omit phone.

@dinis-monite dinis-monite force-pushed the feat/DEV-15902_line-items-negative-price branch from 0b49670 to d4fc251 Compare September 8, 2025 18:21
…Form and PayableLineItemsForm for improved input number handling
Copy link
Contributor

@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: 2

♻️ Duplicate comments (3)
packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (3)

86-89: Use InputAdornment and avoid inline watch; add step='any' for decimals.

-                type="number"
-                fullWidth
-                InputProps={{
-                  endAdornment: getSymbolFromCurrency(watch('currency')),
-                }}
+                type="number"
+                inputProps={{ step: 'any' }}
+                fullWidth
+                InputProps={{
+                  endAdornment: (
+                    <InputAdornment position="end">
+                      {getSymbolFromCurrency(currentCurrency)}
+                    </InputAdornment>
+                  ),
+                }}

Also update imports per comment on Lines 9-10.


44-51: Numeric-input interim state issue applies here too (quantity).

Once RHFTextField is fixed, typing transitions (e.g., clearing then retyping) will behave correctly across all numeric fields.


80-89: Negative prices still impossible due to RHFTextField number coercion (blocks "-" typing).

RHFTextField coerces every keystroke via Number(...), so typing "-" or "1." becomes NaN. This prevents entering negatives, defeating DEV-15902.

Apply the RHFTextField fix below (cross-file) and the adornment/step tweaks from the next comment.

🧹 Nitpick comments (9)
.changeset/rare-singers-sniff.md (1)

5-5: Clarify the note to set expectations (discounts remain ≥ 0).

Consider explicitly stating that only line-item price can be negative while discounts remain non-negative, to avoid confusion in the release notes.

- Allow Line Items with negative price values
+ Allow line items with negative price values (discounts remain non-negative)
packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts (3)

31-39: Quantity constraints are appropriate.

Optional: add a sane upper bound to prevent accidental huge values (e.g., max(1_000_000) with a localized message).

-    quantity: z
-      .number()
+    quantity: z
+      .number()
       .int(t(i18n)`Item quantity must be a whole number`)
       .positive(t(i18n)`Item quantity must be greater than 0`)
+      .max(1_000_000, t(i18n)`Item quantity is too large`)

159-169: Ensure the date is valid, not just a Date instance.

new Date('') is an invalid Date but still an instance. Guard against it.

-        if (isFieldRequiredByValidations('issued_at', payablesValidations)) {
-          return data.invoiceDate instanceof Date;
+        if (isFieldRequiredByValidations('issued_at', payablesValidations)) {
+          return (
+            data.invoiceDate instanceof Date &&
+            !isNaN(data.invoiceDate.getTime())
+          );
         }

127-142: Refine splits are clearer and field-scoped – nice.

Optional: add a cross-field refine to ensure dueDate >= invoiceDate when both are present.

Also applies to: 172-182

packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx (1)

185-191: Decouple form generics from Zod-inferred types.

Per prior guidance, prefer lib-agnostic form value types. Use PayableDetailsFormFields as the generic for useForm and component props; keep Zod purely for validation.

-    const methods = useForm<PayableDetailsValidationFields>({
+    const methods = useForm<PayableDetailsFormFields>({
       resolver: zodResolver(
         getPayableDetailsValidationSchema(i18n, payablesValidations)
       ),

Follow-ups:

  • Also update downstream generics (MoniteCurrency, CounterpartAutocomplete, etc.) to PayableDetailsFormFields.
packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (4)

92-102: Wrap '%' in InputAdornment; consider allowing fractional VAT via step='any'.

-                inputProps={{ min: 0, max: 100 }}
+                inputProps={{ min: 0, max: 100, step: 'any' }}
                 fullWidth
-                InputProps={{
-                  endAdornment: '%',
-                }}
+                InputProps={{
+                  endAdornment: (
+                    <InputAdornment position="end">%</InputAdornment>
+                  ),
+                }}

9-10: Import InputAdornment and useWatch.

-import { Button, Grid, IconButton, Typography } from '@mui/material';
-import { useFieldArray, useFormContext } from 'react-hook-form';
+import { Button, Grid, IconButton, Typography, InputAdornment } from '@mui/material';
+import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';

16-23: Prefer useWatch for currency/lineItems to reduce unnecessary re-renders.

-  const { control, watch } = useFormContext<PayableDetailsFormFields>();
+  const { control } = useFormContext<PayableDetailsFormFields>();
   const { fields, append, remove } = useFieldArray({
     control,
     name: 'lineItems',
   });
-  const currentCurrency = watch('currency');
-  const currentLineItems = watch('lineItems');
+  const currentCurrency = useWatch({ control, name: 'currency' });
+  const currentLineItems = useWatch({ control, name: 'lineItems' });

127-135: Default appended item OK; consider using org default VAT if available.

Happy to wire this to a settings-derived default if you have one.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40dfca5 and b231791.

📒 Files selected for processing (4)
  • .changeset/rare-singers-sniff.md (1 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx (4 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts (1 hunks)
  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
packages/sdk-react/**/*.{ts,tsx}

📄 CodeRabbit inference engine (packages/sdk-react/.cursor/rules/api-calls.mdc)

packages/sdk-react/**/*.{ts,tsx}: Use proper TypeScript types for API data and leverage type inference from the OpenAPI schema
Use custom TypeScript types (e.g., Invoice, CreateInvoiceInput) imported from '@/types/api' for Monite API data structures

Files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts
packages/sdk-react/**/*.tsx

📄 CodeRabbit inference engine (packages/sdk-react/.cursor/rules/ui-components.mdc)

packages/sdk-react/**/*.tsx: Always use shadcn/ui components when creating new UI elements
Import components from '@/ui/components/*' when using shadcn/ui components
All Tailwind classes should have the prefix 'mtw' (e.g., 'mtw:w-full')
Use Tailwind CSS utility classes for all styling
Avoid inline styles or styled-components
Follow Tailwind's mobile-first responsive design approach
Use Tailwind's color palette and spacing scale
Do NOT use Material UI for new components
When modifying files with Material UI: only update to shadcn/ui if the component needs significant changes; otherwise, maintain existing MUI implementation; add TODO comments when MUI components should be migrated
Always prefer shadcn/ui components such as Button, Input, Card, Dialog, etc., from '@/ui/components/[component]'
Use React Hook Form with shadcn/ui form components
Use 'lucide-react' for icons and import icons like 'import { Search, Menu, X } from "lucide-react"'
Always use named imports from the specific ui component file
Use the variant prop for different styles (e.g., variant="outline" for buttons)
Use the size prop when available (e.g., size="sm" for smaller buttons)
Extend components with className prop using Tailwind utilities
When updating Material UI components to shadcn/ui: map MUI component to shadcn/ui equivalent, convert sx props and makeStyles to Tailwind classes, update event handlers if API differs, test thoroughly, remove MUI imports only after confirming all usages are replaced
Use TypeScript for all new components
Leverage component prop types for better IntelliSense

Files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
🧠 Learnings (4)
📚 Learning: 2025-07-21T08:10:47.057Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: examples/with-nextjs-and-clerk-auth/.cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-21T08:10:47.057Z
Learning: Applies to examples/with-nextjs-and-clerk-auth/src/components/**/*.tsx : Use React Hook Form with shadcn/ui form components for form handling

Applied to files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
📚 Learning: 2025-07-21T08:11:43.801Z
Learnt from: CR
PR: team-monite/monite-sdk#0
File: packages/sdk-react/.cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-21T08:11:43.801Z
Learning: Applies to packages/sdk-react/**/*.tsx : Use React Hook Form with shadcn/ui form components

Applied to files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
📚 Learning: 2025-09-08T09:09:14.252Z
Learnt from: costa-monite
PR: team-monite/monite-sdk#785
File: packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreviewMonite.tsx:320-336
Timestamp: 2025-09-08T09:09:14.252Z
Learning: In InvoicePreviewMonite component at packages/sdk-react/src/components/receivables/InvoiceDetails/CreateReceivable/sections/components/InvoicePreviewMonite.tsx, the price values from item.product.price.value are already in minor units (as defined in the PriceFloat API schema), so they can be passed directly to formatCurrencyToDisplay without conversion.

Applied to files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts
📚 Learning: 2025-09-04T16:18:44.198Z
Learnt from: costa-monite
PR: team-monite/monite-sdk#785
File: packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx:6-6
Timestamp: 2025-09-04T16:18:44.198Z
Learning: For forms in monite-sdk, prefer lib-agnostic TypeScript types (e.g., PayableDetailsFormFields) for component generics and contracts; avoid coupling components to Zod-inferred types. Use Zod only at the validation layer (e.g., zodResolver with a schema typed as ZodType<FormValues>).

Applied to files:

  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx
  • packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts
🧬 Code graph analysis (2)
packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (1)
packages/sdk-react/src/ui/RHF/RHFTextField/RHFTextField.tsx (1)
  • RHFTextField (8-78)
packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx (3)
packages/sdk-react/src/ui/Currency/MoniteCurrency.tsx (1)
  • MoniteCurrency (57-173)
packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts (1)
  • PayableDetailsValidationFields (185-187)
packages/sdk-react/src/ui/RHF/RHFTextField/RHFTextField.tsx (1)
  • RHFTextField (8-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and Push sdk-demo-with-nextjs-and-clerk-auth
  • GitHub Check: Build and Push sdk-drop-in
  • GitHub Check: Build and Push sdk-playground
  • GitHub Check: Build
🔇 Additional comments (10)
packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/validation.ts (2)

14-21: Trim-aware required string helper looks good.


47-55: Tax constraints look correct.

packages/sdk-react/src/components/payables/PayableDetails/PayableDetailsForm/PayableDetailsForm.tsx (6)

5-5: Type-only import re-ordering is fine.


44-44: RHFTextField import looks good.


583-587: Currency field generic: OK after switching the form generic.

If you adopt the form-generic change above, also update this to:

- <MoniteCurrency<PayableDetailsValidationFields, 'currency'>>
+ <MoniteCurrency<PayableDetailsFormFields, 'currency'>>

682-692: Discount field constraints: consistent with schema.

min: 0 and the schema’s min(0) align; end adornment usage is fine.


650-658: formatToMinorUnits handles negative amounts as expected
formatToMinorUnits returns Number(amount) * 10^minor_units, so negative inputs yield negative minor-unit values without sign loss or clamping.


621-623: Price input field has no min: 0; UI already permits negatives.

packages/sdk-react/src/components/payables/PayableDetails/PayableLineItemsForm/PayableLineItemsForm.tsx (2)

36-41: LGTM: name field migration to RHFTextField looks good.


106-123: Confirm unit expectations in total calculation with negatives.

Verify calculateTotalPriceForLineItem + formatToMinorUnits handle negative prices and taxes as intended (e.g., discounts). Ensure the displayed sign matches business rules.

Copy link
Contributor

@caiomachado-monite caiomachado-monite left a comment

Choose a reason for hiding this comment

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

LGTM

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

Labels

pullpreview Generate a live preview for this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants