Skip to content

Document transaction print#7715

Merged
munkhsaikhan merged 8 commits into
mainfrom
document-transaction-print
May 19, 2026
Merged

Document transaction print#7715
munkhsaikhan merged 8 commits into
mainfrom
document-transaction-print

Conversation

@Khuslen122
Copy link
Copy Markdown
Collaborator

@Khuslen122 Khuslen122 commented May 19, 2026

Summary by CodeRabbit

  • New Features

    • Many new printable transaction templates and layout variants (cash, inventory move/out/sale/return, invoice, bank, transaction journal).
    • Mongolian amount-in-words conversion for printed documents.
    • Variant-aware print body selection and per-journal layout options.
  • Improvements

    • Print page: A4-focused wrapper, sticky toolbar, layout selector when multiple variants, and secondary print button.
    • Print action now opens documents in preview mode.
  • Bug Fixes

    • Improved numeric formatting, totals and signature/summary rendering in receipts.

Review Change Stack

…on-print

# Conflicts:
#	frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/TransactionTabs.tsx
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Sorry @Khuslen122, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c363c3c6-c2a3-4687-b6b0-2b65e17fb183

📥 Commits

Reviewing files that changed from the base of the PR and between 132c35a and 8e338ba.

📒 Files selected for processing (8)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/cash.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx
  • frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts
  • frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/cash.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx

📝 Walkthrough

Walkthrough

Adds a complete transaction printing subsystem: Mongolian number-to-words, shared print helpers, per-journal printable components with selectable variants, GraphQL detail expansion for product info, routing/registry updates, and a print page with variant selector and preview integration.

Changes

Transaction Printing System

Layer / File(s) Summary
Mongolian amount conversion utility
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts
Implements Mongolian currency text conversion with integer/fractional handling and negative-prefix support.
Document variant registry and GraphQL detail
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts, frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/graphql/queries/accTransactionDetail.ts
Centralizes per-journal printable layout variants and type-casting helpers; expands transaction detail query to include details.product fields.
Shared print helpers and wrappers
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx
Formatting helpers, receipt row builder, metadata extraction, row padding, sum utilities, FormHeader/SignLine, and A4/Twin sheet wrappers.
Generic transaction document template
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx
Base document for MAIN/PAYABLE journals with ProductTable (inventory) and AccountTable (monetary), totals, metadata, and signatures.
Monetary document variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/cash.tsx, .../bank.tsx
Cash receipts with four variants and bank transfer document with formatted amount, amount-in-words, header metadata, and signature areas.
Inventory intake documents
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx, invMove.tsx
Inventory income receipts (twin/simple/discount/numbered) and internal-move documents (standard/byPrice) with padded rows and totals.
Inventory distribution documents
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx, invSale.tsx, invSaleReturn.tsx
Out receipts, sales (twin/location/discount/numbered), and sales-return documents; normalized rows, VAT/discount handling, and signature blocks.
Invoice document variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx
Invoice templates with payer/responsible variants, itemized rows, VAT/subtotal/total, amount-in-words, and optional stamp.
Print routing and component registry
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx, contants/printDocuments.tsx
PrintBody refactored to explicit per-journal branches with variant normalization; PRINT_DOCUMENTS mapping updated to concrete Print*Document components.
Print page variant UI
frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx, transaction-form/components/TransactionTabs.tsx
TransactionPrintPage adds variant selection and sticky print toolbar; TransactionTabs opens print preview URL with inPreview=true.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • erxes/erxes#7699: The main PR’s frontend TransactionTabs.tsx changes to detect permission === 'hidden' and suppress rendering/printing directly depend on the backend transaction permission/hiding logic updated in the retrieved PR (fix(accounting): check perm on transactions).
  • erxes/erxes#7658: Backend change that adds/populates ptrNumber, used as a fallback document number in the new print templates.

"Би туулай, үсрэн очин хэлнэ,
Баримт хэвлэгдэн ширтэнэ.
Мөнгө үгсийг тоолон хэллээ,
Хоёр хуудсан дээр талзаж нэгдлээ.
Миний морь баярлан туйлна — хэвлэлт бэлэн!" 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Document transaction print' is vague and generic, using non-descriptive terms that don't convey meaningful information about the extensive changeset, which adds comprehensive print document components and layouts. Consider a more specific title that captures the main scope, such as 'Add transaction print document components and layouts' or 'Implement multi-variant transaction document rendering system'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch document-transaction-print

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.

@github-actions
Copy link
Copy Markdown

🌗 Pull Request Overview

This PR implements comprehensive document printing functionality for the accounting module, adding support for various Mongolian financial document formats including invoices, cash receipts, bank payment orders, and inventory transaction forms. It introduces a flexible variant system allowing users to select different layouts per journal type, and adds a utility for converting amounts to Mongolian text.

Reviewed Changes
Kimi performed full review on 15 changed files and found 5 issues.

Show a summary per file
File Description
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/TransactionTabs.tsx Added inPreview=true query parameter to transaction print URL
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx New generic transaction document component with inventory/monetary table rendering
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/bank.tsx Refactored bank payment order document with improved layout and amount-to-words display
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/cash.tsx New cash receipt document with 4 layout variants (twin-table, lined, dotted, twin-dotted)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx Updated print document registry with variant routing logic
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx New inventory income document with 4 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx New inventory movement document with 2 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx New inventory out (supply expense) document with twin receipt layout
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx New inventory sale document with 4 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx New sales return document with VAT support
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx New invoice document (НХМаягт Т1) with payer/responsible variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts New Mongolian number-to-words conversion utility
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts New document variant definitions and type guards
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/contants/printDocuments.tsx Updated mapping of journal types to document components
frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx Major refactoring with variant selection UI and improved print layout

📋 Review Findings

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx

🟡 MEDIUM bug: Missing key prop stability for inventory rows

Line 67-85

Using d._id || idx as the key can cause rendering issues when _id is undefined for some rows but defined for others. The key can shift from index-based to ID-based mid-render.

💡 Suggested fix:

// Use a stable key - if _id might be undefined, use index consistently
// or ensure _id is always present for array items
{rows.map((d, idx) => {
  const lineAmount = d.amount ?? (d.count ?? 0) * (d.unitPrice ?? 0);
  const rowKey = d._id ? `${d._id}-${idx}` : `row-${idx}`;
  return (
    <tr key={rowKey}>
      {/* ... */}
    </tr>
  );
})}

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts

🔵 LOW performance: Potential precision loss with large numbers

Line 128-133

The Math.abs(amount) and Math.round() operations can lose precision for very large numbers (greater than Number.MAX_SAFE_INTEGER / 9,007,199,254,740,991).

💡 Suggested fix:

export const amountToMongolianText = (amount: number): string => {
  // Guard against unsafe integer values
  if (!Number.isFinite(amount) || Math.abs(amount) > Number.MAX_SAFE_INTEGER) {
    return 'Хэт их дүн';
  }
  
  const safeAmount = Math.abs(amount);
  const tugrug = Math.floor(safeAmount);
  // Use Math.round with caution for decimal precision
  const mongo = Math.min(99, Math.round((safeAmount - tugrug) * 100));

  const sign = amount < 0 ? 'хасах ' : '';
  const tugrugText = `${integerToMongolianText(tugrug)} төгрөг`;

  if (mongo > 0) {
    return `${sign}${tugrugText} ${integerToMongolianText(mongo)} мөнгө`;
  }

  return `${sign}${tugrugText}`;
};

📄 frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx

🟡 MEDIUM bug: Auto-print triggers on every transaction change

Line 35-42

The auto-print effect only guards against multiple prints with the same transaction, but will re-trigger if the transaction object reference changes (even with identical data). This could cause unwanted prints.

💡 Suggested fix:

// Track the transaction ID, not the object reference
const printedIdRef = useRef<string | null>(null);

useEffect(() => {
  if (!loading && transaction && printedIdRef.current !== transaction._id) {
    printedIdRef.current = transaction._id;
    const timer = setTimeout(() => {
      window.print();
    }, 300);
    return () => clearTimeout(timer);
  }
}, [loading, transaction?._id]); // Use stable ID reference

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx

🔵 LOW bug: Inconsistent row key usage in filled array

Line 108-138

Similar to TransactionDocument.tsx, using array index as key when rows are filled with null placeholders can cause key collisions if the transaction data changes.

💡 Suggested fix:

{filled.map((row, idx) => (
  <tr key={`sale-row-${idx}`}>
    {/* ... */}
  </tr>
))}

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx

🟡 MEDIUM maintainability: Missing default case for unknown journal types

Line 45-69

The function handles specific journal types explicitly but falls through to a fallback. If a new journal type is added without updating this switch, it may silently fail with the generic "Баримт олдсонгүй" message.

💡 Suggested fix:

export const PrintBody = ({
  transaction,
  variant,
}: {
  transaction: ITransaction;
  variant?: string;
}) => {
  const { journal } = transaction;
  
  switch (journal) {
    case TrJournalEnum.RECEIVABLE:
      return (
        <PrintInvoiceDocument
          transaction={transaction}
          variant={variant === 'responsible' ? 'responsible' : 'payer'}
        />
      );
    // ... other cases ...
    
    default: {
      // Explicit fallback with potential warning in dev mode
      const Component = PRINT_DOCUMENTS[journal];
      if (!Component) {
        if (process.env.NODE_ENV === 'development') {
          console.warn(`No print component registered for journal: ${journal}`);
        }
        return <div className="p-10 text-red-500">Баримт олдсонгүй.</div>;
      }
      return <Component transaction={transaction} />;
    }
  }
};

✅ Summary

Overall this is a well-structured PR that adds significant functionality for Mongolian accounting document printing. The code quality is good with proper TypeScript typing and consistent styling patterns. The main issues identified are:

  1. Potential key instability in table row rendering (medium priority)
  2. Number precision edge case in the amount-to-words utility (low priority)
  3. Auto-print logic could be more robust against reference changes (medium priority)
  4. Missing default case handling could be improved for maintainability

No critical security or logic errors were found. The document variant system is cleanly implemented and the Mongolian number conversion utility appears mathematically sound for typical financial amounts.


Powered by Kimi | Model: kimi-k2.5

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx (1)

14-15: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor inPreview=true before auto-printing.

TransactionTabs appends inPreview=true, but this page ignores it and always triggers window.print(). That breaks preview-first flow and forces the dialog before layout selection.

🐛 Proposed fix
   const query = new URLSearchParams(useLocation().search);
   const transactionId = query.get('_id');
+  const inPreview = query.get('inPreview') === 'true';
@@
   useEffect(() => {
-    if (!loading && transaction && !hasPrintedRef.current) {
+    if (!loading && transaction && !inPreview && !hasPrintedRef.current) {
       hasPrintedRef.current = true;
@@
     }
-  }, [loading, transaction]);
+  }, [loading, transaction, inPreview]);

Also applies to: 37-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx` around
lines 14 - 15, The page always calls window.print() regardless of the inPreview
flag; update the auto-print logic in TransactionPrintPage to read the location
query (useLocation()/URLSearchParams) and skip calling window.print() when
query.get('inPreview') (or 'inPreview') is truthy/equals 'true' so the preview
flow from TransactionTabs is honored; locate the code that derives transactionId
from query and the place where window.print() is invoked (and the similar block
at lines ~37-45) and wrap/guard those print calls with a check that inPreview
!== 'true' before triggering window.print().
🧹 Nitpick comments (5)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts (1)

62-63: ⚡ Quick win

Use function declarations for pure utilities in this file

The pure helpers/formatter are declared as arrow-function constants; this file’s TS guideline requires function keyword for pure functions.

♻️ Proposed refactor
-const belowThousand = (num: number, attr: boolean): string => {
+function belowThousand(num: number, attr: boolean): string {
   const parts: string[] = [];
   ...
-};
+}

-const integerToMongolianText = (value: number): string => {
+function integerToMongolianText(value: number): string {
   ...
-};
+}

-export const amountToMongolianText = (amount: number): string => {
+export function amountToMongolianText(amount: number): string {
   ...
-};
+}
As per coding guidelines, **"Use the 'function' keyword for pure functions"**.

Also applies to: 88-89, 117-117

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts`
around lines 62 - 63, Replace pure helper arrow-function constants in this
module with function declarations: change the const belowThousand = (num:
number, attr: boolean): string => { ... } to a function belowThousand(num:
number, attr: boolean): string { ... } and do the same for the other pure helper
constants in this file (the two remaining arrow-function helpers declared
later). Ensure signatures, exported names (if any), and internal behavior remain
identical while switching to the function keyword to follow the TS guideline.
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts (1)

3-6: ⚡ Quick win

Use path aliases instead of relative imports

Switch these local relative imports to the project’s absolute alias style for consistency and easier refactors.
As per coding guidelines, “Always use absolute paths when importing” and “Use path aliases for imports: ~/* for service root, @/* for modules directory, and erxes-api-shared/* for shared library in backend services”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts`
around lines 3 - 6, The imports for CashVariant, InvIncomeVariant,
InvMoveVariant and InvSaleVariant use relative paths; change them to the
project's absolute alias style (e.g., use `@/`* for modules) so they follow the
coding guideline "Always use absolute paths when importing." Update the four
import statements to use the module alias (for example replacing './cash' etc.
with the appropriate `@/`... alias for this module) ensuring each symbol
(CashVariant, InvIncomeVariant, InvMoveVariant, InvSaleVariant) still resolves
correctly and the project build/tsconfig path mappings remain satisfied.
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx (1)

5-5: ⚡ Quick win

Align imports and prop typing with project TS/TSX conventions

Use absolute-path imports and extract prop shapes into named interfaces (with I prefix) instead of inline object types for components/helpers.
As per coding guidelines, “Always use absolute paths when importing”, “Use path aliases for imports: ~/* for service root, @/* for modules directory, and erxes-api-shared/* for shared library in backend services”, and “Use TypeScript interface naming convention: prefix interfaces with I (e.g., IUser, IUserDetails)”.

Also applies to: 30-38, 167-171

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx`
at line 5, Update the import and prop typing to follow project TS conventions:
change the relative import "import { amountToMongolianText } from
'./numberToWords';" to the absolute/path-alias variant (e.g., import from
"`@/modules/transactions/transaction-form/components/documents/numberToWords`" or
the project's ~/@ alias as appropriate) and extract any inline prop types used
by the TransactionDocument component into a named interface with an I prefix
(for example declare interface ITransactionDocumentProps { /* fields previously
inline */ } and use it on the TransactionDocument function/component). Also
create named interface(s) for any helper argument shapes returned/consumed by
amountToMongolianText (e.g., IAmountToMongolianArgs) and update references to
use those interfaces; then replace inline object types in the component and
helper signatures with these I-prefixed interfaces.
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx (2)

16-23: ⚡ Quick win

Use an interface for PrintBody props (IPrintBodyProps) instead of an inline type literal.

This keeps prop contracts reusable and aligned with the project TS conventions.

♻️ Proposed refactor
+interface IPrintBodyProps {
+  transaction: ITransaction
+  variant?: string
+}
+
 export const PrintBody = ({
   transaction,
   variant,
-}: {
-  transaction: ITransaction;
-  // Selected layout for journals that offer several — see DOCUMENT_VARIANTS.
-  variant?: string;
-}) => {
+}: IPrintBodyProps) => {

As per coding guidelines, "Use TypeScript for all code; prefer interfaces over types" and "Use TypeScript interface naming convention: prefix interfaces with I (e.g., IUser, IUserDetails)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx`
around lines 16 - 23, Replace the inline prop type for the PrintBody component
with a named interface: create an interface IPrintBodyProps that declares
transaction: ITransaction and optional variant?: string (add a brief comment if
desired referencing DOCUMENT_VARIANTS), then update the PrintBody signature to
accept props: IPrintBodyProps; ensure any existing usages or imports still
compile and export the new interface if it should be reused elsewhere.

33-37: ⚡ Quick win

Avoid duplicating default variant literals in the dispatcher.

Defaults are hardcoded here while variant defaults are also managed in the variants registry flow. Centralizing on one default source avoids drift and inconsistent first render behavior when variant is empty.

♻️ Proposed refactor
 import {
   asCashVariant,
   asInvIncomeVariant,
   asInvMoveVariant,
   asInvSaleVariant,
+  getDefaultVariant,
 } from './variants';

 export const PrintBody = ({
   transaction,
   variant,
 }: IPrintBodyProps) => {
+  const fallbackVariant = getDefaultVariant(transaction.journal)
+  const selectedVariant = variant || fallbackVariant
+
   if (transaction.journal === TrJournalEnum.CASH) {
     return (
       <PrintCashDocument
         transaction={transaction}
-        variant={asCashVariant(variant || 'lined')}
+        variant={asCashVariant(selectedVariant)}
       />
     );
   }
@@
   if (transaction.journal === TrJournalEnum.INV_MOVE) {
     return (
       <PrintInvMoveDocument
         transaction={transaction}
-        variant={asInvMoveVariant(variant || 'standard')}
+        variant={asInvMoveVariant(selectedVariant)}
       />
     );
   }
@@
   if (transaction.journal === TrJournalEnum.INV_SALE) {
     return (
       <PrintInvSaleDocument
         transaction={transaction}
-        variant={asInvSaleVariant(variant || 'numbered')}
+        variant={asInvSaleVariant(selectedVariant)}
       />
     );
   }
@@
   if (transaction.journal === TrJournalEnum.INV_INCOME) {
     return (
       <PrintInvIncomeDocument
         transaction={transaction}
-        variant={asInvIncomeVariant(variant || 'numbered')}
+        variant={asInvIncomeVariant(selectedVariant)}
       />
     );
   }

Also applies to: 42-46, 51-55, 60-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx`
around lines 33 - 37, The component is duplicating the default variant literal
('lined') when calling the variant coercion helpers, which conflicts with the
central variants registry defaults; remove the local fallback and pass the raw
variant through so the registry-provided default is used. Concretely, update the
branches that render PrintCashDocument, PrintCardDocument, PrintChequeDocument,
PrintTransferDocument (identify by transaction.journal checks and components
PrintCashDocument/PrintCardDocument/PrintChequeDocument/PrintTransferDocument)
to call asCashVariant/asCardVariant/asChequeVariant/asTransferVariant with the
original variant value (variant) instead of using variant || 'lined' (and
analogous literal fallbacks). Ensure no other local hardcoded default literals
remain in those render calls so the variants registry flow controls the default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`:
- Around line 81-83: The header fields in invIncome.tsx are using static
placeholders instead of the transaction data; update the JSX that renders
"Огноо", "Хэнээс(хаанаас)" and "Утга" to bind to the transaction object (e.g.,
use transaction.date formatted for the date, transaction.party or
transaction.from for the sender, and transaction.description or transaction.note
for the text) with safe fallbacks to the existing placeholder text; apply the
same change to the other occurrences referenced (the blocks around the other
line ranges) so each header displays transaction.date, transaction.from/party
and transaction.description rather than hardcoded placeholders.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx`:
- Line 31: The toAccount field in invMove.tsx is being hardcoded to an empty
string (toAccount: '') so the “Дансанд” column never displays; update the places
that set toAccount (the initial object at the top and the similar block around
lines 127-129) to populate it from the actual source on the movement/line item
(e.g., use the movement/toAccount or item.toAccount property used elsewhere in
this component/context) instead of ''. Locate the object literal(s) where
toAccount is defined and replace the empty string with the appropriate value
from the incoming props/state (for example props.movement.toAccount or
currentRow.toAccount) so the UI can render the real destination account.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx`:
- Around line 124-126: The invoice view currently renders empty placeholders for
phone and email; update the JSX that renders the "Утас, факс:" and "Э-шуудан:"
lines to output the computed values (use the existing variables
counterpartyPhone and counterpartyEmail) instead of the hardcoded empty
strings—e.g., replace the empty-string expressions with the variables (falling
back to an empty string if null/undefined) so printed invoices include contact
details.
- Around line 83-89: The counterpartyName expression currently returns
transaction.customer?.firstName before attempting the full name, so lastName is
never used; update the logic around the counterpartyName variable to construct
and prefer the full name (e.g., join firstName and lastName when either exists)
then fall back to transaction.customer?.firstName, then
transaction.customer?.code, then ''. Locate and modify the counterpartyName
assignment in invoice.tsx to build an array [transaction.customer?.firstName,
transaction.customer?.lastName].filter(Boolean).join(' ') first (if non-empty),
otherwise use the single-field fallbacks.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx`:
- Line 71: Replace the misspelled form code in the FormHeader call inside
invOut.tsx (the line using FormHeader code="НМХмаяг МХ1") with the correct form
code string; update the code prop on FormHeader (component FormHeader) to the
proper text "НМХ маяг МХ1" so the printed header matches the official document
formatting.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`:
- Around line 92-94: The rendered UI shows only static labels instead of the
computed values; update the JSX to output the computed variables partyName and
description (e.g., replace the static label divs "Хэнд(хаана):" and "Утга:" with
divs that render {partyName} and {description}) and do the same for the other
occurrences where the same labels are printed (the other label blocks in this
component). Ensure you safely render them (e.g., {partyName || ''}, {description
|| ''}) so missing metadata doesn't break rendering.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts`:
- Around line 120-122: The current split computes tugrug and mongo from
safeAmount separately and can yield mongo === 100 for values like 1.999; change
the logic to round total cents first (e.g., totalCents = Math.round(safeAmount *
100)) and then derive tugrug = Math.floor(totalCents / 100) and mongo =
totalCents % 100 so mongo is always 0..99; update the variables in
numberToWords.ts accordingly where tugrug, mongo and safeAmount are used.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx`:
- Around line 173-183: For inventory journals the totalAmount currently sums all
transaction.details but ProductTable only renders product rows, causing
mismatch; update the isInventory branch that computes totalAmount (and the
similar logic at the other occurrence) to first filter details to the same
subset ProductTable renders (e.g., details.filter(d => d.productId || d.type ===
'product' or whatever predicate ProductTable uses such as presence of
count/unitPrice) and then reduce over that filtered array (summing d.amount ??
(d.count ?? 0) * (d.unitPrice ?? 0)) so the printed total and amount-in-words
match the visible ProductTable rows.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts`:
- Around line 57-67: The helpers asCashVariant, asInvMoveVariant,
asInvSaleVariant and asInvIncomeVariant currently cast any string to the variant
types unsafely; update each to validate the input string against
DOCUMENT_VARIANTS (or a per-journal allowlist) before narrowing and return the
journal default when validation fails; specifically, replace the direct
assertions with a check like "if DOCUMENT_VARIANTS.includes(variant) (or
allowlist.has(variant)) return variant as <Type> else return journalDefault" so
only allowed values are narrowed and invalid strings fall back to the default.

---

Outside diff comments:
In `@frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx`:
- Around line 14-15: The page always calls window.print() regardless of the
inPreview flag; update the auto-print logic in TransactionPrintPage to read the
location query (useLocation()/URLSearchParams) and skip calling window.print()
when query.get('inPreview') (or 'inPreview') is truthy/equals 'true' so the
preview flow from TransactionTabs is honored; locate the code that derives
transactionId from query and the place where window.print() is invoked (and the
similar block at lines ~37-45) and wrap/guard those print calls with a check
that inPreview !== 'true' before triggering window.print().

---

Nitpick comments:
In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx`:
- Around line 16-23: Replace the inline prop type for the PrintBody component
with a named interface: create an interface IPrintBodyProps that declares
transaction: ITransaction and optional variant?: string (add a brief comment if
desired referencing DOCUMENT_VARIANTS), then update the PrintBody signature to
accept props: IPrintBodyProps; ensure any existing usages or imports still
compile and export the new interface if it should be reused elsewhere.
- Around line 33-37: The component is duplicating the default variant literal
('lined') when calling the variant coercion helpers, which conflicts with the
central variants registry defaults; remove the local fallback and pass the raw
variant through so the registry-provided default is used. Concretely, update the
branches that render PrintCashDocument, PrintCardDocument, PrintChequeDocument,
PrintTransferDocument (identify by transaction.journal checks and components
PrintCashDocument/PrintCardDocument/PrintChequeDocument/PrintTransferDocument)
to call asCashVariant/asCardVariant/asChequeVariant/asTransferVariant with the
original variant value (variant) instead of using variant || 'lined' (and
analogous literal fallbacks). Ensure no other local hardcoded default literals
remain in those render calls so the variants registry flow controls the default.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts`:
- Around line 62-63: Replace pure helper arrow-function constants in this module
with function declarations: change the const belowThousand = (num: number, attr:
boolean): string => { ... } to a function belowThousand(num: number, attr:
boolean): string { ... } and do the same for the other pure helper constants in
this file (the two remaining arrow-function helpers declared later). Ensure
signatures, exported names (if any), and internal behavior remain identical
while switching to the function keyword to follow the TS guideline.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx`:
- Line 5: Update the import and prop typing to follow project TS conventions:
change the relative import "import { amountToMongolianText } from
'./numberToWords';" to the absolute/path-alias variant (e.g., import from
"`@/modules/transactions/transaction-form/components/documents/numberToWords`" or
the project's ~/@ alias as appropriate) and extract any inline prop types used
by the TransactionDocument component into a named interface with an I prefix
(for example declare interface ITransactionDocumentProps { /* fields previously
inline */ } and use it on the TransactionDocument function/component). Also
create named interface(s) for any helper argument shapes returned/consumed by
amountToMongolianText (e.g., IAmountToMongolianArgs) and update references to
use those interfaces; then replace inline object types in the component and
helper signatures with these I-prefixed interfaces.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts`:
- Around line 3-6: The imports for CashVariant, InvIncomeVariant, InvMoveVariant
and InvSaleVariant use relative paths; change them to the project's absolute
alias style (e.g., use `@/`* for modules) so they follow the coding guideline
"Always use absolute paths when importing." Update the four import statements to
use the module alias (for example replacing './cash' etc. with the appropriate
`@/`... alias for this module) ensuring each symbol (CashVariant,
InvIncomeVariant, InvMoveVariant, InvSaleVariant) still resolves correctly and
the project build/tsconfig path mappings remain satisfied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 879705fb-da44-4417-84e5-9ab6a5dd22af

📥 Commits

Reviewing files that changed from the base of the PR and between 0876bae and fcfdbbe.

📒 Files selected for processing (16)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/TransactionTabs.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/bank.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/cash.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/contants/printDocuments.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/graphql/queries/accTransactionDetail.ts
  • frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx

from: d.branch?.title || '',
fromAccount: d.account?.code || '',
to: d.department?.title || '',
toAccount: '',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Populate destination account instead of hardcoding blank values.

toAccount is always '', so the “Дансанд” column can never show data.

Proposed fix
- toAccount: '',
+ toAccount:
+   d?.extra?.toAccount ||
+   transaction?.extraData?.toAccount ||
+   '',

Also applies to: 127-129

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx`
at line 31, The toAccount field in invMove.tsx is being hardcoded to an empty
string (toAccount: '') so the “Дансанд” column never displays; update the places
that set toAccount (the initial object at the top and the similar block around
lines 127-129) to populate it from the actual source on the movement/line item
(e.g., use the movement/toAccount or item.toAccount property used elsewhere in
this component/context) instead of ''. Locate the object literal(s) where
toAccount is defined and replace the empty string with the appropriate value
from the incoming props/state (for example props.movement.toAccount or
currentRow.toAccount) so the UI can render the real destination account.

Comment on lines +83 to +89
const counterpartyName =
transaction.customer?.firstName ||
[transaction.customer?.firstName, transaction.customer?.lastName]
.filter(Boolean)
.join(' ') ||
transaction.customer?.code ||
'';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix counterparty full-name fallback order.

Current logic returns firstName before attempting full name, so lastName is never included when firstName exists.

Proposed fix
- const counterpartyName =
-   transaction.customer?.firstName ||
-   [transaction.customer?.firstName, transaction.customer?.lastName]
-     .filter(Boolean)
-     .join(' ') ||
-   transaction.customer?.code ||
-   '';
+ const fullName = [transaction.customer?.firstName, transaction.customer?.lastName]
+   .filter(Boolean)
+   .join(' ')
+ const counterpartyName = fullName || transaction.customer?.code || ''
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx`
around lines 83 - 89, The counterpartyName expression currently returns
transaction.customer?.firstName before attempting the full name, so lastName is
never used; update the logic around the counterpartyName variable to construct
and prefer the full name (e.g., join firstName and lastName when either exists)
then fall back to transaction.customer?.firstName, then
transaction.customer?.code, then ''. Locate and modify the counterpartyName
assignment in invoice.tsx to build an array [transaction.customer?.firstName,
transaction.customer?.lastName].filter(Boolean).join(' ') first (if non-empty),
otherwise use the single-field fallbacks.


return (
<div className="flex-1">
<FormHeader code="НМХмаяг МХ1" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Correct form code typo in header label.

The printed form code text has a typo (НМХмаяг МХ1), which can invalidate/undermine official document formatting.

Proposed fix
- <FormHeader code="НМХмаяг МХ1" />
+ <FormHeader code="НХМаягт МХ1" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<FormHeader code="НМХмаяг МХ1" />
<FormHeader code="НХМаягт МХ1" />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx`
at line 71, Replace the misspelled form code in the FormHeader call inside
invOut.tsx (the line using FormHeader code="НМХмаяг МХ1") with the correct form
code string; update the code prop on FormHeader (component FormHeader) to the
proper text "НМХ маяг МХ1" so the printed header matches the official document
formatting.

Comment on lines +120 to +122
const tugrug = Math.floor(safeAmount);
const mongo = Math.round((safeAmount - tugrug) * 100);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle cent overflow after rounding (mongo === 100)

The current split can output invalid text like “100 мөнгө” for values such as 1.999. Round total cents first, then derive integer/fractional parts from that rounded value.

💡 Proposed fix
-  const tugrug = Math.floor(safeAmount);
-  const mongo = Math.round((safeAmount - tugrug) * 100);
+  const totalMongo = Math.round(safeAmount * 100)
+  const tugrug = Math.floor(totalMongo / 100)
+  const mongo = totalMongo % 100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts`
around lines 120 - 122, The current split computes tugrug and mongo from
safeAmount separately and can yield mongo === 100 for values like 1.999; change
the logic to round total cents first (e.g., totalCents = Math.round(safeAmount *
100)) and then derive tugrug = Math.floor(totalCents / 100) and mongo =
totalCents % 100 so mongo is always 0..99; update the variables in
numberToWords.ts accordingly where tugrug, mongo and safeAmount are used.

Comment on lines +173 to +183
const details = transaction?.details || [];
const isInventory = INVENTORY_JOURNALS.has(journal);

const title = DOCUMENT_TITLES[journal] || 'Гүйлгээний баримт';

const totalAmount = isInventory
? details.reduce(
(sum, d) => sum + (d.amount ?? (d.count ?? 0) * (d.unitPrice ?? 0)),
0,
)
: details.reduce((sum, d) => sum + (d.currencyAmount ?? d.amount ?? 0), 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep inventory total source consistent with rendered rows

For inventory journals, totalAmount is computed from all details, but ProductTable renders only product rows. This can print a total/amount-in-words that doesn’t match the visible table.

Suggested fix
-  const details = transaction?.details || [];
+  const details = transaction?.details || [];
+  const inventoryRows = details.filter((d) => d.productId || d.product);

   const totalAmount = isInventory
-    ? details.reduce(
+    ? inventoryRows.reduce(
         (sum, d) => sum + (d.amount ?? (d.count ?? 0) * (d.unitPrice ?? 0)),
         0,
       )
     : details.reduce((sum, d) => sum + (d.currencyAmount ?? d.amount ?? 0), 0);

-      {isInventory ? (
-        <ProductTable details={details} />
+      {isInventory ? (
+        <ProductTable details={inventoryRows} />
       ) : (
         <AccountTable details={details} />
       )}

Also applies to: 225-227

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx`
around lines 173 - 183, For inventory journals the totalAmount currently sums
all transaction.details but ProductTable only renders product rows, causing
mismatch; update the isInventory branch that computes totalAmount (and the
similar logic at the other occurrence) to first filter details to the same
subset ProductTable renders (e.g., details.filter(d => d.productId || d.type ===
'product' or whatever predicate ProductTable uses such as presence of
count/unitPrice) and then reduce over that filtered array (summing d.amount ??
(d.count ?? 0) * (d.unitPrice ?? 0)) so the printed total and amount-in-words
match the visible ProductTable rows.

Comment on lines +57 to +67
export const asCashVariant = (variant: string): CashVariant =>
variant as CashVariant;

export const asInvMoveVariant = (variant: string): InvMoveVariant =>
variant as InvMoveVariant;

export const asInvSaleVariant = (variant: string): InvSaleVariant =>
variant as InvSaleVariant;

export const asInvIncomeVariant = (variant: string): InvIncomeVariant =>
variant as InvIncomeVariant;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid unchecked string-to-variant assertions

These helpers erase runtime safety: any arbitrary string is accepted as a valid variant. Validate against DOCUMENT_VARIANTS (or a per-journal allowlist) before narrowing, and fallback to the journal default when invalid.

Suggested fix
+const hasVariant = (journal: TrJournalEnum, variant: string) =>
+  (DOCUMENT_VARIANTS[journal] ?? []).some((item) => item.value === variant)

 export const asCashVariant = (variant: string): CashVariant =>
-  variant as CashVariant;
+  (hasVariant(TrJournalEnum.CASH, variant)
+    ? variant
+    : getDefaultVariant(TrJournalEnum.CASH)) as CashVariant;

 export const asInvMoveVariant = (variant: string): InvMoveVariant =>
-  variant as InvMoveVariant;
+  (hasVariant(TrJournalEnum.INV_MOVE, variant)
+    ? variant
+    : getDefaultVariant(TrJournalEnum.INV_MOVE)) as InvMoveVariant;

 export const asInvSaleVariant = (variant: string): InvSaleVariant =>
-  variant as InvSaleVariant;
+  (hasVariant(TrJournalEnum.INV_SALE, variant)
+    ? variant
+    : getDefaultVariant(TrJournalEnum.INV_SALE)) as InvSaleVariant;

 export const asInvIncomeVariant = (variant: string): InvIncomeVariant =>
-  variant as InvIncomeVariant;
+  (hasVariant(TrJournalEnum.INV_INCOME, variant)
+    ? variant
+    : getDefaultVariant(TrJournalEnum.INV_INCOME)) as InvIncomeVariant;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts`
around lines 57 - 67, The helpers asCashVariant, asInvMoveVariant,
asInvSaleVariant and asInvIncomeVariant currently cast any string to the variant
types unsafely; update each to validate the input string against
DOCUMENT_VARIANTS (or a per-journal allowlist) before narrowing and return the
journal default when validation fails; specifically, replace the direct
assertions with a check like "if DOCUMENT_VARIANTS.includes(variant) (or
allowlist.has(variant)) return variant as <Type> else return journalDefault" so
only allowed values are narrowed and invalid strings fall back to the default.

@github-actions
Copy link
Copy Markdown

🌗 Pull Request Overview

This PR implements a comprehensive document printing system for accounting transactions, adding support for multiple document types (cash, bank, inventory, invoices) with various layout variants. It includes a new number-to-words utility for Mongolian currency formatting and introduces a variant selection system for different document layouts.

Reviewed Changes
Kimi performed full review on 17 changed files and found 5 issues.

Show a summary per file
File Description
TransactionTabs.tsx Added inPreview=true query parameter to print URL
TransactionDocument.tsx New - Generic transaction document renderer with inventory/account tables
bank.tsx Refactored bank document with new layout and integrated amount-to-words
cash.tsx New - Cash document with 4 layout variants (twin-table, lined, dotted, twin-dotted)
index.tsx Updated PrintBody to support variant-based document selection
invIncome.tsx New - Inventory income document with 4 layout variants
invMove.tsx New - Inventory movement document with 2 variants
invOut.tsx New - Inventory out/expense document
invSale.tsx New - Inventory sales document with 4 layout variants
invSaleReturn.tsx New - Sales return document with VAT calculations
invoice.tsx New - Invoice document with payer/responsible variants
numberToWords.ts New - Mongolian number-to-words conversion utility
shared.tsx New - Shared document components and helper functions
variants.ts New - Document variant configurations and type helpers
printDocuments.tsx Registered new document components for all journal types
accTransactionDetail.ts Added product details to GraphQL query
TransactionPrintPage.tsx Refactored to support variant selection and improved toolbar UI

📋 Review Findings

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx

🟡 MEDIUM bug: Empty conditional rendering for phone number

Line 192

The conditional renders an empty string regardless of whether counterpartyPhone has a value. The phone number is never displayed.

💡 Suggested fix:

Current code:

<div>Утас, факс: {counterpartyPhone ? '' : ''}</div>

Improved code:

<div>Утас, факс: {counterpartyPhone || ''}</div>

🔵 LOW logic: Redundant logic in counterparty name construction

Lines 81-85

The firstName is checked twice. If the first condition fails (firstName is falsy), the array join will also not include firstName, making the logic redundant.

💡 Suggested fix:

Current code:

const counterpartyName =
  transaction.customer?.firstName ||
  [transaction.customer?.firstName, transaction.customer?.lastName]
    .filter(Boolean)
    .join(' ') ||
  transaction.customer?.code ||
  '';

Improved code:

const counterpartyName =
  [transaction.customer?.firstName, transaction.customer?.lastName]
    .filter(Boolean)
    .join(' ') ||
  transaction.customer?.code ||
  '';

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/bank.tsx

🟡 MEDIUM bug: Numeric values display as blank when value is 0

Lines 16, 46, 50, 63, 67

Using || ' ' for fallback causes 0 to render as a space instead of "0". This affects monetary displays.

💡 Suggested fix:

Current code:

<div className="flex-1 px-2 py-1">{value || ' '}</div>

Improved code:

<div className="flex-1 px-2 py-1">{value ?? ' '}</div>

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx

🔵 LOW bug: Same issue with || for numeric values

Lines 61, 65, 68, 94, 98, 122, 126, 149, 152

Multiple locations use || ' ' which causes 0 values to display as blank spaces.

💡 Suggested fix:

Use nullish coalescing (??) instead of logical OR (||) for numeric fallbacks:

// Line 61
{formatNumber(d.unitPrice ?? 0)}

// Line 65  
{formatNumber(lineAmount)}

// Line 68
{(d.count ?? 0).toLocaleString()}

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx

🔵 LOW data: Hardcoded empty value for destination account

Line 28

toAccount is hardcoded to empty string while to (department) is populated from data. If source account is shown, destination account should likely also be populated.

💡 Suggested fix:

Check if there's a destination account field in the transaction detail data:

const toAccount = d.toAccount?.code || d.destinationAccount?.code || '';

If the data truly doesn't exist, consider removing the column or adding a TODO comment explaining why it's empty.


📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/cash.tsx

🔵 LOW maintainability: Missing variant prop in exported wrapper components

Lines 388-407

The wrapper components (PrintCashTwinTableDocument, etc.) don't allow passing a custom variant, limiting flexibility. They force a specific variant.

💡 Suggested fix:

This appears to be by design for the registry, but consider documenting this or ensuring the registry doesn't need dynamic variant passing for these specific exports.



Powered by Kimi | Model: kimi-k2.5

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx (1)

12-12: ⚡ Quick win

Use absolute import path for shared document helpers.

Please replace the relative import with the project alias path for consistency with frontend import conventions.

Proposed fix
-} from './shared';
+} from '~/modules/transactions/transaction-form/components/documents/shared';
As per coding guidelines, "Always use absolute paths when importing".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`
at line 12, In invIncome.tsx replace the relative import string './shared' with
the project's absolute alias import used elsewhere in the frontend (i.e. use the
same alias pattern as other imports in this module) so the shared document
helpers are imported via the project alias instead of a relative path; update
the import statement that currently reads "from './shared'" to use the absolute
alias equivalent to keep import conventions consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`:
- Around line 160-162: Clamp the discount so totals never go negative: instead
of computing grandTotal as "grandTotal = total - discount" use a clampedDiscount
= Math.min(Math.max(discount, 0), total) and then compute grandTotal = total -
clampedDiscount (or simply grandTotal = Math.max(0, total - discount)); apply
this change where totals are rendered (references: variables discount, total,
grandTotal in invIncome.tsx and the analogous totals block later in the file) so
the displayed payable amount can never be negative.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`:
- Around line 178-180: The payable calculation can become negative when discount
> subtotal; update the logic in the invSale component so payable is clamped to a
minimum of zero (e.g., compute payable as the non-negative result of total minus
discount or clamp discount to at most total) instead of letting total - discount
produce negatives; apply the same change to the duplicate logic block around the
other occurrence (the second payable/discount calculation at the later section)
so both uses of discount, payable, total are protected against negative payable
values.

---

Nitpick comments:
In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`:
- Line 12: In invIncome.tsx replace the relative import string './shared' with
the project's absolute alias import used elsewhere in the frontend (i.e. use the
same alias pattern as other imports in this module) so the shared document
helpers are imported via the project alias instead of a relative path; update
the import statement that currently reads "from './shared'" to use the absolute
alias equivalent to keep import conventions consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca5c7aa1-b279-4530-a58a-c84e48ec4eaf

📥 Commits

Reviewing files that changed from the base of the PR and between fcfdbbe and eb39ed5.

📒 Files selected for processing (6)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx

Comment on lines +160 to +162
const discount = transaction?.extraData?.discount ?? 0;
const grandTotal = total - discount;
const filled = padRows(rows, 4);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against negative grand total when discount exceeds subtotal.

grandTotal = total - discount can go negative and print an invalid payable amount. Clamp the applied discount before rendering totals.

Proposed fix
-  const discount = transaction?.extraData?.discount ?? 0;
-  const grandTotal = total - discount;
+  const rawDiscount = Number(transaction?.extraData?.discount ?? 0);
+  const discount = Math.max(0, Number.isFinite(rawDiscount) ? rawDiscount : 0);
+  const appliedDiscount = Math.min(discount, total);
+  const grandTotal = total - appliedDiscount;

Also applies to: 233-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`
around lines 160 - 162, Clamp the discount so totals never go negative: instead
of computing grandTotal as "grandTotal = total - discount" use a clampedDiscount
= Math.min(Math.max(discount, 0), total) and then compute grandTotal = total -
clampedDiscount (or simply grandTotal = Math.max(0, total - discount)); apply
this change where totals are rendered (references: variables discount, total,
grandTotal in invIncome.tsx and the analogous totals block later in the file) so
the displayed payable amount can never be negative.

Comment on lines +178 to +180
const discount = transaction?.extraData?.discount ?? 0;
const payable = total - discount;
const filled = padRows(rows, 5);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent negative payable when discount is greater than subtotal.

payable = total - discount can render an invalid negative amount on the document.

Proposed fix
-  const discount = transaction?.extraData?.discount ?? 0;
-  const payable = total - discount;
+  const rawDiscount = Number(transaction?.extraData?.discount ?? 0);
+  const discount = Math.max(0, Number.isFinite(rawDiscount) ? rawDiscount : 0);
+  const appliedDiscount = Math.min(discount, total);
+  const payable = total - appliedDiscount;

Also applies to: 253-261

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`
around lines 178 - 180, The payable calculation can become negative when
discount > subtotal; update the logic in the invSale component so payable is
clamped to a minimum of zero (e.g., compute payable as the non-negative result
of total minus discount or clamp discount to at most total) instead of letting
total - discount produce negatives; apply the same change to the duplicate logic
block around the other occurrence (the second payable/discount calculation at
the later section) so both uses of discount, payable, total are protected
against negative payable values.

@github-actions
Copy link
Copy Markdown

🌗 Pull Request Overview

This PR implements document printing functionality for accounting transactions with multiple layout variants. It adds new document components for various journal types (cash, bank, inventory income/out/move/sale/return, invoices), shared UI components, a number-to-Mongolian-words utility, and a print toolbar with variant selection.

Reviewed Changes
Kimi performed full review on 17 changed files and found 4 issues.

Show a summary per file
File Description
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/TransactionTabs.tsx Added inPreview=true query parameter to print URL
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx New generic transaction document component with product/account tables
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/bank.tsx Refactored bank payment document with improved layout and Mongolian text amount
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/cash.tsx New cash receipt document component with 4 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx Updated to route to journal-specific document components with variant support
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx New inventory income document component with 4 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx New inventory movement document component with 2 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx New inventory out/supply document component
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx New inventory sale document component with 4 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx New inventory sale return document component
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx New invoice document component with 2 payer variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts New utility for converting numbers to Mongolian words
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx New shared components for document layouts (sheets, tables, headers)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts New configuration for document layout variants per journal type
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/contants/printDocuments.tsx Registered new document components for all journal types
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/graphql/queries/accTransactionDetail.ts Added product fields (code, name, unitPrice) to transaction details query
frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx Refactored print page with toolbar, variant selector, and improved styling

📋 Review Findings

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx

🟡 MEDIUM logic: Key prop using array index may cause issues on row reordering

Line 63

Using idx as a React key is acceptable for static lists but could cause UI issues if rows are reordered or if details can be edited.

💡 Suggested fix:

<tr key={d._id || `${idx}-${d.product?.name || ''}`}>

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx

🟡 MEDIUM logic: Missing withCode parameter in buildRows calls

Lines 116 and 228

The buildRows function supports a withCode parameter to prefix product codes to names, but LocationReceipt and NumberedReceipt don't pass it. This creates inconsistency with other inventory documents that might want to show codes.

💡 Suggested fix:
If product codes should be shown (consistent with invOut.tsx), add true as second parameter:

const rows = buildRows(transaction, true);

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx

🟡 MEDIUM logic: Unused withCode parameter consistency

Lines 106, 160, and 204

Similar to invSale.tsx, buildRows calls don't specify withCode. The DiscountReceipt and NumberedReceipt components might benefit from showing product codes for better traceability in inventory documents.

💡 Suggested fix:

const rows = buildRows(transaction, true);

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx

🟠 HIGH logic: Potential division by zero in unitPrice calculation

Lines 62-63

When count is 0 and unitPrice is undefined, the fallback calculation amount / count will result in NaN or Infinity.

💡 Suggested fix:

const unitPrice =
  d.unitPrice ?? (count > 0 ? amount / count : 0);

✅ Additional Observations

Good practices found:

  • Proper TypeScript typing throughout
  • Good separation of concerns with shared components
  • Mongolian number-to-words utility handles edge cases well
  • Consistent styling approach using Tailwind CSS
  • Proper handling of nullable values with || ' ' fallbacks
  • Print media queries hide UI elements appropriately

Powered by Kimi | Model: kimi-k2.5

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (5)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx (1)

197-220: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bind shared receipt header fields to transaction metadata instead of placeholders.

Both reusable receipt components ignore available date, partyName, and description, so printed documents lose real transaction context.

Proposed fix
 export const TwinReceipt = ({
   transaction,
   labels,
   minRows,
 }: {
   transaction: ITransaction;
   labels: IReceiptLabels;
   minRows: number;
 }) => {
+  const { date, partyName, description } = getMeta(transaction)
   const rows = buildRows(transaction);
   const filled = padRows(rows, minRows);

   return (
     <div className="flex-1">
       <FormHeader code={labels.formCode} />
@@
-      <div className="text-[11px] font-bold">Огноо: 20.../.../...</div>
-      <div className="mt-1 text-[11px] font-bold">{labels.partyLabel}</div>
-      <div className="mt-1 mb-2 text-[11px] font-bold">Утга:</div>
+      <div className="text-[11px] font-bold">
+        Огноо: {date || '20.../.../...'}
+      </div>
+      <div className="mt-1 text-[11px] font-bold">
+        {labels.partyLabel} {partyName || ''}
+      </div>
+      <div className="mt-1 mb-2 text-[11px] font-bold">
+        Утга: {description || ''}
+      </div>
@@
 export const SimpleReceipt = ({
   transaction,
   labels,
   minRows,
 }: {
   transaction: ITransaction;
   labels: IReceiptLabels;
   minRows: number;
 }) => {
+  const { date, partyName, description } = getMeta(transaction)
   const rows = buildRows(transaction);
   const filled = padRows(rows, minRows);

   return (
@@
-      <div className="font-bold">Огноо: 20.../.../...</div>
-      <div className="mt-1 font-bold">{labels.partyLabel}</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="font-bold">Огноо: {date || '20.../.../...'}</div>
+      <div className="mt-1 font-bold">
+        {labels.partyLabel} {partyName || ''}
+      </div>
+      <div className="mt-1 mb-2 font-bold">Утга: {description || ''}</div>

Also applies to: 234-257

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx`
around lines 197 - 220, The TwinReceipt component is rendering placeholder
header values (e.g. "Огноо: 20.../.../...", empty Утга) instead of using
transaction metadata; update TwinReceipt to bind its date, party name and
description fields to the transaction object (use transaction.date,
transaction.partyName and transaction.description or the correct ITransaction
property names) where currently labels or static placeholders are rendered
(references: TwinReceipt, buildRows, padRows, labels.formCode, labels.orgLabel,
labels.partyLabel); make the same change in the sibling receipt component around
the 234-257 region so both reusable receipt components show real transaction
data.
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx (2)

48-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp and sanitize discount before computing grandTotal.

Current logic can print negative totals (and NaN for bad discount payloads).

Proposed fix
-  const discount = transaction?.extraData?.discount ?? 0;
-  const grandTotal = total - discount;
+  const rawDiscount = Number(transaction?.extraData?.discount ?? 0)
+  const discount = Number.isFinite(rawDiscount) ? Math.max(0, rawDiscount) : 0
+  const appliedDiscount = Math.min(discount, total)
+  const grandTotal = total - appliedDiscount
@@
-            <td className={`${TD} text-right`}>{formatNumber(discount)}</td>
+            <td className={`${TD} text-right`}>
+              {formatNumber(appliedDiscount)}
+            </td>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`
around lines 48 - 49, The discount pulled from transaction?.extraData?.discount
is not sanitized and can be NaN or negative, producing negative or invalid
grandTotal; parse and coerce discount to a finite number (e.g. Number(...) or
parseFloat), fall back to 0 when not finite, then clamp it between 0 and total
(use Math.max(0, Math.min(total, discount))) before computing grandTotal = total
- discount; update the logic around the discount variable and the grandTotal
calculation (references: discount, transaction?.extraData?.discount, grandTotal,
total) so grandTotal cannot be NaN or negative.

61-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Render receipt metadata fields from transaction instead of fixed placeholders.

These variants currently drop real date/party/description values on print.

Proposed fix
-  const { documentNo } = getMeta(transaction);
+  const { documentNo, date, partyName, description } = getMeta(transaction);
@@
-      <div className="font-bold">Огноо: 20.../.../...</div>
-      <div className="mt-1 font-bold">Хэнээс(хаанаас):</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="font-bold">Огноо: {date || '20.../.../...'}</div>
+      <div className="mt-1 font-bold">
+        Хэнээс(хаанаас): {partyName || ''}
+      </div>
+      <div className="mt-1 mb-2 font-bold">Утга: {description || ''}</div>
-  const { documentNo } = getMeta(transaction);
+  const { documentNo, date, partyName, description } = getMeta(transaction);
@@
-      <div className="font-bold">20... он ... сар ... өдөр</div>
-      <div className="mt-1 font-bold">Бэлтгэн нийлүүлэгчийн нэр:</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="font-bold">{date || '20... он ... сар ... өдөр'}</div>
+      <div className="mt-1 font-bold">
+        Бэлтгэн нийлүүлэгчийн нэр: {partyName || ''}
+      </div>
+      <div className="mt-1 mb-2 font-bold">Утга: {description || ''}</div>

Also applies to: 159-161

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`
around lines 61 - 63, The JSX in invIncome.tsx currently renders hard-coded
placeholders for date/party/description; replace those with the actual
transaction fields (e.g., transaction.date formatted for display,
transaction.party or transaction.from / transaction.fromName, and
transaction.description or transaction.note) instead of the static strings.
Update the render lines inside the invIncome component to output the formatted
transaction.date (use an existing formatDate helper or new
Date(transaction.date).toLocaleDateString()), transaction.party/name, and
transaction.description with optional chaining and sensible fallbacks (e.g.,
transaction?.date ?? '', transaction?.party ?? '', transaction?.description ??
''). Do the same replacement for the duplicate block noted at lines 159-161.
Ensure you reference the component's transaction prop and keep null-safe access.
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx (2)

118-120: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Protect payable from going negative and sanitize discount input.

total - discount can render invalid negative payable values.

Proposed fix
-  const discount = transaction?.extraData?.discount ?? 0;
-  const payable = total - discount;
+  const rawDiscount = Number(transaction?.extraData?.discount ?? 0)
+  const discount = Number.isFinite(rawDiscount) ? Math.max(0, rawDiscount) : 0
+  const appliedDiscount = Math.min(discount, total)
+  const payable = total - appliedDiscount
@@
-            <td className={`${TD} text-right`}>{formatNumber(discount)}</td>
+            <td className={`${TD} text-right`}>
+              {formatNumber(appliedDiscount)}
+            </td>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`
around lines 118 - 120, The payable calculation can go negative because discount
is taken directly from transaction?.extraData?.discount; sanitize discount by
coercing it to a finite non-negative number and cap it at total before computing
payable (e.g., derive a safeDiscount from transaction?.extraData?.discount that
uses Number()/parseFloat, defaults to 0 for invalid values, clamps to >=0 and <=
total), then compute payable = total - safeDiscount and ensure payable is at
least 0; update the variables near discount, payable, total and keep
padRows(rows, 5) unchanged.

50-51: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Show party/description fields from transaction metadata in all sale variants.

These blocks currently print only labels, which leaves documents incomplete.

Proposed fix
-  const { date } = getMeta(transaction);
+  const { date, partyName, description } = getMeta(transaction);
@@
-      <div className="mt-1 font-bold">Хэнд(хаана):</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="mt-1 font-bold">Хэнд(хаана): {partyName || ''}</div>
+      <div className="mt-1 mb-2 font-bold">Утга: {description || ''}</div>
-  const { date } = getMeta(transaction);
+  const { date, partyName, description } = getMeta(transaction);
@@
-      <div className="mt-1 font-bold">Хэнд(хаана):</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="mt-1 font-bold">Хэнд(хаана): {partyName || ''}</div>
+      <div className="mt-1 mb-2 font-bold">Утга: {description || ''}</div>
-  const { documentNo, date } = getMeta(transaction);
+  const { documentNo, date, partyName, description } = getMeta(transaction);
@@
-      <div className="mt-1 font-bold">(Худалдан авагчийн нэр):</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="mt-1 font-bold">
+        (Худалдан авагчийн нэр): {partyName || ''}
+      </div>
+      <div className="mt-1 mb-2 font-bold">Утга: {description || ''}</div>

Also applies to: 132-133, 232-233

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`
around lines 50 - 51, The JSX in invSale.tsx currently renders only labels for
"Хэнд(хаана):" and "Утга:" (and the analogous blocks at the other two locations)
but never outputs the corresponding values from the transaction metadata; update
the component to read and render the metadata fields (e.g.,
transaction.metadata.party and transaction.metadata.description or the
equivalent keys used in your transaction object) alongside the labels in all
sale variants (the blocks around the existing labels and the similar blocks at
the other two occurrences) so the party and description from the transaction are
displayed when present, falling back to an empty string or a localized "N/A"
when missing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`:
- Around line 48-49: The discount pulled from transaction?.extraData?.discount
is not sanitized and can be NaN or negative, producing negative or invalid
grandTotal; parse and coerce discount to a finite number (e.g. Number(...) or
parseFloat), fall back to 0 when not finite, then clamp it between 0 and total
(use Math.max(0, Math.min(total, discount))) before computing grandTotal = total
- discount; update the logic around the discount variable and the grandTotal
calculation (references: discount, transaction?.extraData?.discount, grandTotal,
total) so grandTotal cannot be NaN or negative.
- Around line 61-63: The JSX in invIncome.tsx currently renders hard-coded
placeholders for date/party/description; replace those with the actual
transaction fields (e.g., transaction.date formatted for display,
transaction.party or transaction.from / transaction.fromName, and
transaction.description or transaction.note) instead of the static strings.
Update the render lines inside the invIncome component to output the formatted
transaction.date (use an existing formatDate helper or new
Date(transaction.date).toLocaleDateString()), transaction.party/name, and
transaction.description with optional chaining and sensible fallbacks (e.g.,
transaction?.date ?? '', transaction?.party ?? '', transaction?.description ??
''). Do the same replacement for the duplicate block noted at lines 159-161.
Ensure you reference the component's transaction prop and keep null-safe access.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`:
- Around line 118-120: The payable calculation can go negative because discount
is taken directly from transaction?.extraData?.discount; sanitize discount by
coercing it to a finite non-negative number and cap it at total before computing
payable (e.g., derive a safeDiscount from transaction?.extraData?.discount that
uses Number()/parseFloat, defaults to 0 for invalid values, clamps to >=0 and <=
total), then compute payable = total - safeDiscount and ensure payable is at
least 0; update the variables near discount, payable, total and keep
padRows(rows, 5) unchanged.
- Around line 50-51: The JSX in invSale.tsx currently renders only labels for
"Хэнд(хаана):" and "Утга:" (and the analogous blocks at the other two locations)
but never outputs the corresponding values from the transaction metadata; update
the component to read and render the metadata fields (e.g.,
transaction.metadata.party and transaction.metadata.description or the
equivalent keys used in your transaction object) alongside the labels in all
sale variants (the blocks around the existing labels and the similar blocks at
the other two occurrences) so the party and description from the transaction are
displayed when present, falling back to an empty string or a localized "N/A"
when missing.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx`:
- Around line 197-220: The TwinReceipt component is rendering placeholder header
values (e.g. "Огноо: 20.../.../...", empty Утга) instead of using transaction
metadata; update TwinReceipt to bind its date, party name and description fields
to the transaction object (use transaction.date, transaction.partyName and
transaction.description or the correct ITransaction property names) where
currently labels or static placeholders are rendered (references: TwinReceipt,
buildRows, padRows, labels.formCode, labels.orgLabel, labels.partyLabel); make
the same change in the sibling receipt component around the 234-257 region so
both reusable receipt components show real transaction data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 245cc001-4f8b-4c65-a14c-d388428e247f

📥 Commits

Reviewing files that changed from the base of the PR and between eb39ed5 and f0c97b9.

📒 Files selected for processing (5)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx

@github-actions
Copy link
Copy Markdown

🌗 Pull Request Overview

This PR implements document printing functionality for accounting transactions, adding multiple print layout variants for different journal types (cash, bank, inventory, sales, etc.). It introduces reusable document components, Mongolian number-to-words conversion, and a print preview interface with layout selection.

Reviewed Changes
Kimi performed full review on 17 changed files and found 4 issues.

Show a summary per file
File Description
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/TransactionTabs.tsx Added inPreview=true query parameter to print URL
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx New generic transaction document component for MAIN/PAYABLE journals
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/bank.tsx Refactored bank payment order to use shared components and added amount in words
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/cash.tsx New cash document component with 4 layout variants (twin-table, lined, dotted, twin-dotted)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/index.tsx Updated PrintBody to route to journal-specific components with variant support
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx New inventory income document with 4 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invMove.tsx New inventory movement document with 2 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invOut.tsx New inventory out/expense document component
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx New inventory sale document with 4 layout variants
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx New inventory sale return document component
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx New invoice document with 2 variants (payer/responsible)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts New utility to convert numbers to Mongolian text
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx New shared components and helpers for document layouts
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/variants.ts New document variant configuration and type guards
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/contants/printDocuments.tsx Updated journal-to-component mapping with new document components
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/graphql/queries/accTransactionDetail.ts Added product fields to transaction details query
frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx Refactored print page with toolbar, variant selector, and improved print styling

📋 Review Findings

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invoice.tsx

🟡 MEDIUM bug: Redundant counterparty name logic

Lines 65-69

The logic for determining counterpartyName checks firstName twice and uses an array join that will always have an empty firstName when reached. This appears to be a logic error - the intention was likely to join firstName and lastName when both exist.

Current code:

const counterpartyName =
  transaction.customer?.firstName ||
  [transaction.customer?.firstName, transaction.customer?.lastName]
    .filter(Boolean)
    .join(' ') ||
  transaction.customer?.code ||
  '';

Suggested fix:

const counterpartyName =
  [transaction.customer?.firstName, transaction.customer?.lastName]
    .filter(Boolean)
    .join(' ') ||
  transaction.customer?.code ||
  '';

📄 frontend/plugins/accounting_ui/src/pages/TransactionPrintPage.tsx

🟡 MEDIUM race-condition: Auto-print may execute before variant state updates

Lines 26-46

Two useEffect hooks run when transaction changes:

  1. Lines 26-30: Sets the default variant
  2. Lines 32-40: Triggers auto-print after 300ms

The auto-print effect captures the transaction dependency and runs independently of the variant state update. While the PrintBody component has defensive defaults (variant || 'lined'), there's a race condition where the print dialog could open before React finishes rendering the updated variant.

Current code:

useEffect(() => {
  if (transaction) {
    setVariant(getDefaultVariant(transaction.journal));
  }
}, [transaction]);

const hasPrintedRef = useRef(false);
useEffect(() => {
  if (!loading && transaction && !hasPrintedRef.current) {
    hasPrintedRef.current = true;
    setTimeout(() => {
      window.print();
    }, 300);
  }
}, [loading, transaction]);

Suggested fix:

useEffect(() => {
  if (transaction) {
    const defaultVariant = getDefaultVariant(transaction.journal);
    setVariant(defaultVariant);
  }
}, [transaction]);

const hasPrintedRef = useRef(false);
useEffect(() => {
  if (!loading && transaction && variant && !hasPrintedRef.current) {
    hasPrintedRef.current = true;
    setTimeout(() => {
      window.print();
    }, 300);
  }
}, [loading, transaction, variant]);

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/numberToWords.ts

🔵 LOW precision: Floating point precision issue in decimal extraction

Line 125

When extracting the decimal (mongo) portion of the amount, Math.round((safeAmount - tugrug) * 100) can suffer from floating-point precision errors. For example, 1.005 might become 100 instead of 101 due to 1.005 - 1 = 0.004999999... multiplied by 100 becoming 0.499999... and rounding down.

Current code:

const mongo = Math.round((safeAmount - tugrug) * 100);

Suggested fix:

const mongo = Math.round(Math.round(safeAmount * 100) - tugrug * 100);

Or use a small epsilon:

const mongo = Math.round((safeAmount - tugrug) * 100 + 1e-9);

📄 frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx

🔵 LOW performance: Object recreation on every render

Line 108

DISCOUNT_CONFIG is called inline in JSX, creating a new object reference on every render. While DiscountReceipt doesn't currently have memoization that would break, this is unnecessary work and could cause issues if memoization is added later.

Current code:

<DiscountReceipt
  transaction={transaction}
  config={DISCOUNT_CONFIG(getMeta(transaction).date)}
/>

Suggested fix:

const discountConfig = useMemo(
  () => DISCOUNT_CONFIG(getMeta(transaction).date),
  [transaction]
);

// In JSX:
<DiscountReceipt
  transaction={transaction}
  config={discountConfig}
/>

✅ Summary

The code is generally well-structured with good separation of concerns and reusable components. The shared.tsx module effectively consolidates common document patterns. The main concerns are:

  1. Logic error in invoice.tsx counterparty name generation (medium priority)
  2. Race condition in TransactionPrintPage that could affect print output (medium priority)
  3. Floating point precision edge case in number to words conversion (low priority)
  4. Performance micro-optimization for object creation (low priority)

Powered by Kimi | Model: kimi-k2.5

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx (1)

60-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bind numbered receipt header fields to transaction metadata.

This variant still prints static header placeholders even though getMeta already provides date/party/description for the transaction.

💡 Proposed fix
-  const { documentNo } = getMeta(transaction);
+  const { documentNo, date, partyName, description } = getMeta(transaction);
@@
-      <div className="font-bold">20... он ... сар ... өдөр</div>
-      <div className="mt-1 font-bold">Бэлтгэн нийлүүлэгчийн нэр:</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="font-bold">
+        {date || '20... он ... сар ... өдөр'}
+      </div>
+      <div className="mt-1 font-bold">
+        Бэлтгэн нийлүүлэгчийн нэр: {partyName || ''}
+      </div>
+      <div className="mt-1 mb-2 font-bold">
+        Утга: {description || ''}
+      </div>

Also applies to: 75-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`
around lines 60 - 61, The NumberedReceipt component is rendering static header
placeholders instead of using transaction metadata from getMeta; update
NumberedReceipt to extract and bind all relevant metadata (documentNo, date,
party, description returned by getMeta(transaction)) into the header JSX
elements instead of hard-coded text, and do the same replacement for the other
receipt/header variant in the same file (the similar block around lines 75-77)
so both components render dynamic transaction metadata.
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx (2)

279-282: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Render document metadata values instead of static placeholders.

These shared receipt variants still print label-only lines for date/party/description, so printed docs lose available transaction metadata.

💡 Proposed fix
 export const TwinReceipt = ({
   transaction,
   labels,
   minRows,
 }: {
   transaction: ITransaction;
   labels: IReceiptLabels;
   minRows: number;
 }) => {
+  const { date, partyName, description } = getMeta(transaction)
   const rows = buildRows(transaction);
   const filled = padRows(rows, minRows);

   return (
@@
-      <div className="text-[11px] font-bold">Огноо: 20.../.../...</div>
-      <div className="mt-1 text-[11px] font-bold">{labels.partyLabel}</div>
-      <div className="mt-1 mb-2 text-[11px] font-bold">Утга:</div>
+      <div className="text-[11px] font-bold">
+        Огноо: {date || '20.../.../...'}
+      </div>
+      <div className="mt-1 text-[11px] font-bold">
+        {labels.partyLabel} {partyName || ''}
+      </div>
+      <div className="mt-1 mb-2 text-[11px] font-bold">
+        Утга: {description || ''}
+      </div>

Also applies to: 316-319, 442-444

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx`
around lines 279 - 282, The component currently renders static placeholders for
document metadata (date, party, description) instead of real values; update the
JSX in shared.tsx to output the actual transaction/document fields (e.g., use
the component's props such as transaction.date or document.date,
transaction.party or document.party, and
transaction.description/document.description) in place of the hardcoded "Огноо:
20.../.../..." and the empty "Утга:" line, while keeping the existing labels
like labels.partyLabel; make the same replacements in the other occurrences
referenced (lines around 316-319 and 442-444) so printed docs show real
metadata.

428-430: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize and clamp discount before computing payable.

discount is used raw, so non-numeric values can render NaN, and discounts above subtotal can print negative payable amounts.

💡 Proposed fix
-  const discount = transaction?.extraData?.discount ?? 0;
-  const payable = total - discount;
+  const rawDiscount = Number(transaction?.extraData?.discount ?? 0)
+  const normalizedDiscount =
+    Number.isFinite(rawDiscount) ? Math.max(0, rawDiscount) : 0
+  const discount = Math.min(normalizedDiscount, total)
+  const payable = total - discount;

Also applies to: 508-516

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx`
around lines 428 - 430, Normalize the discount value before computing payable:
coerce transaction?.extraData?.discount to a numeric value (e.g., Number or
parseFloat fallback) and clamp it between 0 and total/subtotal so payable =
total - discount cannot become NaN or negative; update the calculation where
discount is defined (the variable using transaction?.extraData?.discount) and
any other identical locations (the second block around the same pattern) to
ensure payable uses the sanitized discount, leaving padRows(rows,
config.minRows) unchanged.
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx (1)

38-39: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Populate party/description from metadata in sale receipt headers.

Both local sale variants render label-only lines, leaving party and description blank in output despite having transaction metadata available.

💡 Proposed fix
-  const { date } = getMeta(transaction);
+  const { date, partyName, description } = getMeta(transaction);
@@
-      <div className="mt-1 font-bold">Хэнд(хаана):</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="mt-1 font-bold">Хэнд(хаана): {partyName || ''}</div>
+      <div className="mt-1 mb-2 font-bold">Утга: {description || ''}</div>
-  const { documentNo, date } = getMeta(transaction);
+  const { documentNo, date, partyName, description } = getMeta(transaction);
@@
-      <div className="mt-1 font-bold">(Худалдан авагчийн нэр):</div>
-      <div className="mt-1 mb-2 font-bold">Утга:</div>
+      <div className="mt-1 font-bold">
+        (Худалдан авагчийн нэр): {partyName || ''}
+      </div>
+      <div className="mt-1 mb-2 font-bold">
+        Утга: {description || ''}
+      </div>

Also applies to: 52-53, 107-108, 122-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`
around lines 38 - 39, The sale receipt headers are rendered with label-only rows
because the header objects created in buildRows (used where rows is assigned)
don't use transaction metadata; call getMeta(transaction) to extract the
metadata (e.g., party and description) and populate those fields on the header
row objects for the local sale variants before returning rows. Concretely, in
the code path where rows are built for sales (the buildRows function and the
place where const { date } = getMeta(transaction) is used), assign header.party
= meta.party (or meta.name) and header.description = meta.description (or
meta.notes) so the rendered header uses those values instead of leaving them
blank.
🧹 Nitpick comments (2)
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx (1)

19-19: ⚡ Quick win

Use absolute import path for shared document helpers.

Please replace this relative import with an absolute alias import to keep import style consistent with repository standards.

As per coding guidelines, "Always use absolute paths when importing".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`
at line 19, Replace the relative import "from './shared'" in invSale.tsx with
the repository's absolute alias import for the shared document helpers (i.e.,
update the import that currently references './shared' to the absolute path your
project uses for modules—such as the project's alias for the
transactions/transaction-form/components/documents/shared module); locate the
import statement in invSale.tsx that imports from './shared' and change it to
the absolute alias form to match repository standards.
frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx (1)

19-19: ⚡ Quick win

Use absolute import path for shared document helpers.

Please switch this relative import to the module alias path to match repo import conventions.

As per coding guidelines, "Always use absolute paths when importing".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`
at line 19, Replace the relative import "from './shared'" in invIncome.tsx with
the repository's absolute/module-alias import for the shared document helpers
(i.e., use the project's alias path that points to the same shared module
instead of './shared'); update the import statement that currently brings in
symbols from './shared' to the corresponding absolute path so it matches the
repo's "always use absolute paths" convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx`:
- Line 142: Replace truthy fallbacks that hide numeric zeroes with nullish
checks: where the JSX uses expressions like value || ' ' or row?.count ? ... : '
', change them to use the nullish coalescing operator (e.g., value ?? ' ' ) or
an explicit undefined/null test (e.g., row?.count ?? ' '). Update all similar
occurrences in this module (shared.tsx) so numerical 0 is rendered instead of
being treated as falsy.

---

Duplicate comments:
In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`:
- Around line 60-61: The NumberedReceipt component is rendering static header
placeholders instead of using transaction metadata from getMeta; update
NumberedReceipt to extract and bind all relevant metadata (documentNo, date,
party, description returned by getMeta(transaction)) into the header JSX
elements instead of hard-coded text, and do the same replacement for the other
receipt/header variant in the same file (the similar block around lines 75-77)
so both components render dynamic transaction metadata.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`:
- Around line 38-39: The sale receipt headers are rendered with label-only rows
because the header objects created in buildRows (used where rows is assigned)
don't use transaction metadata; call getMeta(transaction) to extract the
metadata (e.g., party and description) and populate those fields on the header
row objects for the local sale variants before returning rows. Concretely, in
the code path where rows are built for sales (the buildRows function and the
place where const { date } = getMeta(transaction) is used), assign header.party
= meta.party (or meta.name) and header.description = meta.description (or
meta.notes) so the rendered header uses those values instead of leaving them
blank.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx`:
- Around line 279-282: The component currently renders static placeholders for
document metadata (date, party, description) instead of real values; update the
JSX in shared.tsx to output the actual transaction/document fields (e.g., use
the component's props such as transaction.date or document.date,
transaction.party or document.party, and
transaction.description/document.description) in place of the hardcoded "Огноо:
20.../.../..." and the empty "Утга:" line, while keeping the existing labels
like labels.partyLabel; make the same replacements in the other occurrences
referenced (lines around 316-319 and 442-444) so printed docs show real
metadata.
- Around line 428-430: Normalize the discount value before computing payable:
coerce transaction?.extraData?.discount to a numeric value (e.g., Number or
parseFloat fallback) and clamp it between 0 and total/subtotal so payable =
total - discount cannot become NaN or negative; update the calculation where
discount is defined (the variable using transaction?.extraData?.discount) and
any other identical locations (the second block around the same pattern) to
ensure payable uses the sanitized discount, leaving padRows(rows,
config.minRows) unchanged.

---

Nitpick comments:
In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx`:
- Line 19: Replace the relative import "from './shared'" in invIncome.tsx with
the repository's absolute/module-alias import for the shared document helpers
(i.e., use the project's alias path that points to the same shared module
instead of './shared'); update the import statement that currently brings in
symbols from './shared' to the corresponding absolute path so it matches the
repo's "always use absolute paths" convention.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx`:
- Line 19: Replace the relative import "from './shared'" in invSale.tsx with the
repository's absolute alias import for the shared document helpers (i.e., update
the import that currently references './shared' to the absolute path your
project uses for modules—such as the project's alias for the
transactions/transaction-form/components/documents/shared module); locate the
import statement in invSale.tsx that imports from './shared' and change it to
the absolute alias form to match repository standards.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 906b9508-34ac-4841-94c6-efc8972dd630

📥 Commits

Reviewing files that changed from the base of the PR and between f0c97b9 and 132c35a.

📒 Files selected for processing (6)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/bank.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invIncome.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSale.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/TransactionDocument.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/invSaleReturn.tsx
  • frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/bank.tsx

<div className="w-[42%] shrink-0 bg-black/4 px-2 py-1 font-medium">
{label}
</div>
<div className="flex-1 px-2 py-1">{value || ' '}</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use nullish checks so numeric zero values are printed.

Current truthy checks hide valid 0 values (value || ' ', row?.count ? ... : ' '), which can silently blank numeric fields.

💡 Proposed fix
-    <div className="flex-1 px-2 py-1">{value || ' '}</div>
+    <div className="flex-1 px-2 py-1">{value ?? ' '}</div>
@@
-          {row?.count ? row.count.toLocaleString() : ' '}
+          {row ? row.count.toLocaleString() : ' '}
@@
-          {row?.count ? fixNum(row.count, 2).toLocaleString() : ' '}
+          {row ? fixNum(row.count, 2).toLocaleString() : ' '}
@@
-                {row?.count ? row.count.toLocaleString() : ' '}
+                {row ? row.count.toLocaleString() : ' '}

Also applies to: 211-212, 376-377, 484-485

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/plugins/accounting_ui/src/modules/transactions/transaction-form/components/documents/shared.tsx`
at line 142, Replace truthy fallbacks that hide numeric zeroes with nullish
checks: where the JSX uses expressions like value || ' ' or row?.count ? ... : '
', change them to use the nullish coalescing operator (e.g., value ?? ' ' ) or
an explicit undefined/null test (e.g., row?.count ?? ' '). Update all similar
occurrences in this module (shared.tsx) so numerical 0 is rendered instead of
being treated as falsy.

@github-actions
Copy link
Copy Markdown

🌗 Pull Request Overview

This PR adds comprehensive document printing functionality for various transaction types (cash, bank, inventory, invoices) in the accounting module. It introduces multiple print layout variants, a number-to-Mongolian-words converter, and a print preview interface with layout selection.

Reviewed Changes
Kimi performed full review on 18 changed files and found 4 issues.

Show a summary per file
File Description
TransactionTabs.tsx Added inPreview=true query parameter to print URL
TransactionDocument.tsx New generic transaction document component for MAIN, PAYABLE, and inventory journals
bank.tsx Refactored bank payment order document with new layout and Mongolian text amount
cash.tsx New cash receipt document with 4 layout variants (twin-table, lined, dotted, twin-dotted)
index.tsx Updated PrintBody to route to specific document components based on journal type
invIncome.tsx New inventory income document with 4 layout variants
invMove.tsx New inventory movement document with 2 layout variants
invOut.tsx New inventory out/expense document with twin receipt layout
invSale.tsx New inventory sale document with 4 layout variants
invSaleReturn.tsx New inventory sale return document with VAT breakdown
invoice.tsx New invoice document (НХМаягт Т1) with payer/responsible variants
numberToWords.ts New utility to convert numbers to Mongolian text
shared.tsx New shared components and helpers for document rendering
variants.ts New document variant configuration and type utilities
printDocuments.tsx Updated registry to map journal types to document components
accTransactionDetail.ts Added product details (code, name, unitPrice) to GraphQL query
TransactionPrintPage.tsx Refactored print page with toolbar, layout selector, and auto-print

📋 Review Findings

📄 accTransactionDetail.ts

🟡 MEDIUM data-fetching: Missing uom field in GraphQL query

Line 8-13

The uom (unit of measurement) field is missing from the product query, but it's used in buildRows() in shared.tsx line 40. This will cause the unit column to always display as empty in printed receipts.

💡 Suggested fix:

Current code:

details {
  product {
    _id
    code
    name
    unitPrice
  }
}

Improved code:

details {
  product {
    _id
    code
    name
    unitPrice
    uom
  }
}

📄 invoice.tsx

🟡 MEDIUM logic: Redundant counterparty name logic

Lines 30-37

The counterpartyName logic checks firstName twice, making the array join fallback unreachable when firstName exists. If the intent is to show full name when available, the logic should be restructured.

💡 Suggested fix:

Current code:

const counterpartyName =
  transaction.customer?.firstName ||
  [transaction.customer?.firstName, transaction.customer?.lastName]
    .filter(Boolean)
    .join(' ') ||
  transaction.customer?.code ||
  '';

Improved code:

const counterpartyName =
  [transaction.customer?.firstName, transaction.customer?.lastName]
    .filter(Boolean)
    .join(' ') ||
  transaction.customer?.code ||
  '';

📄 shared.tsx & invSaleReturn.tsx

🟡 MEDIUM data-fetching: Fields not fetched in GraphQL query will always be undefined

shared.tsx Line 335, invSaleReturn.tsx Line 17-18

The code accesses transaction.extraData?.discount and transaction.vatAmount, but these fields are not included in the TRANSACTION_DETAIL_QUERY update. These values will always resolve to the default (0) and never show actual data.

💡 Suggested fix:

Add these fields to the GraphQL query in accTransactionDetail.ts:

export const TRANSACTION_DETAIL_QUERY = gql`
  query accTransactionDetail($_id: String!) {
    accTransactionDetail(_id: $_id) {
      ${commonTransactionFields}
      vatAmount
      extraData
      details {
        product {
          _id
          code
          name
          unitPrice
          uom
        }
      }
      customer {
        _id
        code

📄 variants.ts

🔵 LOW type-safety: Unsafe type assertions without validation

Lines 54-67

The as* helper functions use unchecked type assertions. Invalid variant strings will pass through and cause unexpected behavior in switch statements.

💡 Suggested fix:

Add runtime validation or fallback to default values:

const VALID_CASH_VARIANTS: CashVariant[] = ['twin-table', 'lined', 'dotted', 'twin-dotted'];

export const asCashVariant = (variant: string): CashVariant => {
  if (VALID_CASH_VARIANTS.includes(variant as CashVariant)) {
    return variant as CashVariant;
  }
  return 'lined'; // sensible default
};

✅ Summary

The PR is well-structured with good component reuse through the shared.tsx module. The main concerns are:

  1. Missing GraphQL fields (uom, vatAmount, extraData) that will cause UI to display incomplete data
  2. Minor logic redundancy in name formatting
  3. Unsafe type casting that could be hardened with validation

Powered by Kimi | Model: kimi-k2.5

@sonarqubecloud
Copy link
Copy Markdown

@munkhsaikhan munkhsaikhan merged commit 173d395 into main May 19, 2026
13 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants