Merge pull request #55051 from frappe/version-16-hotfix

chore: release v16
This commit is contained in:
diptanilsaha
2026-05-20 09:39:15 +05:30
committed by GitHub
75 changed files with 4044 additions and 1759 deletions

View File

@@ -43,6 +43,7 @@ class AccountingDimension(Document):
def validate(self):
self.validate_doctype()
validate_column_name(self.fieldname)
self.validate_fieldname_conflict()
self.validate_dimension_defaults()
def validate_doctype(self):
@@ -74,6 +75,27 @@ class AccountingDimension(Document):
message += _("Please create a new Accounting Dimension if required.")
frappe.throw(message)
def validate_fieldname_conflict(self):
conflicting_doctypes = []
for doctype in get_doctypes_with_dimensions():
meta = frappe.get_meta(doctype, cached=False)
if any(f.fieldname == self.fieldname for f in meta.get("fields")):
conflicting_doctypes.append(doctype)
if conflicting_doctypes:
frappe.msgprint(
_(
"Fieldname {0} already exists in the following doctypes: {1}. "
"A separate dimension field will not be added to these doctypes. "
"GL Entries will use the value of the existing field as the dimension value."
).format(
frappe.bold(self.fieldname),
", ".join(frappe.bold(d) for d in conflicting_doctypes),
),
title=_("Fieldname Conflict"),
indicator="orange",
)
def validate_dimension_defaults(self):
companies = []
for default in self.get("dimension_defaults"):

View File

@@ -38,16 +38,6 @@ frappe.ui.form.on("Accounts Settings", {
add_taxes_from_item_tax_template(frm) {
toggle_tax_settings(frm, "add_taxes_from_item_tax_template");
},
drop_ar_procedures: function (frm) {
frm.call({
doc: frm.doc,
method: "drop_ar_sql_procedures",
callback: function (r) {
frappe.show_alert(__("Procedures dropped"), 5);
},
});
},
});
function toggle_tax_settings(frm, field_name) {

View File

@@ -96,7 +96,6 @@
"receivable_payable_fetch_method",
"default_ageing_range",
"column_break_ntmi",
"drop_ar_procedures",
"legacy_section",
"ignore_is_opening_check_for_reporting",
"tab_break_dpet",
@@ -523,7 +522,7 @@
"fieldname": "receivable_payable_fetch_method",
"fieldtype": "Select",
"label": "Data Fetch Method",
"options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL"
"options": "Buffered Cursor\nUnBuffered Cursor"
},
{
"fieldname": "accounts_receivable_payable_tuning_section",
@@ -592,13 +591,6 @@
"fieldname": "column_break_ntmi",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.receivable_payable_fetch_method == \"Raw SQL\"",
"description": "Drops existing SQL Procedures and Function setup by Accounts Receivable report",
"fieldname": "drop_ar_procedures",
"fieldtype": "Button",
"label": "Drop Procedures"
},
{
"default": "0",
"fieldname": "fetch_valuation_rate_for_internal_transaction",
@@ -725,7 +717,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-04-13 15:30:28.729627",
"modified": "2026-05-18 12:16:33.679345",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -89,7 +89,7 @@ class AccountsSettings(Document):
make_payment_via_journal_entry: DF.Check
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"]
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
receivable_payable_remarks_length: DF.Int
reconciliation_queue_size: DF.Int
repost_allowed_types: DF.Table[RepostAllowedTypes]
@@ -209,13 +209,6 @@ class AccountsSettings(Document):
set_allow_on_submit_for_dimension_fields(doctypes)
@frappe.whitelist()
def drop_ar_sql_procedures(self):
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")
def toggle_accounting_dimension_sections(hide):
accounting_dimension_doctypes = frappe.get_hooks("accounting_dimension_doctypes")

View File

@@ -70,6 +70,10 @@ frappe.ui.form.on("Journal Entry", {
},
refresh: function (frm) {
if (frm.doc.reversal_of && (frm.is_new() || frm.doc.docstatus == 0)) {
frm.set_read_only();
}
erpnext.toggle_naming_series();
if (frm.doc.docstatus > 0) {

View File

@@ -710,31 +710,12 @@ frappe.ui.form.on("Payment Entry", {
if (!frm.doc.paid_from_account_currency || !frm.doc.company) return;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from) {
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
from_currency: frm.doc.paid_from_account_currency,
to_currency: company_currency,
transaction_date: frm.doc.posting_date,
},
callback: function (r, rt) {
frm.set_value("source_exchange_rate", r.message);
},
});
} else {
frm.events.set_current_exchange_rate(
frm,
"source_exchange_rate",
frm.doc.paid_from_account_currency,
company_currency
);
}
}
frm.events.set_current_exchange_rate(
frm,
"source_exchange_rate",
frm.doc.paid_from_account_currency,
company_currency
);
},
paid_to_account_currency: function (frm) {
@@ -766,49 +747,24 @@ frappe.ui.form.on("Payment Entry", {
posting_date: function (frm) {
frm.events.paid_from_account_currency(frm);
frm.events.paid_to_account_currency(frm);
},
source_exchange_rate: function (frm) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_amount) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
// target exchange rate should always be same as source if both account currencies is same
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
} else if (company_currency == frm.doc.paid_to_account_currency) {
frm.set_value("received_amount", frm.doc.base_paid_amount);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}
// set_unallocated_amount is called by below method,
// no need trigger separately
frm.events.set_total_allocated_amount(frm);
}
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},
target_exchange_rate: function (frm) {
frm.set_paid_amount_based_on_received_amount = true;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.received_amount) {
frm.set_value(
"base_received_amount",
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
);
if (frm.doc.base_received_amount && frm.doc.source_exchange_rate) {
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
if (
!frm.doc.source_exchange_rate &&
frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
} else if (company_currency == frm.doc.paid_from_account_currency) {
frm.set_value("paid_amount", frm.doc.base_received_amount);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
// target exchange rate should always be same as source if both account currencies is same
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
} else {
frm.set_value(
"paid_amount",
flt(frm.doc.base_paid_amount) / flt(frm.doc.source_exchange_rate)
);
}
// set_unallocated_amount is called by below method,
@@ -817,6 +773,32 @@ frappe.ui.form.on("Payment Entry", {
}
frm.set_paid_amount_based_on_received_amount = false;
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},
target_exchange_rate: function (frm) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.base_paid_amount && frm.doc.target_exchange_rate) {
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
if (
!frm.doc.source_exchange_rate &&
frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
} else {
frm.set_value(
"received_amount",
flt(frm.doc.base_received_amount) / flt(frm.doc.target_exchange_rate)
);
}
// set_unallocated_amount is called by below method,
// no need trigger separately
frm.events.set_total_allocated_amount(frm);
}
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("target_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},

View File

@@ -322,7 +322,7 @@
"reqd": 1
},
{
"depends_on": "doc.received_amount",
"depends_on": "eval:doc.received_amount;",
"fieldname": "base_received_amount",
"fieldtype": "Currency",
"label": "Received Amount (Company Currency)",
@@ -795,7 +795,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2026-03-09 17:15:30.453920",
"modified": "2026-05-15 13:31:01.166010",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -677,7 +677,7 @@ def validate_due_date_with_template(posting_date, due_date, bill_date, template_
if not default_due_date:
return
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
if getdate(default_due_date) != getdate(posting_date) and getdate(due_date) > getdate(default_due_date):
if frappe.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles():
party_type = "supplier" if doctype == "Purchase Invoice" else "customer"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,161 +0,0 @@
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}
{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
{% if print_heading_template %}
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
{% else %}
{% endif %}
{%- if doc.meta.is_submittable and doc.docstatus==2-%}
<div class="text-center" document-status="cancelled">
<h4 style="margin: 0px;">{{ _("CANCELLED") }}</h4>
</div>
{%- endif -%}
{%- endmacro -%}
{% for page in layout %}
<div class="page-break">
<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) }}
</div>
<style>
.taxes-section .order-taxes.mt-5{
margin-top: 0px !important;
}
.taxes-section .order-taxes .border-btm.pb-5{
padding-bottom: 0px !important;
}
.print-format label{
color: #74808b;
font-size: 12px;
margin-bottom: 4px;
}
</style>
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if not no_letterhead and footer %}
<div class="letter-head-footer">
{{ footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
</p>
</div>
{% endif %}
<div class="row section-break" style="margin-bottom: 10px;">
<div class="col-xs-6 p-0">
<div class="col-xs-12 value text-uppercase"><b>{{ doc.customer }}</b></div>
<div class="col-xs-12">
{{ doc.address_display }}
</div>
<div class="col-xs-12">
{{ _("Contact: ")+doc.contact_display if doc.contact_display else '' }}
</div>
<div class="col-xs-12">
{{ _("Mobile: ")+doc.contact_mobile if doc.contact_mobile else '' }}
</div>
</div>
<div class="col-xs-3"></div>
<div class="col-xs-3" style="padding-left: 5px;">
<div>
<div><label>{{ _("Invoice ID") }}</label></div>
<div>{{ doc.name }}</div>
</div>
<div style="margin-top: 20px;">
<div><label>{{ _("Invoice Date") }}</label></div>
<div>{{ frappe.utils.format_date(doc.posting_date) }}</div>
</div>
<div style="margin-top: 20px;">
<div><label>{{ _("Due Date") }}</label></div>
<div>{{ frappe.utils.format_date(doc.due_date) }}</div>
</div>
</div>
</div>
<div class="section-break">
<table class="table table-bordered table-condensed mb-0" style="width: 100%; border-collapse: collapse; font-size: 12px;">
<colgroup>
<col style="width: 5%">
<col style="width: 45%">
<col style="width: 10%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr>
<th class="text-uppercase" style="text-align:center">{{ _("Sr") }}</th>
<th class="text-uppercase" style="text-align:center">{{ _("Details") }}</th>
<th class="text-uppercase" style="text-align:center">{{ _("Qty") }}</th>
<th class="text-uppercase" style="text-align:right">{{ _("Rate") }}</th>
<th class="text-uppercase" style="text-align:right">{{ _("Amount") }}</th>
</tr>
</thead>
{% for item in doc.items %}
<tr>
<td style="text-align:center">{{ loop.index }}</td>
<td>
<b>{{ item.item_code }}: {{ item.item_name }}</b>
{% if (item.description != item.item_name) %}
<br>{{ item.description }}
{% endif %}
</td>
<td style="text-align: center;">
{{ item.get_formatted("qty", 0) }}
{{ item.get_formatted("uom", 0) }}
</td>
<td style="text-align: right;">{{ item.get_formatted("net_rate", doc) }}</td>
<td style="text-align: right;">{{ item.get_formatted("net_amount", doc) }}</td>
</tr>
{% endfor %}
</table>
<!-- total -->
<div class="row">
<div class="col-xs-6">
<div>
<label>{{ _("Amount in Words") }}</label>
{{ doc.in_words }}
</div>
<div style="margin-top: 20px;">
<label>{{ _("Payment Status") }}</label>
{{ doc.status }}
</div>
</div>
<div class="col-xs-6">
<div class="row section-break">
<div class="col-xs-7"><div>{{ _("Sub Total") }}</div></div>
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("net_total", doc) }}</div>
</div>
<div>
{% for d in doc.taxes %}
{% if d.tax_amount %}
<div class="row">
<div class="col-xs-8"><div>{{ _(d.description) }}</div></div>
<div class="col-xs-4" style="text-align: right;">{{ d.get_formatted("tax_amount") }}</div>
</div>
{% endif %}
{% endfor %}
</div>
<div class="row">
<div class="col-xs-7"><div>{{ _("Total") }}</div></div>
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("grand_total", doc) }}</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row important data-field">
<div class="col-xs-12"><label>{{ _("Terms and Conditions") }}: </label></div>
<div class="col-xs-12">{{ doc.terms if doc.terms else '' }}</div>
</div>
</div>
</div>
</div>
{% endfor %}

View File

@@ -1,32 +0,0 @@
{
"absolute_value": 0,
"align_labels_right": 0,
"creation": "2025-01-22 16:23:51.012200",
"css": "",
"custom_format": 0,
"default_print_language": "en",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "",
"font_size": 14,
"idx": 0,
"line_breaks": 0,
"margin_bottom": 0.0,
"margin_left": 0.0,
"margin_right": 0.0,
"margin_top": 0.0,
"modified": "2025-01-22 16:23:51.012200",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Print",
"owner": "Administrator",
"page_number": "Hide",
"print_format_builder": 0,
"print_format_builder_beta": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1,227 @@
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
<style type="text/css">
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table thead th {
background: #f8f8f8;
text-align: center;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.text-center { text-align: center; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<br>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Supplier") %}:</strong>
{%= (filters.party.length && filters.party.join(", ")) || __("All Parties") %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Report Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.report_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
<th style="text-align: left;">{%= __("Reference") %}</th>
{% if(filters.show_remarks) { %}
<th style="text-align: left;">{%= __("Remarks") %}</th>
{% } %}
<th style="width: 10em; text-align: right;">{%= __("Age (Days)") %}</th>
<th style="width: 10em; text-align: right;">{%= __("Invoiced Amount") %}</th>
<th style="width: 11em; text-align: right;">{%= __("Outstanding Amount") %}</th>
</tr>
</thead>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
<td class="text-left">{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td class="{% if(i == data.length - 1) { %}text-left text-bold{% } %}">
{% if(i == data.length - 1) { %}
{%= __("Total") %}
{% } else { %}
{%= data[i]["voucher_no"] %}
{% } %}
</td>
{% if(filters.show_remarks) { %}
<td class="text-left">
{% if(data[i]["remarks"] && data[i]["remarks"] != "No Remarks") { %}
{%= data[i]["remarks"] %}
{% } %}
</td>
{% } %}
<td class="text-right">{%= data[i]["age"] %}</td>
<td class="text-right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td class="text-right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
</tr>
{% } %}
</tbody>
</table>
</div>
&nbsp;
{% if(filters.show_future_payments) { %}
{%
var balance_row = data.slice(-1).pop();
var start = report.columns.findIndex(e => e.fieldname == 'age');
var currency = data[data.length - 1]["currency"];
var ranges = [
report.columns[start].label,
report.columns[start+1].label,
report.columns[start+2].label,
report.columns[start+3].label,
report.columns[start+4].label,
report.columns[start+5].label
];
%}
{% if(balance_row) { %}
<div class="report-table">
<table>
<thead>
<tr>
<th style="text-align: right;"></th>
{% for(var i = 0; i < ranges.length; i++) { %}
<th style="text-align: right;">{%= __(ranges[i]) %}</th>
{% } %}
<th style="text-align: right;">{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["age"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row["range1"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range2"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range3"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range4"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range5"], currency) %}</td>
<td class="text-right">{%= format_currency(flt(balance_row["outstanding"]), currency) %}</td>
</tr>
</tbody>
</table>
</div>
{% } %}
{% } %}
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1 +1,180 @@
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
<style type="text/css">
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table thead th {
background: #f8f8f8;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<br>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Supplier") %}:</strong>
{%= (filters.party && filters.party.length && filters.party.join(", ")) || __("All Parties") %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Ageing Based On") %}:</strong>
{%= __(filters.ageing_based_on) %}
</div>
<div>
<strong>{%= __("As on Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.report_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th class="text-left">{%= __("Supplier") %}</th>
<th class="text-right">{%= __("Total Invoiced Amount") %}</th>
<th class="text-right">{%= __("Total Paid Amount") %}</th>
<th class="text-right">{%= __("Debit Note Amount") %}</th>
<th class="text-right">{%= __("Total Outstanding Amount") %}</th>
</tr>
</thead>
<tbody>
{% for (var i = 0, l = data.length; i < l; i++) {
var row = data[i];
if (!(row.party || row.is_total_row)) continue;
%}
<tr>
<td class="{% if (row.is_total_row) { %}text-bold{% } %}">
{% if (row.is_total_row) { %}
{%= __("Total") %}
{% } else { %}
{%= row.party %}
{% } %}
</td>
<td class="text-right">
{%= format_currency(row.invoiced, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.paid, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.debit_note, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.outstanding, row.currency) %}
</td>
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1,291 +1,225 @@
<style>
.print-format {
padding: 4mm;
font-size: 8.0pt !important;
}
.print-format td {
vertical-align:middle !important;
}
</style>
<style type="text/css">
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
<h4 class="text-center">
{% if (filters.party) { %}
{%= __(filters.party) %}
{% } %}
</h4>
<h6 class="text-center">
{% if (filters.tax_id) { %}
{%= __("Tax Id: ")%} {%= filters.tax_id %}
{% } %}
</h6>
<h5 class="text-center">
{%= __(filters.ageing_based_on) %}
{%= __("Until") %}
{%= frappe.datetime.str_to_user(filters.report_date) %}
</h5>
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
<div class="clearfix">
<div class="pull-left">
{% if(filters.payment_terms) { %}
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
{% } %}
</div>
<div class="pull-right">
{% if(filters.credit_limit) { %}
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
{% } %}
</div>
</div>
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
{% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop();
var start = report.columns.findIndex((elem) => (elem.fieldname == 'age'));
var range1 = report.columns[start].label;
var range2 = report.columns[start+1].label;
var range3 = report.columns[start+2].label;
var range4 = report.columns[start+3].label;
var range5 = report.columns[start+4].label;
var range6 = report.columns[start+5].label;
%}
{% if(balance_row) { %}
<table class="table table-bordered table-condensed">
<caption class="text-right">(Amount in {%= data[0]["currency"] || "" %})</caption>
<colgroup>
<col style="width: 30mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
</colgroup>
.report-table thead th {
background: #f8f8f8;
text-align: center;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.text-center { text-align: center; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-left { text-align: left; }
.text-bold { font-weight: 700;}
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<br>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Customer") %}:</strong>
{%= (filters.party.length && filters.party.join(", ")) || __("All Parties") %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Report Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.report_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th>{%= __(" ") %}</th>
<th>{%= __(range1) %}</th>
<th>{%= __(range2) %}</th>
<th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th>
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
<th style="text-align: left;">{%= __("Reference") %}</th>
{% if(filters.show_remarks) { %}
<th style="text-align: left;">{%= __("Remarks") %}</th>
{% } %}
<th style="width: 10em; text-align: right;">{%= __("Age (Days)") %}</th>
<th style="width: 10em; text-align: right;">{%= __("Invoiced Amount") %}</th>
<th style="width: 11em; text-align: right;">{%= __("Outstanding Amount") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">
{%= format_number(balance_row["age"], null, 2) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td>
</tr>
<td>{%= __("Future Payments") %}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td>
<tr class="cvs-footer">
<th class="text-left">{%= __("Cheques Required") %}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr>
</tbody>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
<td class="text-left">{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td class="{% if(i == data.length - 1) { %}text-left text-bold{% } %}">
{% if(i == data.length - 1) { %}
{%= __("Total") %}
{% } else { %}
{%= data[i]["voucher_no"] %}
{% } %}
</td>
{% if(filters.show_remarks) { %}
<td class="text-left">
{% if(data[i]["remarks"] && data[i]["remarks"] != "No Remarks") { %}
{%= data[i]["remarks"] %}
{% } %}
</td>
{% } %}
<td class="text-right">{%= data[i]["age"] %}</td>
<td class="text-right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td class="text-right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
</tr>
{% } %}
</tbody>
</table>
</div>
&nbsp;
{% if(filters.show_future_payments) { %}
{%
var balance_row = data.slice(-1).pop();
var start = report.columns.findIndex(e => e.fieldname == 'age');
var currency = data[data.length - 1]["currency"];
var ranges = [
report.columns[start].label,
report.columns[start+1].label,
report.columns[start+2].label,
report.columns[start+3].label,
report.columns[start+4].label,
report.columns[start+5].label
];
%}
{% if(balance_row) { %}
<div class="report-table">
<table>
<thead>
<tr>
<th style="text-align: right;"></th>
{% for(var i = 0; i < ranges.length; i++) { %}
<th style="text-align: right;">{%= __(ranges[i]) %}</th>
{% } %}
<th style="text-align: right;">{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["age"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row["range1"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range2"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range3"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range4"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range5"], currency) %}</td>
<td class="text-right">{%= format_currency(flt(balance_row["outstanding"]), currency) %}</td>
</tr>
</tbody>
</table>
</div>
{% } %}
{% } %}
<table class="table table-bordered">
<thead>
<tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
<th style="width: 10%">{%= __("Date") %}</th>
<th style="width: 4%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<th style="width: 14%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %}
<th style="width: 24%">{%= __("Reference") %}</th>
{% } %}
{% if(!filters.show_future_payments) { %}
<th style="width: 20%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_future_payments) { %}
<th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
<th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 12%">{%= __("Customer LPO No.") %}</th>
{% } %}
<th style="width: 10%">{%= __("Future Payment Ref") %}</th>
<th style="width: 10%">{%= __("Future Payment Amount") %}</th>
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
{% } %}
{% } else { %}
<th style="width: 40%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
<th style="width: 15%">{%= __("Total Invoiced Amount") %}</th>
<th style="width: 15%">{%= __("Total Paid Amount") %}</th>
<th style="width: 15%">{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}</th>
<th style="width: 15%">{%= __("Total Outstanding Amount") %}</th>
{% } %}
</tr>
</thead>
<div class="show-filters">
{% if subtitle %}
{{ subtitle }}
<hr>
{% endif %}
</div>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
{% if(data[i]["party"]) { %}
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td style="text-align: right">{%= data[i]["age"] %}</td>
<td>
{% if(!filters.show_future_payments) { %}
{%= data[i]["voucher_type"] %}
<br>
{% } %}
{%= data[i]["voucher_no"] %}
</td>
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td>{%= data[i]["sales_person"] %}</td>
{% } %}
{% if(!filters.show_future_payments) { %}
<td>
{% if(!filters.party?.length) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
<br> {%= data[i]["supplier_name"] %}
{% } %}
{% } %}
<div>
{% if data[i]["remarks"] %}
{%= __("Remarks") %}:
{%= data[i]["remarks"] %}
{% } %}
</div>
</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">
{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } else { %}
<td></td>
{% if(!filters.show_future_payments) { %}
<td></td>
{% } %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td></td>
{% } %}
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
{% } else { %}
{% if(data[i]["party"]|| "&nbsp;") { %}
{% if(!data[i]["is_total_row"]) { %}
<td>
{% if(!filters.party?.length) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
<br> {%= data[i]["supplier_name"] %}
{% } %}
{% } %}
<br>{%= __("Remarks") %}:
{%= data[i]["remarks"] %}
</td>
{% } else { %}
<td><b>{%= __("Total") %}</b></td>
{% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
</tr>
{% } %}
</tbody>
</table>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>
</div>

View File

@@ -131,8 +131,6 @@ class ReceivablePayableReport:
self.fetch_ple_in_buffered_cursor()
elif self.ple_fetch_method == "UnBuffered Cursor":
self.fetch_ple_in_unbuffered_cursor()
elif self.ple_fetch_method == "Raw SQL":
self.fetch_ple_in_sql_procedures()
# Build delivery note map against all sales invoices
self.build_delivery_note_map()
@@ -323,81 +321,6 @@ class ReceivablePayableReport:
row.paid -= amount
row.paid_in_account_currency -= amount_in_account_currency
def fetch_ple_in_sql_procedures(self):
self.proc = InitSQLProceduresForAR()
build_balance = f"""
begin not atomic
declare done boolean default false;
declare rec1 row type of `{self.proc._row_def_table_name}`;
declare ple cursor for {self.ple_query.get_sql()};
declare continue handler for not found set done = true;
open ple;
fetch ple into rec1;
while not done do
call {self.proc.init_procedure_name}(rec1);
fetch ple into rec1;
end while;
close ple;
set done = false;
open ple;
fetch ple into rec1;
while not done do
call {self.proc.allocate_procedure_name}(rec1);
fetch ple into rec1;
end while;
close ple;
end;
"""
frappe.db.sql(build_balance)
balances = frappe.db.sql(
f"""select
name,
voucher_type,
voucher_no,
party,
party_account `account`,
posting_date,
account_currency,
cost_center,
project,
sum(invoiced) `invoiced`,
sum(paid) `paid`,
sum(credit_note) `credit_note`,
sum(invoiced) - sum(paid) - sum(credit_note) `outstanding`,
sum(invoiced_in_account_currency) `invoiced_in_account_currency`,
sum(paid_in_account_currency) `paid_in_account_currency`,
sum(credit_note_in_account_currency) `credit_note_in_account_currency`,
sum(invoiced_in_account_currency) - sum(paid_in_account_currency) - sum(credit_note_in_account_currency) `outstanding_in_account_currency`
from `{self.proc._voucher_balance_name}` group by name order by posting_date;""",
as_dict=True,
)
for x in balances:
if self.filters.get("ignore_accounts"):
key = (x.voucher_type, x.voucher_no, x.party)
else:
key = (x.account, x.voucher_type, x.voucher_no, x.party)
_d = self.build_voucher_dict(x)
for field in [
"invoiced",
"paid",
"credit_note",
"outstanding",
"invoiced_in_account_currency",
"paid_in_account_currency",
"credit_note_in_account_currency",
"outstanding_in_account_currency",
"cost_center",
"project",
]:
_d[field] = x.get(field)
self.voucher_balance[key] = _d
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)
@@ -1400,120 +1323,3 @@ def get_party_group_with_children(party, party_groups):
frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d))
return list(set(all_party_groups))
class InitSQLProceduresForAR:
"""
Initialize SQL Procedures, Functions and Temporary tables to build Receivable / Payable report
"""
_varchar_type = get_definition("Data")
_currency_type = get_definition("Currency")
# Temporary Tables
_voucher_balance_name = "_ar_voucher_balance"
_voucher_balance_definition = f"""
create temporary table `{_voucher_balance_name}`(
name {_varchar_type},
voucher_type {_varchar_type},
voucher_no {_varchar_type},
party {_varchar_type},
party_account {_varchar_type},
posting_date date,
account_currency {_varchar_type},
cost_center {_varchar_type},
project {_varchar_type},
invoiced {_currency_type},
paid {_currency_type},
credit_note {_currency_type},
invoiced_in_account_currency {_currency_type},
paid_in_account_currency {_currency_type},
credit_note_in_account_currency {_currency_type}) engine=memory;
"""
_row_def_table_name = "_ar_ple_row"
_row_def_table_definition = f"""
create temporary table `{_row_def_table_name}`(
name {_varchar_type},
account {_varchar_type},
voucher_type {_varchar_type},
voucher_no {_varchar_type},
against_voucher_type {_varchar_type},
against_voucher_no {_varchar_type},
party_type {_varchar_type},
cost_center {_varchar_type},
project {_varchar_type},
party {_varchar_type},
posting_date date,
due_date date,
account_currency {_varchar_type},
amount {_currency_type},
amount_in_account_currency {_currency_type}) engine=memory;
"""
# Procedures
init_procedure_name = "ar_init_tmp_table"
init_procedure_sql = f"""
create procedure ar_init_tmp_table(in ple row type of `{_row_def_table_name}`)
begin
if not exists (select name from `{_voucher_balance_name}` where name = sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)))
then
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)), ple.voucher_type, ple.voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency, ple.cost_center, ple.project, 0, 0, 0, 0, 0, 0);
end if;
end;
"""
allocate_procedure_name = "ar_allocate_to_tmp_table"
allocate_procedure_sql = f"""
create procedure ar_allocate_to_tmp_table(in ple row type of `{_row_def_table_name}`)
begin
declare invoiced {_currency_type} default 0;
declare invoiced_in_account_currency {_currency_type} default 0;
declare paid {_currency_type} default 0;
declare paid_in_account_currency {_currency_type} default 0;
declare credit_note {_currency_type} default 0;
declare credit_note_in_account_currency {_currency_type} default 0;
if ple.amount > 0 then
if (ple.voucher_type in ("Journal Entry", "Payment Entry") and (ple.voucher_no != ple.against_voucher_no)) then
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
else
set invoiced = ple.amount;
set invoiced_in_account_currency = ple.amount_in_account_currency;
end if;
else
if ple.voucher_type in ("Sales Invoice", "Purchase Invoice") then
if (ple.voucher_no = ple.against_voucher_no) then
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
else
set credit_note = -1 * ple.amount;
set credit_note_in_account_currency = -1 * ple.amount_in_account_currency;
end if;
else
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
end if;
end if;
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.voucher_type, ple.voucher_no, ple.party)), ple.against_voucher_type, ple.against_voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency,'', '', invoiced, paid, 0, invoiced_in_account_currency, paid_in_account_currency, 0);
end;
"""
def __init__(self):
existing_procedures = frappe.db.get_routines()
if self.init_procedure_name not in existing_procedures:
frappe.db.sql(self.init_procedure_sql)
if self.allocate_procedure_name not in existing_procedures:
frappe.db.sql(self.allocate_procedure_sql)
frappe.db.sql(f"drop table if exists `{self._voucher_balance_name}`")
frappe.db.sql(self._voucher_balance_definition)
frappe.db.sql(f"drop table if exists `{self._row_def_table_name}`")
frappe.db.sql(self._row_def_table_definition)

View File

@@ -1 +1,180 @@
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
<style type="text/css">
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table thead th {
background: #f8f8f8;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<br>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Customer") %}:</strong>
{%= (filters.party && filters.party.length && filters.party.join(", ")) || __("All Parties") %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Ageing Based On") %}:</strong>
{%= __(filters.ageing_based_on) %}
</div>
<div>
<strong>{%= __("As on Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.report_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th class="text-left">{%= __("Customer") %}</th>
<th class="text-right">{%= __("Total Invoiced Amount") %}</th>
<th class="text-right">{%= __("Total Paid Amount") %}</th>
<th class="text-right">{%= __("Credit Note Amount") %}</th>
<th class="text-right">{%= __("Total Outstanding Amount") %}</th>
</tr>
</thead>
<tbody>
{% for (var i = 0, l = data.length; i < l; i++) {
var row = data[i];
if (!(row.party || row.is_total_row)) continue;
%}
<tr>
<td class="{% if (row.is_total_row) { %}text-bold{% } %}">
{% if (row.is_total_row) { %}
{%= __("Total") %}
{% } else { %}
{%= row.party %}
{% } %}
</td>
<td class="text-right">
{%= format_currency(row.invoiced, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.paid, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.credit_note, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.outstanding, row.currency) %}
</td>
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1 +1,224 @@
{% include "accounts/report/financial_statements.html" %}
{%
const report_columns = report
.get_columns_for_print()
.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(
__("Too many columns. Export the report and print it using a spreadsheet application.")
);
}
%}
<style>
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.financial-statements-important td { font-weight: bold; }
.financial-statements-blank-row td { height: 20px; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right {
text-align: right;
font-variant-numeric: tabular-nums;
}
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table th,
.report-table td {
padding: 6px 8px;
font-size: 14px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
}
.report-table thead th {
background: #f8f8f8;
font-weight: 500;
color: #7c7c7c;
}
.report-table tbody td {
vertical-align: top;
word-wrap: break-word;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
</div>
<div>
<strong>{%= __("Currency") %}:</strong>
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Period Based On") %}:</strong>
{%= filters.filter_based_on %}
</div>
{% if (filters.filter_based_on === "Fiscal Year") { %}
<div>
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
</div>
<div>
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
</div>
{% } else if (filters.filter_based_on === "Date Range") { %}
<div>
<strong>{%= __("Start Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
</div>
<div>
<strong>{%= __("End Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
</div>
{% } %}
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const align = i === 0 ? "text-left" : "text-right";
%}
<th class="{%= align %}">
{%= col.label %}
</th>
{% } %}
</tr>
</thead>
<tbody>
{% for (let j = 0, k = data.length; j < k; j++) { %}
{%
const row = data[j];
let row_class = "";
if (!(row.parent_account || row.parent_section)) {
row_class = "financial-statements-important";
}
if (!(row.account_name || row.section)) {
row_class += " financial-statements-blank-row";
}
%}
<tr class="{%= row_class %}">
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const value = row[col.fieldname];
const align = i === 0 ? "text-left" : "text-right";
%}
<td class="{%= align %}">
{% if (i === 0) { %}
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
</span>
{% } else if (!is_null(value)) { %}
{%= frappe.format(value, col, {}, row) %}
{% } %}
</td>
{% } %}
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right text-muted">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1 +1,224 @@
{% include "accounts/report/financial_statements.html" %}
{%
const report_columns = report
.get_columns_for_print()
.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(
__("Too many columns. Export the report and print it using a spreadsheet application.")
);
}
%}
<style>
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.financial-statements-important td { font-weight: bold; }
.financial-statements-blank-row td { height: 20px; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right {
text-align: right;
font-variant-numeric: tabular-nums;
}
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table th,
.report-table td {
padding: 6px 8px;
font-size: 14px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
}
.report-table thead th {
background: #f8f8f8;
font-weight: 500;
color: #7c7c7c;
}
.report-table tbody td {
vertical-align: top;
word-wrap: break-word;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
</div>
<div>
<strong>{%= __("Currency") %}:</strong>
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Period Based On") %}:</strong>
{%= filters.filter_based_on %}
</div>
{% if (filters.filter_based_on === "Fiscal Year") { %}
<div>
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
</div>
<div>
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
</div>
{% } else if (filters.filter_based_on === "Date Range") { %}
<div>
<strong>{%= __("Start Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
</div>
<div>
<strong>{%= __("End Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
</div>
{% } %}
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const align = i === 0 ? "text-left" : "text-right";
%}
<th class="{%= align %}">
{%= col.label %}
</th>
{% } %}
</tr>
</thead>
<tbody>
{% for (let j = 0, k = data.length; j < k; j++) { %}
{%
const row = data[j];
let row_class = "";
if (!(row.parent_account || row.parent_section)) {
row_class = "financial-statements-important";
}
if (!(row.account_name || row.section)) {
row_class += " financial-statements-blank-row";
}
%}
<tr class="{%= row_class %}">
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const value = row[col.fieldname];
const align = i === 0 ? "text-left" : "text-right";
%}
<td class="{%= align %}">
{% if (i === 0) { %}
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
</span>
{% } else if (!is_null(value)) { %}
{%= frappe.format(value, col, {}, row) %}
{% } %}
</td>
{% } %}
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right text-muted">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1,51 +1,114 @@
<!-- Modified on 25-11-2024
-->
<style type="text/css">
/* General styles for both screen display and print */
body, html {
margin-top: 10;
margin-top: 10px;
padding: 0;
width: 100%;
height: auto; /* Allow content to expand */
font-family: Arial, sans-serif; /* Example font */
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
/* Ensure consistent letter spacing across all media */
.title-letter-spacing {
letter-spacing: .2rem;
font-size: 15px;
font-weight: 600;
color: #171717;
}
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table thead th {
background: #f8f8f8;
text-align: center;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.date-col {
white-space: nowrap;
}
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-bold { font-weight: 700; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
color: #171717;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta .filter-row {
margin-bottom: 4px;
line-height: 1.5;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
/* Styles specific to printing and PDF generation */
@media print {
/* Set page size and margins for printing */
@page {
size: A4; /* Use fixed A4 page size */
size: A4;
margin-top: 10mm;
}
/* Force a page break before elements with the class "page-break" */
.page-break {
page-break-before: always;
margin-top: 10mm; /* Add some space after the break */
}
/* Ensure table headers repeat on each printed page */
thead {
display: table-header-group;
}
/* Ensure table footers repeat on each printed page */
tfoot {
display: table-footer-group;
}
th, td {
padding: 1px;
border: 1px solid black; /* Example border for clarity */
tr {
page-break-inside: avoid;
}
/* Hide elements that should not appear in print (optional) */
.no-print {
display: none !important;
}
@@ -53,129 +116,154 @@
</style>
<br>
<div style="font-family:Arial">
<div>
<div class="title-letter-spacing" style="text-align:center; font-size:15px; text-decoration:underline;">
<b>
{%= __("STATEMENT OF ACCOUNTS") %}<br>
{% if (filters.party_name) { %}
<br>{%= filters.party_name %}
{% } else if (filters.party && filters.party.length) { %}
<br>{%= filters.party %}
{% } else if (filters.account) { %}
<br>{%= filters.account %}
{% } else { %}
<br>{%= __("All Parties ") %}
{% } %}
</b>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __("Statement Of Accounts") %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div class="filter-row">
<strong>{%= __("Customer") %}:</strong>
{%=
(filters.party.length && filters.party.join(", ")) || filters.party_name || "All Parties"
%}
</div>
</div>
<div class="right text-right">
<div class="filter-row">
<strong>{%= __("Statement Period") %}:</strong>
{%= __("{0} to {1}", [
frappe.datetime.str_to_user(filters.from_date),
frappe.datetime.str_to_user(filters.to_date)
]) %}
</div>
</div>
</div>
<div style="text-align:center; font-size:13px;">
<b>
{%= __("{0} to {1}", [frappe.datetime.str_to_user(filters.from_date), frappe.datetime.str_to_user(filters.to_date)]) %}<br><br>
</b>
</div>
</div>
<div class="show-filters">
{% if subtitle %}
{{ subtitle }}
<hr>
{% endif %}
</div>
<table style="width:100%; font-size: 11px">
<thead>
<tr class="title-letter-spacing" style="text-align: center; font-weight:bold">
<td style="border: 1.5px solid black; width: 7em">{%= __("Date").toLocaleUpperCase() %}</td>
<td style="border: 1.5px solid black">{%= __("Particulars").toLocaleUpperCase() %}</td>
{% if(filters.show_remarks) { %}
<td style="border: 1.5px solid black">{%= __("Remarks").toLocaleUpperCase() %}</td>
{% } %}
<td style="border: 1.5px solid black; width: 9em">{%= __("Debit").toLocaleUpperCase() %}</td>
<td style="border: 1.5px solid black; width: 9em">{%= __("Credit").toLocaleUpperCase() %}</td>
<td style="border: 1.5px solid black; width: 10.2em">{%= __("Balance").toLocaleUpperCase() %}</td>
</tr>
</thead>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr style="border-bottom: 1px solid black">
{% if(data[i].posting_date) { %}
<td style="text-align: center; border: 1px dotted black">
{%= frappe.datetime.str_to_user(data[i].posting_date) %}
</td>
<td style="border-right: 1px dotted black">
{%= data[i].voucher_type %} {%= data[i].voucher_no %}
{% if(!(filters.party || filters.account)) { %}
{%= data[i].party || data[i].account %}
{% } %}<br>
{% if(data[i].bill_no) { %}
{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
{% } %}
</td>
{% if(filters.show_remarks) { %}
<td style="border-right: 1px dotted black; font-size: 10px">
{% if(data[i].remarks != "No Remarks" && data[i].remarks != "") { %}
{%= __("Remarks") %}: {%= data[i].remarks %}<br>
{% } %}
</td>
{% } %}
<td style="text-align: right; border-right: 1px dotted black">
{% if data[i].debit != 0 %}
{%= format_currency(data[i].debit, filters.presentation_currency) %}
{% } %}
</td>
<td style="text-align: right; border-right: 1px dotted black">
{% if data[i].credit != 0 %}
{%= format_currency(data[i].credit, filters.presentation_currency) %}
{% } %}
</td>
{% } else { %}
<td style="text-align: center; border: 1px dotted black">
{% if(i == 0) { %}
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
<th style="text-align: left;">{%= __("Voucher Details") %}</th>
{% if(filters.show_remarks) { %}
<th style="text-align: left;">{%= __("Remarks") %}</th>
{% } %}
<th style="width: 10em; text-align: right;">{%= __("Debit") %}</th>
<th style="width: 10em; text-align: right;">{%= __("Credit") %}</th>
<th style="width: 10em; text-align: right;">{%= __("Balance") %}</th>
</tr>
</thead>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
{% var row = data[i]; %}
{% var is_entry = row.posting_date; %}
{% var is_last = i == l-1; %}
{% var is_second_last = i == l-2; %}
<tr>
<td class="text-left date-col">
{% if(is_entry) { %}
{%= frappe.datetime.str_to_user(row.posting_date) %}
{% } else if(i == 0) { %}
{%= frappe.datetime.str_to_user(filters.from_date) %}
{% } %}
</td>
<td style="text-align: left; border-right: 1px dotted black"><b>
{% if(i == l-2) { %}
{%= __("Total") %}
<td class="{% if(!is_entry) { %}text-left text-bold{% } %}">
{% if(is_entry) { %}
{%= row.voucher_type %} {%= row.voucher_no %}
{% if(!(filters.party || filters.account)) { %}
<div style="margin-top: 2px;">
{%= row.party || row.account %}
</div>
{% } %}
{% if(row.bill_no) { %}
<div style="margin-top: 2px;">
{%= __("Supplier Invoice No") %}: {%= row.bill_no %}
</div>
{% } %}
{% } else { %}
{% if(i == l-1) { %}
{% if(is_second_last) { %}
{%= __("Total") %}
{% } else if(is_last) { %}
{%= __("Closing [Opening + Total] ") %}
{% } else { %}
{%= frappe.format(data[i].account, {fieldtype: "Link"}) || "&nbsp;" %}
{% } %}
{% } %}</b>
</td>
{% if(filters.show_remarks) { %} <td style="text-align: left; border-right: 1px dotted black"></td>{% } %}
<td style="text-align: right; border-right: 1px dotted black">
{% if(i != 0){ %}
{% if(i != l-1){ %}
{%= data[i].account && format_currency(data[i].debit, filters.presentation_currency) %}
{%= frappe.format(row.account, {fieldtype: "Link"}) || "&nbsp;" %}
{% } %}
{% } %}
</td>
<td style="text-align: right; border-right: 1px dotted black">
{% if(i != 0){ %}
{% if(i != l-1){ %}
{%= data[i].account && format_currency(data[i].credit, filters.presentation_currency) %}
{% } %}
{% if(filters.show_remarks) { %}
<td class="text-left">
{% if(is_entry && row.remarks && row.remarks != "No Remarks") { %}
{%= row.remarks %}
{% } %}
</td>
{% } %}
{% if(i == l-1) { %}
<td style="text-align: right; font-weight:bold; border-right: 1px dotted black">
{%= format_currency(data[i].balance, filters.presentation_currency) %}
{% if(data[i].balance < 0){ %}Cr{% } %}
{% if(data[i].balance > 0){ %}Dr{% } %}
</td>
{% } else { %}
<td style="text-align: right; border-right: 1px dotted black">
{% if(i != l-2) { %}
{%= format_currency(data[i].balance, filters.presentation_currency) %}
{% } %}
</td>
{% } %}
</tr>
{% endfor%}
</tbody>
</table>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>
</div>
<td class="text-right">
{% if(is_entry) { %}
{% if(row.debit != 0) { %}
{%= format_currency(row.debit, filters.presentation_currency) %}
{% } %}
{% } else if(i != 0 && !is_last) { %}
{%= row.account && format_currency(row.debit, filters.presentation_currency) %}
{% } %}
</td>
<td class="text-right">
{% if(is_entry) { %}
{% if(row.credit != 0) { %}
{%= format_currency(row.credit, filters.presentation_currency) %}
{% } %}
{% } else if(i != 0 && !is_last) { %}
{%= row.account && format_currency(row.credit, filters.presentation_currency) %}
{% } %}
</td>
<td class="text-right {% if(is_last) { %}text-bold{% } %}">
{% if(is_last) { %}
{%= format_currency(row.balance, filters.presentation_currency) %}
{% if(row.balance < 0) { %} Cr{% } %}
{% if(row.balance > 0) { %} Dr{% } %}
{% } else { %}
{%= format_currency(row.balance, filters.presentation_currency) %}
{% } %}
</td>
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -425,7 +425,13 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
# Opening for filtered account
add_total_to_data(totals, "opening")
if filters.get("categorize_by") != "Categorize by Voucher (Consolidated)":
if not filters.get("categorize_by"):
all_entries = []
for acc_dict in gle_map.values():
all_entries.extend(acc_dict.entries)
data += all_entries
elif filters.get("categorize_by") != "Categorize by Voucher (Consolidated)":
set_opening_closing = (not filters.get("categorize_by") and not filters.get("voucher_no")) or (
filters.get("categorize_by") and filters.get("categorize_by") != "Categorize by Voucher"
)
@@ -491,7 +497,6 @@ def initialize_gle_map(gl_entries, filters):
totals=get_totals_dict(),
entries=[],
)
return gle_map

View File

@@ -812,19 +812,11 @@ class GrossProfitGenerator:
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, row.item_row, item_code
)
elif self.delivery_notes.get((row.parent, row.item_code), None):
# check if Invoice has delivery notes
dn = self.delivery_notes.get((row.parent, row.item_code))
parenttype, parent, item_row, dn_warehouse = (
"Delivery Note",
dn["delivery_note"],
dn["item_row"],
dn["warehouse"],
)
my_sle = self.get_stock_ledger_entries(item_code, dn_warehouse)
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
elif row.item_row and self.delivery_notes.get(row.item_row):
dn = self.delivery_notes[row.item_row]
if flt(dn.total_qty):
return flt(row.qty) * flt(dn.total_incoming_value) / flt(dn.total_qty)
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
elif row.sales_order and row.so_detail:
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
if incoming_amount:
@@ -1076,25 +1068,29 @@ class GrossProfitGenerator:
def get_delivery_notes(self):
self.delivery_notes = frappe._dict({})
if self.si_list:
from frappe.query_builder.functions import Sum
invoices = [x.parent for x in self.si_list]
dni = qb.DocType("Delivery Note Item")
delivery_notes = (
qb.from_(dni)
.select(
dni.against_sales_invoice.as_("sales_invoice"),
dni.item_code,
dni.warehouse,
dni.parent.as_("delivery_note"),
dni.name.as_("item_row"),
dni.si_detail,
Sum(dni.stock_qty * dni.incoming_rate).as_("total_incoming_value"),
Sum(dni.stock_qty).as_("total_qty"),
)
.where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices)))
.groupby(dni.against_sales_invoice, dni.item_code)
.orderby(dni.creation, order=Order.desc)
.where(
(dni.docstatus == 1)
& (dni.against_sales_invoice.isin(invoices))
& (dni.si_detail.isnotnull())
& (dni.si_detail != "")
)
.groupby(dni.si_detail)
.run(as_dict=True)
)
for entry in delivery_notes:
self.delivery_notes[(entry.sales_invoice, entry.item_code)] = entry
self.delivery_notes[entry.si_detail] = entry
def group_items_by_invoice(self):
"""

View File

@@ -1 +1,224 @@
{% include "accounts/report/financial_statements.html" %}
{%
const report_columns = report
.get_columns_for_print()
.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(
__("Too many columns. Export the report and print it using a spreadsheet application.")
);
}
%}
<style>
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.financial-statements-important td { font-weight: bold; }
.financial-statements-blank-row td { height: 20px; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right {
text-align: right;
font-variant-numeric: tabular-nums;
}
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table th,
.report-table td {
padding: 6px 8px;
font-size: 14px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
}
.report-table thead th {
background: #f8f8f8;
font-weight: 500;
color: #7c7c7c;
}
.report-table tbody td {
vertical-align: top;
word-wrap: break-word;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
</div>
<div>
<strong>{%= __("Currency") %}:</strong>
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Period Based On") %}:</strong>
{%= filters.filter_based_on %}
</div>
{% if (filters.filter_based_on === "Fiscal Year") { %}
<div>
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
</div>
<div>
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
</div>
{% } else if (filters.filter_based_on === "Date Range") { %}
<div>
<strong>{%= __("Start Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
</div>
<div>
<strong>{%= __("End Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
</div>
{% } %}
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const align = i === 0 ? "text-left" : "text-right";
%}
<th class="{%= align %}">
{%= col.label %}
</th>
{% } %}
</tr>
</thead>
<tbody>
{% for (let j = 0, k = data.length; j < k; j++) { %}
{%
const row = data[j];
let row_class = "";
if (!(row.parent_account || row.parent_section)) {
row_class = "financial-statements-important";
}
if (!(row.account_name || row.section)) {
row_class += " financial-statements-blank-row";
}
%}
<tr class="{%= row_class %}">
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const value = row[col.fieldname];
const align = i === 0 ? "text-left" : "text-right";
%}
<td class="{%= align %}">
{% if (i === 0) { %}
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
</span>
{% } else if (!is_null(value)) { %}
{%= frappe.format(value, col, {}, row) %}
{% } %}
</td>
{% } %}
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right text-muted">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1 +1,215 @@
{% include "accounts/report/financial_statements.html" %}
{%
const report_columns = report
.get_columns_for_print()
.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(
__("Too many columns. Export the report and print it using a spreadsheet application.")
);
}
%}
<style>
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.financial-statements-important td { font-weight: bold; }
.financial-statements-blank-row td { height: 20px; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right {
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table th,
.report-table td {
padding: 6px 8px;
font-size: 14px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
}
.report-table thead th {
background: #f8f8f8;
font-weight: 500;
color: #7c7c7c;
}
.report-table tbody td.text-left {
vertical-align: top;
word-wrap: break-word;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
</div>
<div>
<strong>{%= __("Currency") %}:</strong>
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("From Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.from_date) %}
</div>
<div>
<strong>{%= __("To Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.to_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const align = i === 0 ? "text-left" : "text-right";
const styling = i === 0 ? "" : "width: 9em";
%}
<th class="{%= align %}" style= "{%= styling%}">
{%= col.label %}
</th>
{% } %}
</tr>
</thead>
<tbody>
{% for (let j = 0, k = data.length; j < k; j++) { %}
{%
const row = data[j];
let row_class = "";
if (!(row.parent_account || row.parent_section)) {
row_class = "financial-statements-important";
}
if (!(row.account_name || row.section)) {
row_class += " financial-statements-blank-row";
}
%}
<tr class="{%= row_class %}">
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const value = row[col.fieldname];
const align = i === 0 ? "text-left" : "text-right";
%}
<td class="{%= align %}">
{% if (i === 0) { %}
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
</span>
{% } else if (!is_null(value)) { %}
{%= frappe.format(value, col, {}, row) %}
{% } %}
</td>
{% } %}
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right text-muted">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -14,7 +14,7 @@
"for_user": "",
"hide_custom": 0,
"icon": "table",
"idx": 0,
"idx": 1,
"indicator_color": "",
"is_hidden": 0,
"label": "Financial Reports",
@@ -266,13 +266,13 @@
"type": "Link"
}
],
"modified": "2025-12-24 12:49:25.266357",
"modified": "2026-05-18 09:49:45.138296",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Financial Reports",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",

View File

@@ -1977,7 +1977,7 @@ def create_asset_category(enable_cwip=1):
asset_category.insert()
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0):
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0, asset_category=None):
meta = frappe.get_meta("Asset")
naming_series = meta.get_field("naming_series").options.splitlines()[0] or "ACC-ASS-.YYYY.-"
try:
@@ -1987,7 +1987,7 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass
"item_code": item_code or "Macbook Pro",
"item_name": "Macbook Pro",
"description": "Macbook Pro Retina Display",
"asset_category": "Computers",
"asset_category": asset_category or "Computers",
"item_group": "All Item Groups",
"stock_uom": "Nos",
"is_stock_item": 0,

View File

@@ -2,10 +2,59 @@
// For license information, please see license.txt
frappe.ui.form.on("Buying Settings", {
// refresh: function(frm) {
// }
refresh(frm) {
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
const display = frm.doc.supp_master_name === "Naming Series";
frm.set_df_property("naming_series_details", "hidden", !display);
frm.set_df_property("configure", "hidden", !display);
if (display) {
frm.naming_controller.load_master_series("Supplier", "naming_series_details");
}
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
},
supp_master_name(frm) {
const display = frm.doc.supp_master_name === "Naming Series";
frm.set_df_property("naming_series_details", "hidden", !display);
frm.set_df_property("configure", "hidden", !display);
if (display) {
frm.naming_controller.load_master_series("Supplier", "naming_series_details");
} else {
frm.doc.naming_series_details = "";
frm.refresh_field("naming_series_details");
}
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
},
configure(frm) {
frm.naming_controller.show_naming_series_dialog("Supplier", ({ naming_series_options }) => {
frm.doc.naming_series_details = naming_series_options;
frm.refresh_field("naming_series_details");
});
},
});
function get_transactions(frm) {
const transactions = [
{ label: __("Supplier"), doctype: "Supplier" },
{ label: __("Material Request"), doctype: "Material Request" },
{ label: __("Request for Quotation"), doctype: "Request for Quotation" },
{ label: __("Purchase Order"), doctype: "Purchase Order" },
{ label: __("Purchase Invoice"), doctype: "Purchase Invoice" },
{ label: __("Purchase Receipt"), doctype: "Purchase Receipt" },
];
if (frm.doc.supp_master_name !== "Naming Series") {
return transactions.filter((t) => t.doctype !== "Supplier");
}
return transactions;
}
frappe.tour["Buying Settings"] = [
{
fieldname: "supp_master_name",

View File

@@ -6,44 +6,51 @@
"engine": "InnoDB",
"field_order": [
"supplier_and_price_defaults_section",
"supplier_defaults_section",
"supp_master_name",
"supplier_group",
"buying_price_list",
"naming_series_details",
"configure",
"column_break_4",
"supplier_group",
"pricing_tab",
"buying_price_list",
"section_break_vwgg",
"maintain_same_rate",
"column_break_lwxs",
"maintain_same_rate_action",
"role_to_override_stop_action",
"section_break_xmlt",
"po_required",
"blanket_order_allowance",
"column_break_sbwq",
"pr_required",
"project_update_frequency",
"transaction_settings_section",
"column_break_fcyl",
"set_landed_cost_based_on_purchase_invoice_rate",
"allow_zero_qty_in_supplier_quotation",
"use_transaction_date_exchange_rate",
"allow_zero_qty_in_request_for_quotation",
"allow_negative_rates_for_items",
"po_required",
"pr_required",
"project_update_frequency",
"column_break_12",
"maintain_same_rate",
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
"allow_negative_rates_for_items",
"set_valuation_rate_for_rejected_materials",
"disable_last_purchase_rate",
"show_pay_button",
"purchase_invoice_settings_section",
"bill_for_rejected_quantity_in_purchase_invoice",
"use_transaction_date_exchange_rate",
"set_landed_cost_based_on_purchase_invoice_rate",
"zero_quantity_line_items_section",
"allow_zero_qty_in_supplier_quotation",
"allow_zero_qty_in_request_for_quotation",
"allow_zero_qty_in_purchase_order",
"blanket_order_section",
"blanket_order_allowance",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
"over_transfer_allowance",
"validate_consumed_qty",
"section_break_xcug",
"auto_create_subcontracting_order",
"column_break_izrr",
"auto_create_purchase_receipt",
"request_for_quotation_tab",
"fixed_email"
"fixed_email",
"document_naming_tab",
"transaction_naming_html"
],
"fields": [
{
@@ -54,6 +61,7 @@
"options": "Supplier Name\nNaming Series\nAuto Name"
},
{
"documentation_url": "https://docs.frappe.io/erpnext/buying-settings#2-default-supplier-group",
"fieldname": "supplier_group",
"fieldtype": "Link",
"label": "Default Supplier Group",
@@ -68,26 +76,27 @@
{
"fieldname": "po_required",
"fieldtype": "Select",
"label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
"label": "Is Purchase Order required for Purchase Invoice & Receipt creation?",
"options": "No\nYes"
},
{
"fieldname": "pr_required",
"fieldtype": "Select",
"label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
"label": "Is Purchase Receipt required for Purchase Invoice creation?",
"options": "No\nYes"
},
{
"default": "0",
"description": "Warn or stop if Item rate is changed in Purchase Invoice or Purchase Receipt generated from a Purchase Order.",
"fieldname": "maintain_same_rate",
"fieldtype": "Check",
"label": "Maintain Same Rate Throughout the Purchase Cycle"
"label": "Maintain same rate throughout the purchase cycle"
},
{
"default": "0",
"fieldname": "allow_multiple_items",
"fieldtype": "Check",
"label": "Allow Item To Be Added Multiple Times in a Transaction"
"label": "Allow Item to be added multiple times in a transaction"
},
{
"fieldname": "subcontract",
@@ -96,9 +105,10 @@
},
{
"default": "BOM",
"documentation_url": "https://docs.frappe.io/erpnext/buying-settings#1-backflush-raw-materials-of-subcontract-based-on",
"fieldname": "backflush_raw_materials_of_subcontract_based_on",
"fieldtype": "Select",
"label": "Backflush Raw Materials of Subcontract Based On",
"label": "Backflush raw materials of subcontract based on",
"options": "BOM\nMaterial Transferred for Subcontract"
},
{
@@ -108,25 +118,21 @@
"fieldtype": "Float",
"label": "Over Transfer Allowance (%)"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"default": "Stop",
"depends_on": "maintain_same_rate",
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
"fieldname": "maintain_same_rate_action",
"fieldtype": "Select",
"label": "Action If Same Rate is Not Maintained",
"label": "Action if same rate is not maintained",
"mandatory_depends_on": "maintain_same_rate",
"options": "Stop\nWarn"
},
{
"depends_on": "eval:doc.maintain_same_rate_action == 'Stop'",
"depends_on": "maintain_same_rate",
"fieldname": "role_to_override_stop_action",
"fieldtype": "Link",
"label": "Role Allowed to Override Stop Action",
"label": "Role allowed to override stop action",
"options": "Role"
},
{
@@ -134,12 +140,12 @@
"description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.",
"fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
"fieldtype": "Check",
"label": "Bill for Rejected Quantity in Purchase Invoice"
"label": "Bill for rejected quantity in Purchase Invoice"
},
{
"fieldname": "supplier_and_price_defaults_section",
"fieldtype": "Tab Break",
"label": "Naming Series and Price Defaults"
"label": "Defaults"
},
{
"fieldname": "column_break_4",
@@ -156,16 +162,17 @@
},
{
"default": "0",
"description": "Prevents the system from automatically using the rate from the last purchase transaction when creating new purchase orders or transactions.",
"fieldname": "disable_last_purchase_rate",
"fieldtype": "Check",
"label": "Disable Last Purchase Rate"
"label": "Disable last purchase rate"
},
{
"default": "1",
"depends_on": "eval: frappe.boot.versions && frappe.boot.versions.payments",
"fieldname": "show_pay_button",
"fieldtype": "Check",
"label": "Show Pay Button in Purchase Order Portal"
"label": "Show pay button in Purchase Order portal"
},
{
"default": "0",
@@ -193,30 +200,25 @@
"fieldname": "section_break_xcug",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_izrr",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Subcontracting Order (Draft) will be auto-created on submission of Purchase Order.",
"fieldname": "auto_create_subcontracting_order",
"fieldtype": "Check",
"label": "Auto Create Subcontracting Order"
"label": "Auto create Subcontracting Order"
},
{
"default": "0",
"description": "Purchase Receipt (Draft) will be auto-created on submission of Subcontracting Receipt.",
"fieldname": "auto_create_purchase_receipt",
"fieldtype": "Check",
"label": "Auto Create Purchase Receipt"
"label": "Auto create Purchase Receipt"
},
{
"default": "Each Transaction",
"description": "How often should Project be updated of Total Purchase Cost ?",
"fieldname": "project_update_frequency",
"fieldtype": "Select",
"label": "Update frequency of Project",
"label": "How often should project be updated of Total Purchase Cost ?",
"options": "Each Transaction\nManual"
},
{
@@ -240,14 +242,6 @@
"fieldtype": "Check",
"label": "Allow Supplier Quotation with Zero Quantity"
},
{
"fieldname": "section_break_xmlt",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_sbwq",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_fcyl",
"fieldtype": "Column Break"
@@ -258,7 +252,7 @@
"description": "If enabled, the system will generate an accounting entry for materials rejected in the Purchase Receipt.",
"fieldname": "set_valuation_rate_for_rejected_materials",
"fieldtype": "Check",
"label": "Set Valuation Rate for Rejected Materials"
"label": "Set valuation rate for rejected Materials"
},
{
"fieldname": "request_for_quotation_tab",
@@ -279,23 +273,77 @@
"description": "Raw materials consumed qty will be validated based on FG BOM required qty",
"fieldname": "validate_consumed_qty",
"fieldtype": "Check",
"label": "Validate Consumed Qty (as per BOM)"
"label": "Validate consumed quantity (as per BOM)"
},
{
"default": "0",
"fieldname": "allow_negative_rates_for_items",
"fieldtype": "Check",
"label": "Allow Negative rates for Items"
"label": "Allow negative rates for Items"
},
{
"fieldname": "supplier_defaults_section",
"fieldtype": "Section Break",
"label": "Supplier Defaults"
},
{
"fieldname": "section_break_vwgg",
"fieldtype": "Section Break"
},
{
"fieldname": "blanket_order_section",
"fieldtype": "Section Break",
"label": "Blanket Orders"
},
{
"fieldname": "zero_quantity_line_items_section",
"fieldtype": "Section Break",
"label": "Zero-Quantity Line Items"
},
{
"fieldname": "purchase_invoice_settings_section",
"fieldtype": "Section Break",
"label": "Purchase Invoice Settings"
},
{
"fieldname": "column_break_lwxs",
"fieldtype": "Column Break"
},
{
"fieldname": "pricing_tab",
"fieldtype": "Tab Break",
"label": "Pricing"
},
{
"fieldname": "document_naming_tab",
"fieldtype": "Tab Break",
"label": "Document Naming"
},
{
"fieldname": "configure",
"fieldtype": "Button",
"hidden": 1,
"label": "Configure Series"
},
{
"fieldname": "transaction_naming_html",
"fieldtype": "HTML"
},
{
"fieldname": "naming_series_details",
"fieldtype": "Small Text",
"hidden": 1,
"is_virtual": 1,
"label": "Naming Series options"
}
],
"grid_page_length": 50,
"hide_toolbar": 0,
"icon": "fa fa-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-04-15 16:07:35.484787",
"modified": "2026-05-05 16:30:37.184607",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
@@ -313,7 +361,6 @@
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"role": "Purchase Manager",

View File

@@ -269,14 +269,15 @@ class StockController(AccountsController):
)
is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items"))
if (
need_inventory_map = (self.get_stock_items() or self.get("packed_items")) and (
cint(erpnext.is_perpetual_inventory_enabled(self.company))
or provisional_accounting_for_non_stock_items
or is_asset_pr
):
)
inventory_account_map = frappe._dict()
if need_inventory_map:
inventory_account_map = self.get_inventory_account_map()
if need_inventory_map or provisional_accounting_for_non_stock_items or is_asset_pr:
if self.docstatus == 1:
if not gl_entries:
gl_entries = (

File diff suppressed because it is too large Load Diff

View File

@@ -292,7 +292,7 @@ class MasterProductionSchedule(Document):
return item_wise_data
def add_mps_data(self, data):
data = frappe._dict(sorted(data.items(), key=lambda x: x[0][1]))
data = frappe._dict(sorted(data.items(), key=lambda x: x[0][1] or ""))
for key in data:
row = data[key]

View File

@@ -94,6 +94,7 @@ def create_items():
"is_stock_item": 1,
"standard_rate": 100,
"opening_stock": 100,
"valuation_rate": 100,
"last_purchase_rate": 100,
"item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}],
}
@@ -103,6 +104,7 @@ def create_items():
"is_stock_item": 1,
"standard_rate": 200,
"opening_stock": 200,
"valuation_rate": 200,
"last_purchase_rate": 200,
"item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}],
}

View File

@@ -481,3 +481,4 @@ erpnext.patches.v16_0.scr_inv_dimension
erpnext.patches.v16_0.packed_item_inv_dimen
erpnext.patches.v16_0.fix_titles
erpnext.patches.v16_0.set_not_applicable_on_german_item_tax_templates
erpnext.patches.v16_0.clear_procedures_from_receivable_report

View File

@@ -0,0 +1,12 @@
import frappe
def execute():
frappe.db.sql("drop function if exists ar_genkey")
frappe.db.sql("drop procedure if exists ar_init_tmp_table")
frappe.db.sql("drop procedure if exists ar_allocate_to_tmp_table")
if frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method") == "Raw SQL":
frappe.db.set_single_value(
"Accounts Settings", "receivable_payable_fetch_method", "UnBuffered Cursor"
)

View File

@@ -37,6 +37,6 @@ import "./utils/demo.js";
import "./financial_statements.js";
import "./sales_trends_filters.js";
import "./purchase_trends_filters.js";
import "./utils/naming_series_dialog.js";
import "./utils/naming_series.js";
// import { sum } from 'frappe/public/utils/util.js'

View File

@@ -207,6 +207,7 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
this.frm = opts.frm;
this.transactions = opts.transactions || [];
this.$wrapper = opts.frm.get_field(opts.fieldname).$wrapper;
this.theme_observer = null;
}
render() {
this.$wrapper.html(`
@@ -231,6 +232,21 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
const $rows = this.$wrapper.find(".naming-series-table-rows");
this.map_configure_button($rows);
this.get_row_data($rows);
if (this.theme_observer) {
this.theme_observer.disconnect();
}
const observer = new MutationObserver(() => {
const badge_class = this.get_current_badge_class();
this.$wrapper.find(".badge").removeClass("badge-light badge-dark").addClass(badge_class);
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
}
map_configure_button($rows) {
@@ -258,9 +274,11 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
});
}
get_row_data($rows) {
this.transactions.forEach((t) => {
frappe.model.with_doctype(t.doctype, () => {
async get_row_data($rows) {
const rows = await Promise.all(
this.transactions.map(async (t) => {
await new Promise((resolve) => frappe.model.with_doctype(t.doctype, resolve));
const meta = frappe.get_meta(t.doctype);
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
const series = (naming_df?.options || "")
@@ -268,9 +286,12 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
.map((s) => s.trim())
.filter(Boolean);
$rows.append(this.make_row(t, series));
});
});
return this.make_row(t, series);
})
);
$rows.empty();
rows.forEach(($row) => $rows.append($row));
}
make_row(t, series) {
@@ -296,13 +317,20 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
`);
}
get_current_badge_class() {
return document.documentElement.getAttribute("data-theme") === "dark" ? "badge-dark" : "badge-light";
}
series_list_background(series_list) {
if (!series_list.length) {
return `<span class="text-muted">${__("Not configured")}</span>`;
}
const badge_class = this.get_current_badge_class();
return series_list
.map(
(s) => `<span class="badge badge-light"
(s) => `<span class="badge ${badge_class}"
style="margin: 2px; font-family: monospace; font-weight: normal;">
${frappe.utils.escape_html(s)}
</span>`
@@ -310,3 +338,80 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
.join("");
}
};
/**
* @param {Object} frm - Frappe form instance.
*/
erpnext.NamingSeriesController = class NamingSeriesController {
constructor(frm) {
this.frm = frm;
}
/**
* Renders the naming series table in the given field.
*
* @param {string} fieldname - Fieldname where the table should be rendered.
* @param {Array<{doctype: string, label: string}>} [transactions=[]] - Transactions to display.
* @returns {void}
*/
render_table(fieldname, transactions = []) {
this.frm._naming_series_table = new erpnext.NamingSeriesTable({
frm: this.frm,
fieldname: fieldname,
transactions: transactions,
});
this.frm._naming_series_table.render();
}
/**
* Loads naming series from the given master doctype into a field.
*
* @param {string} doctype - Master doctype name.
* @param {string} field - Fieldname where series should be shown.
* @returns {void}
*/
load_master_series(doctype, field) {
frappe.model.with_doctype(doctype, () => {
const meta = frappe.get_meta(doctype);
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
const options = naming_df?.options || "";
const series_list = options
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
this.frm.doc[field] = series_list.length
? series_list.join("\n")
: __("No naming series defined");
this.frm.refresh_field(field);
});
}
/**
* Opens the naming series dialog for a doctype.
*
* @param {string} doctype - Transaction doctype.
* @param {Function} [on_update] - Called after series are updated.
* @returns {void}
*/
show_naming_series_dialog(doctype, on_update) {
if (!this.frm._naming_dialogs) this.frm._naming_dialogs = {};
if (!this.frm._naming_dialogs[doctype]) {
this.frm._naming_dialogs[doctype] = new erpnext.NamingSeriesDialog({
doctype: doctype,
title: __("{0} Naming Series", [__(doctype)]),
on_update: ({ naming_series_options }) => {
const series = naming_series_options.split("\n").filter(Boolean);
this.frm
.get_field(this.opts.table_field)
.$wrapper.find(`.series-cell-${frappe.scrub(doctype)}`)
.html(this.frm._naming_series_table?.series_list_background(series));
on_update?.({ doctype, naming_series_options });
},
});
}
this.frm._naming_dialogs[doctype].show();
}
};

View File

@@ -6,6 +6,7 @@ import erpnext
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.regional.report.uae_vat_201.uae_vat_201 import (
execute,
get_exempt_total,
get_standard_rated_expenses_tax,
get_standard_rated_expenses_total,
@@ -32,6 +33,13 @@ class TestUaeVat201(ERPNextTestSuite):
make_item("_Test UAE VAT Zero Rated Item", properties={"is_zero_rated": 1, "is_exempt": 0})
make_item("_Test UAE VAT Exempt Item", properties={"is_zero_rated": 0, "is_exempt": 1})
def test_validate_company_region(self):
self.assertRaises(
frappe.exceptions.ValidationError,
execute,
{"company": "_Test Company"},
)
def test_uae_vat_201_report(self):
make_sales_invoices()
create_purchase_invoices()

View File

@@ -10,6 +10,13 @@ frappe.query_reports["UAE VAT 201"] = {
options: "Company",
reqd: 1,
default: frappe.defaults.get_user_default("Company"),
get_query: function () {
return {
filters: {
country: "United Arab Emirates",
},
};
},
},
{
fieldname: "from_date",

View File

@@ -5,13 +5,25 @@
import frappe
from frappe import _
from erpnext import get_region
def execute(filters=None):
validate_company_region(filters)
columns = get_columns()
data, emirates, amounts_by_emirate = get_data(filters)
return columns, data
def validate_company_region(filters):
if filters.get("company") and get_region(filters.get("company")) != "United Arab Emirates":
frappe.throw(
_(
"The company {0} is not in United Arab Emirates. UAE VAT 201 report is only available for companies in United Arab Emirates."
).format(frappe.bold(filters.get("company")))
)
def get_columns():
"""Creates a list of dictionaries that are used to generate column headers of the data table."""
return [

View File

@@ -3,27 +3,39 @@
frappe.ui.form.on("Selling Settings", {
refresh(frm) {
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
const display = frm.doc.cust_master_name === "Naming Series";
frm.set_df_property("naming_series_details", "hidden", !display);
frm.set_df_property("configure", "hidden", !display);
if (display) {
find_naming_series("Customer", "naming_series_details", frm);
frm.naming_controller.load_master_series("Customer", "naming_series_details");
}
load_default_naming_series(frm);
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
},
cust_master_name(frm) {
const display = frm.doc.cust_master_name === "Naming Series";
frm.set_df_property("naming_series_details", "hidden", !display);
frm.set_df_property("configure", "hidden", !display);
if (display) {
find_naming_series("Customer", "naming_series_details", frm);
frm.naming_controller.load_master_series("Customer", "naming_series_details");
} else {
frm.set_value("naming_series_details", "");
frm.doc.naming_series_details = "";
frm.refresh_field("naming_series_details");
}
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
},
configure(frm) {
show_naming_series_dialog("Customer", frm);
frm.naming_controller.show_naming_series_dialog("Customer", ({ naming_series_options }) => {
frm.doc.naming_series_details = naming_series_options;
frm.refresh_field("naming_series_details");
});
},
after_save(frm) {
@@ -31,51 +43,18 @@ frappe.ui.form.on("Selling Settings", {
},
});
function show_naming_series_dialog(doctype, frm) {
if (!frm._naming_series_dialog) {
frm._naming_series_dialog = new erpnext.NamingSeriesDialog({
doctype: doctype,
title: __("Naming Series for {0}", [__(doctype)]),
on_update: ({ naming_series_options }) => {
frm.set_value("naming_series_details", naming_series_options);
},
});
}
frm._naming_series_dialog.show();
}
function find_naming_series(doctype, field, frm) {
frappe.model.with_doctype(doctype, () => {
const meta = frappe.get_meta(doctype);
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
const options = naming_df?.options || "";
const series_list = options
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
frm.doc[field] = series_list.length ? series_list.join("\n") : __("No naming series defined");
frm.refresh_field(field);
});
}
function load_default_naming_series(frm) {
let transactions = [
function get_transactions(frm) {
const transactions = [
{ label: __("Customer"), doctype: "Customer" },
{ label: __("Quotation"), doctype: "Quotation" },
{ label: __("Sales Order"), doctype: "Sales Order" },
{ label: __("Sales Invoice"), doctype: "Sales Invoice" },
{ label: __("Delivery Note"), doctype: "Delivery Note" },
{ label: __("Payment Entry"), doctype: "Payment Entry" },
{ label: __("POS Invoice"), doctype: "POS Invoice" },
];
if (frm.doc.cust_master_name !== "Naming Series") {
transactions = transactions.filter((t) => t.doctype !== "Customer");
return transactions.filter((t) => t.doctype !== "Customer");
}
new erpnext.NamingSeriesTable({
frm: frm,
fieldname: "transaction_naming_html",
transactions: transactions,
}).render();
return transactions;
}

View File

@@ -1085,7 +1085,6 @@ def create_transaction_deletion_request(company):
tdr.reload()
tdr.submit()
tdr.start_deletion_tasks()
frappe.msgprint(
_("Transaction Deletion Document {0} has been triggered for company {1}").format(

View File

@@ -396,7 +396,6 @@ def create_and_submit_transaction_deletion_doc(company):
tdr.process_in_single_transaction = True
tdr.submit()
tdr.start_deletion_tasks()
return tdr

View File

@@ -736,10 +736,11 @@ class TransactionDeletionRecord(Document):
self.enqueue_task(task="Clear Notifications")
return
company_obj = frappe.get_doc("Company", self.company)
company_obj.total_monthly_sales = 0
company_obj.sales_monthly_history = None
company_obj.save()
frappe.db.set_value(
"Company",
self.company,
{"total_monthly_sales": 0, "sales_monthly_history": None},
)
self.db_set("reset_company_default_values_status", "Completed")
self.enqueue_task(task="Clear Notifications")

View File

@@ -48,9 +48,18 @@ class DeprecatedSerialNoValuation:
if not posting_datetime and self.sle.posting_date:
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
do_not_fetch_rate = frappe.db.get_single_value(
"Stock Reposting Settings", "do_not_fetch_incoming_rate_from_serial_no"
)
for serial_no in serial_nos:
sn_details = frappe.db.get_value("Serial No", serial_no, ["purchase_rate", "company"], as_dict=1)
if sn_details and sn_details.purchase_rate and sn_details.company == self.sle.company:
if (
sn_details
and sn_details.purchase_rate
and sn_details.company == self.sle.company
and (not frappe.flags.through_repost_item_valuation or not do_not_fetch_rate)
):
self.serial_no_incoming_rate[serial_no] += flt(sn_details.purchase_rate)
incoming_values += self.serial_no_incoming_rate[serial_no]
continue

View File

@@ -73,6 +73,7 @@ frappe.ui.form.on("Item", {
},
};
},
onload: function (frm) {
erpnext.item.setup_queries(frm);
if (frm.doc.variant_of) {
@@ -127,6 +128,21 @@ frappe.ui.form.on("Item", {
refresh: function (frm) {
frm.trigger("toggle_has_serial_batch_fields");
if (frappe.defaults.get_default("item_naming_by") != "Naming Series" || frm.doc.variant_of) {
frm.toggle_display("naming_series", false);
} else {
erpnext.toggle_naming_series();
}
frm.toggle_display(["standard_rate"], frappe.model.can_create("Item Price"));
if (frm.is_new()) {
frm.toggle_display("disabled", false);
return;
}
frm.toggle_display("disabled", true);
if (frm.doc.is_stock_item) {
frm.add_custom_button(
__("Stock Balance"),
@@ -229,8 +245,6 @@ frappe.ui.form.on("Item", {
__("Create")
);
}
// frm.page.set_inner_btn_group_as_primary(__('Create'));
}
if (frm.doc.variant_of) {
frm.set_intro(
@@ -241,12 +255,6 @@ frappe.ui.form.on("Item", {
);
}
if (frappe.defaults.get_default("item_naming_by") != "Naming Series" || frm.doc.variant_of) {
frm.toggle_display("naming_series", false);
} else {
erpnext.toggle_naming_series();
}
erpnext.item.edit_prices_button(frm);
erpnext.item.toggle_attributes(frm);
@@ -286,8 +294,6 @@ frappe.ui.form.on("Item", {
},
};
});
frm.toggle_display(["standard_rate"], frappe.model.can_create("Item Price"));
},
validate: function (frm) {
@@ -661,10 +667,10 @@ $.extend(erpnext.item, {
make_dashboard: function (frm) {
if (frm.doc.__islocal) return;
// Show Stock Levels only if is_stock_item
if (frm.doc.is_stock_item) {
frappe.require("item-dashboard.bundle.js", function () {
const section = frm.dashboard.add_section("", __("Stock Levels"));
const section = frm.fields_dict["stock_levels_html"].$wrapper;
erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
parent: section,
item_code: frm.doc.name,

View File

@@ -18,30 +18,57 @@
"stock_uom",
"column_break0",
"disabled",
"allow_alternative_item",
"is_stock_item",
"has_variants",
"is_fixed_asset",
"auto_create_assets",
"is_grouped_asset",
"asset_category",
"asset_naming_series",
"section_break_zlmj",
"is_sales_item",
"allow_alternative_item",
"has_variants",
"column_break_kpmi",
"is_purchase_item",
"is_customer_provided_item",
"customer",
"section_break_gjns",
"opening_stock",
"column_break_ixrh",
"standard_rate",
"column_break_ixrh",
"opening_stock",
"section_break_znra",
"over_delivery_receipt_allowance",
"column_break_wugd",
"over_billing_allowance",
"image",
"section_break_11",
"description",
"brand",
"description",
"accounting",
"section_break_wfkx",
"item_defaults",
"deferred_accounting_section",
"enable_deferred_expense",
"no_of_months_exp",
"column_break_9s9o",
"enable_deferred_revenue",
"no_of_months",
"uom_tab",
"unit_of_measure_conversion",
"uom_conversion_details_column",
"uom_help_html",
"uoms",
"dashboard_tab",
"item_tax_section_break",
"section_break_oilf",
"column_break_aytr",
"taxes",
"section_break_fxqz",
"purchase_tax_withholding_category",
"column_break_ltlb",
"sales_tax_withholding_category",
"inventory_section",
"stock_levels_section",
"stock_levels_html",
"inventory_valuation_section",
"valuation_method",
"column_break_cqdk",
@@ -69,32 +96,20 @@
"column_break_37",
"has_serial_no",
"serial_no_series",
"defaults_tab",
"item_defaults",
"variants_section",
"variant_of",
"variant_based_on",
"attributes",
"accounting",
"deferred_accounting_section",
"enable_deferred_expense",
"no_of_months_exp",
"column_break_9s9o",
"enable_deferred_revenue",
"no_of_months",
"purchasing_tab",
"purchase_uom",
"min_order_qty",
"safety_stock",
"is_purchase_item",
"purchase_details_cb",
"lead_time_days",
"last_purchase_rate",
"is_customer_provided_item",
"customer",
"supplier_details",
"delivered_by_supplier",
"column_break2",
"section_break_ylma",
"supplier_items",
"foreign_trade_details",
"country_of_origin",
@@ -103,24 +118,10 @@
"sales_details",
"sales_uom",
"grant_commission",
"is_sales_item",
"column_break3",
"max_discount",
"customer_details",
"customer_items",
"item_tax_section_break",
"section_break_oilf",
"column_break_aytr",
"taxes",
"section_break_fxqz",
"purchase_tax_withholding_category",
"column_break_ltlb",
"sales_tax_withholding_category",
"quality_tab",
"inspection_required_before_purchase",
"inspection_required_before_delivery",
"column_break_pxjh",
"quality_inspection_template",
"manufacturing",
"include_item_in_manufacturing",
"is_sub_contracted_item",
@@ -129,10 +130,16 @@
"production_capacity",
"total_projected_qty",
"section_break_xili",
"customer_code",
"column_break_vipt",
"default_manufacturer_part_no",
"default_item_manufacturer"
"default_item_manufacturer",
"column_break_vipt",
"customer_code",
"quality_tab",
"inspection_required_before_purchase",
"inspection_required_before_delivery",
"column_break_pxjh",
"quality_inspection_template",
"dashboard_tab"
],
"fields": [
{
@@ -208,32 +215,38 @@
},
{
"default": "0",
"description": "Disabled items cannot be selected in any transaction.",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled",
"search_index": 1
"search_index": 1,
"show_description_on_click": 1
},
{
"default": "0",
"description": "Allow substituting this item with an alternative from the Item Alternative list when stock is unavailable.",
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"label": "Allow Alternative Item"
"label": "Allow Alternative Item",
"show_description_on_click": 1
},
{
"allow_in_quick_entry": 1,
"bold": 1,
"default": "1",
"depends_on": "eval:!doc.is_fixed_asset",
"description": "ERPNext will make a stock ledger entry for each transaction of this item. Keep unchecked for non-stock or service items.",
"fieldname": "is_stock_item",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Maintain Stock",
"oldfieldname": "is_stock_item",
"oldfieldtype": "Select"
"oldfieldtype": "Select",
"read_only_depends_on": "eval:doc.is_fixed_asset"
},
{
"default": "1",
"depends_on": "eval:!doc.is_fixed_asset",
"description": "Enable for raw material items used in BOM. Uncheck for additional services like 'washing' used in manufacturing.",
"fieldname": "include_item_in_manufacturing",
"fieldtype": "Check",
"label": "Include Item In Manufacturing"
@@ -241,6 +254,7 @@
{
"bold": 1,
"depends_on": "eval:(doc.__islocal&&doc.is_stock_item && !doc.has_serial_no && !doc.has_batch_no)",
"description": "Used to create an opening Stock Entry with the Valuation Rate when the item is saved",
"fieldname": "opening_stock",
"fieldtype": "Float",
"label": "Opening Stock"
@@ -253,7 +267,8 @@
},
{
"bold": 1,
"depends_on": "eval:doc.__islocal",
"depends_on": "eval:doc.__islocal && doc.is_sales_item",
"description": "Creates an Item Price automatically when the item is saved",
"fieldname": "standard_rate",
"fieldtype": "Currency",
"label": "Standard Selling Rate"
@@ -261,9 +276,11 @@
{
"allow_in_quick_entry": 1,
"default": "0",
"description": "Enable if this item is a company asset like machinery or furniture.",
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
"label": "Is Fixed Asset"
"label": "Is Fixed Asset",
"read_only_depends_on": "eval:doc.is_stock_item"
},
{
"allow_in_quick_entry": 1,
@@ -413,16 +430,12 @@
"options": "Item Reorder"
},
{
"collapsible": 1,
"fieldname": "unit_of_measure_conversion",
"fieldtype": "Section Break",
"label": "Units of Measure"
"fieldtype": "Section Break"
},
{
"description": "Will also apply for variants",
"fieldname": "uoms",
"fieldtype": "Table",
"label": "UOMs",
"oldfieldname": "uom_conversion_details",
"oldfieldtype": "Table",
"options": "UOM Conversion Detail"
@@ -438,15 +451,18 @@
{
"default": "0",
"depends_on": "eval:doc.is_stock_item",
"description": "Track this item in batches. Cannot be changed after a stock transaction exists.",
"fieldname": "has_batch_no",
"fieldtype": "Check",
"label": "Has Batch No",
"oldfieldname": "has_batch_no",
"oldfieldtype": "Select"
"oldfieldtype": "Select",
"show_description_on_click": 1
},
{
"default": "0",
"depends_on": "has_batch_no",
"description": "Batch number will be auto-created in format AAAA.00001 if not specified in transactions. Leave blank to always enter batch numbers manually.",
"fieldname": "create_new_batch",
"fieldtype": "Check",
"label": "Automatically Create New Batch"
@@ -463,6 +479,7 @@
{
"default": "0",
"depends_on": "has_batch_no",
"description": "Batch number will be created based on expiry date. Expiry dates can be set in the Batch master.",
"fieldname": "has_expiry_date",
"fieldtype": "Check",
"label": "Has Expiry Date"
@@ -491,11 +508,13 @@
{
"default": "0",
"depends_on": "eval:doc.is_stock_item",
"description": "Track each unit with a unique serial number for warranty and return tracking. Cannot be changed after a stock transaction exists.",
"fieldname": "has_serial_no",
"fieldtype": "Check",
"label": "Has Serial No",
"oldfieldname": "has_serial_no",
"oldfieldtype": "Select"
"oldfieldtype": "Select",
"show_description_on_click": 1
},
{
"depends_on": "has_serial_no",
@@ -520,7 +539,8 @@
"fieldname": "has_variants",
"fieldtype": "Check",
"in_standard_filter": 1,
"label": "Has Variants"
"label": "Has Variants",
"show_description_on_click": 1
},
{
"default": "Item Attribute",
@@ -547,11 +567,15 @@
},
{
"default": "1",
"description": "Allow this item to be used in purchase transactions.",
"fieldname": "is_purchase_item",
"fieldtype": "Check",
"label": "Allow Purchase"
"label": "Allow Purchase",
"read_only_depends_on": "eval: doc.is_customer_provided_item",
"show_description_on_click": 1
},
{
"depends_on": "eval: doc.is_purchase_item",
"fieldname": "purchase_uom",
"fieldtype": "Link",
"label": "Default Purchase Unit of Measure",
@@ -560,7 +584,7 @@
{
"default": "0.00",
"depends_on": "is_stock_item",
"description": "Minimum quantity should be as per Stock UOM",
"description": "Minimum quantity should be as per Stock UOM\n\n",
"fieldname": "min_order_qty",
"fieldtype": "Float",
"label": "Minimum Order Qty",
@@ -569,6 +593,7 @@
"oldfieldtype": "Currency"
},
{
"description": "Minimum stock level to maintain as a buffer. Used to calculate recommended reorder level: Reorder Level = Safety Stock + (Average Daily Consumption \u00d7 Lead Time).",
"fieldname": "safety_stock",
"fieldtype": "Float",
"label": "Safety Stock",
@@ -588,6 +613,7 @@
"oldfieldtype": "Int"
},
{
"description": "The rate at which this item was last purchased via a Purchase Invoice. Auto-updated by the system.",
"fieldname": "last_purchase_rate",
"fieldtype": "Float",
"label": "Last Purchase Rate",
@@ -599,9 +625,12 @@
},
{
"default": "0",
"description": "Enable if this item is provided by a customer and received via Stock Entry.",
"fieldname": "is_customer_provided_item",
"fieldtype": "Check",
"label": "Is Customer Provided Item"
"label": "Is Customer Provided Item",
"read_only_depends_on": "eval: doc.is_purchase_item",
"show_description_on_click": 1
},
{
"depends_on": "eval:doc.is_customer_provided_item",
@@ -611,29 +640,24 @@
"options": "Customer"
},
{
"collapsible": 1,
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "supplier_details",
"fieldtype": "Section Break",
"hide_border": 1,
"label": "Supplier Details"
},
{
"default": "0",
"description": "Enable for drop shipping - supplier delivers directly to the customer without passing through your warehouse.",
"fieldname": "delivered_by_supplier",
"fieldtype": "Check",
"label": "Delivered by Supplier (Drop Ship)",
"print_hide": 1
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "supplier_items",
"fieldtype": "Table",
"label": "Supplier Items",
"label": "Item Supplier",
"options": "Item Supplier"
},
{
@@ -660,6 +684,7 @@
},
{
"collapsible": 1,
"depends_on": "eval: doc.is_sales_item",
"fieldname": "sales_details",
"fieldtype": "Tab Break",
"label": "Sales",
@@ -667,6 +692,7 @@
"options": "fa fa-tag"
},
{
"depends_on": "eval: doc.is_sales_item",
"fieldname": "sales_uom",
"fieldtype": "Link",
"label": "Default Sales Unit of Measure",
@@ -674,9 +700,11 @@
},
{
"default": "1",
"description": "Allow this item to be used in sales transactions.",
"fieldname": "is_sales_item",
"fieldtype": "Check",
"label": "Allow Sales"
"label": "Allow Sales",
"show_description_on_click": 1
},
{
"fieldname": "column_break3",
@@ -685,6 +713,7 @@
"width": "50%"
},
{
"description": "Maximum discount % allowed when selling this item. Eg: if set to 20%, a discount greater than 20% cannot be applied in sales transactions.",
"fieldname": "max_discount",
"fieldtype": "Float",
"label": "Max Discount (%)",
@@ -693,6 +722,7 @@
},
{
"default": "0",
"description": "Expense for this item will be recognized over a period of months. Eg: prepaid insurance or annual software license",
"fieldname": "enable_deferred_revenue",
"fieldtype": "Check",
"label": "Enable Deferred Revenue"
@@ -705,6 +735,7 @@
},
{
"default": "0",
"description": "Income from this item will be recognized over a period of months instead of all at once. Eg: annual subscription paid upfront.",
"fieldname": "enable_deferred_expense",
"fieldtype": "Check",
"label": "Enable Deferred Expense"
@@ -716,7 +747,6 @@
"label": "No of Months (Expense)"
},
{
"collapsible": 1,
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "customer_details",
"fieldtype": "Section Break",
@@ -748,17 +778,21 @@
},
{
"default": "0",
"description": "A quality inspection must be completed before generating a Purchase Receipt for this item.",
"fieldname": "inspection_required_before_purchase",
"fieldtype": "Check",
"label": "Inspection Required before Purchase",
"oldfieldname": "inspection_required",
"oldfieldtype": "Select"
"oldfieldtype": "Select",
"show_description_on_click": 1
},
{
"default": "0",
"description": "A quality inspection must be completed before generating a Delivery Note for this item.",
"fieldname": "inspection_required_before_delivery",
"fieldtype": "Check",
"label": "Inspection Required before Delivery"
"label": "Inspection Required before Delivery",
"show_description_on_click": 1
},
{
"fieldname": "quality_inspection_template",
@@ -789,6 +823,7 @@
},
{
"default": "0",
"description": "Enable if a vendor manufactures this item for you. You can choose to provide them raw materials using the default BOM.",
"fieldname": "is_sub_contracted_item",
"fieldtype": "Check",
"label": "Is Subcontracted Item",
@@ -817,6 +852,7 @@
},
{
"depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset",
"description": "Percentage by which over-delivery or over-receipt is allowed against a Sales/Purchase Order for this item. If not set, value from Stock Settings will be used.",
"fieldname": "over_delivery_receipt_allowance",
"fieldtype": "Float",
"label": "Over Delivery/Receipt Allowance (%)",
@@ -825,6 +861,7 @@
},
{
"depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset",
"description": "Percentage by which over-billing is allowed against a Sales/Purchase Order for this item. If not set, value from Accounts Settings will be used.",
"fieldname": "over_billing_allowance",
"fieldtype": "Float",
"label": "Over Billing Allowance (%)"
@@ -834,7 +871,7 @@
"depends_on": "is_fixed_asset",
"fieldname": "auto_create_assets",
"fieldtype": "Check",
"label": "Auto Create Assets on Purchase"
"label": "Auto create assets on purchase"
},
{
"fieldname": "default_item_manufacturer",
@@ -851,19 +888,24 @@
},
{
"default": "1",
"description": "If enabled, sales from this item will be included in Sales Person and Sales Partner commission calculations",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission"
"label": "Grant Commission",
"show_description_on_click": 1
},
{
"default": "0",
"depends_on": "auto_create_assets",
"depends_on": "eval: doc.is_fixed_asset && doc.auto_create_assets",
"description": "Creates a single grouped asset instead of individual assets when purchased in bulk.",
"fieldname": "is_grouped_asset",
"fieldtype": "Check",
"label": "Create Grouped Asset"
"label": "Create Grouped Asset",
"show_description_on_click": 1
},
{
"default": "0",
"description": "Allow stock to go below zero for this item, even if negative stock is disabled in Stock Settings.",
"fieldname": "allow_negative_stock",
"fieldtype": "Check",
"label": "Allow Negative Stock"
@@ -874,6 +916,7 @@
"label": "Inventory Settings"
},
{
"depends_on": "eval: doc.is_purchase_item",
"fieldname": "purchasing_tab",
"fieldtype": "Tab Break",
"label": "Purchasing"
@@ -893,7 +936,7 @@
{
"fieldname": "dashboard_tab",
"fieldtype": "Tab Break",
"label": "Dashboard",
"label": "Connections",
"show_dashboard": 1
},
{
@@ -916,11 +959,6 @@
"fieldtype": "Int",
"label": "Production Capacity"
},
{
"fieldname": "defaults_tab",
"fieldtype": "Tab Break",
"label": "Defaults"
},
{
"fieldname": "section_break_znra",
"fieldtype": "Section Break"
@@ -988,6 +1026,50 @@
{
"fieldname": "column_break_vipt",
"fieldtype": "Column Break"
},
{
"fieldname": "stock_levels_html",
"fieldtype": "HTML",
"label": "Stock Levels HTML",
"options": "<div id=\"stock-levels-placeholder\"></div>"
},
{
"fieldname": "section_break_wfkx",
"fieldtype": "Section Break"
},
{
"fieldname": "section_break_ylma",
"fieldtype": "Section Break"
},
{
"fieldname": "uom_tab",
"fieldtype": "Tab Break",
"label": "UOM"
},
{
"depends_on": "eval: !doc.__islocal && doc.is_stock_item",
"fieldname": "stock_levels_section",
"fieldtype": "Section Break",
"label": "Stock Levels"
},
{
"fieldname": "uom_conversion_details_column",
"fieldtype": "Column Break",
"label": "UOM Conversion Details"
},
{
"fieldname": "uom_help_html",
"fieldtype": "HTML",
"options": "<div style=\"margin-bottom:10px; color: var(--gray-500) !important;\">Define alternate units for this item. Eg: 1 Box = 12 Nos, set conversion factor as 12. (Will also apply for variants) <a href=\"https://docs.frappe.io/erpnext/uom\" target=\"_blank\">Learn more &#8594;</a></div>"
},
{
"fieldname": "section_break_zlmj",
"fieldtype": "Section Break",
"label": "Item Attributes"
},
{
"fieldname": "column_break_kpmi",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-tag",
@@ -995,7 +1077,7 @@
"image_field": "image",
"links": [],
"make_attachments_public": 1,
"modified": "2026-04-14 13:37:00.183583",
"modified": "2026-04-28 17:31:47.613279",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -182,6 +182,14 @@ class Item(Document):
for default in self.item_defaults or [frappe._dict()]:
self.add_price(default.default_price_list)
frappe.msgprint(
_("Item Price created at rate {0}").format(
frappe.format(self.standard_rate, {"fieldtype": "Currency"})
),
indicator="green",
alert=True,
)
if self.opening_stock:
self.set_opening_stock()
@@ -285,7 +293,7 @@ class Item(Document):
if not self.is_stock_item or self.has_serial_no or self.has_batch_no:
return
if not self.valuation_rate and not self.standard_rate and not self.is_customer_provided_item:
if self.valuation_rate is None and not self.is_customer_provided_item:
frappe.throw(_("Valuation Rate is mandatory if Opening Stock entered"))
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -310,14 +318,38 @@ class Item(Document):
item_code=self.name,
target=default_warehouse,
qty=self.opening_stock,
rate=self.valuation_rate or self.standard_rate,
rate=self.valuation_rate,
company=default.company,
posting_date=getdate(),
posting_time=nowtime(),
do_not_save=True,
)
if self.valuation_rate == 0:
for item in stock_entry.items:
item.allow_zero_valuation_rate = 1
stock_entry.insert()
stock_entry.submit()
stock_entry.load_from_db()
stock_entry.add_comment("Comment", _("Opening Stock"))
stock_entry_link = frappe.utils.get_link_to_form("Stock Entry", stock_entry.name)
if self.valuation_rate == 0:
frappe.msgprint(
_("Opening Stock entry created with zero valuation rate: {0}").format(
stock_entry_link
),
indicator="orange",
alert=True,
)
else:
frappe.msgprint(
_("Opening Stock entry created: {0}").format(stock_entry_link),
indicator="green",
alert=True,
)
def validate_fixed_asset(self):
if self.is_fixed_asset:
if self.is_stock_item:

View File

@@ -36,6 +36,7 @@
"options": "Customer Group"
},
{
"description": "Enter the Item Code that this customer uses at their end. This will be shown in Sales Orders for the customer's reference.",
"fieldname": "ref_code",
"fieldtype": "Data",
"in_filter": 1,
@@ -52,13 +53,14 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:54.332166",
"modified": "2026-04-23 02:09:39.602010",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Customer Detail",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -55,11 +55,13 @@
"fieldtype": "Column Break"
},
{
"description": "Default price list for buying or selling this item",
"fieldname": "default_price_list",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Default Price List",
"options": "Price List"
"options": "Price List",
"show_description_on_click": 1
},
{
"fieldname": "purchase_defaults",
@@ -67,12 +69,15 @@
"label": "Purchase Defaults"
},
{
"description": "Cost center used for tracking purchase expenses for this item",
"fieldname": "buying_cost_center",
"fieldtype": "Link",
"label": "Default Buying Cost Center",
"options": "Cost Center"
"options": "Cost Center",
"show_description_on_click": 1
},
{
"description": "This supplier will be auto-selected in new purchase transactions",
"fieldname": "default_supplier",
"fieldtype": "Link",
"label": "Default Supplier",
@@ -83,10 +88,12 @@
"fieldtype": "Column Break"
},
{
"description": "Account where the cost of this item will be debited on purchase",
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Default Expense Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"fieldname": "selling_defaults",
@@ -94,20 +101,24 @@
"label": "Sales Defaults"
},
{
"description": "Cost center used for tracking sales revenue for this item",
"fieldname": "selling_cost_center",
"fieldtype": "Link",
"label": "Default Selling Cost Center",
"options": "Cost Center"
"options": "Cost Center",
"show_description_on_click": 1
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"description": "Account where revenue from selling this item will be credited",
"fieldname": "income_account",
"fieldtype": "Link",
"label": "Default Income Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"fieldname": "default_discount_account",
@@ -116,6 +127,7 @@
"options": "Account"
},
{
"description": "Provisional liability account used for service items before invoice is received",
"fieldname": "default_provisional_account",
"fieldtype": "Link",
"label": "Default Provisional Account (Service)",
@@ -128,17 +140,21 @@
},
{
"depends_on": "eval: parent.enable_deferred_expense",
"description": "When you pay for something upfront (like annual insurance), the cost is held here and recognized gradually over time",
"fieldname": "deferred_expense_account",
"fieldtype": "Link",
"label": "Deferred Expense Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"depends_on": "eval: parent.enable_deferred_revenue",
"description": "Revenue received in advance (e.g. annual subscription) is held here and recognized gradually over time",
"fieldname": "deferred_revenue_account",
"fieldtype": "Link",
"label": "Deferred Revenue Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"fieldname": "column_break_kwad",
@@ -154,28 +170,35 @@
"label": "Cost of Goods Sold"
},
{
"description": "Account where cost of goods sold will be posted when this item is sold",
"fieldname": "default_cogs_account",
"fieldtype": "Link",
"label": "Default COGS Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"description": "Account to record additional purchase expenses like freight or customs for this item",
"fieldname": "purchase_expense_account",
"fieldtype": "Link",
"label": "Purchase Expense Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"description": "Used to balance the books when recording extra purchase costs like freight or customs",
"fieldname": "purchase_expense_contra_account",
"fieldtype": "Link",
"label": "Purchase Expense Contra Account",
"options": "Account"
},
{
"description": "Stock account where inventory value for this item will be tracked",
"fieldname": "default_inventory_account",
"fieldtype": "Link",
"label": "Default Inventory Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"fetch_from": "default_inventory_account.account_currency",
@@ -188,7 +211,7 @@
],
"istable": 1,
"links": [],
"modified": "2025-10-21 10:50:46.144721",
"modified": "2026-04-27 01:49:01.396845",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Default",

View File

@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"supplier",
"column_break_vcuv",
"supplier_part_no"
],
"fields": [
@@ -25,19 +26,24 @@
"label": "Supplier Part Number",
"print_width": "200px",
"width": "200px"
},
{
"fieldname": "column_break_vcuv",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:55.339052",
"modified": "2026-04-22 02:08:38.777228",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Supplier",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -5487,6 +5487,147 @@ class TestPurchaseReceipt(ERPNextTestSuite):
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value
)
def test_purchase_receipt_gl_entries_for_asset_item(self):
from erpnext.assets.doctype.asset.test_asset import create_fixed_asset_item
# Create a Company without Stock Accounts Linked.
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": "Asset Company",
"country": "India",
"default_currency": "INR",
}
).insert()
stock_accounts = (
company.default_inventory_account,
company.stock_adjustment_account,
company.stock_received_but_not_billed,
)
company.update(
{"stock_in_hand_account": "", "stock_adjustment_account": "", "stock_received_but_not_billed": ""}
).save()
for account in stock_accounts:
frappe.db.delete("Account", account)
asset_category = create_asset_category_for_pr_test()
asset_item = create_fixed_asset_item(
item_code="Test Fixed Asset Item for PR GL Test", asset_category=asset_category.name
)
arnb_account = frappe.db.get_value("Company", company.name, "asset_received_but_not_billed")
# Purchase Receipt should be able to create even without any stock accounts linked to company
pr = make_purchase_receipt(
item_code=asset_item.name, warehouse="Stores - AC", qty=1, rate=10000, company=company.name
)
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
gl_accounts = [d.account for d in gl_entries]
# The fixed asset account set on the item row must be debited
asset_expense_account = pr.items[0].expense_account
self.assertIn(asset_expense_account, gl_accounts)
# Asset Received But Not Billed must be credited
self.assertIn(arnb_account, gl_accounts)
# No Stock-type account should appear — the inventory account map is not
# needed and must not be consulted for an asset-only receipt
for entry in gl_entries:
account_type = frappe.db.get_value("Account", entry.account, "account_type")
self.assertNotEqual(account_type, "Stock")
pr.cancel()
def test_purchase_receipt_gl_entries_with_mixed_asset_and_stock_items(self):
from erpnext.assets.doctype.asset.test_asset import create_fixed_asset_item
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": "Asset Company",
"country": "India",
"default_currency": "INR",
}
).insert()
asset_category = create_asset_category_for_pr_test()
asset_item = create_fixed_asset_item(
item_code="Test Fixed Asset Item for PR GL Test", asset_category=asset_category.name
)
arnb_account = frappe.db.get_value("Company", company.name, "asset_received_but_not_billed")
pr = make_purchase_receipt(
item_code=asset_item.name,
qty=1,
rate=10000,
warehouse="Stores - AC",
do_not_save=True,
company=company.name,
)
pr.append(
"items",
{
"item_code": "_Test Item",
"warehouse": "Stores - AC",
"qty": 5,
"received_qty": 5,
"rejected_qty": 0,
"rate": 50,
"uom": "_Test UOM",
"stock_uom": "_Test UOM",
"conversion_factor": 1.0,
"cost_center": frappe.get_cached_value("Company", pr.company, "cost_center"),
},
)
pr.insert()
pr.submit()
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
gl_accounts = [d.account for d in gl_entries]
self.assertIn(arnb_account, gl_accounts)
# The fixed asset account set on the item row must be debited
asset_expense_account = pr.items[0].expense_account
self.assertIn(asset_expense_account, gl_accounts)
# Asset Received But Not Billed must be credited
self.assertIn(asset_category.accounts[0].fixed_asset_account, gl_accounts)
# Stock Accounts should be used for Stock Items
self.assertIn(company.stock_received_but_not_billed, gl_accounts)
self.assertIn(company.default_inventory_account, gl_accounts)
pr.cancel()
def create_asset_category_for_pr_test():
category_name = "Test Asset Category for PR"
asset_category = frappe.get_doc(
{
"doctype": "Asset Category",
"asset_category_name": category_name,
"enable_cwip_accounting": 0,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 12,
"frequency_of_depreciation": 1,
"accounts": [
{
"company_name": "Asset Company",
"fixed_asset_account": "Electronic Equipment - AC",
}
],
}
).insert()
return asset_category
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -852,7 +852,7 @@ class StockEntry(StockController, SubcontractingInwardController):
else:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
if self.purpose == "Manufacture":
if self.purpose in ["Manufacture", "Repack"]:
if d.is_finished_item or d.type or d.is_legacy_scrap_item:
d.s_warehouse = None
if not d.t_warehouse:

View File

@@ -83,6 +83,7 @@ class StockReconciliation(StockController):
self.set_total_qty_and_amount()
self.validate_putaway_capacity()
self.validate_inventory_dimension()
self.validate_uom_is_integer("stock_uom", "qty")
if self._action == "submit":
self.validate_reserved_stock()

View File

@@ -1,5 +1,6 @@
{
"actions": [],
"allow_bulk_edit": 1,
"allow_rename": 1,
"beta": 1,
"creation": "2021-10-01 10:56:30.814787",
@@ -13,6 +14,8 @@
"end_time",
"limits_dont_apply_on",
"item_based_reposting",
"column_break_mavd",
"do_not_fetch_incoming_rate_from_serial_no",
"section_break_dxuf",
"enable_parallel_reposting",
"no_of_parallel_reposting",
@@ -99,13 +102,23 @@
"fieldname": "enable_separate_reposting_for_gl",
"fieldtype": "Check",
"label": "Enable Separate Reposting for GL"
},
{
"fieldname": "column_break_mavd",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "For legacy serial nos, do not fetch incoming rate from serial no and calculate it based on the inward transaction",
"fieldname": "do_not_fetch_incoming_rate_from_serial_no",
"fieldtype": "Check",
"label": "Do not fetch incoming rate from Serial No"
}
],
"hide_toolbar": 0,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-03-16 13:28:20.978007",
"modified": "2026-05-15 12:59:34.392491",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reposting Settings",

View File

@@ -16,6 +16,7 @@ class StockRepostingSettings(Document):
if TYPE_CHECKING:
from frappe.types import DF
do_not_fetch_incoming_rate_from_serial_no: DF.Check
enable_parallel_reposting: DF.Check
enable_separate_reposting_for_gl: DF.Check
end_time: DF.Time | None

View File

@@ -7,6 +7,7 @@
"engine": "InnoDB",
"field_order": [
"uom",
"column_break_nmeg",
"conversion_factor"
],
"fields": [
@@ -27,12 +28,16 @@
"non_negative": 1,
"oldfieldname": "conversion_factor",
"oldfieldtype": "Float"
},
{
"fieldname": "column_break_nmeg",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-11-19 21:27:13.968771",
"modified": "2026-04-27 02:22:52.652036",
"modified_by": "Administrator",
"module": "Stock",
"name": "UOM Conversion Detail",

View File

@@ -1129,7 +1129,7 @@ def insert_item_price(ctx: ItemDetailsCtx):
)
item_price.insert()
frappe.msgprint(
_("Item Price Added for {0} in Price List {1}").format(
_("Item Price added for {0} in Price List - {1}").format(
get_link_to_form("Item", ctx.item_code), ctx.price_list
),
alert=True,
@@ -1153,9 +1153,10 @@ def insert_item_price(ctx: ItemDetailsCtx):
)
item_price.insert()
frappe.msgprint(
_("Item Price added for {0} in Price List {1}").format(
_("Item Price added for {0} in Price List - {1}").format(
get_link_to_form("Item", ctx.item_code), ctx.price_list
)
),
alert=True,
)

View File

@@ -2433,7 +2433,9 @@ def get_stock_value_difference(
)
if voucher_detail_no:
query = query.where(table.voucher_detail_no != voucher_detail_no)
query = query.where(
(table.voucher_detail_no != voucher_detail_no) | (table.voucher_detail_no.isnull())
)
elif voucher_no:
query = query.where(table.voucher_no != voucher_no)

View File

@@ -80,14 +80,13 @@
{
"child": 1,
"collapsible": 1,
"filters": "[[\"Sales Invoice\",\"is_return\",\"=\",1]]",
"icon": "",
"indent": 0,
"keep_closed": 0,
"label": "Credit Note",
"link_to": "Sales Invoice",
"link_type": "DocType",
"route_options": "",
"route_options": "{\"is_return\": 1}",
"show_arrow": 0,
"type": "Link"
},
@@ -139,12 +138,12 @@
{
"child": 1,
"collapsible": 1,
"filters": "[[\"Purchase Invoice\",\"is_return\",\"=\",1]]",
"indent": 0,
"keep_closed": 0,
"label": "Debit Note",
"link_to": "Purchase Invoice",
"link_type": "DocType",
"route_options": "{\"is_return\": 1}",
"show_arrow": 0,
"type": "Link"
},