feat: Add max negative caps and enforce ledger bounds - #618

Merged
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control
Dec 15, 2025
Merged

feat: Add max negative caps and enforce ledger bounds#618
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control

Conversation

@coodos

@coodoscoodos commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

closes#553
closes#554
closes#555

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added ability to set and manage maximum negative balance limits on currencies that allow negative balances.
    • Administrators can now configure and adjust balance floor thresholds through a new settings interface.
    • System validates transactions to prevent account balances from exceeding configured negative limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces maxNegativeBalance as a persistent field on currencies, enabling administrators to set limits on how far below zero account balances may fall. The feature spans database schema (new column via migration), API layer (new controller endpoint), business logic (validation in service and ledger), and client UI (management interface with save/clear actions).

Changes

Cohort / File(s)Summary
Database Schema
platforms/eCurrency-api/src/database/entities/Currency.ts, platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts
Adds maxNegativeBalance field to Currency entity as nullable decimal(18,2) column; introduces corresponding TypeORM migration for up/down schema changes.
API Layer
platforms/eCurrency-api/src/controllers/CurrencyController.ts, platforms/eCurrency-api/src/index.ts
Adds new updateMaxNegativeBalance controller method with auth checks and validation; registers PATCH endpoint /api/currencies/:id/max-negative.
Service Layer
platforms/eCurrency-api/src/services/CurrencyService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Extends createCurrency to accept optional maxNegativeBalance parameter; introduces updateMaxNegativeBalance service method with admin and validation checks; enhances transfer in LedgerService to validate balance limits and optimize balance queries.
Client UI
platforms/eCurrency/client/src/pages/currency-detail.tsx
Adds state management, input controls (range + numeric inputs), and save/clear functionality for max negative balance; integrates React Query cache invalidation post-update.

Sequence Diagram

sequenceDiagram
actor User as Administrator
participant UI as currency-detail.tsx
participant API as CurrencyController
participant Service as CurrencyService
participant Ledger as LedgerService
participant DB as Database
User->>UI: Set max negative balance value
UI->>API: PATCH /api/currencies/:id/max-negative {value}
API->>Service: updateMaxNegativeBalance(id, value, admin)
Note over Service: Validate:<br/>- Currency exists<br/>- Admin permission<br/>- allowNegative=true<br/>- Value is null or ≤0
Service->>DB: Update currency.maxNegativeBalance
DB-->>Service: Updated Currency
Service-->>API: Currency entity
API-->>UI: {id, maxNegativeBalance, ...}
UI->>UI: Invalidate cache<br/>(currency, accountDetails)
Note over User,Ledger: Later: User attempts transfer
User->>API: POST /transfer {amount, ...}
API->>Ledger: transfer(currencyId, amount, ...)
Note over Ledger: Compute balance change<br/>if (maxNegativeBalance set<br/>AND balance+amount < limit)<br/>throw error
alt Balance within limit
Ledger->>DB: addLedgerEntry(...)
DB-->>Ledger: Ledger entry
Ledger-->>API: Success
else Balance exceeds limit
Ledger-->>API: Insufficient funds error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • LedgerService modifications: Review the enhanced validation logic in transfer() to ensure maxNegativeBalance checks are correct and the new existingBalance parameter usage doesn't introduce balance-calculation discrepancies.
  • CurrencyService validation: Verify that updateMaxNegativeBalance enforces all required constraints (admin role, allowNegative check, numeric validation).
  • Client state synchronization: Confirm that cache invalidation strategy properly refreshes all dependent UI sections after updates.

Possibly related PRs

  • chore: move eCurrency #471: Directly related—this PR builds upon and extends the Currency, CurrencyController, CurrencyService, and migration patterns introduced there to add the new maxNegativeBalance management feature.

Suggested reviewers

  • sosweetham
  • xPathin
  • ananyayaya129

Poem

🐰 A hop, a skip, a balance to keep,
The burrow's new limit runs deep,
Max negative set with a careful paw,
No debt below this earned law,
Our ledgers now wiser, our wallets more fair! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR addresses issue #554 (negative limit) comprehensively with maxNegativeBalance implementation. However, no evidence of work on #553 (hashing/timestamps) or #555 (burn money functionality) is found in the code changes.Verify whether issues #553 and #555 were intended to be included in this PR. If so, add the missing functionality; otherwise, remove them from the PR description.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title directly describes the main feature: adding max negative balance caps and enforcing ledger bounds, which aligns with the primary changes across the codebase.
Description check✅ PassedThe description includes required sections: issue numbers, type of change, and change checklist. However, the 'How the change has been tested' section is missing content and no testing details are provided.
Out of Scope Changes check✅ PassedAll changes are directly related to implementing the max negative balance feature referenced in the PR title and issue #554. No unrelated or out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/burn-and-negative-control

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eCurrency-api/src/services/LedgerService.ts (1)

96-124: Wrap the transfer method in a database transaction to prevent concurrent balance validation violations.

There is a time-of-check to time-of-use (TOCTOU) gap between fetching currentBalance (line 96) and persisting ledger entries (lines 112-139). Concurrent transfers from the same account could both pass validation before either commits, potentially violating the maxNegativeBalance constraint.

Use AppDataSource.transaction() with proper isolation level (similar to the pattern in GroupService.ts), or implement row-level locking with FOR UPDATE to ensure balance validation and ledger creation are atomic.

🧹 Nitpick comments (4)
platforms/eCurrency-api/src/database/entities/Currency.ts (1)

39-40: TypeORM decimal columns return strings at runtime.

TypeORM's decimal type returns values as strings, not JavaScript numbers. The TypeScript type number | null is misleading and may cause subtle bugs when performing arithmetic or comparisons without explicit conversion.

Consider adjusting the type or using a transformer:

- @Column("decimal", { precision: 18, scale: 2, nullable: true })- maxNegativeBalance!: number | null;+ @Column("decimal", { precision: 18, scale: 2, nullable: true, transformer: {+ to: (value: number | null) => value,+ from: (value: string | null) => value === null ? null : parseFloat(value)+ }})+ maxNegativeBalance!: number | null;

Alternatively, update the type to string | null and convert explicitly at usage sites.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (1)

162-166: Consider adding semantic validation for maxNegativeBalance.

The current validation only checks that the value is a valid number (or null). Given the semantic meaning (a floor for how negative a balance can go), consider validating that non-null values are <= 0:

 // Allow null to clear; otherwise must be a number
const parsedValue = value === null || value === undefined ? null : Number(value);
if (parsedValue !== null && Number.isNaN(parsedValue)) {
return res.status(400).json({ error: "Invalid value for maxNegativeBalance" });
}
+ if (parsedValue !== null && parsedValue > 0) {+ return res.status(400).json({ error: "maxNegativeBalance must be zero or negative" });+ }
platforms/eCurrency-api/src/services/CurrencyService.ts (1)

20-47: Align createCurrency maxNegativeBalance invariants with update path

createCurrency now accepts and persists maxNegativeBalance but does not enforce the same constraints you added in updateMaxNegativeBalance (must only be set when allowNegative is true, must be null or <= 0, not NaN). That means callers can create a currency in a state that the update path would later reject (e.g. allowNegative = false with a non‑null cap, or a positive cap).

To keep invariants consistent and avoid surprising states, consider normalizing and validating here as well, e.g.:

 // Generate eName (UUID with @ prefix)
const ename = `@${uuidv4()}`;
+ const normalizedMaxNegativeBalance =+ allowNegative && maxNegativeBalance !== null ? maxNegativeBalance : null;++ if (normalizedMaxNegativeBalance !== null) {+ if (Number.isNaN(normalizedMaxNegativeBalance)) {+ throw new Error("Invalid max negative balance value");+ }+ if (normalizedMaxNegativeBalance > 0) {+ throw new Error("Max negative balance must be zero or negative");+ }+ }+
const currency = this.currencyRepository.create({
name,
description,
ename,
groupId,
createdBy,
allowNegative,
- maxNegativeBalance,+ maxNegativeBalance: normalizedMaxNegativeBalance,
});
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

292-301: Optional: Format maxNegativeBalance consistently with other monetary values

Here you display maxNegativeBalance with a bare toLocaleString(), while balance and total supply are formatted with two decimal places. For consistency and readability (especially since the DB column is decimal with scale), consider aligning the formatting:

- {currency.allowNegative- ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined- ? Number(currency.maxNegativeBalance).toLocaleString()- : "No cap")- : "Not applicable"}+ {currency.allowNegative+ ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined+ ? Number(currency.maxNegativeBalance).toLocaleString(undefined, {+ minimumFractionDigits: 2,+ maximumFractionDigits: 2,+ })+ : "No cap")+ : "Not applicable"}

Not required for correctness, but it keeps the UI consistent with the way balances and total supply are shown.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbefb26 and 28a6b7b.

📒 Files selected for processing (7)
  • platforms/eCurrency-api/src/controllers/CurrencyController.ts (7 hunks)
  • platforms/eCurrency-api/src/database/entities/Currency.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (1 hunks)
  • platforms/eCurrency-api/src/services/CurrencyService.ts (3 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (3 hunks)
  • platforms/eCurrency/client/src/pages/currency-detail.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)
platforms/eCurrency-api/src/database/migrations/1765039149572-migration.ts (1)
  • Migration1765039149572 (3-14)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/middleware/auth.ts (1)
  • authGuard (36-41)
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
⏰ 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 (8)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)

1-14: LGTM!

The migration correctly adds and removes the maxNegativeBalance column with the appropriate type (numeric(18,2)), matching the entity definition and following the established migration pattern in the codebase.

platforms/eCurrency-api/src/services/LedgerService.ts (2)

43-49: LGTM!

The existingBalance parameter is a sensible optimization that avoids a redundant database query when the caller has already fetched the balance for validation purposes.


103-108: Consider validating maxNegativeBalance is non-positive.

The current logic assumes maxNegativeBalance represents a floor (e.g., -100). If a positive value is accidentally set, the condition newBalance < maxNegativeBalance would never trigger for typical debits leading to negative balances. Consider adding validation in the controller/service to ensure maxNegativeBalance <= 0 when set.

platforms/eCurrency-api/src/index.ts (1)

115-115: LGTM!

The new PATCH endpoint follows the existing route patterns, uses appropriate HTTP semantics for a partial update, and is correctly protected with authGuard.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (2)

18-18: LGTM!

The maxNegativeBalance field is correctly extracted from the request body, passed to the service, and included in the response. The implementation follows the existing patterns for other currency fields.

Also applies to: 29-29, 40-40


174-185: LGTM!

The response structure is consistent with other currency endpoints, including all relevant fields.

platforms/eCurrency-api/src/services/CurrencyService.ts (1)

87-117: Max negative balance update flow and validation look solid

The new updateMaxNegativeBalance method correctly:

  • Verifies the currency exists and the requester is a group admin.
  • Disallows setting a cap when allowNegative is false.
  • Ensures the value is either null or a non‑NaN number that is <= 0.

This keeps the persisted state consistent with the business rules around negative balances.

platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

1-1: State and effect wiring for max negative balance looks consistent

The added React Query client usage, MAX_NEGATIVE_SLIDER constant, and maxNegative* state plus the useEffect that derives the absolute input from currency.maxNegativeBalance form a coherent flow and ensure the UI is initialized from the latest server value when the currency data loads or is refetched. No functional issues spotted here.

Also applies to: 18-18, 25-28, 56-64

Comment threadplatforms/eCurrency/client/src/pages/currency-detail.tsx
@kulmin
kulmin merged commit dd226ce into mainDec 15, 2025
4 checks passed
@coodos
coodos deleted the feat/burn-and-negative-control branch December 15, 2025 09:02
Sign up for freeto 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.

[feature] Ability to Burn money [feature] Implement Negative Limit [feature] Hashing and Timestamps

3 participants

@coodos@sosweetham@kulmin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: Add max negative caps and enforce ledger bounds - #618

Merged
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control
Dec 15, 2025
Merged

feat: Add max negative caps and enforce ledger bounds#618
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control

Conversation

@coodos

@coodoscoodos commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

closes#553
closes#554
closes#555

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added ability to set and manage maximum negative balance limits on currencies that allow negative balances.
    • Administrators can now configure and adjust balance floor thresholds through a new settings interface.
    • System validates transactions to prevent account balances from exceeding configured negative limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces maxNegativeBalance as a persistent field on currencies, enabling administrators to set limits on how far below zero account balances may fall. The feature spans database schema (new column via migration), API layer (new controller endpoint), business logic (validation in service and ledger), and client UI (management interface with save/clear actions).

Changes

Cohort / File(s)Summary
Database Schema
platforms/eCurrency-api/src/database/entities/Currency.ts, platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts
Adds maxNegativeBalance field to Currency entity as nullable decimal(18,2) column; introduces corresponding TypeORM migration for up/down schema changes.
API Layer
platforms/eCurrency-api/src/controllers/CurrencyController.ts, platforms/eCurrency-api/src/index.ts
Adds new updateMaxNegativeBalance controller method with auth checks and validation; registers PATCH endpoint /api/currencies/:id/max-negative.
Service Layer
platforms/eCurrency-api/src/services/CurrencyService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Extends createCurrency to accept optional maxNegativeBalance parameter; introduces updateMaxNegativeBalance service method with admin and validation checks; enhances transfer in LedgerService to validate balance limits and optimize balance queries.
Client UI
platforms/eCurrency/client/src/pages/currency-detail.tsx
Adds state management, input controls (range + numeric inputs), and save/clear functionality for max negative balance; integrates React Query cache invalidation post-update.

Sequence Diagram

sequenceDiagram
actor User as Administrator
participant UI as currency-detail.tsx
participant API as CurrencyController
participant Service as CurrencyService
participant Ledger as LedgerService
participant DB as Database
User->>UI: Set max negative balance value
UI->>API: PATCH /api/currencies/:id/max-negative {value}
API->>Service: updateMaxNegativeBalance(id, value, admin)
Note over Service: Validate:<br/>- Currency exists<br/>- Admin permission<br/>- allowNegative=true<br/>- Value is null or ≤0
Service->>DB: Update currency.maxNegativeBalance
DB-->>Service: Updated Currency
Service-->>API: Currency entity
API-->>UI: {id, maxNegativeBalance, ...}
UI->>UI: Invalidate cache<br/>(currency, accountDetails)
Note over User,Ledger: Later: User attempts transfer
User->>API: POST /transfer {amount, ...}
API->>Ledger: transfer(currencyId, amount, ...)
Note over Ledger: Compute balance change<br/>if (maxNegativeBalance set<br/>AND balance+amount < limit)<br/>throw error
alt Balance within limit
Ledger->>DB: addLedgerEntry(...)
DB-->>Ledger: Ledger entry
Ledger-->>API: Success
else Balance exceeds limit
Ledger-->>API: Insufficient funds error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • LedgerService modifications: Review the enhanced validation logic in transfer() to ensure maxNegativeBalance checks are correct and the new existingBalance parameter usage doesn't introduce balance-calculation discrepancies.
  • CurrencyService validation: Verify that updateMaxNegativeBalance enforces all required constraints (admin role, allowNegative check, numeric validation).
  • Client state synchronization: Confirm that cache invalidation strategy properly refreshes all dependent UI sections after updates.

Possibly related PRs

  • chore: move eCurrency #471: Directly related—this PR builds upon and extends the Currency, CurrencyController, CurrencyService, and migration patterns introduced there to add the new maxNegativeBalance management feature.

Suggested reviewers

  • sosweetham
  • xPathin
  • ananyayaya129

Poem

🐰 A hop, a skip, a balance to keep,
The burrow's new limit runs deep,
Max negative set with a careful paw,
No debt below this earned law,
Our ledgers now wiser, our wallets more fair! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR addresses issue #554 (negative limit) comprehensively with maxNegativeBalance implementation. However, no evidence of work on #553 (hashing/timestamps) or #555 (burn money functionality) is found in the code changes.Verify whether issues #553 and #555 were intended to be included in this PR. If so, add the missing functionality; otherwise, remove them from the PR description.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title directly describes the main feature: adding max negative balance caps and enforcing ledger bounds, which aligns with the primary changes across the codebase.
Description check✅ PassedThe description includes required sections: issue numbers, type of change, and change checklist. However, the 'How the change has been tested' section is missing content and no testing details are provided.
Out of Scope Changes check✅ PassedAll changes are directly related to implementing the max negative balance feature referenced in the PR title and issue #554. No unrelated or out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/burn-and-negative-control

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eCurrency-api/src/services/LedgerService.ts (1)

96-124: Wrap the transfer method in a database transaction to prevent concurrent balance validation violations.

There is a time-of-check to time-of-use (TOCTOU) gap between fetching currentBalance (line 96) and persisting ledger entries (lines 112-139). Concurrent transfers from the same account could both pass validation before either commits, potentially violating the maxNegativeBalance constraint.

Use AppDataSource.transaction() with proper isolation level (similar to the pattern in GroupService.ts), or implement row-level locking with FOR UPDATE to ensure balance validation and ledger creation are atomic.

🧹 Nitpick comments (4)
platforms/eCurrency-api/src/database/entities/Currency.ts (1)

39-40: TypeORM decimal columns return strings at runtime.

TypeORM's decimal type returns values as strings, not JavaScript numbers. The TypeScript type number | null is misleading and may cause subtle bugs when performing arithmetic or comparisons without explicit conversion.

Consider adjusting the type or using a transformer:

- @Column("decimal", { precision: 18, scale: 2, nullable: true })- maxNegativeBalance!: number | null;+ @Column("decimal", { precision: 18, scale: 2, nullable: true, transformer: {+ to: (value: number | null) => value,+ from: (value: string | null) => value === null ? null : parseFloat(value)+ }})+ maxNegativeBalance!: number | null;

Alternatively, update the type to string | null and convert explicitly at usage sites.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (1)

162-166: Consider adding semantic validation for maxNegativeBalance.

The current validation only checks that the value is a valid number (or null). Given the semantic meaning (a floor for how negative a balance can go), consider validating that non-null values are <= 0:

 // Allow null to clear; otherwise must be a number
const parsedValue = value === null || value === undefined ? null : Number(value);
if (parsedValue !== null && Number.isNaN(parsedValue)) {
return res.status(400).json({ error: "Invalid value for maxNegativeBalance" });
}
+ if (parsedValue !== null && parsedValue > 0) {+ return res.status(400).json({ error: "maxNegativeBalance must be zero or negative" });+ }
platforms/eCurrency-api/src/services/CurrencyService.ts (1)

20-47: Align createCurrency maxNegativeBalance invariants with update path

createCurrency now accepts and persists maxNegativeBalance but does not enforce the same constraints you added in updateMaxNegativeBalance (must only be set when allowNegative is true, must be null or <= 0, not NaN). That means callers can create a currency in a state that the update path would later reject (e.g. allowNegative = false with a non‑null cap, or a positive cap).

To keep invariants consistent and avoid surprising states, consider normalizing and validating here as well, e.g.:

 // Generate eName (UUID with @ prefix)
const ename = `@${uuidv4()}`;
+ const normalizedMaxNegativeBalance =+ allowNegative && maxNegativeBalance !== null ? maxNegativeBalance : null;++ if (normalizedMaxNegativeBalance !== null) {+ if (Number.isNaN(normalizedMaxNegativeBalance)) {+ throw new Error("Invalid max negative balance value");+ }+ if (normalizedMaxNegativeBalance > 0) {+ throw new Error("Max negative balance must be zero or negative");+ }+ }+
const currency = this.currencyRepository.create({
name,
description,
ename,
groupId,
createdBy,
allowNegative,
- maxNegativeBalance,+ maxNegativeBalance: normalizedMaxNegativeBalance,
});
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

292-301: Optional: Format maxNegativeBalance consistently with other monetary values

Here you display maxNegativeBalance with a bare toLocaleString(), while balance and total supply are formatted with two decimal places. For consistency and readability (especially since the DB column is decimal with scale), consider aligning the formatting:

- {currency.allowNegative- ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined- ? Number(currency.maxNegativeBalance).toLocaleString()- : "No cap")- : "Not applicable"}+ {currency.allowNegative+ ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined+ ? Number(currency.maxNegativeBalance).toLocaleString(undefined, {+ minimumFractionDigits: 2,+ maximumFractionDigits: 2,+ })+ : "No cap")+ : "Not applicable"}

Not required for correctness, but it keeps the UI consistent with the way balances and total supply are shown.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbefb26 and 28a6b7b.

📒 Files selected for processing (7)
  • platforms/eCurrency-api/src/controllers/CurrencyController.ts (7 hunks)
  • platforms/eCurrency-api/src/database/entities/Currency.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (1 hunks)
  • platforms/eCurrency-api/src/services/CurrencyService.ts (3 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (3 hunks)
  • platforms/eCurrency/client/src/pages/currency-detail.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)
platforms/eCurrency-api/src/database/migrations/1765039149572-migration.ts (1)
  • Migration1765039149572 (3-14)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/middleware/auth.ts (1)
  • authGuard (36-41)
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
⏰ 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 (8)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)

1-14: LGTM!

The migration correctly adds and removes the maxNegativeBalance column with the appropriate type (numeric(18,2)), matching the entity definition and following the established migration pattern in the codebase.

platforms/eCurrency-api/src/services/LedgerService.ts (2)

43-49: LGTM!

The existingBalance parameter is a sensible optimization that avoids a redundant database query when the caller has already fetched the balance for validation purposes.


103-108: Consider validating maxNegativeBalance is non-positive.

The current logic assumes maxNegativeBalance represents a floor (e.g., -100). If a positive value is accidentally set, the condition newBalance < maxNegativeBalance would never trigger for typical debits leading to negative balances. Consider adding validation in the controller/service to ensure maxNegativeBalance <= 0 when set.

platforms/eCurrency-api/src/index.ts (1)

115-115: LGTM!

The new PATCH endpoint follows the existing route patterns, uses appropriate HTTP semantics for a partial update, and is correctly protected with authGuard.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (2)

18-18: LGTM!

The maxNegativeBalance field is correctly extracted from the request body, passed to the service, and included in the response. The implementation follows the existing patterns for other currency fields.

Also applies to: 29-29, 40-40


174-185: LGTM!

The response structure is consistent with other currency endpoints, including all relevant fields.

platforms/eCurrency-api/src/services/CurrencyService.ts (1)

87-117: Max negative balance update flow and validation look solid

The new updateMaxNegativeBalance method correctly:

  • Verifies the currency exists and the requester is a group admin.
  • Disallows setting a cap when allowNegative is false.
  • Ensures the value is either null or a non‑NaN number that is <= 0.

This keeps the persisted state consistent with the business rules around negative balances.

platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

1-1: State and effect wiring for max negative balance looks consistent

The added React Query client usage, MAX_NEGATIVE_SLIDER constant, and maxNegative* state plus the useEffect that derives the absolute input from currency.maxNegativeBalance form a coherent flow and ensure the UI is initialized from the latest server value when the currency data loads or is refetched. No functional issues spotted here.

Also applies to: 18-18, 25-28, 56-64

Comment threadplatforms/eCurrency/client/src/pages/currency-detail.tsx
@kulmin
kulmin merged commit dd226ce into mainDec 15, 2025
4 checks passed
@coodos
coodos deleted the feat/burn-and-negative-control branch December 15, 2025 09:02
Sign up for freeto 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.

[feature] Ability to Burn money [feature] Implement Negative Limit [feature] Hashing and Timestamps

3 participants

@coodos@sosweetham@kulmin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: Add max negative caps and enforce ledger bounds - #618

Merged
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control
Dec 15, 2025
Merged

feat: Add max negative caps and enforce ledger bounds#618
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control

Conversation

@coodos

@coodoscoodos commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

closes#553
closes#554
closes#555

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added ability to set and manage maximum negative balance limits on currencies that allow negative balances.
    • Administrators can now configure and adjust balance floor thresholds through a new settings interface.
    • System validates transactions to prevent account balances from exceeding configured negative limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces maxNegativeBalance as a persistent field on currencies, enabling administrators to set limits on how far below zero account balances may fall. The feature spans database schema (new column via migration), API layer (new controller endpoint), business logic (validation in service and ledger), and client UI (management interface with save/clear actions).

Changes

Cohort / File(s)Summary
Database Schema
platforms/eCurrency-api/src/database/entities/Currency.ts, platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts
Adds maxNegativeBalance field to Currency entity as nullable decimal(18,2) column; introduces corresponding TypeORM migration for up/down schema changes.
API Layer
platforms/eCurrency-api/src/controllers/CurrencyController.ts, platforms/eCurrency-api/src/index.ts
Adds new updateMaxNegativeBalance controller method with auth checks and validation; registers PATCH endpoint /api/currencies/:id/max-negative.
Service Layer
platforms/eCurrency-api/src/services/CurrencyService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Extends createCurrency to accept optional maxNegativeBalance parameter; introduces updateMaxNegativeBalance service method with admin and validation checks; enhances transfer in LedgerService to validate balance limits and optimize balance queries.
Client UI
platforms/eCurrency/client/src/pages/currency-detail.tsx
Adds state management, input controls (range + numeric inputs), and save/clear functionality for max negative balance; integrates React Query cache invalidation post-update.

Sequence Diagram

sequenceDiagram
actor User as Administrator
participant UI as currency-detail.tsx
participant API as CurrencyController
participant Service as CurrencyService
participant Ledger as LedgerService
participant DB as Database
User->>UI: Set max negative balance value
UI->>API: PATCH /api/currencies/:id/max-negative {value}
API->>Service: updateMaxNegativeBalance(id, value, admin)
Note over Service: Validate:<br/>- Currency exists<br/>- Admin permission<br/>- allowNegative=true<br/>- Value is null or ≤0
Service->>DB: Update currency.maxNegativeBalance
DB-->>Service: Updated Currency
Service-->>API: Currency entity
API-->>UI: {id, maxNegativeBalance, ...}
UI->>UI: Invalidate cache<br/>(currency, accountDetails)
Note over User,Ledger: Later: User attempts transfer
User->>API: POST /transfer {amount, ...}
API->>Ledger: transfer(currencyId, amount, ...)
Note over Ledger: Compute balance change<br/>if (maxNegativeBalance set<br/>AND balance+amount < limit)<br/>throw error
alt Balance within limit
Ledger->>DB: addLedgerEntry(...)
DB-->>Ledger: Ledger entry
Ledger-->>API: Success
else Balance exceeds limit
Ledger-->>API: Insufficient funds error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • LedgerService modifications: Review the enhanced validation logic in transfer() to ensure maxNegativeBalance checks are correct and the new existingBalance parameter usage doesn't introduce balance-calculation discrepancies.
  • CurrencyService validation: Verify that updateMaxNegativeBalance enforces all required constraints (admin role, allowNegative check, numeric validation).
  • Client state synchronization: Confirm that cache invalidation strategy properly refreshes all dependent UI sections after updates.

Possibly related PRs

  • chore: move eCurrency #471: Directly related—this PR builds upon and extends the Currency, CurrencyController, CurrencyService, and migration patterns introduced there to add the new maxNegativeBalance management feature.

Suggested reviewers

  • sosweetham
  • xPathin
  • ananyayaya129

Poem

🐰 A hop, a skip, a balance to keep,
The burrow's new limit runs deep,
Max negative set with a careful paw,
No debt below this earned law,
Our ledgers now wiser, our wallets more fair! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR addresses issue #554 (negative limit) comprehensively with maxNegativeBalance implementation. However, no evidence of work on #553 (hashing/timestamps) or #555 (burn money functionality) is found in the code changes.Verify whether issues #553 and #555 were intended to be included in this PR. If so, add the missing functionality; otherwise, remove them from the PR description.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title directly describes the main feature: adding max negative balance caps and enforcing ledger bounds, which aligns with the primary changes across the codebase.
Description check✅ PassedThe description includes required sections: issue numbers, type of change, and change checklist. However, the 'How the change has been tested' section is missing content and no testing details are provided.
Out of Scope Changes check✅ PassedAll changes are directly related to implementing the max negative balance feature referenced in the PR title and issue #554. No unrelated or out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/burn-and-negative-control

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eCurrency-api/src/services/LedgerService.ts (1)

96-124: Wrap the transfer method in a database transaction to prevent concurrent balance validation violations.

There is a time-of-check to time-of-use (TOCTOU) gap between fetching currentBalance (line 96) and persisting ledger entries (lines 112-139). Concurrent transfers from the same account could both pass validation before either commits, potentially violating the maxNegativeBalance constraint.

Use AppDataSource.transaction() with proper isolation level (similar to the pattern in GroupService.ts), or implement row-level locking with FOR UPDATE to ensure balance validation and ledger creation are atomic.

🧹 Nitpick comments (4)
platforms/eCurrency-api/src/database/entities/Currency.ts (1)

39-40: TypeORM decimal columns return strings at runtime.

TypeORM's decimal type returns values as strings, not JavaScript numbers. The TypeScript type number | null is misleading and may cause subtle bugs when performing arithmetic or comparisons without explicit conversion.

Consider adjusting the type or using a transformer:

- @Column("decimal", { precision: 18, scale: 2, nullable: true })- maxNegativeBalance!: number | null;+ @Column("decimal", { precision: 18, scale: 2, nullable: true, transformer: {+ to: (value: number | null) => value,+ from: (value: string | null) => value === null ? null : parseFloat(value)+ }})+ maxNegativeBalance!: number | null;

Alternatively, update the type to string | null and convert explicitly at usage sites.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (1)

162-166: Consider adding semantic validation for maxNegativeBalance.

The current validation only checks that the value is a valid number (or null). Given the semantic meaning (a floor for how negative a balance can go), consider validating that non-null values are <= 0:

 // Allow null to clear; otherwise must be a number
const parsedValue = value === null || value === undefined ? null : Number(value);
if (parsedValue !== null && Number.isNaN(parsedValue)) {
return res.status(400).json({ error: "Invalid value for maxNegativeBalance" });
}
+ if (parsedValue !== null && parsedValue > 0) {+ return res.status(400).json({ error: "maxNegativeBalance must be zero or negative" });+ }
platforms/eCurrency-api/src/services/CurrencyService.ts (1)

20-47: Align createCurrency maxNegativeBalance invariants with update path

createCurrency now accepts and persists maxNegativeBalance but does not enforce the same constraints you added in updateMaxNegativeBalance (must only be set when allowNegative is true, must be null or <= 0, not NaN). That means callers can create a currency in a state that the update path would later reject (e.g. allowNegative = false with a non‑null cap, or a positive cap).

To keep invariants consistent and avoid surprising states, consider normalizing and validating here as well, e.g.:

 // Generate eName (UUID with @ prefix)
const ename = `@${uuidv4()}`;
+ const normalizedMaxNegativeBalance =+ allowNegative && maxNegativeBalance !== null ? maxNegativeBalance : null;++ if (normalizedMaxNegativeBalance !== null) {+ if (Number.isNaN(normalizedMaxNegativeBalance)) {+ throw new Error("Invalid max negative balance value");+ }+ if (normalizedMaxNegativeBalance > 0) {+ throw new Error("Max negative balance must be zero or negative");+ }+ }+
const currency = this.currencyRepository.create({
name,
description,
ename,
groupId,
createdBy,
allowNegative,
- maxNegativeBalance,+ maxNegativeBalance: normalizedMaxNegativeBalance,
});
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

292-301: Optional: Format maxNegativeBalance consistently with other monetary values

Here you display maxNegativeBalance with a bare toLocaleString(), while balance and total supply are formatted with two decimal places. For consistency and readability (especially since the DB column is decimal with scale), consider aligning the formatting:

- {currency.allowNegative- ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined- ? Number(currency.maxNegativeBalance).toLocaleString()- : "No cap")- : "Not applicable"}+ {currency.allowNegative+ ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined+ ? Number(currency.maxNegativeBalance).toLocaleString(undefined, {+ minimumFractionDigits: 2,+ maximumFractionDigits: 2,+ })+ : "No cap")+ : "Not applicable"}

Not required for correctness, but it keeps the UI consistent with the way balances and total supply are shown.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbefb26 and 28a6b7b.

📒 Files selected for processing (7)
  • platforms/eCurrency-api/src/controllers/CurrencyController.ts (7 hunks)
  • platforms/eCurrency-api/src/database/entities/Currency.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (1 hunks)
  • platforms/eCurrency-api/src/services/CurrencyService.ts (3 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (3 hunks)
  • platforms/eCurrency/client/src/pages/currency-detail.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)
platforms/eCurrency-api/src/database/migrations/1765039149572-migration.ts (1)
  • Migration1765039149572 (3-14)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/middleware/auth.ts (1)
  • authGuard (36-41)
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
⏰ 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 (8)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)

1-14: LGTM!

The migration correctly adds and removes the maxNegativeBalance column with the appropriate type (numeric(18,2)), matching the entity definition and following the established migration pattern in the codebase.

platforms/eCurrency-api/src/services/LedgerService.ts (2)

43-49: LGTM!

The existingBalance parameter is a sensible optimization that avoids a redundant database query when the caller has already fetched the balance for validation purposes.


103-108: Consider validating maxNegativeBalance is non-positive.

The current logic assumes maxNegativeBalance represents a floor (e.g., -100). If a positive value is accidentally set, the condition newBalance < maxNegativeBalance would never trigger for typical debits leading to negative balances. Consider adding validation in the controller/service to ensure maxNegativeBalance <= 0 when set.

platforms/eCurrency-api/src/index.ts (1)

115-115: LGTM!

The new PATCH endpoint follows the existing route patterns, uses appropriate HTTP semantics for a partial update, and is correctly protected with authGuard.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (2)

18-18: LGTM!

The maxNegativeBalance field is correctly extracted from the request body, passed to the service, and included in the response. The implementation follows the existing patterns for other currency fields.

Also applies to: 29-29, 40-40


174-185: LGTM!

The response structure is consistent with other currency endpoints, including all relevant fields.

platforms/eCurrency-api/src/services/CurrencyService.ts (1)

87-117: Max negative balance update flow and validation look solid

The new updateMaxNegativeBalance method correctly:

  • Verifies the currency exists and the requester is a group admin.
  • Disallows setting a cap when allowNegative is false.
  • Ensures the value is either null or a non‑NaN number that is <= 0.

This keeps the persisted state consistent with the business rules around negative balances.

platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

1-1: State and effect wiring for max negative balance looks consistent

The added React Query client usage, MAX_NEGATIVE_SLIDER constant, and maxNegative* state plus the useEffect that derives the absolute input from currency.maxNegativeBalance form a coherent flow and ensure the UI is initialized from the latest server value when the currency data loads or is refetched. No functional issues spotted here.

Also applies to: 18-18, 25-28, 56-64

Comment threadplatforms/eCurrency/client/src/pages/currency-detail.tsx
@kulmin
kulmin merged commit dd226ce into mainDec 15, 2025
4 checks passed
@coodos
coodos deleted the feat/burn-and-negative-control branch December 15, 2025 09:02
Sign up for freeto 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.

[feature] Ability to Burn money [feature] Implement Negative Limit [feature] Hashing and Timestamps

3 participants

@coodos@sosweetham@kulmin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: Add max negative caps and enforce ledger bounds - #618

Merged
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control
Dec 15, 2025
Merged

feat: Add max negative caps and enforce ledger bounds#618
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control

Conversation

@coodos

@coodoscoodos commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

closes#553
closes#554
closes#555

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added ability to set and manage maximum negative balance limits on currencies that allow negative balances.
    • Administrators can now configure and adjust balance floor thresholds through a new settings interface.
    • System validates transactions to prevent account balances from exceeding configured negative limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces maxNegativeBalance as a persistent field on currencies, enabling administrators to set limits on how far below zero account balances may fall. The feature spans database schema (new column via migration), API layer (new controller endpoint), business logic (validation in service and ledger), and client UI (management interface with save/clear actions).

Changes

Cohort / File(s)Summary
Database Schema
platforms/eCurrency-api/src/database/entities/Currency.ts, platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts
Adds maxNegativeBalance field to Currency entity as nullable decimal(18,2) column; introduces corresponding TypeORM migration for up/down schema changes.
API Layer
platforms/eCurrency-api/src/controllers/CurrencyController.ts, platforms/eCurrency-api/src/index.ts
Adds new updateMaxNegativeBalance controller method with auth checks and validation; registers PATCH endpoint /api/currencies/:id/max-negative.
Service Layer
platforms/eCurrency-api/src/services/CurrencyService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Extends createCurrency to accept optional maxNegativeBalance parameter; introduces updateMaxNegativeBalance service method with admin and validation checks; enhances transfer in LedgerService to validate balance limits and optimize balance queries.
Client UI
platforms/eCurrency/client/src/pages/currency-detail.tsx
Adds state management, input controls (range + numeric inputs), and save/clear functionality for max negative balance; integrates React Query cache invalidation post-update.

Sequence Diagram

sequenceDiagram
actor User as Administrator
participant UI as currency-detail.tsx
participant API as CurrencyController
participant Service as CurrencyService
participant Ledger as LedgerService
participant DB as Database
User->>UI: Set max negative balance value
UI->>API: PATCH /api/currencies/:id/max-negative {value}
API->>Service: updateMaxNegativeBalance(id, value, admin)
Note over Service: Validate:<br/>- Currency exists<br/>- Admin permission<br/>- allowNegative=true<br/>- Value is null or ≤0
Service->>DB: Update currency.maxNegativeBalance
DB-->>Service: Updated Currency
Service-->>API: Currency entity
API-->>UI: {id, maxNegativeBalance, ...}
UI->>UI: Invalidate cache<br/>(currency, accountDetails)
Note over User,Ledger: Later: User attempts transfer
User->>API: POST /transfer {amount, ...}
API->>Ledger: transfer(currencyId, amount, ...)
Note over Ledger: Compute balance change<br/>if (maxNegativeBalance set<br/>AND balance+amount < limit)<br/>throw error
alt Balance within limit
Ledger->>DB: addLedgerEntry(...)
DB-->>Ledger: Ledger entry
Ledger-->>API: Success
else Balance exceeds limit
Ledger-->>API: Insufficient funds error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • LedgerService modifications: Review the enhanced validation logic in transfer() to ensure maxNegativeBalance checks are correct and the new existingBalance parameter usage doesn't introduce balance-calculation discrepancies.
  • CurrencyService validation: Verify that updateMaxNegativeBalance enforces all required constraints (admin role, allowNegative check, numeric validation).
  • Client state synchronization: Confirm that cache invalidation strategy properly refreshes all dependent UI sections after updates.

Possibly related PRs

  • chore: move eCurrency #471: Directly related—this PR builds upon and extends the Currency, CurrencyController, CurrencyService, and migration patterns introduced there to add the new maxNegativeBalance management feature.

Suggested reviewers

  • sosweetham
  • xPathin
  • ananyayaya129

Poem

🐰 A hop, a skip, a balance to keep,
The burrow's new limit runs deep,
Max negative set with a careful paw,
No debt below this earned law,
Our ledgers now wiser, our wallets more fair! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR addresses issue #554 (negative limit) comprehensively with maxNegativeBalance implementation. However, no evidence of work on #553 (hashing/timestamps) or #555 (burn money functionality) is found in the code changes.Verify whether issues #553 and #555 were intended to be included in this PR. If so, add the missing functionality; otherwise, remove them from the PR description.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title directly describes the main feature: adding max negative balance caps and enforcing ledger bounds, which aligns with the primary changes across the codebase.
Description check✅ PassedThe description includes required sections: issue numbers, type of change, and change checklist. However, the 'How the change has been tested' section is missing content and no testing details are provided.
Out of Scope Changes check✅ PassedAll changes are directly related to implementing the max negative balance feature referenced in the PR title and issue #554. No unrelated or out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/burn-and-negative-control

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eCurrency-api/src/services/LedgerService.ts (1)

96-124: Wrap the transfer method in a database transaction to prevent concurrent balance validation violations.

There is a time-of-check to time-of-use (TOCTOU) gap between fetching currentBalance (line 96) and persisting ledger entries (lines 112-139). Concurrent transfers from the same account could both pass validation before either commits, potentially violating the maxNegativeBalance constraint.

Use AppDataSource.transaction() with proper isolation level (similar to the pattern in GroupService.ts), or implement row-level locking with FOR UPDATE to ensure balance validation and ledger creation are atomic.

🧹 Nitpick comments (4)
platforms/eCurrency-api/src/database/entities/Currency.ts (1)

39-40: TypeORM decimal columns return strings at runtime.

TypeORM's decimal type returns values as strings, not JavaScript numbers. The TypeScript type number | null is misleading and may cause subtle bugs when performing arithmetic or comparisons without explicit conversion.

Consider adjusting the type or using a transformer:

- @Column("decimal", { precision: 18, scale: 2, nullable: true })- maxNegativeBalance!: number | null;+ @Column("decimal", { precision: 18, scale: 2, nullable: true, transformer: {+ to: (value: number | null) => value,+ from: (value: string | null) => value === null ? null : parseFloat(value)+ }})+ maxNegativeBalance!: number | null;

Alternatively, update the type to string | null and convert explicitly at usage sites.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (1)

162-166: Consider adding semantic validation for maxNegativeBalance.

The current validation only checks that the value is a valid number (or null). Given the semantic meaning (a floor for how negative a balance can go), consider validating that non-null values are <= 0:

 // Allow null to clear; otherwise must be a number
const parsedValue = value === null || value === undefined ? null : Number(value);
if (parsedValue !== null && Number.isNaN(parsedValue)) {
return res.status(400).json({ error: "Invalid value for maxNegativeBalance" });
}
+ if (parsedValue !== null && parsedValue > 0) {+ return res.status(400).json({ error: "maxNegativeBalance must be zero or negative" });+ }
platforms/eCurrency-api/src/services/CurrencyService.ts (1)

20-47: Align createCurrency maxNegativeBalance invariants with update path

createCurrency now accepts and persists maxNegativeBalance but does not enforce the same constraints you added in updateMaxNegativeBalance (must only be set when allowNegative is true, must be null or <= 0, not NaN). That means callers can create a currency in a state that the update path would later reject (e.g. allowNegative = false with a non‑null cap, or a positive cap).

To keep invariants consistent and avoid surprising states, consider normalizing and validating here as well, e.g.:

 // Generate eName (UUID with @ prefix)
const ename = `@${uuidv4()}`;
+ const normalizedMaxNegativeBalance =+ allowNegative && maxNegativeBalance !== null ? maxNegativeBalance : null;++ if (normalizedMaxNegativeBalance !== null) {+ if (Number.isNaN(normalizedMaxNegativeBalance)) {+ throw new Error("Invalid max negative balance value");+ }+ if (normalizedMaxNegativeBalance > 0) {+ throw new Error("Max negative balance must be zero or negative");+ }+ }+
const currency = this.currencyRepository.create({
name,
description,
ename,
groupId,
createdBy,
allowNegative,
- maxNegativeBalance,+ maxNegativeBalance: normalizedMaxNegativeBalance,
});
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

292-301: Optional: Format maxNegativeBalance consistently with other monetary values

Here you display maxNegativeBalance with a bare toLocaleString(), while balance and total supply are formatted with two decimal places. For consistency and readability (especially since the DB column is decimal with scale), consider aligning the formatting:

- {currency.allowNegative- ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined- ? Number(currency.maxNegativeBalance).toLocaleString()- : "No cap")- : "Not applicable"}+ {currency.allowNegative+ ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined+ ? Number(currency.maxNegativeBalance).toLocaleString(undefined, {+ minimumFractionDigits: 2,+ maximumFractionDigits: 2,+ })+ : "No cap")+ : "Not applicable"}

Not required for correctness, but it keeps the UI consistent with the way balances and total supply are shown.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbefb26 and 28a6b7b.

📒 Files selected for processing (7)
  • platforms/eCurrency-api/src/controllers/CurrencyController.ts (7 hunks)
  • platforms/eCurrency-api/src/database/entities/Currency.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (1 hunks)
  • platforms/eCurrency-api/src/services/CurrencyService.ts (3 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (3 hunks)
  • platforms/eCurrency/client/src/pages/currency-detail.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)
platforms/eCurrency-api/src/database/migrations/1765039149572-migration.ts (1)
  • Migration1765039149572 (3-14)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/middleware/auth.ts (1)
  • authGuard (36-41)
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
⏰ 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 (8)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)

1-14: LGTM!

The migration correctly adds and removes the maxNegativeBalance column with the appropriate type (numeric(18,2)), matching the entity definition and following the established migration pattern in the codebase.

platforms/eCurrency-api/src/services/LedgerService.ts (2)

43-49: LGTM!

The existingBalance parameter is a sensible optimization that avoids a redundant database query when the caller has already fetched the balance for validation purposes.


103-108: Consider validating maxNegativeBalance is non-positive.

The current logic assumes maxNegativeBalance represents a floor (e.g., -100). If a positive value is accidentally set, the condition newBalance < maxNegativeBalance would never trigger for typical debits leading to negative balances. Consider adding validation in the controller/service to ensure maxNegativeBalance <= 0 when set.

platforms/eCurrency-api/src/index.ts (1)

115-115: LGTM!

The new PATCH endpoint follows the existing route patterns, uses appropriate HTTP semantics for a partial update, and is correctly protected with authGuard.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (2)

18-18: LGTM!

The maxNegativeBalance field is correctly extracted from the request body, passed to the service, and included in the response. The implementation follows the existing patterns for other currency fields.

Also applies to: 29-29, 40-40


174-185: LGTM!

The response structure is consistent with other currency endpoints, including all relevant fields.

platforms/eCurrency-api/src/services/CurrencyService.ts (1)

87-117: Max negative balance update flow and validation look solid

The new updateMaxNegativeBalance method correctly:

  • Verifies the currency exists and the requester is a group admin.
  • Disallows setting a cap when allowNegative is false.
  • Ensures the value is either null or a non‑NaN number that is <= 0.

This keeps the persisted state consistent with the business rules around negative balances.

platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

1-1: State and effect wiring for max negative balance looks consistent

The added React Query client usage, MAX_NEGATIVE_SLIDER constant, and maxNegative* state plus the useEffect that derives the absolute input from currency.maxNegativeBalance form a coherent flow and ensure the UI is initialized from the latest server value when the currency data loads or is refetched. No functional issues spotted here.

Also applies to: 18-18, 25-28, 56-64

Comment threadplatforms/eCurrency/client/src/pages/currency-detail.tsx
@kulmin
kulmin merged commit dd226ce into mainDec 15, 2025
4 checks passed
@coodos
coodos deleted the feat/burn-and-negative-control branch December 15, 2025 09:02
Sign up for freeto 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.

[feature] Ability to Burn money [feature] Implement Negative Limit [feature] Hashing and Timestamps

3 participants

@coodos@sosweetham@kulmin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: Add max negative caps and enforce ledger bounds - #618

Merged
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control
Dec 15, 2025
Merged

feat: Add max negative caps and enforce ledger bounds#618
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control

Conversation

@coodos

@coodoscoodos commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

closes#553
closes#554
closes#555

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added ability to set and manage maximum negative balance limits on currencies that allow negative balances.
    • Administrators can now configure and adjust balance floor thresholds through a new settings interface.
    • System validates transactions to prevent account balances from exceeding configured negative limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces maxNegativeBalance as a persistent field on currencies, enabling administrators to set limits on how far below zero account balances may fall. The feature spans database schema (new column via migration), API layer (new controller endpoint), business logic (validation in service and ledger), and client UI (management interface with save/clear actions).

Changes

Cohort / File(s)Summary
Database Schema
platforms/eCurrency-api/src/database/entities/Currency.ts, platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts
Adds maxNegativeBalance field to Currency entity as nullable decimal(18,2) column; introduces corresponding TypeORM migration for up/down schema changes.
API Layer
platforms/eCurrency-api/src/controllers/CurrencyController.ts, platforms/eCurrency-api/src/index.ts
Adds new updateMaxNegativeBalance controller method with auth checks and validation; registers PATCH endpoint /api/currencies/:id/max-negative.
Service Layer
platforms/eCurrency-api/src/services/CurrencyService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Extends createCurrency to accept optional maxNegativeBalance parameter; introduces updateMaxNegativeBalance service method with admin and validation checks; enhances transfer in LedgerService to validate balance limits and optimize balance queries.
Client UI
platforms/eCurrency/client/src/pages/currency-detail.tsx
Adds state management, input controls (range + numeric inputs), and save/clear functionality for max negative balance; integrates React Query cache invalidation post-update.

Sequence Diagram

sequenceDiagram
actor User as Administrator
participant UI as currency-detail.tsx
participant API as CurrencyController
participant Service as CurrencyService
participant Ledger as LedgerService
participant DB as Database
User->>UI: Set max negative balance value
UI->>API: PATCH /api/currencies/:id/max-negative {value}
API->>Service: updateMaxNegativeBalance(id, value, admin)
Note over Service: Validate:<br/>- Currency exists<br/>- Admin permission<br/>- allowNegative=true<br/>- Value is null or ≤0
Service->>DB: Update currency.maxNegativeBalance
DB-->>Service: Updated Currency
Service-->>API: Currency entity
API-->>UI: {id, maxNegativeBalance, ...}
UI->>UI: Invalidate cache<br/>(currency, accountDetails)
Note over User,Ledger: Later: User attempts transfer
User->>API: POST /transfer {amount, ...}
API->>Ledger: transfer(currencyId, amount, ...)
Note over Ledger: Compute balance change<br/>if (maxNegativeBalance set<br/>AND balance+amount < limit)<br/>throw error
alt Balance within limit
Ledger->>DB: addLedgerEntry(...)
DB-->>Ledger: Ledger entry
Ledger-->>API: Success
else Balance exceeds limit
Ledger-->>API: Insufficient funds error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • LedgerService modifications: Review the enhanced validation logic in transfer() to ensure maxNegativeBalance checks are correct and the new existingBalance parameter usage doesn't introduce balance-calculation discrepancies.
  • CurrencyService validation: Verify that updateMaxNegativeBalance enforces all required constraints (admin role, allowNegative check, numeric validation).
  • Client state synchronization: Confirm that cache invalidation strategy properly refreshes all dependent UI sections after updates.

Possibly related PRs

  • chore: move eCurrency #471: Directly related—this PR builds upon and extends the Currency, CurrencyController, CurrencyService, and migration patterns introduced there to add the new maxNegativeBalance management feature.

Suggested reviewers

  • sosweetham
  • xPathin
  • ananyayaya129

Poem

🐰 A hop, a skip, a balance to keep,
The burrow's new limit runs deep,
Max negative set with a careful paw,
No debt below this earned law,
Our ledgers now wiser, our wallets more fair! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR addresses issue #554 (negative limit) comprehensively with maxNegativeBalance implementation. However, no evidence of work on #553 (hashing/timestamps) or #555 (burn money functionality) is found in the code changes.Verify whether issues #553 and #555 were intended to be included in this PR. If so, add the missing functionality; otherwise, remove them from the PR description.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title directly describes the main feature: adding max negative balance caps and enforcing ledger bounds, which aligns with the primary changes across the codebase.
Description check✅ PassedThe description includes required sections: issue numbers, type of change, and change checklist. However, the 'How the change has been tested' section is missing content and no testing details are provided.
Out of Scope Changes check✅ PassedAll changes are directly related to implementing the max negative balance feature referenced in the PR title and issue #554. No unrelated or out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/burn-and-negative-control

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eCurrency-api/src/services/LedgerService.ts (1)

96-124: Wrap the transfer method in a database transaction to prevent concurrent balance validation violations.

There is a time-of-check to time-of-use (TOCTOU) gap between fetching currentBalance (line 96) and persisting ledger entries (lines 112-139). Concurrent transfers from the same account could both pass validation before either commits, potentially violating the maxNegativeBalance constraint.

Use AppDataSource.transaction() with proper isolation level (similar to the pattern in GroupService.ts), or implement row-level locking with FOR UPDATE to ensure balance validation and ledger creation are atomic.

🧹 Nitpick comments (4)
platforms/eCurrency-api/src/database/entities/Currency.ts (1)

39-40: TypeORM decimal columns return strings at runtime.

TypeORM's decimal type returns values as strings, not JavaScript numbers. The TypeScript type number | null is misleading and may cause subtle bugs when performing arithmetic or comparisons without explicit conversion.

Consider adjusting the type or using a transformer:

- @Column("decimal", { precision: 18, scale: 2, nullable: true })- maxNegativeBalance!: number | null;+ @Column("decimal", { precision: 18, scale: 2, nullable: true, transformer: {+ to: (value: number | null) => value,+ from: (value: string | null) => value === null ? null : parseFloat(value)+ }})+ maxNegativeBalance!: number | null;

Alternatively, update the type to string | null and convert explicitly at usage sites.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (1)

162-166: Consider adding semantic validation for maxNegativeBalance.

The current validation only checks that the value is a valid number (or null). Given the semantic meaning (a floor for how negative a balance can go), consider validating that non-null values are <= 0:

 // Allow null to clear; otherwise must be a number
const parsedValue = value === null || value === undefined ? null : Number(value);
if (parsedValue !== null && Number.isNaN(parsedValue)) {
return res.status(400).json({ error: "Invalid value for maxNegativeBalance" });
}
+ if (parsedValue !== null && parsedValue > 0) {+ return res.status(400).json({ error: "maxNegativeBalance must be zero or negative" });+ }
platforms/eCurrency-api/src/services/CurrencyService.ts (1)

20-47: Align createCurrency maxNegativeBalance invariants with update path

createCurrency now accepts and persists maxNegativeBalance but does not enforce the same constraints you added in updateMaxNegativeBalance (must only be set when allowNegative is true, must be null or <= 0, not NaN). That means callers can create a currency in a state that the update path would later reject (e.g. allowNegative = false with a non‑null cap, or a positive cap).

To keep invariants consistent and avoid surprising states, consider normalizing and validating here as well, e.g.:

 // Generate eName (UUID with @ prefix)
const ename = `@${uuidv4()}`;
+ const normalizedMaxNegativeBalance =+ allowNegative && maxNegativeBalance !== null ? maxNegativeBalance : null;++ if (normalizedMaxNegativeBalance !== null) {+ if (Number.isNaN(normalizedMaxNegativeBalance)) {+ throw new Error("Invalid max negative balance value");+ }+ if (normalizedMaxNegativeBalance > 0) {+ throw new Error("Max negative balance must be zero or negative");+ }+ }+
const currency = this.currencyRepository.create({
name,
description,
ename,
groupId,
createdBy,
allowNegative,
- maxNegativeBalance,+ maxNegativeBalance: normalizedMaxNegativeBalance,
});
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

292-301: Optional: Format maxNegativeBalance consistently with other monetary values

Here you display maxNegativeBalance with a bare toLocaleString(), while balance and total supply are formatted with two decimal places. For consistency and readability (especially since the DB column is decimal with scale), consider aligning the formatting:

- {currency.allowNegative- ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined- ? Number(currency.maxNegativeBalance).toLocaleString()- : "No cap")- : "Not applicable"}+ {currency.allowNegative+ ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined+ ? Number(currency.maxNegativeBalance).toLocaleString(undefined, {+ minimumFractionDigits: 2,+ maximumFractionDigits: 2,+ })+ : "No cap")+ : "Not applicable"}

Not required for correctness, but it keeps the UI consistent with the way balances and total supply are shown.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbefb26 and 28a6b7b.

📒 Files selected for processing (7)
  • platforms/eCurrency-api/src/controllers/CurrencyController.ts (7 hunks)
  • platforms/eCurrency-api/src/database/entities/Currency.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (1 hunks)
  • platforms/eCurrency-api/src/services/CurrencyService.ts (3 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (3 hunks)
  • platforms/eCurrency/client/src/pages/currency-detail.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)
platforms/eCurrency-api/src/database/migrations/1765039149572-migration.ts (1)
  • Migration1765039149572 (3-14)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/middleware/auth.ts (1)
  • authGuard (36-41)
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
⏰ 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 (8)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)

1-14: LGTM!

The migration correctly adds and removes the maxNegativeBalance column with the appropriate type (numeric(18,2)), matching the entity definition and following the established migration pattern in the codebase.

platforms/eCurrency-api/src/services/LedgerService.ts (2)

43-49: LGTM!

The existingBalance parameter is a sensible optimization that avoids a redundant database query when the caller has already fetched the balance for validation purposes.


103-108: Consider validating maxNegativeBalance is non-positive.

The current logic assumes maxNegativeBalance represents a floor (e.g., -100). If a positive value is accidentally set, the condition newBalance < maxNegativeBalance would never trigger for typical debits leading to negative balances. Consider adding validation in the controller/service to ensure maxNegativeBalance <= 0 when set.

platforms/eCurrency-api/src/index.ts (1)

115-115: LGTM!

The new PATCH endpoint follows the existing route patterns, uses appropriate HTTP semantics for a partial update, and is correctly protected with authGuard.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (2)

18-18: LGTM!

The maxNegativeBalance field is correctly extracted from the request body, passed to the service, and included in the response. The implementation follows the existing patterns for other currency fields.

Also applies to: 29-29, 40-40


174-185: LGTM!

The response structure is consistent with other currency endpoints, including all relevant fields.

platforms/eCurrency-api/src/services/CurrencyService.ts (1)

87-117: Max negative balance update flow and validation look solid

The new updateMaxNegativeBalance method correctly:

  • Verifies the currency exists and the requester is a group admin.
  • Disallows setting a cap when allowNegative is false.
  • Ensures the value is either null or a non‑NaN number that is <= 0.

This keeps the persisted state consistent with the business rules around negative balances.

platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

1-1: State and effect wiring for max negative balance looks consistent

The added React Query client usage, MAX_NEGATIVE_SLIDER constant, and maxNegative* state plus the useEffect that derives the absolute input from currency.maxNegativeBalance form a coherent flow and ensure the UI is initialized from the latest server value when the currency data loads or is refetched. No functional issues spotted here.

Also applies to: 18-18, 25-28, 56-64

Comment threadplatforms/eCurrency/client/src/pages/currency-detail.tsx
@kulmin
kulmin merged commit dd226ce into mainDec 15, 2025
4 checks passed
@coodos
coodos deleted the feat/burn-and-negative-control branch December 15, 2025 09:02
Sign up for freeto 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.

[feature] Ability to Burn money [feature] Implement Negative Limit [feature] Hashing and Timestamps

3 participants

@coodos@sosweetham@kulmin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: Add max negative caps and enforce ledger bounds - #618

Merged
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control
Dec 15, 2025
Merged

feat: Add max negative caps and enforce ledger bounds#618
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control

Conversation

@coodos

@coodoscoodos commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

closes#553
closes#554
closes#555

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added ability to set and manage maximum negative balance limits on currencies that allow negative balances.
    • Administrators can now configure and adjust balance floor thresholds through a new settings interface.
    • System validates transactions to prevent account balances from exceeding configured negative limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces maxNegativeBalance as a persistent field on currencies, enabling administrators to set limits on how far below zero account balances may fall. The feature spans database schema (new column via migration), API layer (new controller endpoint), business logic (validation in service and ledger), and client UI (management interface with save/clear actions).

Changes

Cohort / File(s)Summary
Database Schema
platforms/eCurrency-api/src/database/entities/Currency.ts, platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts
Adds maxNegativeBalance field to Currency entity as nullable decimal(18,2) column; introduces corresponding TypeORM migration for up/down schema changes.
API Layer
platforms/eCurrency-api/src/controllers/CurrencyController.ts, platforms/eCurrency-api/src/index.ts
Adds new updateMaxNegativeBalance controller method with auth checks and validation; registers PATCH endpoint /api/currencies/:id/max-negative.
Service Layer
platforms/eCurrency-api/src/services/CurrencyService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Extends createCurrency to accept optional maxNegativeBalance parameter; introduces updateMaxNegativeBalance service method with admin and validation checks; enhances transfer in LedgerService to validate balance limits and optimize balance queries.
Client UI
platforms/eCurrency/client/src/pages/currency-detail.tsx
Adds state management, input controls (range + numeric inputs), and save/clear functionality for max negative balance; integrates React Query cache invalidation post-update.

Sequence Diagram

sequenceDiagram
actor User as Administrator
participant UI as currency-detail.tsx
participant API as CurrencyController
participant Service as CurrencyService
participant Ledger as LedgerService
participant DB as Database
User->>UI: Set max negative balance value
UI->>API: PATCH /api/currencies/:id/max-negative {value}
API->>Service: updateMaxNegativeBalance(id, value, admin)
Note over Service: Validate:<br/>- Currency exists<br/>- Admin permission<br/>- allowNegative=true<br/>- Value is null or ≤0
Service->>DB: Update currency.maxNegativeBalance
DB-->>Service: Updated Currency
Service-->>API: Currency entity
API-->>UI: {id, maxNegativeBalance, ...}
UI->>UI: Invalidate cache<br/>(currency, accountDetails)
Note over User,Ledger: Later: User attempts transfer
User->>API: POST /transfer {amount, ...}
API->>Ledger: transfer(currencyId, amount, ...)
Note over Ledger: Compute balance change<br/>if (maxNegativeBalance set<br/>AND balance+amount < limit)<br/>throw error
alt Balance within limit
Ledger->>DB: addLedgerEntry(...)
DB-->>Ledger: Ledger entry
Ledger-->>API: Success
else Balance exceeds limit
Ledger-->>API: Insufficient funds error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • LedgerService modifications: Review the enhanced validation logic in transfer() to ensure maxNegativeBalance checks are correct and the new existingBalance parameter usage doesn't introduce balance-calculation discrepancies.
  • CurrencyService validation: Verify that updateMaxNegativeBalance enforces all required constraints (admin role, allowNegative check, numeric validation).
  • Client state synchronization: Confirm that cache invalidation strategy properly refreshes all dependent UI sections after updates.

Possibly related PRs

  • chore: move eCurrency #471: Directly related—this PR builds upon and extends the Currency, CurrencyController, CurrencyService, and migration patterns introduced there to add the new maxNegativeBalance management feature.

Suggested reviewers

  • sosweetham
  • xPathin
  • ananyayaya129

Poem

🐰 A hop, a skip, a balance to keep,
The burrow's new limit runs deep,
Max negative set with a careful paw,
No debt below this earned law,
Our ledgers now wiser, our wallets more fair! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR addresses issue #554 (negative limit) comprehensively with maxNegativeBalance implementation. However, no evidence of work on #553 (hashing/timestamps) or #555 (burn money functionality) is found in the code changes.Verify whether issues #553 and #555 were intended to be included in this PR. If so, add the missing functionality; otherwise, remove them from the PR description.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title directly describes the main feature: adding max negative balance caps and enforcing ledger bounds, which aligns with the primary changes across the codebase.
Description check✅ PassedThe description includes required sections: issue numbers, type of change, and change checklist. However, the 'How the change has been tested' section is missing content and no testing details are provided.
Out of Scope Changes check✅ PassedAll changes are directly related to implementing the max negative balance feature referenced in the PR title and issue #554. No unrelated or out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/burn-and-negative-control

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eCurrency-api/src/services/LedgerService.ts (1)

96-124: Wrap the transfer method in a database transaction to prevent concurrent balance validation violations.

There is a time-of-check to time-of-use (TOCTOU) gap between fetching currentBalance (line 96) and persisting ledger entries (lines 112-139). Concurrent transfers from the same account could both pass validation before either commits, potentially violating the maxNegativeBalance constraint.

Use AppDataSource.transaction() with proper isolation level (similar to the pattern in GroupService.ts), or implement row-level locking with FOR UPDATE to ensure balance validation and ledger creation are atomic.

🧹 Nitpick comments (4)
platforms/eCurrency-api/src/database/entities/Currency.ts (1)

39-40: TypeORM decimal columns return strings at runtime.

TypeORM's decimal type returns values as strings, not JavaScript numbers. The TypeScript type number | null is misleading and may cause subtle bugs when performing arithmetic or comparisons without explicit conversion.

Consider adjusting the type or using a transformer:

- @Column("decimal", { precision: 18, scale: 2, nullable: true })- maxNegativeBalance!: number | null;+ @Column("decimal", { precision: 18, scale: 2, nullable: true, transformer: {+ to: (value: number | null) => value,+ from: (value: string | null) => value === null ? null : parseFloat(value)+ }})+ maxNegativeBalance!: number | null;

Alternatively, update the type to string | null and convert explicitly at usage sites.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (1)

162-166: Consider adding semantic validation for maxNegativeBalance.

The current validation only checks that the value is a valid number (or null). Given the semantic meaning (a floor for how negative a balance can go), consider validating that non-null values are <= 0:

 // Allow null to clear; otherwise must be a number
const parsedValue = value === null || value === undefined ? null : Number(value);
if (parsedValue !== null && Number.isNaN(parsedValue)) {
return res.status(400).json({ error: "Invalid value for maxNegativeBalance" });
}
+ if (parsedValue !== null && parsedValue > 0) {+ return res.status(400).json({ error: "maxNegativeBalance must be zero or negative" });+ }
platforms/eCurrency-api/src/services/CurrencyService.ts (1)

20-47: Align createCurrency maxNegativeBalance invariants with update path

createCurrency now accepts and persists maxNegativeBalance but does not enforce the same constraints you added in updateMaxNegativeBalance (must only be set when allowNegative is true, must be null or <= 0, not NaN). That means callers can create a currency in a state that the update path would later reject (e.g. allowNegative = false with a non‑null cap, or a positive cap).

To keep invariants consistent and avoid surprising states, consider normalizing and validating here as well, e.g.:

 // Generate eName (UUID with @ prefix)
const ename = `@${uuidv4()}`;
+ const normalizedMaxNegativeBalance =+ allowNegative && maxNegativeBalance !== null ? maxNegativeBalance : null;++ if (normalizedMaxNegativeBalance !== null) {+ if (Number.isNaN(normalizedMaxNegativeBalance)) {+ throw new Error("Invalid max negative balance value");+ }+ if (normalizedMaxNegativeBalance > 0) {+ throw new Error("Max negative balance must be zero or negative");+ }+ }+
const currency = this.currencyRepository.create({
name,
description,
ename,
groupId,
createdBy,
allowNegative,
- maxNegativeBalance,+ maxNegativeBalance: normalizedMaxNegativeBalance,
});
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

292-301: Optional: Format maxNegativeBalance consistently with other monetary values

Here you display maxNegativeBalance with a bare toLocaleString(), while balance and total supply are formatted with two decimal places. For consistency and readability (especially since the DB column is decimal with scale), consider aligning the formatting:

- {currency.allowNegative- ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined- ? Number(currency.maxNegativeBalance).toLocaleString()- : "No cap")- : "Not applicable"}+ {currency.allowNegative+ ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined+ ? Number(currency.maxNegativeBalance).toLocaleString(undefined, {+ minimumFractionDigits: 2,+ maximumFractionDigits: 2,+ })+ : "No cap")+ : "Not applicable"}

Not required for correctness, but it keeps the UI consistent with the way balances and total supply are shown.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbefb26 and 28a6b7b.

📒 Files selected for processing (7)
  • platforms/eCurrency-api/src/controllers/CurrencyController.ts (7 hunks)
  • platforms/eCurrency-api/src/database/entities/Currency.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (1 hunks)
  • platforms/eCurrency-api/src/services/CurrencyService.ts (3 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (3 hunks)
  • platforms/eCurrency/client/src/pages/currency-detail.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)
platforms/eCurrency-api/src/database/migrations/1765039149572-migration.ts (1)
  • Migration1765039149572 (3-14)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/middleware/auth.ts (1)
  • authGuard (36-41)
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
⏰ 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 (8)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)

1-14: LGTM!

The migration correctly adds and removes the maxNegativeBalance column with the appropriate type (numeric(18,2)), matching the entity definition and following the established migration pattern in the codebase.

platforms/eCurrency-api/src/services/LedgerService.ts (2)

43-49: LGTM!

The existingBalance parameter is a sensible optimization that avoids a redundant database query when the caller has already fetched the balance for validation purposes.


103-108: Consider validating maxNegativeBalance is non-positive.

The current logic assumes maxNegativeBalance represents a floor (e.g., -100). If a positive value is accidentally set, the condition newBalance < maxNegativeBalance would never trigger for typical debits leading to negative balances. Consider adding validation in the controller/service to ensure maxNegativeBalance <= 0 when set.

platforms/eCurrency-api/src/index.ts (1)

115-115: LGTM!

The new PATCH endpoint follows the existing route patterns, uses appropriate HTTP semantics for a partial update, and is correctly protected with authGuard.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (2)

18-18: LGTM!

The maxNegativeBalance field is correctly extracted from the request body, passed to the service, and included in the response. The implementation follows the existing patterns for other currency fields.

Also applies to: 29-29, 40-40


174-185: LGTM!

The response structure is consistent with other currency endpoints, including all relevant fields.

platforms/eCurrency-api/src/services/CurrencyService.ts (1)

87-117: Max negative balance update flow and validation look solid

The new updateMaxNegativeBalance method correctly:

  • Verifies the currency exists and the requester is a group admin.
  • Disallows setting a cap when allowNegative is false.
  • Ensures the value is either null or a non‑NaN number that is <= 0.

This keeps the persisted state consistent with the business rules around negative balances.

platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

1-1: State and effect wiring for max negative balance looks consistent

The added React Query client usage, MAX_NEGATIVE_SLIDER constant, and maxNegative* state plus the useEffect that derives the absolute input from currency.maxNegativeBalance form a coherent flow and ensure the UI is initialized from the latest server value when the currency data loads or is refetched. No functional issues spotted here.

Also applies to: 18-18, 25-28, 56-64

Comment threadplatforms/eCurrency/client/src/pages/currency-detail.tsx
@kulmin
kulmin merged commit dd226ce into mainDec 15, 2025
4 checks passed
@coodos
coodos deleted the feat/burn-and-negative-control branch December 15, 2025 09:02
Sign up for freeto 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.

[feature] Ability to Burn money [feature] Implement Negative Limit [feature] Hashing and Timestamps

3 participants

@coodos@sosweetham@kulmin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: Add max negative caps and enforce ledger bounds - #618

Merged
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control
Dec 15, 2025
Merged

feat: Add max negative caps and enforce ledger bounds#618
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control

Conversation

@coodos

@coodoscoodos commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

closes#553
closes#554
closes#555

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added ability to set and manage maximum negative balance limits on currencies that allow negative balances.
    • Administrators can now configure and adjust balance floor thresholds through a new settings interface.
    • System validates transactions to prevent account balances from exceeding configured negative limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces maxNegativeBalance as a persistent field on currencies, enabling administrators to set limits on how far below zero account balances may fall. The feature spans database schema (new column via migration), API layer (new controller endpoint), business logic (validation in service and ledger), and client UI (management interface with save/clear actions).

Changes

Cohort / File(s)Summary
Database Schema
platforms/eCurrency-api/src/database/entities/Currency.ts, platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts
Adds maxNegativeBalance field to Currency entity as nullable decimal(18,2) column; introduces corresponding TypeORM migration for up/down schema changes.
API Layer
platforms/eCurrency-api/src/controllers/CurrencyController.ts, platforms/eCurrency-api/src/index.ts
Adds new updateMaxNegativeBalance controller method with auth checks and validation; registers PATCH endpoint /api/currencies/:id/max-negative.
Service Layer
platforms/eCurrency-api/src/services/CurrencyService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Extends createCurrency to accept optional maxNegativeBalance parameter; introduces updateMaxNegativeBalance service method with admin and validation checks; enhances transfer in LedgerService to validate balance limits and optimize balance queries.
Client UI
platforms/eCurrency/client/src/pages/currency-detail.tsx
Adds state management, input controls (range + numeric inputs), and save/clear functionality for max negative balance; integrates React Query cache invalidation post-update.

Sequence Diagram

sequenceDiagram
actor User as Administrator
participant UI as currency-detail.tsx
participant API as CurrencyController
participant Service as CurrencyService
participant Ledger as LedgerService
participant DB as Database
User->>UI: Set max negative balance value
UI->>API: PATCH /api/currencies/:id/max-negative {value}
API->>Service: updateMaxNegativeBalance(id, value, admin)
Note over Service: Validate:<br/>- Currency exists<br/>- Admin permission<br/>- allowNegative=true<br/>- Value is null or ≤0
Service->>DB: Update currency.maxNegativeBalance
DB-->>Service: Updated Currency
Service-->>API: Currency entity
API-->>UI: {id, maxNegativeBalance, ...}
UI->>UI: Invalidate cache<br/>(currency, accountDetails)
Note over User,Ledger: Later: User attempts transfer
User->>API: POST /transfer {amount, ...}
API->>Ledger: transfer(currencyId, amount, ...)
Note over Ledger: Compute balance change<br/>if (maxNegativeBalance set<br/>AND balance+amount < limit)<br/>throw error
alt Balance within limit
Ledger->>DB: addLedgerEntry(...)
DB-->>Ledger: Ledger entry
Ledger-->>API: Success
else Balance exceeds limit
Ledger-->>API: Insufficient funds error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • LedgerService modifications: Review the enhanced validation logic in transfer() to ensure maxNegativeBalance checks are correct and the new existingBalance parameter usage doesn't introduce balance-calculation discrepancies.
  • CurrencyService validation: Verify that updateMaxNegativeBalance enforces all required constraints (admin role, allowNegative check, numeric validation).
  • Client state synchronization: Confirm that cache invalidation strategy properly refreshes all dependent UI sections after updates.

Possibly related PRs

  • chore: move eCurrency #471: Directly related—this PR builds upon and extends the Currency, CurrencyController, CurrencyService, and migration patterns introduced there to add the new maxNegativeBalance management feature.

Suggested reviewers

  • sosweetham
  • xPathin
  • ananyayaya129

Poem

🐰 A hop, a skip, a balance to keep,
The burrow's new limit runs deep,
Max negative set with a careful paw,
No debt below this earned law,
Our ledgers now wiser, our wallets more fair! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR addresses issue #554 (negative limit) comprehensively with maxNegativeBalance implementation. However, no evidence of work on #553 (hashing/timestamps) or #555 (burn money functionality) is found in the code changes.Verify whether issues #553 and #555 were intended to be included in this PR. If so, add the missing functionality; otherwise, remove them from the PR description.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title directly describes the main feature: adding max negative balance caps and enforcing ledger bounds, which aligns with the primary changes across the codebase.
Description check✅ PassedThe description includes required sections: issue numbers, type of change, and change checklist. However, the 'How the change has been tested' section is missing content and no testing details are provided.
Out of Scope Changes check✅ PassedAll changes are directly related to implementing the max negative balance feature referenced in the PR title and issue #554. No unrelated or out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/burn-and-negative-control

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eCurrency-api/src/services/LedgerService.ts (1)

96-124: Wrap the transfer method in a database transaction to prevent concurrent balance validation violations.

There is a time-of-check to time-of-use (TOCTOU) gap between fetching currentBalance (line 96) and persisting ledger entries (lines 112-139). Concurrent transfers from the same account could both pass validation before either commits, potentially violating the maxNegativeBalance constraint.

Use AppDataSource.transaction() with proper isolation level (similar to the pattern in GroupService.ts), or implement row-level locking with FOR UPDATE to ensure balance validation and ledger creation are atomic.

🧹 Nitpick comments (4)
platforms/eCurrency-api/src/database/entities/Currency.ts (1)

39-40: TypeORM decimal columns return strings at runtime.

TypeORM's decimal type returns values as strings, not JavaScript numbers. The TypeScript type number | null is misleading and may cause subtle bugs when performing arithmetic or comparisons without explicit conversion.

Consider adjusting the type or using a transformer:

- @Column("decimal", { precision: 18, scale: 2, nullable: true })- maxNegativeBalance!: number | null;+ @Column("decimal", { precision: 18, scale: 2, nullable: true, transformer: {+ to: (value: number | null) => value,+ from: (value: string | null) => value === null ? null : parseFloat(value)+ }})+ maxNegativeBalance!: number | null;

Alternatively, update the type to string | null and convert explicitly at usage sites.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (1)

162-166: Consider adding semantic validation for maxNegativeBalance.

The current validation only checks that the value is a valid number (or null). Given the semantic meaning (a floor for how negative a balance can go), consider validating that non-null values are <= 0:

 // Allow null to clear; otherwise must be a number
const parsedValue = value === null || value === undefined ? null : Number(value);
if (parsedValue !== null && Number.isNaN(parsedValue)) {
return res.status(400).json({ error: "Invalid value for maxNegativeBalance" });
}
+ if (parsedValue !== null && parsedValue > 0) {+ return res.status(400).json({ error: "maxNegativeBalance must be zero or negative" });+ }
platforms/eCurrency-api/src/services/CurrencyService.ts (1)

20-47: Align createCurrency maxNegativeBalance invariants with update path

createCurrency now accepts and persists maxNegativeBalance but does not enforce the same constraints you added in updateMaxNegativeBalance (must only be set when allowNegative is true, must be null or <= 0, not NaN). That means callers can create a currency in a state that the update path would later reject (e.g. allowNegative = false with a non‑null cap, or a positive cap).

To keep invariants consistent and avoid surprising states, consider normalizing and validating here as well, e.g.:

 // Generate eName (UUID with @ prefix)
const ename = `@${uuidv4()}`;
+ const normalizedMaxNegativeBalance =+ allowNegative && maxNegativeBalance !== null ? maxNegativeBalance : null;++ if (normalizedMaxNegativeBalance !== null) {+ if (Number.isNaN(normalizedMaxNegativeBalance)) {+ throw new Error("Invalid max negative balance value");+ }+ if (normalizedMaxNegativeBalance > 0) {+ throw new Error("Max negative balance must be zero or negative");+ }+ }+
const currency = this.currencyRepository.create({
name,
description,
ename,
groupId,
createdBy,
allowNegative,
- maxNegativeBalance,+ maxNegativeBalance: normalizedMaxNegativeBalance,
});
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

292-301: Optional: Format maxNegativeBalance consistently with other monetary values

Here you display maxNegativeBalance with a bare toLocaleString(), while balance and total supply are formatted with two decimal places. For consistency and readability (especially since the DB column is decimal with scale), consider aligning the formatting:

- {currency.allowNegative- ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined- ? Number(currency.maxNegativeBalance).toLocaleString()- : "No cap")- : "Not applicable"}+ {currency.allowNegative+ ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined+ ? Number(currency.maxNegativeBalance).toLocaleString(undefined, {+ minimumFractionDigits: 2,+ maximumFractionDigits: 2,+ })+ : "No cap")+ : "Not applicable"}

Not required for correctness, but it keeps the UI consistent with the way balances and total supply are shown.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbefb26 and 28a6b7b.

📒 Files selected for processing (7)
  • platforms/eCurrency-api/src/controllers/CurrencyController.ts (7 hunks)
  • platforms/eCurrency-api/src/database/entities/Currency.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (1 hunks)
  • platforms/eCurrency-api/src/services/CurrencyService.ts (3 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (3 hunks)
  • platforms/eCurrency/client/src/pages/currency-detail.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)
platforms/eCurrency-api/src/database/migrations/1765039149572-migration.ts (1)
  • Migration1765039149572 (3-14)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/middleware/auth.ts (1)
  • authGuard (36-41)
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
⏰ 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 (8)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)

1-14: LGTM!

The migration correctly adds and removes the maxNegativeBalance column with the appropriate type (numeric(18,2)), matching the entity definition and following the established migration pattern in the codebase.

platforms/eCurrency-api/src/services/LedgerService.ts (2)

43-49: LGTM!

The existingBalance parameter is a sensible optimization that avoids a redundant database query when the caller has already fetched the balance for validation purposes.


103-108: Consider validating maxNegativeBalance is non-positive.

The current logic assumes maxNegativeBalance represents a floor (e.g., -100). If a positive value is accidentally set, the condition newBalance < maxNegativeBalance would never trigger for typical debits leading to negative balances. Consider adding validation in the controller/service to ensure maxNegativeBalance <= 0 when set.

platforms/eCurrency-api/src/index.ts (1)

115-115: LGTM!

The new PATCH endpoint follows the existing route patterns, uses appropriate HTTP semantics for a partial update, and is correctly protected with authGuard.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (2)

18-18: LGTM!

The maxNegativeBalance field is correctly extracted from the request body, passed to the service, and included in the response. The implementation follows the existing patterns for other currency fields.

Also applies to: 29-29, 40-40


174-185: LGTM!

The response structure is consistent with other currency endpoints, including all relevant fields.

platforms/eCurrency-api/src/services/CurrencyService.ts (1)

87-117: Max negative balance update flow and validation look solid

The new updateMaxNegativeBalance method correctly:

  • Verifies the currency exists and the requester is a group admin.
  • Disallows setting a cap when allowNegative is false.
  • Ensures the value is either null or a non‑NaN number that is <= 0.

This keeps the persisted state consistent with the business rules around negative balances.

platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

1-1: State and effect wiring for max negative balance looks consistent

The added React Query client usage, MAX_NEGATIVE_SLIDER constant, and maxNegative* state plus the useEffect that derives the absolute input from currency.maxNegativeBalance form a coherent flow and ensure the UI is initialized from the latest server value when the currency data loads or is refetched. No functional issues spotted here.

Also applies to: 18-18, 25-28, 56-64

Comment threadplatforms/eCurrency/client/src/pages/currency-detail.tsx
@kulmin
kulmin merged commit dd226ce into mainDec 15, 2025
4 checks passed
@coodos
coodos deleted the feat/burn-and-negative-control branch December 15, 2025 09:02
Sign up for freeto 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.

[feature] Ability to Burn money [feature] Implement Negative Limit [feature] Hashing and Timestamps

3 participants

@coodos@sosweetham@kulmin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: Add max negative caps and enforce ledger bounds - #618

Merged
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control
Dec 15, 2025
Merged

feat: Add max negative caps and enforce ledger bounds#618
kulmin merged 1 commit into
mainfrom
feat/burn-and-negative-control

Conversation

@coodos

@coodoscoodos commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Description of change

Issue Number

closes#553
closes#554
closes#555

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added ability to set and manage maximum negative balance limits on currencies that allow negative balances.
    • Administrators can now configure and adjust balance floor thresholds through a new settings interface.
    • System validates transactions to prevent account balances from exceeding configured negative limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces maxNegativeBalance as a persistent field on currencies, enabling administrators to set limits on how far below zero account balances may fall. The feature spans database schema (new column via migration), API layer (new controller endpoint), business logic (validation in service and ledger), and client UI (management interface with save/clear actions).

Changes

Cohort / File(s)Summary
Database Schema
platforms/eCurrency-api/src/database/entities/Currency.ts, platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts
Adds maxNegativeBalance field to Currency entity as nullable decimal(18,2) column; introduces corresponding TypeORM migration for up/down schema changes.
API Layer
platforms/eCurrency-api/src/controllers/CurrencyController.ts, platforms/eCurrency-api/src/index.ts
Adds new updateMaxNegativeBalance controller method with auth checks and validation; registers PATCH endpoint /api/currencies/:id/max-negative.
Service Layer
platforms/eCurrency-api/src/services/CurrencyService.ts, platforms/eCurrency-api/src/services/LedgerService.ts
Extends createCurrency to accept optional maxNegativeBalance parameter; introduces updateMaxNegativeBalance service method with admin and validation checks; enhances transfer in LedgerService to validate balance limits and optimize balance queries.
Client UI
platforms/eCurrency/client/src/pages/currency-detail.tsx
Adds state management, input controls (range + numeric inputs), and save/clear functionality for max negative balance; integrates React Query cache invalidation post-update.

Sequence Diagram

sequenceDiagram
actor User as Administrator
participant UI as currency-detail.tsx
participant API as CurrencyController
participant Service as CurrencyService
participant Ledger as LedgerService
participant DB as Database
User->>UI: Set max negative balance value
UI->>API: PATCH /api/currencies/:id/max-negative {value}
API->>Service: updateMaxNegativeBalance(id, value, admin)
Note over Service: Validate:<br/>- Currency exists<br/>- Admin permission<br/>- allowNegative=true<br/>- Value is null or ≤0
Service->>DB: Update currency.maxNegativeBalance
DB-->>Service: Updated Currency
Service-->>API: Currency entity
API-->>UI: {id, maxNegativeBalance, ...}
UI->>UI: Invalidate cache<br/>(currency, accountDetails)
Note over User,Ledger: Later: User attempts transfer
User->>API: POST /transfer {amount, ...}
API->>Ledger: transfer(currencyId, amount, ...)
Note over Ledger: Compute balance change<br/>if (maxNegativeBalance set<br/>AND balance+amount < limit)<br/>throw error
alt Balance within limit
Ledger->>DB: addLedgerEntry(...)
DB-->>Ledger: Ledger entry
Ledger-->>API: Success
else Balance exceeds limit
Ledger-->>API: Insufficient funds error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • LedgerService modifications: Review the enhanced validation logic in transfer() to ensure maxNegativeBalance checks are correct and the new existingBalance parameter usage doesn't introduce balance-calculation discrepancies.
  • CurrencyService validation: Verify that updateMaxNegativeBalance enforces all required constraints (admin role, allowNegative check, numeric validation).
  • Client state synchronization: Confirm that cache invalidation strategy properly refreshes all dependent UI sections after updates.

Possibly related PRs

  • chore: move eCurrency #471: Directly related—this PR builds upon and extends the Currency, CurrencyController, CurrencyService, and migration patterns introduced there to add the new maxNegativeBalance management feature.

Suggested reviewers

  • sosweetham
  • xPathin
  • ananyayaya129

Poem

🐰 A hop, a skip, a balance to keep,
The burrow's new limit runs deep,
Max negative set with a careful paw,
No debt below this earned law,
Our ledgers now wiser, our wallets more fair! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR addresses issue #554 (negative limit) comprehensively with maxNegativeBalance implementation. However, no evidence of work on #553 (hashing/timestamps) or #555 (burn money functionality) is found in the code changes.Verify whether issues #553 and #555 were intended to be included in this PR. If so, add the missing functionality; otherwise, remove them from the PR description.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title directly describes the main feature: adding max negative balance caps and enforcing ledger bounds, which aligns with the primary changes across the codebase.
Description check✅ PassedThe description includes required sections: issue numbers, type of change, and change checklist. However, the 'How the change has been tested' section is missing content and no testing details are provided.
Out of Scope Changes check✅ PassedAll changes are directly related to implementing the max negative balance feature referenced in the PR title and issue #554. No unrelated or out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/burn-and-negative-control

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eCurrency-api/src/services/LedgerService.ts (1)

96-124: Wrap the transfer method in a database transaction to prevent concurrent balance validation violations.

There is a time-of-check to time-of-use (TOCTOU) gap between fetching currentBalance (line 96) and persisting ledger entries (lines 112-139). Concurrent transfers from the same account could both pass validation before either commits, potentially violating the maxNegativeBalance constraint.

Use AppDataSource.transaction() with proper isolation level (similar to the pattern in GroupService.ts), or implement row-level locking with FOR UPDATE to ensure balance validation and ledger creation are atomic.

🧹 Nitpick comments (4)
platforms/eCurrency-api/src/database/entities/Currency.ts (1)

39-40: TypeORM decimal columns return strings at runtime.

TypeORM's decimal type returns values as strings, not JavaScript numbers. The TypeScript type number | null is misleading and may cause subtle bugs when performing arithmetic or comparisons without explicit conversion.

Consider adjusting the type or using a transformer:

- @Column("decimal", { precision: 18, scale: 2, nullable: true })- maxNegativeBalance!: number | null;+ @Column("decimal", { precision: 18, scale: 2, nullable: true, transformer: {+ to: (value: number | null) => value,+ from: (value: string | null) => value === null ? null : parseFloat(value)+ }})+ maxNegativeBalance!: number | null;

Alternatively, update the type to string | null and convert explicitly at usage sites.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (1)

162-166: Consider adding semantic validation for maxNegativeBalance.

The current validation only checks that the value is a valid number (or null). Given the semantic meaning (a floor for how negative a balance can go), consider validating that non-null values are <= 0:

 // Allow null to clear; otherwise must be a number
const parsedValue = value === null || value === undefined ? null : Number(value);
if (parsedValue !== null && Number.isNaN(parsedValue)) {
return res.status(400).json({ error: "Invalid value for maxNegativeBalance" });
}
+ if (parsedValue !== null && parsedValue > 0) {+ return res.status(400).json({ error: "maxNegativeBalance must be zero or negative" });+ }
platforms/eCurrency-api/src/services/CurrencyService.ts (1)

20-47: Align createCurrency maxNegativeBalance invariants with update path

createCurrency now accepts and persists maxNegativeBalance but does not enforce the same constraints you added in updateMaxNegativeBalance (must only be set when allowNegative is true, must be null or <= 0, not NaN). That means callers can create a currency in a state that the update path would later reject (e.g. allowNegative = false with a non‑null cap, or a positive cap).

To keep invariants consistent and avoid surprising states, consider normalizing and validating here as well, e.g.:

 // Generate eName (UUID with @ prefix)
const ename = `@${uuidv4()}`;
+ const normalizedMaxNegativeBalance =+ allowNegative && maxNegativeBalance !== null ? maxNegativeBalance : null;++ if (normalizedMaxNegativeBalance !== null) {+ if (Number.isNaN(normalizedMaxNegativeBalance)) {+ throw new Error("Invalid max negative balance value");+ }+ if (normalizedMaxNegativeBalance > 0) {+ throw new Error("Max negative balance must be zero or negative");+ }+ }+
const currency = this.currencyRepository.create({
name,
description,
ename,
groupId,
createdBy,
allowNegative,
- maxNegativeBalance,+ maxNegativeBalance: normalizedMaxNegativeBalance,
});
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

292-301: Optional: Format maxNegativeBalance consistently with other monetary values

Here you display maxNegativeBalance with a bare toLocaleString(), while balance and total supply are formatted with two decimal places. For consistency and readability (especially since the DB column is decimal with scale), consider aligning the formatting:

- {currency.allowNegative- ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined- ? Number(currency.maxNegativeBalance).toLocaleString()- : "No cap")- : "Not applicable"}+ {currency.allowNegative+ ? (currency.maxNegativeBalance !== null && currency.maxNegativeBalance !== undefined+ ? Number(currency.maxNegativeBalance).toLocaleString(undefined, {+ minimumFractionDigits: 2,+ maximumFractionDigits: 2,+ })+ : "No cap")+ : "Not applicable"}

Not required for correctness, but it keeps the UI consistent with the way balances and total supply are shown.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbefb26 and 28a6b7b.

📒 Files selected for processing (7)
  • platforms/eCurrency-api/src/controllers/CurrencyController.ts (7 hunks)
  • platforms/eCurrency-api/src/database/entities/Currency.ts (1 hunks)
  • platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1 hunks)
  • platforms/eCurrency-api/src/index.ts (1 hunks)
  • platforms/eCurrency-api/src/services/CurrencyService.ts (3 hunks)
  • platforms/eCurrency-api/src/services/LedgerService.ts (3 hunks)
  • platforms/eCurrency/client/src/pages/currency-detail.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)
platforms/eCurrency-api/src/database/migrations/1765039149572-migration.ts (1)
  • Migration1765039149572 (3-14)
platforms/eCurrency-api/src/index.ts (1)
platforms/eCurrency-api/src/middleware/auth.ts (1)
  • authGuard (36-41)
platforms/eCurrency/client/src/pages/currency-detail.tsx (1)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
⏰ 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 (8)
platforms/eCurrency-api/src/database/migrations/1765784749012-migration.ts (1)

1-14: LGTM!

The migration correctly adds and removes the maxNegativeBalance column with the appropriate type (numeric(18,2)), matching the entity definition and following the established migration pattern in the codebase.

platforms/eCurrency-api/src/services/LedgerService.ts (2)

43-49: LGTM!

The existingBalance parameter is a sensible optimization that avoids a redundant database query when the caller has already fetched the balance for validation purposes.


103-108: Consider validating maxNegativeBalance is non-positive.

The current logic assumes maxNegativeBalance represents a floor (e.g., -100). If a positive value is accidentally set, the condition newBalance < maxNegativeBalance would never trigger for typical debits leading to negative balances. Consider adding validation in the controller/service to ensure maxNegativeBalance <= 0 when set.

platforms/eCurrency-api/src/index.ts (1)

115-115: LGTM!

The new PATCH endpoint follows the existing route patterns, uses appropriate HTTP semantics for a partial update, and is correctly protected with authGuard.

platforms/eCurrency-api/src/controllers/CurrencyController.ts (2)

18-18: LGTM!

The maxNegativeBalance field is correctly extracted from the request body, passed to the service, and included in the response. The implementation follows the existing patterns for other currency fields.

Also applies to: 29-29, 40-40


174-185: LGTM!

The response structure is consistent with other currency endpoints, including all relevant fields.

platforms/eCurrency-api/src/services/CurrencyService.ts (1)

87-117: Max negative balance update flow and validation look solid

The new updateMaxNegativeBalance method correctly:

  • Verifies the currency exists and the requester is a group admin.
  • Disallows setting a cap when allowNegative is false.
  • Ensures the value is either null or a non‑NaN number that is <= 0.

This keeps the persisted state consistent with the business rules around negative balances.

platforms/eCurrency/client/src/pages/currency-detail.tsx (1)

1-1: State and effect wiring for max negative balance looks consistent

The added React Query client usage, MAX_NEGATIVE_SLIDER constant, and maxNegative* state plus the useEffect that derives the absolute input from currency.maxNegativeBalance form a coherent flow and ensure the UI is initialized from the latest server value when the currency data loads or is refetched. No functional issues spotted here.

Also applies to: 18-18, 25-28, 56-64

Comment threadplatforms/eCurrency/client/src/pages/currency-detail.tsx
@kulmin
kulmin merged commit dd226ce into mainDec 15, 2025
4 checks passed
@coodos
coodos deleted the feat/burn-and-negative-control branch December 15, 2025 09:02
Sign up for freeto 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.

[feature] Ability to Burn money [feature] Implement Negative Limit [feature] Hashing and Timestamps

3 participants

@coodos@sosweetham@kulmin