Skip to content

Conversation

@aimensahnoun
Copy link
Member

@aimensahnoun aimensahnoun commented Mar 21, 2025

Resolves #22

Automated Invoice Number Generation

Changes

  • Added automated invoice number generation in format YYYYMM-XXXX
  • Made invoice number field read-only in the form
  • Integrated invoice number generation with both create invoice and invoice-me pages

Technical Details

  • Created new utility function generateInvoiceNumber that:
    • Counts existing invoices for the current month per user
    • Formats numbers as YYYYMM-XXXX where XXXX is zero-padded
    • Uses Drizzle ORM for efficient querying
  • Invoice numbers are now generated server-side and passed to the form component
  • Invoice input field is now disabled to prevent manual editing

Screenshots:

CleanShot 2025-03-21 at 05 57 36

Summary by CodeRabbit

  • New Features
    • Updated the invoice creation process to automatically determine and display a sequential invoice number.
    • Invoice numbers now combine current month details with a zero-padded sequence, ensuring accurate monthly tracking.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2025

Walkthrough

This change introduces an auto-increment invoice number feature. A new asynchronous function (getInvoiceCount) retrieves the count of invoices for a user, incrementing it and returning a padded string. The invoice count is passed as a prop to the InvoiceCreator component in multiple pages. Within the component, the count is used by the generateInvoiceNumber function to create a formatted invoice number based on the current date. This integration spans both the front-end pages and backend library modules.

Changes

File(s) Change Summary
src/app/i/[id]/page.tsx
src/app/invoices/create/page.tsx
Added import and asynchronous call to getInvoiceCount(user.id), then passed the resulting invoiceCount prop to the InvoiceCreator component.
src/components/invoice-creator.tsx Updated InvoiceCreatorProps by adding a new invoiceCount property and modified the component to use invoiceCount with the generateInvoiceNumber function for initializing the invoice number.
src/lib/invoice/index.ts Introduced a new asynchronous function getInvoiceCount(userId: string) that retrieves the current month's invoice count for a user, increments it, pads it to four digits, and returns it as a string.
src/lib/invoice/client.ts Added a new function generateInvoiceNumber(invoiceCount: string) that builds a formatted invoice number using the current date (year and month) and the provided invoice count.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant P as Invoice Page
    participant IL as Invoice Library (getInvoiceCount)
    participant IC as InvoiceCreator Component
    participant CL as Client Library (generateInvoiceNumber)

    U->>P: Initiate invoice creation
    P->>IL: getInvoiceCount(user.id)
    IL-->>P: Return invoiceCount
    P->>IC: Pass invoiceCount prop
    IC->>CL: generateInvoiceNumber(invoiceCount)
    CL-->>IC: Return formatted invoiceNumber
Loading

Assessment against linked issues

Objective Addressed Explanation
Auto-increment invoice number generation (#22 - EasyInvoice)
Editable invoice number option (#22 - EasyInvoice) Edit button functionality is not implemented.

Possibly related PRs

Suggested reviewers

  • rodrigopavezi
  • MantisClone
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 1

🧹 Nitpick comments (2)
src/lib/invoice/index.ts (2)

24-27: Consider variable invoice number length for future scalability.

Currently, the invoice count is padded to exactly 4 digits, limiting the monthly invoices to 9999 per user. While this might be sufficient for now, consider making this configurable or adding logic to handle overflow cases for high-volume users.


5-31:

❓ Verification inconclusive

Potential improvements to the invoice number generation function.

The function correctly implements the auto-incrementing invoice number requirement in the format YYYYMM-XXXX. However, there are a few opportunities for improvement:

  1. Multiple new Date() instances are created when a single one could be reused
  2. There's no error handling if the database query returns empty results
  3. Potential race condition if multiple invoices are created simultaneously
export const generateInvoiceNumber = async (userId: string) => {
+  // Use a single Date instance for all calculations
+  const now = new Date();
+  const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
+
  const invoicesCountThisMonth = await db
    .select({
      count: count(),
    })
    .from(requestTable)
    .where(
      and(
        eq(requestTable.userId, userId),
        gte(
          requestTable.createdAt,
-          new Date(new Date().getFullYear(), new Date().getMonth(), 1),
+          firstDayOfMonth,
        ),
      ),
    );

-  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, "0");
+  // Add error handling for empty results
+  const count = invoicesCountThisMonth[0]?.count ?? 0;
  const invoiceCount = String(invoicesCountThisMonth[0].count + 1).padStart(
    4,
    "0",
  ); // Pad with zeros to 4 digits

  const invoiceNumber = `${year}${month}-${invoiceCount}`;
  return invoiceNumber;
};

For addressing the race condition, consider adding a database transaction or implementing a more robust solution if uniqueness is critical.


🏁 Script executed:

#!/bin/bash
# Check if the database schema has any uniqueness constraints on invoice numbers
fd requestTable ./ --exec grep -A 20 "requestTable" {} \;

Length of output: 60


Invoice Number Generation Function Enhancements

The function generally meets the requirement of returning a number in the YYYYMM-XXXX format but still has some actionable improvement points:

  • Reuse Date Instance: Instead of calling new Date() multiple times, store the current date and derive needed values (e.g., first day of the month) once.
  • Error Handling: Guard against cases where the database query might return an empty result by defaulting the count to zero.
  • Race Condition Warning: As the auto-increment logic depends on the query result, there’s potential for race conditions if multiple invoices are generated concurrently. Consider using a database transaction or another concurrency control mechanism. Also, please verify manually whether a uniqueness constraint exists for invoice numbers in the database schema since the initial check produced no conclusive output.
export const generateInvoiceNumber = async (userId: string) => {
+  // Use a single Date instance for all calculations
+  const now = new Date();
+  const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
+
  const invoicesCountThisMonth = await db
    .select({
      count: count(),
    })
    .from(requestTable)
    .where(
      and(
        eq(requestTable.userId, userId),
        gte(
          requestTable.createdAt,
-          new Date(new Date().getFullYear(), new Date().getMonth(), 1),
+          firstDayOfMonth,
        ),
      ),
    );

-  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, "0");
+  // Add error handling for empty results
+  const count = invoicesCountThisMonth[0]?.count ?? 0;
  const invoiceCount = String(invoicesCountThisMonth[0].count + 1).padStart(
    4,
    "0",
  ); // Pad with zeros to 4 digits

  const invoiceNumber = `${year}${month}-${invoiceCount}`;
  return invoiceNumber;
};

Please verify that the database-level uniqueness (if required) is appropriately enforced since our initial check did not yield conclusive results.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a61f0e1 and a6e7d34.

📒 Files selected for processing (5)
  • src/app/i/[id]/page.tsx (3 hunks)
  • src/app/invoices/create/page.tsx (3 hunks)
  • src/components/invoice-creator.tsx (2 hunks)
  • src/components/invoice-form.tsx (1 hunks)
  • src/lib/invoice/index.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
src/app/i/[id]/page.tsx (1)
src/lib/invoice/index.ts (1) (1)
  • generateInvoiceNumber (5-31)
src/app/invoices/create/page.tsx (1)
src/lib/invoice/index.ts (1) (1)
  • generateInvoiceNumber (5-31)
🔇 Additional comments (12)
src/components/invoice-form.tsx (1)

54-58: Successfully implemented read-only invoice number field

The input field for invoice number has been properly modified to include the disabled attribute, which makes it read-only and prevents users from manually editing the auto-generated invoice number.

src/app/i/[id]/page.tsx (3)

5-5: Properly imported the invoice number generator function

The generateInvoiceNumber function is correctly imported from the invoice library.


28-28: Successfully implemented invoice number generation

The code correctly generates a unique invoice number using the user ID from the invoice me link.


55-55: Correctly passing the generated invoice number to the component

The invoice number is properly passed as a prop to the InvoiceCreator component.

src/app/invoices/create/page.tsx (3)

5-5: Properly imported the invoice number generator function

The generateInvoiceNumber function is correctly imported from the invoice library.


18-18: Successfully implemented invoice number generation

The code correctly generates a unique invoice number using the current user's ID.


44-44: Correctly passing the generated invoice number to the component

The invoice number is properly passed as a prop to the InvoiceCreator component.

src/components/invoice-creator.tsx (3)

26-26: Properly extended interface with optional invoice number property

The InvoiceCreatorProps interface has been correctly updated to include an optional invoiceNumber property.


32-32: Correctly destructured the invoice number prop

The invoiceNumber prop is properly destructured from the component parameters.


57-57: Successfully implemented invoice number in form default values

The form's default value for invoiceNumber is now correctly set to use the provided invoiceNumber prop.

src/lib/invoice/index.ts (2)

1-4: LGTM! Appropriate imports for the task.

All necessary dependencies are imported for database access and query operations.


29-31: Function works as expected for the given requirements.

The invoice number format matches the specified requirement (YYYYMM-XXXX) and properly increments based on the user's invoice count for the current month.

Copy link
Member

@MantisClone MantisClone left a comment

Choose a reason for hiding this comment

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

Approved 👍 Pending Comment Resolution 🚧

How do we handle timezones when determining the year and month used in the invoice number?

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)
src/lib/invoice/client.ts (1)

1-14: Add JSDoc comment and improve array destructuring logic.

The function correctly generates invoice numbers in the YYYYMM-XXXX format as specified in the PR objectives. However, there are a few improvements that can be made:

  1. Add JSDoc comments to document the function's purpose, parameters, and return value
  2. The array destructuring to extract month and year assumes a specific order of parts, which could be fragile if locale settings change
+/**
+ * Generates an invoice number in the format YYYYMM-XXXX
+ * @param invoiceCount - The count of invoices for the current month (already zero-padded)
+ * @returns A formatted invoice number string
+ */
 export const generateInvoiceNumber = (invoiceCount: string) => {
   const now = new Date();
   const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

   const formatter = new Intl.DateTimeFormat(undefined, {
     year: "numeric",
     month: "2-digit",
     timeZone: userTimeZone,
   });

-  const [{ value: month }, , { value: year }] = formatter.formatToParts(now);
+  // Find parts by type instead of relying on position
+  const parts = formatter.formatToParts(now);
+  const month = parts.find(part => part.type === 'month')?.value || '';
+  const year = parts.find(part => part.type === 'year')?.value || '';

   return `${year}${month}-${invoiceCount}`;
 };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a6e7d34 and e6a7dae.

📒 Files selected for processing (5)
  • src/app/i/[id]/page.tsx (3 hunks)
  • src/app/invoices/create/page.tsx (3 hunks)
  • src/components/invoice-creator.tsx (3 hunks)
  • src/lib/invoice/client.ts (1 hunks)
  • src/lib/invoice/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/app/invoices/create/page.tsx
  • src/lib/invoice/index.ts
  • src/components/invoice-creator.tsx
  • src/app/i/[id]/page.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build

@aimensahnoun aimensahnoun merged commit c82b6b3 into main Mar 24, 2025
5 checks passed
@aimensahnoun aimensahnoun deleted the 22-easyinvoice---auto-increment-invoice-number branch March 24, 2025 12:19
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.

EasyInvoice - Auto-increment invoice number

3 participants