Skip to content

chore: several agreements#17

Merged
hmbanan666 merged 1 commit into
mainfrom
several-agreements
Jul 24, 2025
Merged

chore: several agreements#17
hmbanan666 merged 1 commit into
mainfrom
several-agreements

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Jul 24, 2025

Summary by CodeRabbit

  • New Features

    • Partner pages now display all agreements associated with a partner, rather than just a single active agreement.
    • The agreements submenu badge now shows the total number of agreements.
  • Enhancements

    • Improved agreement progress calculations to consider the earliest agreement.
    • Finer input increments enabled for marketing fee fields.
  • Style

    • Updated text styling on partner detail pages for improved readability.
  • Bug Fixes

    • Corrected logic to reliably locate agreements by ID rather than relying on an active agreement property.
  • Chores

    • Removed the concept of an "active agreement" from the partner data model and interface.

@hmbanan666 hmbanan666 self-assigned this Jul 24, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 24, 2025

Walkthrough

This set of changes removes the concept of a single "active agreement" from the partner domain model, database schema, and UI. Instead, it introduces handling for multiple agreements per partner by expanding the legal entity relation to include an agreements array. All UI and logic that previously depended on a single active agreement are updated to work with the agreements collection.

Changes

Files/Group Change Summary
apps/web-app/app/components/PartnerCard.vue Refactored to use PartnerLegalEntityWithData type; removed activeAgreement prop; computes earliest agreement; updates progress logic.
apps/web-app/app/components/form/UpdatePartnerAgreement.vue Updates to find partner/agreement by searching agreements array instead of activeAgreementId.
apps/web-app/app/pages/partner/[id].vue Removes formatted active agreement date; submenu badge now shows agreement count.
apps/web-app/app/pages/partner/[id]/agreement.vue,
apps/web-app/app/pages/partner/[id]/index.vue
Renders all agreements from legalEntity.agreements instead of a single activeAgreement.
apps/web-app/app/stores/partner.ts Adds PartnerLegalEntityWithData type; updates PartnerWithData to use it and removes activeAgreement.
apps/web-app/server/api/partner/id/[partnerId]/agreement.post.ts Removes logic updating activeAgreementId after agreement creation.
packages/database/src/repository/partner.ts Updates listWithData to fetch legalEntity with agreements; removes activeAgreement from query.
packages/database/src/tables.ts Removes activeAgreementId column and its relation from the partners table and relations.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant Store
    participant API
    participant DB

    User->>UI: View Partner Details
    UI->>Store: Fetch PartnerWithData
    Store->>API: Request partner data
    API->>DB: Query partners (include legalEntity.agreements)
    DB-->>API: Return partner with agreements array
    API-->>Store: Return PartnerWithData
    Store-->>UI: Provide partner (with agreements)
    UI->>UI: Render all agreements from legalEntity.agreements
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Possibly related PRs

  • feat: legal and agreement create actions #15: Introduces PartnerAgreementCard.vue with agreement progress and UI for displaying/creating agreements; both PRs focus on agreement data structure and progress computation.
  • feat: agreement, legal entity #14: Initially introduced the activeAgreement and legalEntity properties and computed agreement progress based on activeAgreement in PartnerCard.vue, which this PR now refactors.
  • feat: update agreement action #16: Modifies PartnerAgreementCard.vue to ensure agreement progress is never negative and changes its props to require a non-null agreement; both PRs adjust agreement progress computation in related components.

Poem

Hopping through agreements, no longer just one,
Now partners have many—oh, what fun!
The database is lighter, the UI more bright,
With arrays of deals shown left and right.
A bunny’s delight, to see code refactored so neat,
Multiple agreements—what a treat!
🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch several-agreements

🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests 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.

@sonarqubecloud
Copy link
Copy Markdown

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

🔭 Outside diff range comments (1)
apps/web-app/app/pages/partner/[id]/agreement.vue (1)

16-16: Guard CreateCard behind a legalEntity check

Ensure we only render and invoke the create-agreement modal when legalEntityId is defined.

  • File: apps/web-app/app/pages/partner/[id]/agreement.vue, around the <CreateCard> block:
    • Wrap the card in a v-if="partner?.legalEntity?.id" (or disable it) so legalEntityId is never passed as undefined.

Suggested diff:

-      <div>
-        <CreateCard
-          label="Добавить новый договор"
-          icon="i-lucide-scroll-text"
-          @click="modalCreatePartnerAgreement.open({ partnerId: partner?.id, legalEntityId: partner?.legalEntity?.id })"
-        />
-      </div>
+      <div v-if="partner?.legalEntity?.id">
+        <CreateCard
+          label="Добавить новый договор"
+          icon="i-lucide-scroll-text"
+          @click="modalCreatePartnerAgreement.open({ partnerId: partner?.id, legalEntityId: partner.legalEntity.id })"
+        />
+      </div>
+      <!-- Optionally: show a disabled state or message when no legalEntity is available -->
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 78de34c and 8a90a32.

📒 Files selected for processing (9)
  • apps/web-app/app/components/PartnerCard.vue (1 hunks)
  • apps/web-app/app/components/form/UpdatePartnerAgreement.vue (2 hunks)
  • apps/web-app/app/pages/partner/[id].vue (1 hunks)
  • apps/web-app/app/pages/partner/[id]/agreement.vue (1 hunks)
  • apps/web-app/app/pages/partner/[id]/index.vue (3 hunks)
  • apps/web-app/app/stores/partner.ts (1 hunks)
  • apps/web-app/server/api/partner/id/[partnerId]/agreement.post.ts (0 hunks)
  • packages/database/src/repository/partner.ts (1 hunks)
  • packages/database/src/tables.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • apps/web-app/server/api/partner/id/[partnerId]/agreement.post.ts
  • packages/database/src/tables.ts
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (11)
apps/web-app/app/pages/partner/[id]/agreement.vue (1)

4-10: Template refactoring looks correct for multiple agreements support.

The change from displaying a single activeAgreement to iterating over partner?.legalEntity?.agreements is implemented properly with:

  • Appropriate null safety using optional chaining
  • Correct key binding with agreement.id
  • Maintained styling consistency
apps/web-app/app/pages/partner/[id]/index.vue (3)

21-23: Minor styling change noted.

The class change from "text-base" to "text-base/5" appears to be a CSS utility update, possibly adding opacity. This seems unrelated to the main agreements refactoring but is a minor styling adjustment.


39-41: Consistent styling change applied.

Same styling update as line 21-23, changing to "text-base/5" class.


49-55: Correctly refactored to display multiple agreements.

The change from conditional single agreement rendering to v-for iteration over partner?.legalEntity?.agreements is implemented correctly with:

  • Proper null safety using optional chaining
  • Correct key binding with agreement.id
  • Consistent styling with lg:col-span-2

This aligns well with the overall refactoring pattern across the codebase.

packages/database/src/repository/partner.ts (1)

33-38: Database query correctly updated for multiple agreements structure.

The change from separate legalEntity and activeAgreement relations to a nested structure with legalEntity.agreements is correct and aligns with the overall refactoring. This provides the UI components with access to all agreements through the legal entity relationship.

The Drizzle ORM query syntax is properly structured for the nested relation.

apps/web-app/app/stores/partner.ts (2)

3-5: Well-structured type definition for the new agreements model.

The PartnerLegalEntityWithData type correctly extends the base PartnerLegalEntity type with an agreements array, providing proper TypeScript support for the refactored data structure.


7-10: Type definition correctly updated to support multiple agreements.

The updated PartnerWithData type properly:

  • Uses the new PartnerLegalEntityWithData type for the legalEntity property
  • Removes the activeAgreement property, aligning with the database schema changes

This type structure matches the data returned by the updated repository query and supports the UI component requirements.

apps/web-app/app/components/form/UpdatePartnerAgreement.vue (1)

31-31: LGTM! Improved UX with finer input control.

Adding step="0.1" to the marketing fee input enables more precise percentage input, which is appropriate for financial calculations.

apps/web-app/app/components/PartnerCard.vue (3)

52-52: LGTM! Improved content visibility.

Increasing the line clamp from 3 to 4 provides better visibility for city information.


60-66: LGTM! Type updates align with the new data structure.

The type updates correctly reflect the transition from single active agreement to multiple agreements within the legal entity.


73-85: LGTM! Progress calculation logic is sound.

The progress calculation correctly adapts to use the minimal agreement while maintaining the same mathematical logic for determining agreement progress based on concluded and end dates.

Comment on lines +91 to +92
const partner = computed(() => partnerStore.partners.find((partner) => partner.legalEntity?.agreements.some((agreement) => agreement.id === agreementId)))
const agreement = computed(() => partner.value?.legalEntity?.agreements.find((agreement) => agreement.id === agreementId))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify agreement lookup logic and consider performance optimization.

The new agreement lookup logic correctly implements the transition from single active agreement to multiple agreements. However, there are a few considerations:

  1. Performance: The partner lookup uses nested find() and some() operations which could be inefficient with many partners/agreements
  2. Error handling: No explicit handling if the agreement is not found

Consider this more efficient approach:

-const partner = computed(() => partnerStore.partners.find((partner) => partner.legalEntity?.agreements.some((agreement) => agreement.id === agreementId)))
-const agreement = computed(() => partner.value?.legalEntity?.agreements.find((agreement) => agreement.id === agreementId))
+const partnerAndAgreement = computed(() => {
+  for (const partner of partnerStore.partners) {
+    const agreement = partner.legalEntity?.agreements.find(a => a.id === agreementId)
+    if (agreement) {
+      return { partner, agreement }
+    }
+  }
+  return { partner: null, agreement: null }
+})
+const partner = computed(() => partnerAndAgreement.value.partner)
+const agreement = computed(() => partnerAndAgreement.value.agreement)

Please verify that all agreement lookups across the codebase handle the case where agreements might be undefined or empty arrays.


🏁 Script executed:

#!/bin/bash
# Description: Check for potential null/undefined access patterns in agreement lookups
# Expected: Find all places where agreements array is accessed to ensure proper null checking

rg -A 3 -B 1 "\.agreements\." --type vue --type ts
rg -A 3 -B 1 "agreements\[" --type vue --type ts
rg -A 3 -B 1 "agreements\.find" --type vue --type ts
rg -A 3 -B 1 "agreements\.some" --type vue --type ts

Length of output: 325


🏁 Script executed:

#!/bin/bash
# Description: Search for all usages of `agreements` in .vue and .ts files 
# to verify proper null/undefined checks around agreement lookups.

# Search for property access (e.g., partner.legalEntity.agreements)
rg -n "\.agreements" -g "*.vue" -g "*.ts" -A2 -B1

# Search for direct array indexing (e.g., agreements[0])
rg -n "agreements\[" -g "*.vue" -g "*.ts" -A2 -B1

Length of output: 2147


Ensure safe and efficient agreement lookups

Most components guard against missing legalEntity, but we still see un‐handled cases and nested searches that can be optimized.

• apps/web-app/app/components/form/UpdatePartnerAgreement.vue (91–92)
– Two separate find()/some() calls scan the list twice.
– No fallback if no match is found.
• apps/web-app/app/pages/partner/[id].vue (49)
– Uses partner.value?.legalEntity?.agreements.length without default → may yield undefined.
• apps/web-app/app/pages/partner/[id]/index.vue (50–52) & …/agreement.vue (5–7)
v-for="agreement in partner?.legalEntity?.agreements" on undefined can throw.
• apps/web-app/app/components/PartnerCard.vue (71)
partner.legalEntity?.agreements.toSorted(…) calls .toSorted on undefined if agreements is missing.

Suggested fixes:

  1. Consolidate lookup in UpdatePartnerAgreement.vue into a single pass and handle “not found”:
- const partner = computed(() => 
-   partnerStore.partners.find(p => p.legalEntity?.agreements.some(a => a.id === agreementId))
- )
- const agreement = computed(() => 
-   partner.value?.legalEntity?.agreements.find(a => a.id === agreementId)
- )
+ const partnerAndAgreement = computed(() => {
+   for (const p of partnerStore.partners) {
+     const match = p.legalEntity?.agreements?.find(a => a.id === agreementId)
+     if (match) return { partner: p, agreement: match }
+   }
+   return { partner: null, agreement: null }
+ })
+ const partner   = computed(() => partnerAndAgreement.value.partner)
+ const agreement = computed(() => partnerAndAgreement.value.agreement)
  1. Everywhere you iterate or access .agreements, default to an empty array:
- partner?.legalEntity?.agreements
+ partner?.legalEntity?.agreements ?? []

This ensures you never call .length, .find, .some, .toSorted, or v-for on undefined.

📝 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
const partner = computed(() => partnerStore.partners.find((partner) => partner.legalEntity?.agreements.some((agreement) => agreement.id === agreementId)))
const agreement = computed(() => partner.value?.legalEntity?.agreements.find((agreement) => agreement.id === agreementId))
const partnerAndAgreement = computed(() => {
for (const p of partnerStore.partners) {
const match = p.legalEntity?.agreements?.find(a => a.id === agreementId)
if (match) return { partner: p, agreement: match }
}
return { partner: null, agreement: null }
})
const partner = computed(() => partnerAndAgreement.value.partner)
const agreement = computed(() => partnerAndAgreement.value.agreement)
🤖 Prompt for AI Agents
In apps/web-app/app/components/form/UpdatePartnerAgreement.vue lines 91-92,
replace the two separate find/some calls with a single iteration that locates
the partner and agreement together in one pass, and add a fallback for when no
match is found to avoid undefined errors. Also, throughout the app wherever
agreements are accessed or iterated, ensure to default to an empty array if
agreements is undefined, so methods like length, find, some, toSorted, or v-for
do not throw errors.


const { imagesMode } = useApp()

const minimalAgreement = computed(() => partner.legalEntity?.agreements.toSorted((a, b) => new Date(a.willEndAt ?? '').getTime() - new Date(b.willEndAt ?? '').getTime())[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

Potential runtime error with agreement sorting logic.

The minimalAgreement computation has several potential issues:

  1. Null date handling: new Date(a.willEndAt ?? '') will create an invalid date if willEndAt is null/undefined
  2. Empty agreements array: No handling if agreements is empty
  3. Business logic: Using the earliest ending agreement might not always be the most relevant for progress display

Apply this diff to add proper error handling:

-const minimalAgreement = computed(() => partner.legalEntity?.agreements.toSorted((a, b) => new Date(a.willEndAt ?? '').getTime() - new Date(b.willEndAt ?? '').getTime())[0])
+const minimalAgreement = computed(() => {
+  const agreements = partner.legalEntity?.agreements
+  if (!agreements?.length) return null
+  
+  return agreements
+    .filter(agreement => agreement.willEndAt) // Filter out agreements without end dates
+    .toSorted((a, b) => new Date(a.willEndAt!).getTime() - new Date(b.willEndAt!).getTime())[0] || null
+})
📝 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
const minimalAgreement = computed(() => partner.legalEntity?.agreements.toSorted((a, b) => new Date(a.willEndAt ?? '').getTime() - new Date(b.willEndAt ?? '').getTime())[0])
const minimalAgreement = computed(() => {
const agreements = partner.legalEntity?.agreements
if (!agreements?.length) return null
return (
agreements
.filter(agreement => agreement.willEndAt) // Filter out agreements without end dates
.toSorted(
(a, b) =>
new Date(a.willEndAt!).getTime() - new Date(b.willEndAt!).getTime()
)[0] || null
)
})
🤖 Prompt for AI Agents
In apps/web-app/app/components/PartnerCard.vue at line 71, the minimalAgreement
computed property can cause runtime errors due to invalid date parsing when
willEndAt is null or undefined, and it does not handle empty agreements arrays.
To fix this, first check if partner.legalEntity and its agreements array exist
and are non-empty. Then filter out agreements with invalid or missing willEndAt
dates before sorting. Finally, adjust the sorting logic to handle only valid
dates and ensure the selected agreement aligns with the intended business logic
for progress display.

Comment on lines +46 to 50
label: 'Договора',
to: `/partner/${partner.value?.id}/agreement`,
icon: 'i-lucide-scroll-text',
badge: activeAgreementTo.value,
badge: partner.value?.legalEntity?.agreements.length,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Good refactoring to plural form and count display.

The changes appropriately reflect the shift from single to multiple agreements:

  • Label changed to plural "Договора"
  • Badge now shows agreement count instead of end date

However, there's a potential null safety issue with partner.value?.legalEntity?.agreements.length. If agreements is undefined, this will throw a runtime error.

-    badge: partner.value?.legalEntity?.agreements.length,
+    badge: partner.value?.legalEntity?.agreements?.length ?? 0,
📝 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
label: 'Договора',
to: `/partner/${partner.value?.id}/agreement`,
icon: 'i-lucide-scroll-text',
badge: activeAgreementTo.value,
badge: partner.value?.legalEntity?.agreements.length,
},
label: 'Договора',
to: `/partner/${partner.value?.id}/agreement`,
icon: 'i-lucide-scroll-text',
badge: partner.value?.legalEntity?.agreements?.length ?? 0,
},
🤖 Prompt for AI Agents
In apps/web-app/app/pages/partner/[id].vue around lines 46 to 50, the badge
property accesses partner.value?.legalEntity?.agreements.length without checking
if agreements is defined, which can cause a runtime error if agreements is
undefined. Fix this by safely accessing the length using optional chaining or a
fallback, for example, replace partner.value?.legalEntity?.agreements.length
with partner.value?.legalEntity?.agreements?.length ?? 0 to ensure it does not
throw when agreements is undefined.

@hmbanan666 hmbanan666 merged commit a2614a8 into main Jul 24, 2025
8 checks passed
@hmbanan666 hmbanan666 deleted the several-agreements branch July 24, 2025 14:39
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