Skip to content

Conversation

@khushi8112
Copy link
Member

@khushi8112 khushi8112 commented Sep 9, 2025

PF-1-1
PF-1-2
PF-2-1
Screenshot 2025-10-01 at 1 07 43 PM

no-docs

@github-actions github-actions bot added the needs-tests This PR needs automated unit-tests. label Sep 9, 2025
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@khushi8112 khushi8112 force-pushed the print-format-for-sales-invoice branch from ad27994 to 7888500 Compare September 10, 2025 08:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
erpnext/fixtures/letter_head.json (1)

4-4: Fix broken inline CSS on logo (missing semicolon) and add alt text.

The first fixture still has style="width: 90px height: 90px;" which breaks height; also add an alt for accessibility/PDF fallbacks. This was flagged earlier and appears unresolved here.

-          <img src="{{ frappe.utils.get_url(frappe.db.get_value('Company', doc.company, 'company_logo')) }}" style="width: 90px height: 90px;">
+          <img src="{{ frappe.utils.get_url(frappe.db.get_value('Company', doc.company, 'company_logo')) }}" style="width: 90px; height: 90px;" alt="{{ doc.company }} logo">
🧹 Nitpick comments (5)
erpnext/fixtures/letter_head.json (5)

4-4: wkhtmltopdf compatibility: avoid flex gap; add margin fallback.

wkhtmltopdf (Qt WebKit) often ignores gap. Add a child-sibling margin fallback to preserve spacing in PDFs.

-    .logo-address {
-        display: flex;
-        align-items: center;
-        gap: 15px;
-        width: 50%;
-    }
+    .logo-address {
+        display: flex;
+        align-items: center;
+        gap: 15px; /* modern browsers */
+        width: 50%;
+    }
+    .logo-address > * + * { margin-left: 15px; } /* wkhtmltopdf fallback */

4-4: Minor: reduce redundant
tags inside flex rows.

<br> within .letterhead-right-div (a flex row) isn’t needed and can add stray line breaks in PDFs.

-            <div>{{ doc.name }}</div><br>
+            <div>{{ doc.name }}</div>

(Apply similarly to Website/Email/Contact blocks.)


27-27: wkhtmltopdf compatibility: use flex-end instead of end.

justify-content: end; may not be supported by wkhtmltopdf; use flex-end.

-        justify-content: end;
+        justify-content: flex-end;

27-27: Add alt text and ensure logo sizing applies to the image.

Add an alt attribute for accessibility and set explicit dimensions on the <img> or via .logo img {} to avoid layout shifts/oversized logos in PDFs.

-              <img src="{{ frappe.utils.get_url(frappe.db.get_value('Company', doc.company, 'company_logo')) }}">
+              <img src="{{ frappe.utils.get_url(frappe.db.get_value('Company', doc.company, 'company_logo')) }}" alt="{{ doc.company }} logo" style="width: 75px; height: 75px;">

Optionally, move sizing to CSS:

-    .logo {
+    .logo {
         border: 1px solid #EDEDED;
         border-radius: 15px;
-        width: 75px;
+        width: 75px;
     }
+    .logo img { width: 75px; height: 75px; }

27-27: Add gap fallback for flex rows in contact blocks.

As above, gap can be ignored by wkhtmltopdf; add sibling margins to keep spacing consistent.

-    .letterhead-right-div {
-        display: flex;
-        align-items: flex-start;
-        gap: 0 10px;
-    }
+    .letterhead-right-div {
+        display: flex;
+        align-items: flex-start;
+        gap: 0 10px; /* modern browsers */
+    }
+    .letterhead-right-div > * + * { margin-left: 10px; } /* wkhtmltopdf fallback */
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad27994 and 7888500.

📒 Files selected for processing (1)
  • erpnext/fixtures/letter_head.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Patch Test
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Summary

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (7)
erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (7)

3-3: Render letterhead HTML safely.
Output is escaped; mark trusted HTML as safe.

-<div class="letter-head">{{ letter_head }}</div>
+<div class="letter-head">{{ letter_head | safe }}</div>

18-20: wkhtmltopdf: add CSS var fallback for color.
wkhtmltopdf may ignore CSS vars; add a literal fallback.

-				color: var(--black-overlay-700);
+				color: #171717;
+				color: var(--black-overlay-700, #171717);

145-151: Normalize non‑standard font-weight.
420 isn’t a standard weight; use 400.

-				font-weight: 420;
+				font-weight: 400;

281-286: Render Terms HTML safely.
Ensure stored HTML isn’t escaped.

-		{{ doc.terms}}
+		{{ doc.terms | safe }}

206-206: Pass parent doc to get_formatted for qty.
Using 0 as context can mis-format quantities.

-				<td class="text-right text-dark">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
+				<td class="text-right text-dark">{{ item.get_formatted("qty", doc) }} {{ item.uom }}</td>

268-272: Use item_wise_tax_detail for per‑item tax.
Current calc ignores item‑wise rules; use the mapping from each tax row.

-					{% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100) %}
-					<td class="text-right">
-						({{ (row.rate) }}%) {{ frappe.format_value(tax_amount, {"fieldtype": "Currency"}, doc)
-						}}
-					</td>
-					{% endfor %}
+					{% for row in doc.taxes %}
+						{% set detail = frappe.parse_json(row.item_wise_tax_detail or '{}') %}
+						{% set per_item = detail.get(item.item_code) or [row.rate or 0, 0] %}
+						{% set per_item_rate = per_item[0] %}
+						{% set per_item_amount = per_item[1] %}
+						<td class="text-right">
+							({{ per_item_rate }}%) {{ frappe.format_value(per_item_amount, {"fieldtype": "Currency"}, doc) }}
+						</td>
+					{% endfor %}

174-180: “Bill From”/“Bill To” swapped;
trimming off-by-one.

Swap address variables and trim "
", "
", "
" correctly; also coalesce None to "".

-			{% set address = (doc.address_display) %} {% if address and address.rstrip().endswith("<br />") %} {% set
-			address = address[:-5] %} {% endif %} {% set company_address_display =
-			(doc.company_address_display) %} {% if company_address_display and company_address_display.rstrip().endswith("<br />") %} {%
-			set company_address_display = company_address_display[:-5] %} {% endif %}
-			<td><span class="heading">{{ _("Bill From") }}:</span><br />{{ address }}</td>
-			<td><span class="heading">{{ _("Bill To") }}:</span><br />{{ company_address_display }}</td>
+			{% set address = (doc.address_display or "").rstrip() %}
+			{% if address.endswith("<br />") %}{% set address = address[:-6] %}
+			{% elif address.endswith("<br/>") %}{% set address = address[:-5] %}
+			{% elif address.endswith("<br>") %}{% set address = address[:-4] %}{% endif %}
+			{% set company_address_display = (doc.company_address_display or "").rstrip() %}
+			{% if company_address_display.endswith("<br />") %}{% set company_address_display = company_address_display[:-6] %}
+			{% elif company_address_display.endswith("<br/>") %}{% set company_address_display = company_address_display[:-5] %}
+			{% elif company_address_display.endswith("<br>") %}{% set company_address_display = company_address_display[:-4] %}{% endif %}
+			<td><span class="heading">{{ _("Bill From") }}:</span><br />{{ company_address_display }}</td>
+			<td><span class="heading">{{ _("Bill To") }}:</span><br />{{ address }}</td>
🧹 Nitpick comments (3)
erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (3)

162-164: Guard missing due date.
Avoid rendering “None” or empty formatting.

-				<span class="heading">{{ _("Payment Due Date") }}:</span> {{
-				frappe.utils.format_date(doc.due_date) }}
+				<span class="heading">{{ _("Payment Due Date") }}:</span>
+				{% if doc.due_date %}{{ frappe.utils.format_date(doc.due_date) }}{% else %}-{% endif %}

229-233: Show discount row only when applicable; avoid “None%”.
Guard on discount_amount and format percentage safely.

-				<tr>
-					<td class="text-right text-muted">
-						{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
-					</td>
-					<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
-				</tr>
+				{% if doc.discount_amount %}
+				<tr>
+					<td class="text-right text-muted">
+						{{ _("Discount") }}{% if doc.additional_discount_percentage %} ({{ frappe.format_value(doc.additional_discount_percentage, {"fieldtype":"Percent"}, doc) }}){% endif %}:
+					</td>
+					<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
+				</tr>
+				{% endif %}

234-239: Composite “Tax (%)” is misleading.
Summing rates across rows isn’t accurate. Either drop “(x%)” or compute an effective rate from totals.

Option A (drop percent):

-				{% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if
-				tax_rate > 0 %}
+				{% if doc.total_taxes_and_charges %}
 				<tr>
-					<td class="text-right text-muted">{{ _("Tax") }} ({{ tax_rate }}%):</td>
+					<td class="text-right text-muted">{{ _("Tax") }}:</td>
 					<td class="text-right">{{ doc.get_formatted("total_taxes_and_charges", doc) }}</td>
 				</tr>
 				{% endif %}

Option B (effective rate):

{% set base_total = doc.base_total or doc.total %}
{% set eff = (doc.total_taxes_and_charges / base_total * 100) if base_total else None %}
...
<td class="text-right text-muted">{{ _("Tax") }}{% if eff is not none %} ({{ frappe.format_value(eff, {"fieldtype":"Percent"}, doc) }}){% endif %}:</td>
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 047f531 and 989ef53.

📒 Files selected for processing (1)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Patch Test
  • GitHub Check: Summary

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (7)
erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (7)

12-12: Custom heading not rendered: pass print_heading_template into macro call.

The macro supports print_heading_template but the invocation omits it, so custom headings will never show.

Apply this diff:

-        {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
+        {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, print_heading_template) }}

11-13: Avoid multiple id="header-html" inside the per‑page loop.

Duplicate IDs are invalid and wkhtmltopdf picks only the first header. Render #header-html once outside the loop when repeat_header_footer is enabled; render per‑page headers only when it’s disabled.

Proposed structure:

-{% for page in layout %}
-<div class="page-break invoice-page">
-  <div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
-    {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, print_heading_template) }}
-  </div>
+{% if print_settings.repeat_header_footer %}
+  <div id="header-html" class="hidden-pdf">
+    {{ add_header(1, layout|len, doc, letter_head, no_letterhead, footer, print_settings, print_heading_template) }}
+  </div>
+{% endif %}
+{% for page in layout %}
+<div class="page-break invoice-page">
+  {% if not print_settings.repeat_header_footer %}
+    {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, print_heading_template) }}
+  {% endif %}

113-124: Constrain item images to prevent layout shift.

Images aren’t sized; add rules so thumbnails consistently fit the 24px box.

Apply this diff:

 .img-thumb {
   min-width: 24px;
   min-height: 24px;
   max-width: 24px;
   max-height: 24px;
   border-radius: 6px;
   overflow: hidden;
   display: flex;
   align-items: center;
   justify-content: center;
   background: #f0f0f0;
 }
+/* Ensure contained image fills thumbnail consistently */
+.img-thumb img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  display: block;
+}

Also applies to: 141-152


158-176: Localizable labels and address HTML safety.

Wrap static labels with _() and render address_display as safe HTML to preserve line breaks.

Apply this diff:

-          <div class="info-text">Customer Name:</div>
-          <div class="info-text">Bill to:</div>
+          <div class="info-text">{{ _("Customer Name:") }}</div>
+          <div class="info-text">{{ _("Bill to:") }}</div>
...
-          <div class="info-text">Invoice Number:</div>
-          <div class="info-text">Invoice Date:</div>
-          <div class="info-text">Payment Due Date:</div>
+          <div class="info-text">{{ _("Invoice Number:") }}</div>
+          <div class="info-text">{{ _("Invoice Date:") }}</div>
+          <div class="info-text">{{ _("Payment Due Date:") }}</div>
...
-          <div class="info-text">{{ doc.address_display or "" }}</div>
+          <div class="info-text">{{ (doc.address_display or "") | safe }}</div>

194-201: Use table headers (th) for semantics and better accessibility.

Header cells are marked as td. Switch to th with scope.

Apply this diff:

-      <tr>
-        <td class="text-center">{{ _("No") }}</td>
-        <td class="text-left">{{ _("Item") }}</td>
-        <td class="text-center">{{ _("HSN/SAC") }}</td>
-        <td class="text-right">{{ _("Quantity") }}</td>
-        <td class="text-right">{{ _("Rate") }}</td>
-        <td class="text-right">{{ _("Amount") }}</td>
-      </tr>
+      <tr>
+        <th scope="col" class="text-center">{{ _("No") }}</th>
+        <th scope="col" class="text-left">{{ _("Item") }}</th>
+        <th scope="col" class="text-center">{{ _("HSN/SAC") }}</th>
+        <th scope="col" class="text-right">{{ _("Quantity") }}</th>
+        <th scope="col" class="text-right">{{ _("Rate") }}</th>
+        <th scope="col" class="text-right">{{ _("Amount") }}</th>
+      </tr>
-      <tr>
-        <td style="width: 5%" class="text-left">{{ _("HSN/SAC") }}</td>
-        <td style="width: 15%" class="text-right">{{ _("Taxable Amount") }}</td>
-        {% for row in doc.taxes %}
-        <td style="width: 10%" class="text-right">{{ row.description }} ({{ row.rate }}%)</td>
-        {% endfor %}
-      </tr>
+      <tr>
+        <th scope="col" style="width: 5%" class="text-left">{{ _("HSN/SAC") }}</th>
+        <th scope="col" style="width: 15%" class="text-right">{{ _("Taxable Amount") }}</th>
+        {% for row in doc.taxes %}
+        <th scope="col" style="width: 10%" class="text-right">{{ row.description }} ({{ row.rate }}%)</th>
+        {% endfor %}
+      </tr>

Also applies to: 281-286


261-262: Avoid forced capitalization on dynamic text.

text-capitalize can distort “In Words” in some locales/scripts.

Apply this diff:

-                <div class="text-capitalize"><span class="title"> {{ _("In Words: ") }}</span>{{ doc.in_words }}</div>
+                <div><span class="title"> {{ _("In Words: ") }}</span>{{ doc.in_words }}</div>

306-309: Render terms as HTML.

doc.terms is rich text; without | safe it may render escaped.

Apply this diff:

-    {{ doc.terms}}
+    {{ doc.terms | safe }}
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 241667c and 2424437.

📒 Files selected for processing (2)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1 hunks)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Patch Test
  • GitHub Check: Summary

@khushi8112 khushi8112 force-pushed the print-format-for-sales-invoice branch from 2424437 to dd5277d Compare September 15, 2025 15:53
@ruthra-kumar
Copy link
Member

@khushi8112

Is this ready for review?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd5277d and dde8778.

📒 Files selected for processing (1)
  • erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Patch Test
  • GitHub Check: Summary

@frappe frappe deleted a comment from coderabbitai bot Sep 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
erpnext/fixtures/letter_head.json (1)

27-27: Invalid HTML attribute: class="font-size: 13px;" → style="font-size: 13px;"

Same issue previously noted; still present here.

-                <span class="font-size: 13px;">
+                <span style="font-size: 13px;">
🧹 Nitpick comments (5)
erpnext/fixtures/letter_head.json (5)

4-4: Fix logo container alignment and image fit (current CSS doesn’t center or constrain the image).

.align-content on .logo has no effect without display:flex; the may overflow the 180x90 box.

-    .logo {
-        border: 1px solid #EDEDED;
-        border-radius: 15px;
-        width: 180px;
-        height: 90px;
-        align-content: center;
-        margin-right: 5px;
-    }
+    .logo {
+        border: 1px solid #EDEDED;
+        border-radius: 15px;
+        width: 180px;
+        height: 90px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 5px;
+        overflow: hidden;
+    }
+    .logo img {
+        max-width: 100%;
+        max-height: 100%;
+        object-fit: contain;
+        display: block;
+    }
@@
-                <img src="{{ frappe.utils.get_url(company_logo) }}">
+                <img src="{{ frappe.utils.get_url(company_logo) }}" alt="{{ doc.company }} logo">

27-27: Apply the same logo fit/alt improvements to the second fixture.

Mirror the .logo/.logo img rules and add alt on the .

@@
-        <div class="logo">
+        <div class="logo">
@@
-                <img src="{{ frappe.utils.get_url(company_logo) }}">
+                <img src="{{ frappe.utils.get_url(company_logo) }}" alt="{{ doc.company }} logo">

And include the same CSS block added in the first fixture for .logo and .logo img.


4-4: Minor: drop stray
in flex rows to avoid inconsistent spacing.

Line‑breaks after values inside flex rows aren’t needed and can cause odd gaps.

-            <div>{{ doc.name }}</div><br>
+            <div>{{ doc.name }}</div>
@@
-                    <div>{{ website }}</div><br>
+                    <div>{{ website }}</div>
@@
-                    <div>{{ email }}</div><br>
+                    <div>{{ email }}</div>
@@
-                    <div>{{ phone_no }}</div><br>
+                    <div>{{ phone_no }}</div>

Also applies to: 27-27


4-4: i18n/layout: avoid fixed 60px label width; allow wrapping for longer locales.

Hard width can truncate translated labels. Use min-width or auto layout.

-    .letterhead-title {
-        color: #7C7C7C;
-        width: 60px;
-        flex: 1 0 0;
-    }
+    .letterhead-title {
+        color: #7C7C7C;
+        min-width: 60px; /* acts as a hint */
+        flex: 0 0 auto;
+        word-break: break-word;
+    }

Also applies to: 27-27


4-4: Avoid per-render DB calls in the letterhead template

frappe.db.get_value, frappe.get_all and frappe.get_doc are executed during Jinja rendering in erpnext/fixtures/letter_head.json (lines 4 and 27). Replace single-field lookups with frappe.get_cached_value and move the address/company lookups into a precomputed print/context helper (fetch once in Python) to eliminate repeated queries.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e584b1 and 356ab2f.

📒 Files selected for processing (1)
  • erpnext/fixtures/letter_head.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Patch Test
  • GitHub Check: Summary

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (4)

235-235: Bug: wrong get_formatted argument for quantity.

Second arg should be the document, not 0, to ensure correct locale/precision.

Apply:

-					<td class="text-right">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
+					<td class="text-right">{{ item.get_formatted("qty", doc) }} {{ item.uom }}</td>

302-306: Invalid HTML: div inside table; use caption or move label above.

Place “Tax Breakup” as a or above the table.

Apply (preferred):

-		<table class="tax-table mt-15">
-			<div class="mt-40 pb-8">{{ _("Tax Breakup") }}</div>
+		<table class="tax-table mt-15">
+			<caption class="text-left mt-40 pb-8">{{ _("Tax Breakup") }}</caption>
 			<thead class="highlight-bg">

266-274: Tax percent computation is incorrect; remove synthetic percent.

Summing tax.rate is wrong for Actual/zero/compound rows. Show total only.

Apply:

-{% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if
-						if tax_rate > 0 %}
-						<tr class="row-divider">
-							<td class="text-right title">{{ _("Tax") }} ({{ tax_rate }}%):</td>
-							<td class="text-right">
-								{{ doc.get_formatted("total_taxes_and_charges", doc) }}
-							</td>
-						</tr>
-						{% endif %}
+{% if doc.total_taxes_and_charges %}
+						<tr class="row-divider">
+							<td class="text-right title">{{ _("Taxes and Charges:") }}</td>
+							<td class="text-right">{{ doc.get_formatted("total_taxes_and_charges", doc) }}</td>
+						</tr>
+{% endif %}

318-323: Currency mismatch and misleading per‑item tax math.

Use document currency fields; current calc mixes base_* with doc currency and assumes percent charge type.

Apply minimal currency fix:

-					<td class="text-right">{{ item.get_formatted("base_net_amount", doc) }}</td>
-{% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100) %}
+					<td class="text-right">{{ item.get_formatted("net_amount", doc) }}</td>
+{% for row in doc.taxes %} {% set tax_amount = item.net_amount * (row.rate / 100) %}
 					<td class="text-right">
 						{{ frappe.format_value(tax_amount, {"fieldtype": "Currency"}, doc) }}
 					</td>

Better: replace the per‑item breakup with a per‑tax summary to handle Actual/compound rows correctly. Example:

-{% if doc.taxes_and_charges %}
-  ... current per‑item breakup table ...
-{% endif %}
+{% if doc.taxes and doc.taxes|length > 0 %}
+  <div class="mt-40 pb-8">{{ _("Taxes and Charges Breakup") }}</div>
+  <table class="tax-table mt-15">
+    <thead class="highlight-bg">
+      <tr>
+        <th scope="col" class="text-left">{{ _("Description") }}</th>
+        <th scope="col" class="text-right">{{ _("Rate") }}</th>
+        <th scope="col" class="text-right">{{ _("Amount") }}</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for row in doc.taxes %}
+      <tr>
+        <td class="text-left">{{ row.description }}</td>
+        <td class="text-right">
+          {% if row.charge_type in ["On Net Total","On Previous Row Amount","On Previous Row Total"] and row.rate %}
+            {{ row.rate }}%
+          {% else %}—{% endif %}
+        </td>
+        <td class="text-right">{{ row.get_formatted("tax_amount", doc) }}</td>
+      </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+{% endif %}

Also applies to: 319-323

🧹 Nitpick comments (8)
erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (8)

302-302: Show tax breakup based on presence of tax rows, not template name.

doc.taxes_and_charges is a template string; taxes can exist without it.

Apply:

-{% if doc.taxes_and_charges %}
+{% if doc.taxes and doc.taxes|length > 0 %}

260-265: Hide discount row when no discount; avoid “(0%)”.

Only render when discount exists; conditionally show percent label.

Apply:

-						<tr class="row-divider">
-							<td class="text-right title">
-								{{ _("Discount") }} ({{ doc.additional_discount_percentage or 0 }}%):
-							</td>
-							<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
-						</tr>
+{% if doc.discount_amount %}
+						<tr class="row-divider">
+							<td class="text-right title">
+								{{ _("Discount") }}{% if doc.additional_discount_percentage %} ({{ doc.additional_discount_percentage }}%){% endif %}:
+							</td>
+							<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
+						</tr>
+{% endif %}

207-215: A11y: use th for table headers.

Header cells should be .

Apply:

-					<td class="text-center">{{ _("No") }}</td>
-					<td class="text-left">{{ _("Item") }}</td>
-					<td class="text-center">{{ _("HSN/SAC") }}</td>
-					<td class="text-right">{{ _("Quantity") }}</td>
-					<td class="text-right">{{ _("Rate") }}</td>
-					<td class="text-right">{{ _("Amount") }}</td>
+					<th scope="col" class="text-center">{{ _("No") }}</th>
+					<th scope="col" class="text-left">{{ _("Item") }}</th>
+					<th scope="col" class="text-center">{{ _("HSN/SAC") }}</th>
+					<th scope="col" class="text-right">{{ _("Quantity") }}</th>
+					<th scope="col" class="text-right">{{ _("Rate") }}</th>
+					<th scope="col" class="text-right">{{ _("Amount") }}</th>

307-312: A11y: use th in tax table header too.

Improve semantics for screen readers.

Apply:

-					<td style="width: 5%" class="text-left">{{ _("HSN/SAC") }}</td>
-					<td style="width: 15%" class="text-right">{{ _("Taxable Amount") }}</td>
-					{% for row in doc.taxes %}
-					<td style="width: 10%" class="text-right">{{ row.description }} ({{ row.rate }}%)</td>
+					<th scope="col" style="width: 5%" class="text-left">{{ _("HSN/SAC") }}</th>
+					<th scope="col" style="width: 15%" class="text-right">{{ _("Taxable Amount") }}</th>
+					{% for row in doc.taxes %}
+					<th scope="col" style="width: 10%" class="text-right">{{ row.description }}{% if row.rate %} ({{ row.rate }}%){% endif %}</th>
 					{% endfor %}

173-176: Localize static labels in header.

Wrap labels with _() for translation consistency.

Apply:

-						<div class="info-text">Customer Name:</div>
-						<div class="info-text">Bill to:</div>
+						<div class="info-text">{{ _("Customer Name:") }}</div>
+						<div class="info-text">{{ _("Bill to:") }}</div>
...
-						<div class="info-text">Invoice Number:</div>
-						<div class="info-text">Invoice Date:</div>
-						<div class="info-text">Payment Due Date:</div>
+						<div class="info-text">{{ _("Invoice Number:") }}</div>
+						<div class="info-text">{{ _("Invoice Date:") }}</div>
+						<div class="info-text">{{ _("Payment Due Date:") }}</div>

Also applies to: 184-186


285-287: Tweak translation string for “In Words”.

Avoid trailing space inside _(); place space outside.

Apply:

-										<span class="title"> {{ _("In Words: ") }}</span>{{ doc.in_words }}
+										<span class="title">{{ _("In Words:") }}</span> {{ doc.in_words }}

1-5: Unused macro parameter “footer”.

Parameter is accepted and passed but never used. Remove to reduce surface area.

Apply:

-{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None,
-print_heading_template=None) -%}
+{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, print_settings=None,
+print_heading_template=None) -%}
...
-		{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
+		{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, print_settings) }}

Also applies to: 8-8


142-144: Non‑standard font-weight value.

420 is unusual; consider 400 (normal) or 500 (medium) for predictability.

Apply:

-			font-weight: 420;
+			font-weight: 400;
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00afd6f and 8cc2bd5.

📒 Files selected for processing (2)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Patch Test
  • GitHub Check: Summary
🔇 Additional comments (1)
erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1)

197-241: Overall: solid structure and styling.

Layout, formatting calls, and multi‑page header usage look good. After fixing the above, this will be production‑ready.

Please re-render a sample invoice with: no taxes, percent taxes, actual charges, and zero discount to confirm conditional rows behave as expected.

Also applies to: 243-300, 302-337

@khushi8112 khushi8112 force-pushed the print-format-for-sales-invoice branch from 8cc2bd5 to 7d4daa3 Compare September 19, 2025 08:47
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (2)

268-274: Tax percentage display is misleading for mixed/actual taxes.

Summing rates across rows (and filtering by tax_amount) isn’t meaningful; show total taxes without a single “%”.

-					{% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if
-					tax_rate > 0 %}
-					<tr>
-						<td class="text-right text-muted">{{ _("Tax") }} ({{ tax_rate }}%):</td>
-						<td class="text-right">{{ doc.get_formatted("total_taxes_and_charges", doc) }}</td>
-					</tr>
-					{% endif %}
+					{% if doc.total_taxes_and_charges %}
+					<tr>
+						<td class="text-right text-muted">{{ _("Taxes:") }}</td>
+						<td class="text-right">{{ doc.get_formatted("total_taxes_and_charges", doc) }}</td>
+					</tr>
+					{% endif %}

263-267: Guard discount percentage when missing; avoid “(None%)”.

Only show the percentage when present; optionally hide the row when discount_amount is zero.

-						<td class="text-right text-muted">
-							{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
-						</td>
-						<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
+						{% if doc.discount_amount %}
+						<td class="text-right text-muted">
+							{{ _("Discount") }}{% if doc.additional_discount_percentage %} ({{ doc.additional_discount_percentage }}%){% endif %}:
+						</td>
+						<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
+						{% endif %}
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d4daa3 and 7d404c6.

📒 Files selected for processing (2)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Patch Test
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Summary
🔇 Additional comments (5)
erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (5)

27-27: wkhtmltopdf compatibility: add CSS var fallback + normalize font-weight.

Add a literal fallback for the CSS variable and use a standard weight; 420 isn’t valid across engines.

-			color: var(--black-overlay-700);
+			color: var(--black-overlay-700, #171717);
-			font-weight: 420;
+			font-weight: 400;

Also applies to: 166-166


252-254: Verify text-uppercase availability.

If the base print CSS doesn’t provide .text-uppercase, add text-transform: uppercase; in this template.


201-212: “Bill From/To” reversed and
trim off-by-one; also handle
variants.

Show company address under “Bill From” and customer address under “Bill To”; trim 6 chars for "
" and support "
" and "
".

-				{% set address = (doc.address_display) %}
-				{% if address and address.rstrip().endswith("<br />") %}
-					{% set address = address[:-5] %}
-				{% endif %}
-
-				{% set company_address_display = (doc.company_address_display) %}
-				{% if company_address_display and company_address_display.rstrip().endswith("<br />") %}
-					{% set company_address_display = company_address_display[:-5] %}
-				{% endif %}
-				<td><span class="heading">{{ _("Bill From") }}:</span><br />{{ address }}</td>
-				<td><span class="heading">{{ _("Bill To") }}:</span><br />{{ company_address_display }}</td>
+				{% set address = (doc.address_display or "").rstrip() %}
+				{% if address.endswith("<br />") %}{% set address = address[:-6] %}{% elif address.endswith("<br/>") %}{% set address = address[:-5] %}{% elif address.endswith("<br>") %}{% set address = address[:-4] %}{% endif %}
+
+				{% set company_address_display = (doc.company_address_display or "").rstrip() %}
+				{% if company_address_display.endswith("<br />") %}{% set company_address_display = company_address_display[:-6] %}{% elif company_address_display.endswith("<br/>") %}{% set company_address_display = company_address_display[:-5] %}{% elif company_address_display.endswith("<br>") %}{% set company_address_display = company_address_display[:-4] %}{% endif %}
+				<td><span class="heading">{{ _("Bill From") }}:</span><br />{{ company_address_display }}</td>
+				<td><span class="heading">{{ _("Bill To") }}:</span><br />{{ address }}</td>

238-238: Fix qty formatting context.

Pass the parent doc to get_formatted; 0 can mis-format quantities.

-					<td class="text-right text-dark">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
+					<td class="text-right text-dark">{{ item.get_formatted("qty", doc) }} {{ item.uom }}</td>

302-307: Use item_wise_tax_detail for per‑item tax breakup.

Current math ignores item-wise rules and discounts; read the row’s mapping.

-						{% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100)
-						%}
-						<td class="text-right">
-							({{ (row.rate) }}%) {{ frappe.format_value(tax_amount, {"fieldtype": "Currency"},
-							doc) }}
-						</td>
-						{% endfor %}
+						{% for row in doc.taxes %}
+							{% set detail = frappe.parse_json(row.item_wise_tax_detail or '{}') %}
+							{% set per_item = (detail.get(item.item_code) if detail else None) %}
+							{% set per_item_rate = (per_item[0] if per_item else (row.rate or 0)) %}
+							{% set per_item_amount = (per_item[1] if per_item else 0) %}
+							<td class="text-right">
+								({{ per_item_rate }}%) {{ frappe.format_value(per_item_amount, {"fieldtype": "Currency"}, doc) }}
+							</td>
+						{% endfor %}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
erpnext/hooks.py (1)

604-646: Replace invalid DocType in global_search_doctypes

  • In erpnext/hooks.py under the "Default" group, change
    {"doctype": "Sales Taxes and Charges", "index": 27}
    to
    {"doctype": "Sales Taxes and Charges Template", "index": 27}
🧹 Nitpick comments (5)
erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1)

170-176: Wrap static labels with _() for i18n

Localize these user-facing labels.

Apply:

-						<div class="info-text">Customer Name:</div>
-						<div class="info-text">Bill to:</div>
+						<div class="info-text">{{ _("Customer Name:") }}</div>
+						<div class="info-text">{{ _("Bill to:") }}</div>
@@
-						<div class="info-text">Invoice Number:</div>
-						<div class="info-text">Invoice Date:</div>
-						<div class="info-text">Payment Due Date:</div>
+						<div class="info-text">{{ _("Invoice Number:") }}</div>
+						<div class="info-text">{{ _("Invoice Date:") }}</div>
+						<div class="info-text">{{ _("Payment Due Date:") }}</div>

Also applies to: 181-184

erpnext/accounts/letterhead/letterhead_with_background_colour.html (2)

101-106: Avoid doc-specific fields in letterhead

Letterhead should be reusable; don’t hardcode Sales Invoice title and doc.name here (already shown in the print format).

Apply:

-            <div style="height: 90px; margin-bottom: 10px; text-align: right;">
-                <div class="invoice-title">{{ _("Sales Invoice") }}</div>
-                <div class="invoice-number">{{ doc.name }}</div>
-                <br>
-            </div>
+            <div style="height: 90px; margin-bottom: 10px; text-align: right;"></div>

108-120: Reduce DB round-trips: fetch website/email/phone in one call

Consolidate 3 get_value calls into one for efficiency.

Apply:

-                {% set website = frappe.db.get_value("Company", doc.company, "website") %}
-                {% set email = frappe.db.get_value("Company", doc.company, "email") %}
-                {% set phone_no = frappe.db.get_value("Company", doc.company, "phone_no") %}
+                {% set company_info = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
@@
-                {% if website %}
-                    <div><span class="contact-title">{{ _("Website:") }}</span><span class="contact-value">{{ website }}</span></div>
+                {% if company_info and company_info.website %}
+                    <div><span class="contact-title">{{ _("Website:") }}</span><span class="contact-value">{{ company_info.website }}</span></div>
                 {% endif %}
-                {% if email %}
-                    <div><span class="contact-title">{{ _("Email:") }}</span><span class="contact-value">{{ email }}</span></div>
+                {% if company_info and company_info.email %}
+                    <div><span class="contact-title">{{ _("Email:") }}</span><span class="contact-value">{{ company_info.email }}</span></div>
                 {% endif %}
-                {% if phone_no %}
-                    <div><span class="contact-title">{{ _("Contact:") }}</span><span class="contact-value">{{ phone_no }}</span></div>
+                {% if company_info and company_info.phone_no %}
+                    <div><span class="contact-title">{{ _("Contact:") }}</span><span class="contact-value">{{ company_info.phone_no }}</span></div>
                 {% endif %}
erpnext/accounts/letterhead/letterhead_plain.html (2)

98-101: Avoid doc-specific fields in letterhead

Remove Invoice/Name from letterhead; it’s already in the print format and reduces reusability.

Apply:

-                <div class="invoice-info">
-                    <span class="invoice-label">{{ _("Invoice:") }}</span>
-                    <span>{{ doc.name }}</span>
-                </div>

94-96: Reduce DB round-trips for company contact fields

Fetch website/email/phone in one call.

Apply:

-                {% set website = frappe.db.get_value("Company", doc.company, "website") %}
-                {% set email = frappe.db.get_value("Company", doc.company, "email") %}
-                {% set phone_no = frappe.db.get_value("Company", doc.company, "phone_no") %}
+                {% set company_info = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
@@
-                {% if website %}
+                {% if company_info and company_info.website %}
                     <div class="invoice-info">
                         <span class="invoice-label">{{ _("Website:") }}</span>
-                        <span>{{ website }}</span>
+                        <span>{{ company_info.website }}</span>
                     </div>
                 {% endif %}
-                {% if email %}
+                {% if company_info and company_info.email %}
                     <div class="invoice-info">
                         <span class="invoice-label">{{ _("Email:") }}</span>
-                        <span>{{ email }}</span>
+                        <span>{{ company_info.email }}</span>
                     </div>
                 {% endif %}
-                {% if phone_no %}
+                {% if company_info and company_info.phone_no %}
                     <div class="invoice-info">
                         <span class="invoice-label">{{ _("Contact:") }}</span>
-                        <span>{{ phone_no }}</span>
+                        <span>{{ company_info.phone_no }}</span>
                     </div>
                 {% endif %}

Also applies to: 103-119

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d404c6 and 3e4a29d.

📒 Files selected for processing (6)
  • erpnext/accounts/letterhead/letterhead_plain.html (1 hunks)
  • erpnext/accounts/letterhead/letterhead_with_background_colour.html (1 hunks)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (1 hunks)
  • erpnext/hooks.py (1 hunks)
  • erpnext/setup/install.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html
🧰 Additional context used
🪛 GitHub Actions: Linters
erpnext/setup/install.py

[error] 268-280: Blocking rule fired: frappe-semgrep-rules.rules.frappe-manual-commit. Manually committing a transaction is highly discouraged. If manual commit is required, add a justification comment and a 'nosemgrep' on the same line; otherwise refactor to use the framework's transaction management.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Patch Test
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Summary
🔇 Additional comments (2)
erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (2)

246-246: Fix get_formatted arg for qty

Second arg must be the document, not 0.

Apply:

-					<td class="text-right">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
+					<td class="text-right">{{ item.get_formatted("qty", doc) }} {{ item.uom }}</td>

277-285: Remove synthetic tax percent; it’s incorrect

Summing tax.rate is wrong for Actual/zero/compound rows. Show total taxes without a fake percent.

Apply:

-						{% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if tax_rate > 0 %}
-						<tr class="row-divider">
-							<td class="text-right title">{{ _("Tax") }} ({{ tax_rate }}%):</td>
-							<td class="text-right">
-								{{ doc.get_formatted("total_taxes_and_charges", doc) }}
-							</td>
-						</tr>
-						{% endif %}
+						{% if doc.total_taxes_and_charges %}
+						<tr class="row-divider">
+							<td class="text-right title">{{ _("Taxes and Charges:") }}</td>
+							<td class="text-right">{{ doc.get_formatted("total_taxes_and_charges", doc) }}</td>
+						</tr>
+						{% endif %}

@khushi8112 khushi8112 force-pushed the print-format-for-sales-invoice branch from 3e4a29d to b22d1d4 Compare September 25, 2025 07:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
erpnext/accounts/letterhead/letterhead_with_background_colour.html (1)

108-121: Reduce DB round-trips and add alt text

Fetch multiple Company fields in one call and add alt text for the logo for accessibility.

Apply:

-            <div style="text-align: left; float: right;" class="other-details">
-                {% set website = frappe.db.get_value("Company", doc.company, "website") %}
-                {% set email = frappe.db.get_value("Company", doc.company, "email") %}
-                {% set phone_no = frappe.db.get_value("Company", doc.company, "phone_no") %}
+            <div style="text-align: left; float: right;" class="other-details">
+                {% set company = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) or {} %}
 
-                {% if website %}
-                    <div><span class="contact-title">{{ _("Website:") }}</span><span class="contact-value">{{ website }}</span></div>
+                {% if company.website %}
+                    <div><span class="contact-title">{{ _("Website:") }}</span><span class="contact-value">{{ company.website }}</span></div>
                 {% endif %}
-                {% if email %}
-                    <div><span class="contact-title">{{ _("Email:") }}</span><span class="contact-value">{{ email }}</span></div>
+                {% if company.email %}
+                    <div><span class="contact-title">{{ _("Email:") }}</span><span class="contact-value">{{ company.email }}</span></div>
                 {% endif %}
-                {% if phone_no %}
-                    <div><span class="contact-title">{{ _("Contact:") }}</span><span class="contact-value">{{ phone_no }}</span></div>
+                {% if company.phone_no %}
+                    <div><span class="contact-title">{{ _("Contact:") }}</span><span class="contact-value">{{ company.phone_no }}</span></div>
                 {% endif %}
             </div>

And at Line 77:

-                    <img src="{{ frappe.utils.get_url(company_logo) }}">
+                    <img src="{{ frappe.utils.get_url(company_logo) }}" alt="{{ doc.company }} logo">
erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (2)

6-9: Remove empty else branch

No-op else adds noise.

Apply:

 {% if print_heading_template %}
 	{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
-{% else %}
 {% endif %}

263-267: Guard against None for discount percent

Show 0% instead of “None%”.

Apply:

-						<td class="text-right text-muted">
-							{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
-						</td>
+						<td class="text-right text-muted">
+							{{ _("Discount") }} ({{ doc.additional_discount_percentage or 0 }}%):
+						</td>
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e4a29d and b22d1d4.

📒 Files selected for processing (9)
  • erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py (1 hunks)
  • erpnext/accounts/letterhead/letterhead_plain.html (1 hunks)
  • erpnext/accounts/letterhead/letterhead_with_background_colour.html (1 hunks)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1 hunks)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.json (1 hunks)
  • erpnext/hooks.py (1 hunks)
  • erpnext/setup/install.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • erpnext/accounts/letterhead/letterhead_plain.html
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json
  • erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: semgrep
  • GitHub Check: Patch Test
  • GitHub Check: Summary
🔇 Additional comments (12)
erpnext/setup/install.py (3)

38-38: Good call: create letterheads during install

Hooking create_letter_head() into after_install() ensures the assets exist post-setup.


251-257: Correct base path

Using frappe.get_app_path("erpnext", "accounts", "letterhead") points to the right template directory.


259-272: Remove manual commit (CI blocker) and rely on outer transaction

The explicit frappe.db.commit() inside create_letter_head() violates CI rules and is unnecessary—after_install() commits.

Apply:

 	for name, filename in letterheads.items():
 		if not frappe.db.exists("Letter Head", name):
 			content = frappe.read_file(os.path.join(base_path, filename))
 			doc = frappe.get_doc(
 				{
 					"doctype": "Letter Head",
 					"letter_head_name": name,
 					"source": "HTML",
 					"content": content,
 				}
 			)
 			doc.insert(ignore_permissions=True)
 
-	frappe.db.commit()
+
erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.json (1)

1-32: Print Format metadata looks consistent

DocType, module, generator, language, margins, and standard flag are set appropriately.

erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (5)

21-28: wkhtmltopdf compatibility: add CSS var fallback

CSS variables are unreliable in wkhtmltopdf. Provide a literal fallback.

Apply:

 		.invoice-wrapper {
 			font-family: "Inter", sans-serif;
-			color: var(--black-overlay-700);
+			color: var(--black-overlay-700, #171717);
 		}

162-168: Normalize non-standard font-weight

Use standard weight 400 instead of 420 for broader support.

Apply:

 		.print-format {
 			color: #171717;
 			font-size: 14px;
 			font-style: normal;
-			font-weight: 420;
+			font-weight: 400;
 			line-height: 21px;
 			padding: 0px;
 			letter-spacing: 0.14px;

201-212: “Bill From”/“Bill To” swapped; also incorrect trim length

Swap company/customer addresses and remove the full "
" (6 chars). Handle variants.

Apply:

-				{% set address = (doc.address_display) %}
-				{% if address and address.rstrip().endswith("<br />") %}
-					{% set address = address[:-5] %}
-				{% endif %}
-
-				{% set company_address_display = (doc.company_address_display) %}
-				{% if company_address_display and company_address_display.rstrip().endswith("<br />") %}
-					{% set company_address_display = company_address_display[:-5] %}
-				{% endif %}
-				<td><span class="heading">{{ _("Bill From") }}:</span><br />{{ address }}</td>
-				<td><span class="heading">{{ _("Bill To") }}:</span><br />{{ company_address_display }}</td>
+				{% set address = (doc.address_display or "").rstrip() %}
+				{% if address.endswith("<br />") %}{% set address = address[:-6] %}{% elif address.endswith("<br/>") %}{% set address = address[:-5] %}{% elif address.endswith("<br>") %}{% set address = address[:-4] %}{% endif %}
+
+				{% set company_address_display = (doc.company_address_display or "").rstrip() %}
+				{% if company_address_display.endswith("<br />") %}{% set company_address_display = company_address_display[:-6] %}{% elif company_address_display.endswith("<br/>") %}{% set company_address_display = company_address_display[:-5] %}{% elif company_address_display.endswith("<br>") %}{% set company_address_display = company_address_display[:-4] %}{% endif %}
+				<td><span class="heading">{{ _("Bill From") }}:</span><br />{{ company_address_display }}</td>
+				<td><span class="heading">{{ _("Bill To") }}:</span><br />{{ address }}</td>

238-238: Fix qty formatting context

Pass the parent doc, not 0.

Apply:

-					<td class="text-right text-dark">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
+					<td class="text-right text-dark">{{ item.get_formatted("qty", doc) }} {{ item.uom }}</td>

268-274: Remove synthetic tax percent; show total taxes only

Summing rate is incorrect across charge types. Display the total taxes without a derived percent.

Apply:

-					{% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if
-					tax_rate > 0 %}
-					<tr>
-						<td class="text-right text-muted">{{ _("Tax") }} ({{ tax_rate }}%):</td>
-						<td class="text-right">{{ doc.get_formatted("total_taxes_and_charges", doc) }}</td>
-					</tr>
-					{% endif %}
+					{% if doc.total_taxes_and_charges %}
+					<tr>
+						<td class="text-right text-muted">{{ _("Taxes and Charges:") }}</td>
+						<td class="text-right">{{ doc.get_formatted("total_taxes_and_charges", doc) }}</td>
+					</tr>
+					{% endif %}
erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (2)

12-14: Header wrapper: repeat support looks good

Using id="header-html" with hidden-pdf gated by repeat_header_footer is correct.


246-246: Fix qty formatting context

Pass the parent doc, not 0.

Apply:

-					<td class="text-right">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
+					<td class="text-right">{{ item.get_formatted("qty", doc) }} {{ item.uom }}</td>
erpnext/hooks.py (1)

605-646: Confirmed unique global_search_doctypes definition — no other assignments found across the codebase.

@frappe frappe deleted a comment from coderabbitai bot Sep 26, 2025
@frappe frappe deleted a comment from coderabbitai bot Sep 26, 2025
@frappe frappe deleted a comment from coderabbitai bot Sep 26, 2025
@frappe frappe deleted a comment from coderabbitai bot Sep 26, 2025
@khushi8112 khushi8112 force-pushed the print-format-for-sales-invoice branch from b22d1d4 to d4a459e Compare September 30, 2025 05:23
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

📝 Walkthrough

Walkthrough

This change introduces a new Sales Invoice print flow. A before_print hook on SalesInvoice checks company details and emits a realtime event when data is missing. A client script (page_js for print page) listens to this event, opens a dialog to collect company/logo/contact/address, and saves via a new whitelisted method that updates Company, creates/links Address, and updates the invoice’s address display. Two new Sales Invoice print formats are added. Two letterhead HTML templates are introduced. Setup now creates default letterheads. Global search doctypes and page_js are added in hooks. One test gains a blank line.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • ruthra-kumar

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary change by indicating the addition of a print format for Sales Invoice and uses clear, concise language without extraneous details, making it easy for teammates to understand the main focus of the PR.
Description Check ✅ Passed The description, though primarily composed of screenshots, directly relates to the print format changes by visually illustrating the new layouts, and it remains on-topic with the PR’s objective to add a Sales Invoice print format.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (4)
erpnext/accounts/letterhead/letterhead_with_background_colour.html (1)

102-106: Letterhead should not hardcode document-specific content.

Lines 103-104 embed "Sales Invoice" and doc.name in the letterhead, reducing reusability across DocTypes. Move document-specific title and number into the print format template, keeping the letterhead focused on branding only.

Apply this diff:

-            <div style="height: 90px; margin-bottom: 10px; text-align: right;">
-                <div class="invoice-title">{{ _("Sales Invoice") }}</div>
-                <div class="invoice-number">{{ doc.name }}</div>
-                <br>
-            </div>
+            <!-- Document title/number belongs in print format, not letterhead -->
erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1)

246-246: Fix incorrect get_formatted argument for quantity.

The second argument to get_formatted should be the document, not 0. Passing 0 risks incorrect formatting.

Apply this diff:

-					<td class="text-right">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
+					<td class="text-right">{{ item.get_formatted("qty", doc) }} {{ item.uom }}</td>
erpnext/setup/install.py (1)

272-272: Remove manual commit (violates CI rule).

Manual commits are flagged by CI. The outer after_install() transaction already commits at line 39, so this commit is unnecessary.

Apply this diff:

-	frappe.db.commit()
erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (1)

237-247: Qty formatting still passes incorrect context.

As noted in previous review, Line 241 passes 0 as the doc/context to get_formatted("qty", 0), which may mis-format quantities.

Apply this fix:

-<td class="text-right text-dark">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
+<td class="text-right text-dark">{{ item.get_formatted("qty", doc) }} {{ item.uom }}</td>
🧹 Nitpick comments (4)
erpnext/accounts/letterhead/letterhead_plain.html (1)

93-120: Invoice information rendering is correct.

The conditional rendering of website, email, and phone number properly handles optional fields.

The three separate frappe.db.get_value calls could be combined into a single query for slight efficiency:

-{% set website = frappe.db.get_value("Company", doc.company, "website") %}
-{% set email = frappe.db.get_value("Company", doc.company, "email") %}
-{% set phone_no = frappe.db.get_value("Company", doc.company, "phone_no") %}
+{% set company_info = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
+{% set website = company_info.website if company_info else None %}
+{% set email = company_info.email if company_info else None %}
+{% set phone_no = company_info.phone_no if company_info else None %}
erpnext/public/js/print.js (3)

97-99: Reconsider the hard-coded reload delay.

The 1-second timeout before reloading is arbitrary and could cause issues:

  • If the server save is slow or the network is congested, the reload might occur before the transaction commits, showing stale data.
  • The timeout provides no user control and ignores actual completion status.

Consider one of these approaches:

  1. Reload immediately after the successful callback (the server has already responded, so the data is committed).
  2. Use frappe.set_route() to navigate away and back, triggering a clean refresh without a full page reload.
  3. If a delay is truly necessary, increase it or poll for completion.

Example for approach 1:

 					callback: function () {
 						companyDetailsDialog.hide();
-						frappe.msgprint(__("Saving document..."));
-						setTimeout(() => {
-							window.location.reload();
-						}, 1000);
+						frappe.show_alert({
+							message: __("Company details saved successfully"),
+							indicator: "green"
+						});
+						window.location.reload();
 					},

15-16: Use boolean literals for field properties.

The dialog field definitions use 0 and 1 for the reqd and hidden properties instead of false and true. While this works (JavaScript is truthy/falsy), using proper booleans is more idiomatic and improves readability.

Example for the first field:

 				{
 					label: "Company Logo",
 					fieldname: "company_logo",
 					fieldtype: "Attach Image",
-					reqd: data.company_logo ? 0 : 1,
-					hidden: data.company_logo ? 1 : 0,
+					reqd: !data.company_logo,
+					hidden: !!data.company_logo,
 				},

Apply similar changes to all other fields with conditional reqd/hidden properties.

Also applies to: 22-23, 29-30, 38-39, 45-46, 52-52, 59-60, 66-66, 81-82


86-102: Consider adding a loading indicator during save.

When the user clicks "Save", there's no visual feedback that the operation is in progress until the frappe.msgprint appears. For slow networks or large transactions, this could leave the user uncertain whether their action registered.

Add a loading state to the primary action:

 			primary_action(values) {
+				companyDetailsDialog.set_primary_action("Saving...", null);
+				companyDetailsDialog.disable_primary_action();
 				frappe.call({
 					method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.save_company_master_details",
 					args: {
 						name: data.name,
 						company: data.company,
 						details: values,
 					},
 					callback: function () {
 						companyDetailsDialog.hide();
 						frappe.msgprint(__("Saving document..."));
 						setTimeout(() => {
 							window.location.reload();
 						}, 1000);
 					},
 					error: function () {
+						companyDetailsDialog.set_primary_action("Save", companyDetailsDialog.primary_action);
+						companyDetailsDialog.enable_primary_action();
 						beforePrintHandled = false;
 						frappe.msgprint(__("Failed to save company details. Please try again."));
 					},
 				});
 			},
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b09dd0 and d4a459e.

📒 Files selected for processing (11)
  • erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py (1 hunks)
  • erpnext/accounts/doctype/sales_invoice/sales_invoice.py (2 hunks)
  • erpnext/accounts/letterhead/letterhead_plain.html (1 hunks)
  • erpnext/accounts/letterhead/letterhead_with_background_colour.html (1 hunks)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (1 hunks)
  • erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.json (1 hunks)
  • erpnext/hooks.py (2 hunks)
  • erpnext/public/js/print.js (1 hunks)
  • erpnext/setup/install.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
erpnext/accounts/doctype/sales_invoice/sales_invoice.py (2)
erpnext/stock/doctype/delivery_note/delivery_note.py (1)
  • before_print (233-251)
erpnext/controllers/accounts_controller.py (1)
  • before_print (607-629)
🪛 GitHub Actions: Linters
erpnext/setup/install.py

[error] 272-272: frappe-semgrep-rules.rules.frappe-manual-commit: Manually committing a transaction is highly discouraged. If manual commit is required, add a comment explaining why and add '# nosemgrep' on the same line. (See https://frappeframework.com/docs/user/en/api/database#database-transaction-model)

🪛 Ruff (0.13.1)
erpnext/accounts/doctype/sales_invoice/sales_invoice.py

282-282: Unused method argument: settings

(ARG002)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Patch Test
  • GitHub Check: Summary
🔇 Additional comments (16)
erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py (1)

84-84: LGTM – formatting improvement.

The added blank line improves readability with no functional impact.

erpnext/hooks.py (2)

54-54: LGTM – print script wired correctly.

The page_js mapping loads the new print flow script on the print page.


605-648: LGTM – global search configuration added.

The global_search_doctypes configuration establishes search priority for common ERPNext DocTypes.

erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html (2)

269-276: LGTM – tax display corrected.

The tax display now iterates individual tax rows without synthetic percent computation, aligning with the previous fix.


288-304: LGTM – grand total section correct.

The grand total and in-words display use correct field references and formatting.

erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.json (1)

1-33: LGTM! Print format configuration is well-structured.

The JSON configuration for the Sales Invoice print format is properly structured with appropriate settings for a Jinja-based template using wkhtmltopdf.

erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json (1)

1-33: Clarify the need for two similar print formats.

Two print format configurations are being added with nearly identical settings:

  • "Sales Invoice Print Format" (in sales_invoice_print_format/)
  • "Print Format Sales Invoice" (in print_format_sales_invoice/)

Both target the same DocType ("Sales Invoice") with the same settings (Jinja type, wkhtmltopdf, font size 14, margins 15.0). Please verify whether both are intentional or if one should be removed to avoid duplication and potential user confusion.

erpnext/accounts/letterhead/letterhead_plain.html (3)

1-55: CSS styling looks appropriate for letterhead.

The inline styles are well-structured for a print/email letterhead template with appropriate spacing, borders, and layout.


57-67: Logo rendering logic is correct.

The conditional rendering and URL handling for the company logo are implemented properly.


69-91: Address resolution logic is correct.

The Dynamic Link query pattern properly finds the company's address, and the conditional rendering safely handles cases where no address exists.

erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html (5)

1-19: Macro and page layout structure is correct.

The add_header macro properly handles letterhead, print heading, and document status, and the page loop correctly iterates through the layout.


21-184: CSS styling is comprehensive and well-organized.

The styles appropriately handle tables, text formatting, spacing, and print-specific layout needs.


251-270: Totals section structure and Net Total discount logic are correct.

The "Total in words" display and conditional Net Total discount rendering are properly implemented.


271-286: Tax rendering logic is correct.

The tax loop properly displays each tax's description, rate, and amount, respecting the conditions for zero-amount taxes and inclusive taxes. The conditional Grand Total discount rendering is also correct.


287-303: Grand Total and Terms sections are correct.

The grand total display and conditional terms rendering are properly implemented.

erpnext/public/js/print.js (1)

3-5: Validate data object structure.

The code accesses properties on the data object (e.g., data.company_logo, data.address_line, data.company, data.name) without validating that the object or these properties exist. If the server-side before_print hook publishes an incomplete payload, this will cause runtime errors.

Add defensive checks or validate the data structure at the start of the handler:

frappe.realtime.on("sales_invoice_before_print", (data) => {
	if (!data || !data.name || !data.company) {
		console.error("Invalid sales_invoice_before_print data:", data);
		return;
	}
	
	const route = frappe.get_route();
	// ... rest of the code
});

Please verify that the server-side before_print hook in sales_invoice.py always publishes the required fields (name, company, company_logo, address_line) to ensure this client code receives valid data.

@khushi8112 khushi8112 removed the request for review from nabinhait October 1, 2025 10:46
@khushi8112 khushi8112 force-pushed the print-format-for-sales-invoice branch from 6a1176e to 6e07aac Compare October 12, 2025 07:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
erpnext/accounts/doctype/sales_invoice/sales_invoice.py (1)

2850-2899: Harden save_company_master_details against unauthorized updates.

Any logged-in user can call this endpoint and update Company fields or mutate a Sales Invoice regardless of permissions or company ownership, because we never verify the invoice, its company, or writer rights before issuing raw db.set_value calls. That’s a security issue.

Please guard the entry point before mutating anything:

 @frappe.whitelist()
 def save_company_master_details(name, company, details):
 	from frappe.utils import validate_email_address
 
+	si = frappe.get_doc("Sales Invoice", name)
+	si.check_permission("write")
+
+	if si.company != company:
+		frappe.throw(
+			_("Sales Invoice {0} does not belong to Company {1}.").format(name, company)
+		)
+
+	company_doc = frappe.get_doc("Company", company)
+	company_doc.check_permission("write")
+
 	if isinstance(details, str):
 		details = frappe.parse_json(details)

With the guard in place, only users who can actually edit the target Sales Invoice and Company will get through, and cross-company tampering is blocked.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a1176e and 6e07aac.

📒 Files selected for processing (9)
  • erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py (1 hunks)
  • erpnext/accounts/doctype/sales_invoice/sales_invoice.py (2 hunks)
  • erpnext/accounts/letterhead/letterhead_plain.html (1 hunks)
  • erpnext/accounts/letterhead/letterhead_with_background_colour.html (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_standard/sales_invoice_standard.json (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_with_item_image/sales_invoice_with_item_image.json (1 hunks)
  • erpnext/hooks.py (2 hunks)
  • erpnext/public/js/print.js (1 hunks)
  • erpnext/setup/install.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • erpnext/accounts/letterhead/letterhead_with_background_colour.html
  • erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py
  • erpnext/public/js/print.js
  • erpnext/accounts/letterhead/letterhead_plain.html
🧰 Additional context used
🧬 Code graph analysis (1)
erpnext/accounts/doctype/sales_invoice/sales_invoice.py (1)
erpnext/controllers/accounts_controller.py (1)
  • before_print (607-629)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Patch Test
  • GitHub Check: Summary

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
erpnext/accounts/letterhead/company_letterhead.html (1)

80-105: Remove invoice-specific content from letterhead for reusability.

The letterhead template currently includes invoice-specific fields at lines 83-86:

<div class="invoice-info">
    <span class="invoice-label">{{ _("Invoice:") }}</span>
    <span>{{ doc.name }}</span>
</div>

Letterheads should be generic and reusable across different document types (Purchase Orders, Delivery Notes, etc.). Document-specific metadata like the invoice number belongs in the print format, not the letterhead.

Apply this diff to remove the invoice-specific block:

         {% set company_details = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
 
-        <div class="invoice-info">
-            <span class="invoice-label">{{ _("Invoice:") }}</span>
-            <span>{{ doc.name }}</span>
-        </div>
         {% if company_details.website %}
             <div class="invoice-info">
                 <span class="invoice-label">{{ _("Website:") }}</span>

This ensures the letterhead can be used across all document types in ERPNext.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e07aac and f14b3ed.

📒 Files selected for processing (5)
  • erpnext/accounts/doctype/sales_invoice/sales_invoice.py (2 hunks)
  • erpnext/accounts/letterhead/company_letterhead.html (1 hunks)
  • erpnext/accounts/letterhead/company_letterhead_grey.html (1 hunks)
  • erpnext/accounts/print_format/sales_invoice_standard/sales_invoice_standard.json (1 hunks)
  • erpnext/setup/install.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
erpnext/accounts/doctype/sales_invoice/sales_invoice.py (1)
erpnext/controllers/accounts_controller.py (1)
  • before_print (607-629)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Patch Test
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Summary
🔇 Additional comments (4)
erpnext/setup/install.py (1)

285-305: LGTM! Previous issues have been addressed.

The create_letter_head function correctly:

  • Uses the proper path with "accounts" and "letterhead" subdirectories
  • Avoids manual commits (relies on outer transaction)
  • Uses consistent letterhead names in both the exists check and document creation

The implementation properly reads HTML templates and creates Letter Head documents during installation.

erpnext/accounts/doctype/sales_invoice/sales_invoice.py (1)

282-334: LGTM! Previous concerns have been addressed.

The before_print method now properly:

  • Calls super().before_print(settings) to ensure parent class logic executes
  • Uses safe dict access with .get() when extracting address_line1
  • Checks permissions before prompting for missing company details
  • Publishes the realtime event only when required fields are missing and the user has appropriate permissions
erpnext/accounts/letterhead/company_letterhead_grey.html (1)

1-125: LGTM! Well-structured letterhead template.

The grey-themed letterhead template follows good practices:

  • Uses conditional rendering to show sections only when data exists
  • Employs safe dictionary access with .get() to avoid KeyError
  • Properly fetches company details via frappe.db.get_value()
  • Includes appropriate styling with rounded corners and padding
  • Displays company logo, name, address, and contact information in a clean layout

The template integrates well with the broader print flow where before_print gathers company details and the save_company_master_details endpoint can update them.

erpnext/accounts/print_format/sales_invoice_standard/sales_invoice_standard.json (1)

1-33: LGTM! Bill From/Bill To sections are correctly implemented.

The print format correctly assigns:

  • Bill From: Company name and company address (who is issuing the invoice)
  • Bill To: Customer name and customer address (who should pay the invoice)

This is the standard and correct convention for invoices. The past review comment suggesting these should be swapped was incorrect.

The print format also includes:

  • Proper conditional rendering for optional fields
  • Dynamic Item Code column based on naming settings
  • Well-structured totals section with discount and tax handling
  • Appropriate styling and layout

@khushi8112 khushi8112 merged commit f4c37f1 into frappe:develop Oct 12, 2025
17 checks passed
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 27, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

needs-tests This PR needs automated unit-tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants