Skip to content

Conversation

@munkhsaikhan
Copy link
Collaborator

@munkhsaikhan munkhsaikhan commented Dec 10, 2025

Summary by Sourcery

Enforce client portal password history with validation across all password flows, extend loans schedules querying and UI filtering capabilities, and simplify loan research detail fetching while tightening dependencies on optional services.

Bug Fixes:

  • Prevent reuse of recent client portal passwords when resetting or changing passwords, and ensure passwords are always validated and stored with history.

Enhancements:

  • Add password history tracking to client portal users with a configurable history limit.
  • Expose related loan contracts on schedule records via GraphQL and support querying schedules per client portal user.
  • Allow loans UI schedule lists to be filtered by year via local toggling in the table instead of external handlers.
  • Simplify loan research detail lookup by deriving it from either deal or related customer in a single query flow.
  • Guard deal scoring by checking that the loyalties plugin is enabled before processing payments-based campaigns.

Important

Enhance password management, add new GraphQL queries, and improve UI components for better functionality and security.

  • Password Management:
    • Introduced PASSWORD_HISTORY_LIMIT in constants.ts to limit password reuse.
    • Updated checkPassword() in ClientPortalUser.ts to check against password history and enforce complexity.
    • Added passwordHistory field to IUser and IUserDocument in clientPortalUser.ts.
  • GraphQL Enhancements:
    • Added userSchedules query in schedules.ts to fetch schedules by user ID.
    • Added contract resolver in schedule.ts to fetch contract details for a schedule.
  • UI Improvements:
    • Updated SchedulesList.tsx to allow toggling schedule years.
    • Modified ScheduleRow.tsx to filter schedules by selected year.
    • Removed unused onClickYear prop in ContractSection.tsx.

This description was created by Ellipsis for 26e5165. You can customize this summary. It will automatically update as commits are pushed.

Gerelsukh and others added 30 commits May 29, 2025 00:16
* chore: syncloanspolaris improved

* chore: validator added on loans and savings

* refactor: refactor code

* refactor: refactor code

* refactor: refactor code
* chore: syncloanspolaris improved

* chore: validator added on loans and savings

* refactor: refactor code

* refactor: refactor code

* refactor: refactor code

* feat: savings transaction first commit

* feat: savings transaction sync commit2

* feat: income deposit added

* refactor: refactor code

* refactor: refactor code

* refactor: refactor code

* refactor: refactor code

* chore: create loan with polaris date

* feat: loan give transactin added

* refactor: refactor code

* chore: peyment transaction add

* feat: saving & deposit income add

* feat: deposit and loans some imrpvement
@sourcery-ai
Copy link

sourcery-ai bot commented Dec 10, 2025

Reviewer's Guide

Implements client portal password history enforcement, introduces user-specific loan schedule querying and year-based schedule filtering in the loans UI/API, simplifies loan research detail fetching, and adds a contract resolver to schedules, along with minor integration and style fixes.

Sequence diagram for client portal password change with history enforcement

sequenceDiagram
  actor User
  participant API as ClientPortalAPI
  participant Model as ClientPortalUserModel
  participant DB as ClientPortalUsersCollection

  User->>API: changePassword(_id, currentPassword, newPassword)
  API->>Model: changePassword(params)
  Model->>DB: findOne({_id})
  DB-->>Model: user
  Model-->>API: throw Error(User not found) if user null

  Model->>Model: checkPassword(user, newPassword)
  activate Model
  Model->>Model: validate strength (regex)
  Model->>Model: for each passwordHistory entry comparePassword(newPassword, oldHash)
  Model-->>Model: throw Error(Password already used before) if match
  Model->>Model: generatePassword(newPassword) -> hashedPassword
  Model->>Model: trim passwordHistory to PASSWORD_HISTORY_LIMIT
  Model-->>Model: return {hashedPassword, passwordHistory}
  deactivate Model

  Model->>Model: comparePassword(currentPassword, user.password)
  Model-->>API: throw Error(Incorrect current password) if invalid

  Model->>DB: findByIdAndUpdate(user._id, {password: hashedPassword, passwordHistory})
  DB-->>Model: updatedUser
  Model-->>API: updatedUser
  API-->>User: success with updated user
Loading

Updated class diagram for client portal user password history

classDiagram
  class IUser {
    +String _id
    +String email
    +String phone
    +String password
    +String[] passwordHistory
    +String resetPasswordToken
    +Date resetPasswordExpires
  }

  class IUserDocument {
    <<interface>>
    +String _id
    +String email
    +String phone
    +String password
    +String[] passwordHistory
  }

  class IUserModel {
    <<interface>>
    +checkPassword(user, password) Promise
    +generatePassword(password) Promise~String~
    +comparePassword(plainPassword, hashedPassword) Promise~Boolean~
    +clientPortalResetPassword(params) Promise
    +changePassword(params) Promise
    +verifyPhoneOrEmail(params) Promise
  }

  class ClientPortalUserModel {
    <<static methods>>
    +getSecret() String
    +generateToken() Promise
    +generatePassword(password) Promise~String~
    +comparePassword(plainPassword, hashedPassword) Promise~Boolean~
    +checkPassword(user, password) Promise~CheckPasswordResult~
    +clientPortalResetPassword(params) Promise
    +changePassword(params) Promise
    +verifyPhoneOrEmail(params) Promise
  }

  class CheckPasswordResult {
    +String hashedPassword
    +String[] passwordHistory
  }

  class clientPortalUserSchema {
    +String password
    +String[] passwordHistory
    +String resetPasswordToken
    +Date resetPasswordExpires
  }

  class PASSWORD_HISTORY_LIMIT {
    +Number value
  }

  class handleContacts {
    +handleContacts(args) Promise
  }

  IUserDocument --|> IUser
  ClientPortalUserModel ..|> IUserModel
  ClientPortalUserModel ..> CheckPasswordResult
  clientPortalUserSchema ..> IUser
  ClientPortalUserModel ..> clientPortalUserSchema
  ClientPortalUserModel ..> PASSWORD_HISTORY_LIMIT
  handleContacts ..> ClientPortalUserModel

  note for ClientPortalUserModel "checkPassword now validates strength, checks passwordHistory, enforces PASSWORD_HISTORY_LIMIT, returns hashedPassword and updated passwordHistory. All password-set flows (reset, change, verify, handleContacts) use this method and persist passwordHistory on the user document."
Loading

Updated class diagram for loan schedules GraphQL with contract resolver and userSchedules query

classDiagram
  class LoanSchedule {
    +String _id
    +String contractId
    +LoanContract contract
    +String status
    +Date payDate
    +Number interest
    +Number didInterest
  }

  class LoanContract {
    +String _id
    +String customerId
  }

  class ScheduleResolvers {
    +interest(schedule) Number
    +didInterest(schedule) Number
    +contract(schedule, args, context) Promise~LoanContract~
  }

  class ScheduleQueries {
    +scheduleYears(contractId, context) [ScheduleYear]
    +schedules(contractId, isFirst, year, context) [LoanSchedule]
    +userSchedules(userId, contractIds, context) [LoanSchedule]
  }

  class ScheduleYear {
    +Number year
  }

  class LoansModels {
    +Schedules
    +Contracts
  }

  class sendClientPortalMessage {
    +sendClientPortalMessage(args) Promise
  }

  LoanSchedule --> LoanContract : contract
  ScheduleResolvers ..> LoanSchedule
  ScheduleResolvers ..> LoansModels
  ScheduleQueries ..> LoansModels
  ScheduleQueries ..> sendClientPortalMessage

  note for ScheduleResolvers "New field LoanSchedule.contract is resolved by looking up the related LoanContract via schedule.contractId."
  note for ScheduleQueries "New userSchedules query resolves the calling user via client portal service, fetches that user's Contracts, then returns Schedules for those contractIds."
Loading

File-Level Changes

Change Details Files
Enforce password history and centralize password validation for client portal users
  • Extended IUser and Mongoose schema to store an array of previous password hashes (passwordHistory) and introduced PASSWORD_HISTORY_LIMIT constant
  • Refactored checkPassword from a pure validator into an async method that validates complexity, checks new password against passwordHistory using comparePassword, hashes the new password, rotates history with PASSWORD_HISTORY_LIMIT, and returns both hashedPassword and updated history
  • Updated all password set/reset flows (reset by token, change password, verifyOtp, registration completion, user update, handleContacts) to call checkPassword(user, newPassword) and persist both password and passwordHistory
  • Removed legacy direct generatePassword calls where validation/history is now handled by checkPassword, and added missing user existence checks in some flows
packages/plugin-clientportal-api/src/models/ClientPortalUser.ts
packages/plugin-clientportal-api/src/models/utils.ts
packages/plugin-clientportal-api/src/models/definitions/clientPortalUser.ts
packages/plugin-clientportal-api/src/constants.ts
Add user-based schedules query in loans API and expose contract relation on schedule type
  • Added sendClientPortalMessage wrapper in loans messageBroker to talk to clientportal service
  • Introduced userSchedules GraphQL query that resolves a client portal user (by _id or erxesCustomerId), finds their contracts (optionally filtered by contractIds), and returns schedules for those contracts
  • Extended LoanSchedule GraphQL type with contract field and added a resolver that fetches the associated Contract by contractId
packages/plugin-loans-api/src/messageBroker.ts
packages/plugin-loans-api/src/graphql/resolvers/queries/schedules.ts
packages/plugin-loans-api/src/graphql/resolvers/schedule.ts
packages/plugin-loans-api/src/graphql/schema/schedule.ts
Implement year-based schedule filtering toggle in loans UI schedules list
  • Extended ScheduleRow to accept currentYear and early-return null when the schedule payDate year does not match, while keeping status icon rendering unchanged
  • Refactored SchedulesList to manage a local toggleCurrentYear state instead of delegating via onClickYear prop, rendering year buttons that toggle filter on/off and passing the selected year to ScheduleRow
  • Removed unused onClickYear prop from ContractSection usage and wired currentYear only
packages/plugin-loans-ui/src/contracts/components/schedules/ScheduleRow.tsx
packages/plugin-loans-ui/src/contracts/components/schedules/SchedulesList.tsx
packages/plugin-loans-ui/src/contracts/components/common/ContractSection.tsx
Simplify loan research detail fetching logic and ensure consistent Apollo hook usage
  • Reordered and simplified useHasDetail to always fetch related customers first, then run a single loanResearchDetail query parameterized by dealId and customerId, avoiding multiple conditional queries and maintaining stable hook ordering
  • Updated customers query variables to rely directly on dealId and cleaned up now-unused intermediate state and derived loading flags
packages/plugin-loansresearch-ui/src/utils.tsx
Guard deal scoring against missing loyalties plugin
  • Updated doScoreCampaign to short-circuit when paymentsData is absent or the loyalties service is not enabled via service discovery helper
packages/plugin-sales-api/src/graphql/resolvers/mutations/utils.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link

coderabbitai bot commented Dec 10, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch staging

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

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • In useHasDetail the new query flow changes behavior: loanResearchDetail now always runs with { dealId, customerId } and the skip: !dealId && !customerId condition means it will fire even before customerId is known (when only dealId is present); consider restoring the original two-step behavior (deal-first, then customer fallback) or tightening the skip logic so the backend is not called with partially populated variables.
  • userSchedules currently returns schedules without any sort; since other schedule queries sort by payDate (and sometimes createdAt), you may want to add a similar sort to keep ordering consistent for consumers.
  • In the loans UI, SchedulesList still accepts a currentYear prop that is no longer used now that toggleCurrentYear controls filtering and is passed down to ScheduleRow; consider removing this unused prop to avoid confusion and keep the interface minimal.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `useHasDetail` the new query flow changes behavior: `loanResearchDetail` now always runs with `{ dealId, customerId }` and the `skip: !dealId && !customerId` condition means it will fire even before `customerId` is known (when only `dealId` is present); consider restoring the original two-step behavior (deal-first, then customer fallback) or tightening the skip logic so the backend is not called with partially populated variables.
- `userSchedules` currently returns schedules without any sort; since other schedule queries sort by `payDate` (and sometimes `createdAt`), you may want to add a similar sort to keep ordering consistent for consumers.
- In the loans UI, `SchedulesList` still accepts a `currentYear` prop that is no longer used now that `toggleCurrentYear` controls filtering and is passed down to `ScheduleRow`; consider removing this unused prop to avoid confusion and keep the interface minimal.

## Individual Comments

### Comment 1
<location> `packages/plugin-loansresearch-ui/src/utils.tsx:35-11` </location>
<code_context>
   const customerId = customerData?.customers?.[0]?._id;

-  // Third query: If no deal details, fetch customer loan research
-  const { data: customerLoanData, loading: customerLoanLoading } =
+  const { data: dealData, loading: dealLoading } =
     useQuery<DetailQueryResponse>(gql(queries.loanResearchDetail), {
-      variables: { customerId },
-      skip: !!loansResearch || !customerId, // Skip if loansResearch exists or no customerId
</code_context>

<issue_to_address>
**issue (bug_risk):** The refactor drops the fallback to customer-based loan research, which changes the hook’s behavior.

The new implementation always queries `loanResearchDetail` by deal and never falls back to customer-level data, so in cases where only customer-based research exists, `loansResearch` will now be `undefined`. If preserving prior behavior is required, consider reintroducing a customer-based query or updating the resolver to accept both `dealId` and `customerId` while maintaining the previous fallback semantics.
</issue_to_address>

### Comment 2
<location> `packages/plugin-loansresearch-ui/src/utils.tsx:11-20` </location>
<code_context>
-  // Third query: If no deal details, fetch customer loan research
-  const { data: customerLoanData, loading: customerLoanLoading } =
+  const { data: dealData, loading: dealLoading } =
     useQuery<DetailQueryResponse>(gql(queries.loanResearchDetail), {
-      variables: { customerId },
-      skip: !!loansResearch || !customerId, // Skip if loansResearch exists or no customerId
+      variables: { dealId, customerId },
+      skip: !dealId && !customerId, // Ensures hook order remains consistent
     });

</code_context>

<issue_to_address>
**question (bug_risk):** The `loanResearchDetail` query is now called with both `dealId` and `customerId`, which may not match the server-side expectations.

Previously, `loanResearchDetail` was called either with `{ dealId }` or, when needed, with `{ customerId }`. The new call always passes both, which changes the contract and assumes the resolver supports and correctly combines both arguments. If the backend expects exactly one identifier, this could change behavior or return incorrect results. Please either keep the one-identifier-at-a-time pattern or confirm and update the resolver to handle both safely.
</issue_to_address>

### Comment 3
<location> `packages/plugin-loans-ui/src/contracts/components/schedules/SchedulesList.tsx:21-23` </location>
<code_context>

 const SchedulesList = (props: IProps) => {
-  const { schedules, loading, leaseType, scheduleYears, onClickYear } = props;
+  const { schedules, loading, leaseType, scheduleYears, currentYear } = props;
+
+  const [toggleCurrentYear, setToggleCurrentYear] = useState<number | null>(
+    null
+  );
</code_context>

<issue_to_address>
**suggestion (bug_risk):** `currentYear` prop is no longer used to control the selected year, which may break existing expectations.

The selection state is now fully internal and starts at `null`, so `currentYear` is effectively ignored. This removes the ability for callers to control or react to the active year. If this prop is intended to be controlled (e.g. in `ContractSection`), either initialize and sync `toggleCurrentYear` from `currentYear`, or remove the prop to avoid misleading API surface.

Suggested implementation:

```typescript
  const [toggleCurrentYear, setToggleCurrentYear] = useState<number | null>(
    currentYear ?? null
  );

  useEffect(() => {
    setToggleCurrentYear(currentYear ?? null);
  }, [currentYear]);

  const renderYear = () => {

```

1. Ensure `useEffect` is imported from React at the top of the file. For example, if you currently have:
   `import React, { useState } from "react";`
   change it to:
   `import React, { useState, useEffect } from "react";`
2. If `currentYear` can never be `null`/`undefined`, you can simplify the initialization and effect to use `currentYear` directly instead of `currentYear ?? null`.
</issue_to_address>

### Comment 4
<location> `packages/plugin-loans-ui/src/contracts/components/schedules/ScheduleRow.tsx:14-17` </location>
<code_context>
-  const renderIcon = status => {
-    if (status === 'done') {
-      return <Icon icon={'medal'} color={'orange'} />;
+function ScheduleRow({ schedule, leaseType, currentYear }: Props) {
+  const { payDate } = schedule || {};
+
+  if (currentYear && payDate && dayjs(payDate).year() !== currentYear) {
+    return null;
+  }
</code_context>

<issue_to_address>
**suggestion:** Returning `null` based on `currentYear` overloads row responsibility with filtering logic.

Since `SchedulesList` already owns the year selection state, it would be better to apply the year filter there and keep `ScheduleRow` as a purely presentational component responsible only for rendering a single row. This improves separation of concerns and makes its render behavior easier to understand.

Suggested implementation:

```typescript
type Props = {
  schedule: ISchedule;
  leaseType?: string;
};

```

```typescript
function ScheduleRow({ schedule, leaseType }: Props) {
  const { payDate } = schedule || {};

```

To fully apply the separation of concerns you described, you’ll also need to:
1. Update `SchedulesList` (or whatever parent component renders `ScheduleRow`) to:
   - Stop passing `currentYear` to `ScheduleRow`.
   - Apply the `currentYear` filter when computing the list of schedules, e.g.:
     `schedules.filter(s => !currentYear || (s.payDate && dayjs(s.payDate).year() === currentYear))`.
2. Update any other usages of `ScheduleRow` that still pass `currentYear` so they only pass `schedule` and `leaseType`.
</issue_to_address>

### Comment 5
<location> `packages/plugin-clientportal-api/src/models/utils.ts:65-69` </location>
<code_context>
-        throw new Error('Password is required');
-      }
-
-      if (password) {
-        this.checkPassword(password);
+        throw new Error("Password is required");
</code_context>

<issue_to_address>
**issue:** `checkPassword` is called with a possibly undefined user, which conflicts with its declared type.

In the `!user` branch, `user` is definitely `undefined`, but `checkPassword` is typed as `checkPassword(user: IUserDocument, password: string)`. The implementation tolerates a falsy user via `user || {}`, but the signature doesn’t. Please update the type to accept `IUserDocument | null | undefined` (or similar) and adjust call sites to match the actual usage.
</issue_to_address>

### Comment 6
<location> `packages/plugin-loans-api/src/graphql/resolvers/queries/schedules.ts:43-52` </location>
<code_context>
+  userSchedules: async (
</code_context>

<issue_to_address>
**suggestion (bug_risk):** `userSchedules` does not enforce any ordering on the returned schedules.

Other schedule queries (`schedules`, `scheduleYears`) explicitly sort by `payDate` (and sometimes `createdAt`), but `userSchedules` calls `models.Schedules.find(filter)` with no sort, leaving order up to the datastore. For consistent client behavior (UI, exports), consider adding an explicit sort, e.g. `.sort({ payDate: 1, createdAt: 1 })`.

Suggested implementation:

```typescript
    return models.Schedules.find(filter).sort({ payDate: 1, createdAt: 1 });

```

I only see part of the `userSchedules` implementation. If the `find` call in `userSchedules` uses a different variable name than `filter` (e.g. `query`, `selector`, etc.), you should update the search/replace to match that exact line and append `.sort({ payDate: 1, createdAt: 1 })` there. The key is: in every code path where `userSchedules` returns `models.Schedules.find(...)`, ensure that call is followed by `.sort({ payDate: 1, createdAt: 1 })`.
</issue_to_address>

### Comment 7
<location> `packages/plugin-clientportal-api/src/models/ClientPortalUser.ts:508` </location>
<code_context>
     public static async changePassword({
</code_context>

<issue_to_address>
**🚨 suggestion (security):** The new password is validated against history before verifying the current password, which may not be the intended flow.

In `changePassword`, `checkPassword(user, newPassword)` runs before verifying `currentPassword`. This allows a “Password already used before” error to be shown even when the current password is wrong, and can leak password-history information for invalid sessions. If the intended behavior is to first validate the current password, consider swapping the order so `currentPassword` is checked before calling `checkPassword` on the new password.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

export const useHasDetail = (dealId: string) => {
// First query: Check if deal has details
const { data: dealData, loading: dealLoading } =
useQuery<DetailQueryResponse>(gql(queries.loanResearchDetail), {
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): The refactor drops the fallback to customer-based loan research, which changes the hook’s behavior.

The new implementation always queries loanResearchDetail by deal and never falls back to customer-level data, so in cases where only customer-based research exists, loansResearch will now be undefined. If preserving prior behavior is required, consider reintroducing a customer-based query or updating the resolver to accept both dealId and customerId while maintaining the previous fallback semantics.

Comment on lines +14 to +17
function ScheduleRow({ schedule, leaseType, currentYear }: Props) {
const { payDate } = schedule || {};

if (currentYear && payDate && dayjs(payDate).year() !== currentYear) {
Copy link

Choose a reason for hiding this comment

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

suggestion: Returning null based on currentYear overloads row responsibility with filtering logic.

Since SchedulesList already owns the year selection state, it would be better to apply the year filter there and keep ScheduleRow as a purely presentational component responsible only for rendering a single row. This improves separation of concerns and makes its render behavior easier to understand.

Suggested implementation:

type Props = {
  schedule: ISchedule;
  leaseType?: string;
};
function ScheduleRow({ schedule, leaseType }: Props) {
  const { payDate } = schedule || {};

To fully apply the separation of concerns you described, you’ll also need to:

  1. Update SchedulesList (or whatever parent component renders ScheduleRow) to:
    • Stop passing currentYear to ScheduleRow.
    • Apply the currentYear filter when computing the list of schedules, e.g.:
      schedules.filter(s => !currentYear || (s.payDate && dayjs(s.payDate).year() === currentYear)).
  2. Update any other usages of ScheduleRow that still pass currentYear so they only pass schedule and leaseType.

Comment on lines +43 to +52
userSchedules: async (
_root,
params: { userId: string; contractIds?: string[] },
{ models, subdomain }: IContext
) => {
const cpUser = await sendClientPortalMessage({
subdomain,
action: "clientPortalUsers.findOne",
data: {
$or: [{ _id: params.userId }, { erxesCustomerId: params.userId }],
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): userSchedules does not enforce any ordering on the returned schedules.

Other schedule queries (schedules, scheduleYears) explicitly sort by payDate (and sometimes createdAt), but userSchedules calls models.Schedules.find(filter) with no sort, leaving order up to the datastore. For consistent client behavior (UI, exports), consider adding an explicit sort, e.g. .sort({ payDate: 1, createdAt: 1 }).

Suggested implementation:

    return models.Schedules.find(filter).sort({ payDate: 1, createdAt: 1 });

I only see part of the userSchedules implementation. If the find call in userSchedules uses a different variable name than filter (e.g. query, selector, etc.), you should update the search/replace to match that exact line and append .sort({ payDate: 1, createdAt: 1 }) there. The key is: in every code path where userSchedules returns models.Schedules.find(...), ensure that call is followed by .sort({ payDate: 1, createdAt: 1 }).

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Caution

Changes requested ❌

Reviewed everything up to 26e5165 in 2 minutes and 35 seconds. Click for details.
  • Reviewed 1300 lines of code in 13 files
  • Skipped 0 files when reviewing.
  • Skipped posting 14 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/plugin-loansresearch-ui/src/utils.tsx:27
  • Draft comment:
    Review skip condition: The second query uses 'skip: !dealId && !customerId'. This may run even if dealId exists but customerId is undefined. Consider using an OR condition if that’s the intended behavior.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 15% vs. threshold = 50% The comment is speculative - it says "This may run even if dealId exists but customerId is undefined. Consider using an OR condition if that's the intended behavior." The key phrase is "if that's the intended behavior" - the reviewer is not certain there's a bug. Looking at the code, the query accepts both dealId and customerId, suggesting it can work with either one. The current logic !dealId && !customerId means "skip only if we have neither dealId nor customerId", which seems reasonable - we want to run the query if we have at least one identifier. The suggested change !dealId || !customerId would mean "skip if we're missing either one", which would be overly restrictive. The comment appears to be incorrect or at least speculative without strong evidence. I might be missing context about what the loanResearchDetail query actually requires. Perhaps it needs both parameters, not just one. The comment could be correct if the query fails when only one parameter is provided. However, the fact that the query accepts both parameters as variables and the code explicitly passes both suggests it's designed to handle either/or scenarios. Without seeing the actual query definition, I cannot be certain, but the comment is clearly speculative ("may run", "Consider...if that's the intended behavior") rather than definitively identifying a bug. This violates the rule against speculative comments. The comment is speculative and doesn't provide strong evidence of a bug. It uses phrases like "may run" and "if that's the intended behavior" which indicate uncertainty. The current logic appears intentional - allowing the query to run if at least one identifier is available. This should be deleted.
2. packages/plugin-sales-api/src/graphql/resolvers/mutations/utils.ts:69
  • Draft comment:
    Performance suggestion: In the loop iterating over sub-resolvers (lines 69-76), consider running asynchronous calls in parallel (e.g. using Promise.all) to improve performance.
  • Reason this comment was not posted:
    Confidence changes required: 50% <= threshold 50% None
3. packages/plugin-loans-api/src/messageBroker.ts:315
  • Draft comment:
    Minor improvement: When catching errors in sendSms, rethrowing with 'new Error(e.message)' loses stack trace. Consider preserving the original error (or use proper error wrapping).
  • Reason this comment was not posted:
    Confidence changes required: 50% <= threshold 50% None
4. packages/plugin-clientportal-api/src/constants.ts:3
  • Draft comment:
    Typo/Inconsistency: In the CLIENTPORTALUSER_BASIC_INFO object, some labels (e.g. for 'lastName') use inconsistent capitalization compared to others. Consider standardizing labels.
  • Reason this comment was not posted:
    Confidence changes required: 30% <= threshold 50% None
5. packages/plugin-loans-ui/src/contracts/components/schedules/ScheduleRow.tsx:39
  • Draft comment:
    Minor: The helper function 'renderCell' does not type its parameters ('name' and 'didName'). Consider adding explicit types for clarity and maintainability.
  • Reason this comment was not posted:
    Confidence changes required: 30% <= threshold 50% None
6. packages/plugin-clientportal-api/src/models/ClientPortalUser.ts:18
  • Draft comment:
    Typographical error: The interface name "IInvitiation" may be a typo. Consider renaming it to "IInvitation".
  • Reason this comment was not posted:
    Comment was on unchanged code.
7. packages/plugin-clientportal-api/src/models/ClientPortalUser.ts:19
  • Draft comment:
    Typographical error: "INotifcationSettings" appears to be misspelled. It might be intended to be "INotificationSettings".
  • Reason this comment was not posted:
    Comment was on unchanged code.
8. packages/plugin-clientportal-api/src/models/ClientPortalUser.ts:454
  • Draft comment:
    Typo in the error message: Instead of "Must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters", it should read something like "Must contain at least one number, one uppercase letter, one lowercase letter, and be at least 8 characters long".
  • Reason this comment was not posted:
    Comment was on unchanged code.
9. packages/plugin-clientportal-api/src/models/ClientPortalUser.ts:465
  • Draft comment:
    Consider revising the error message "Password already used before" to "Password has already been used" for clearer grammar.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50% This is a minor grammar/style suggestion for an error message. While the suggested wording is slightly better grammatically, this falls under "code quality refactors" territory. The rules state that "Comments that suggest code quality refactors are good! But only if they are actionable and clear." This suggestion is actionable and clear. However, I need to consider if this is "clearly a code change required" or if it's just a minor stylistic preference. The current error message is understandable and functional, even if not perfectly grammatical. The rules also say "Do NOT comment unless there is clearly a code change required" and "Do NOT make comments that are obvious or unimportant." This seems like it falls into the "unimportant" category - it's a very minor wording change that doesn't affect functionality or clarity significantly. The suggested change does improve grammar slightly, and error messages should be clear and professional. This could be considered a legitimate quality improvement rather than just a trivial change. The message will be shown to end users, so clarity and proper grammar matter. While user-facing messages should be clear, the current message "Password already used before" is perfectly understandable, even if not grammatically perfect. This is a very minor stylistic preference that doesn't meaningfully improve clarity or fix any real issue. The rules emphasize not making "obvious or unimportant" comments, and this falls into that category. This comment suggests a minor grammar improvement to an error message. While the suggestion is slightly better, the current message is clear and functional. This is an unimportant stylistic change that doesn't meet the threshold of "clearly a code change required." The comment should be deleted.
10. packages/plugin-clientportal-api/src/models/ClientPortalUser.ts:714
  • Draft comment:
    Typo: The parameter name 'codeLenth' in generateVerificationCode appears to be a typo. It should likely be 'codeLength' to match its usage in subsequent code.
  • Reason this comment was not posted:
    Comment was on unchanged code.
11. packages/plugin-clientportal-api/src/models/ClientPortalUser.ts:1195
  • Draft comment:
    Typographical suggestion: Consider changing the error message from "Can not create user" to "Cannot create user" for consistency.
  • Reason this comment was not posted:
    Comment was on unchanged code.
12. packages/plugin-clientportal-api/src/models/ClientPortalUser.ts:1241
  • Draft comment:
    Typographical note: The error message reads "Can not create user". Consider changing it to "Cannot create user" for proper wording.
  • Reason this comment was not posted:
    Comment was on unchanged code.
13. packages/plugin-loans-api/src/graphql/schema/schedule.ts:50
  • Draft comment:
    Typo: There is an extraneous comma after leaseAmount: Float in the function parameters. Please remove it to ensure correct syntax.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
14. packages/plugin-sales-api/src/graphql/resolvers/mutations/utils.ts:972
  • Draft comment:
    Typographical note: The string "loyalties" may be a misspelling. If the intended feature flag is related to loyalty (or a similar term), consider checking if it should be "loyalty" instead.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% The comment is speculative ("may be a misspelling") and asks the author to check if it should be "loyalty" instead. Looking at the file context, I see multiple references to "loyalties" in function names like sendLoyaltiesMessage, confirmLoyalties, refundScoreCampaign which deals with loyalty scores, etc. This strongly suggests "loyalties" is the correct and intentional service name. The comment violates the rule about not making speculative comments and asking the author to confirm/check things. There's no strong evidence this is actually wrong. Could "loyalties" be a legitimate plural form referring to a service that handles multiple loyalty programs? The presence of sendLoyaltiesMessage throughout the file suggests this is intentional naming. The consistent use of "loyalties" throughout the codebase (in function names like sendLoyaltiesMessage, confirmLoyalties, and message actions like "refundLoyaltyScore") strongly indicates this is intentional naming, not a typo. The comment is purely speculative without evidence. This comment should be deleted. It's speculative, asks the author to check/verify something, and there's strong evidence in the file that "loyalties" is the correct and intentional service name used consistently throughout the codebase.

Workflow ID: wflow_ySY5wnN2iTSysF9C

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

}

if (password) {
const { hashedPassword, passwordHistory } = await models.ClientPortalUsers.checkPassword(user, password);
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: checkPassword is called with user which is undefined. In this context no user exists yet, so the function may throw an error (or iterate over an undefined passwordHistory).

@sonarqubecloud
Copy link

@munkhsaikhan munkhsaikhan merged commit d60dd39 into master Dec 10, 2025
150 of 152 checks passed
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.

5 participants