mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-04 20:59:11 +00:00
Merge pull request #51912 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -307,7 +307,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
|
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\">Common Party</a>",
|
||||||
"fieldname": "enable_common_party_accounting",
|
"fieldname": "enable_common_party_accounting",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Common Party Accounting"
|
"label": "Enable Common Party Accounting"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
|
<h2 class="text-center">{{ _("GENERAL LEDGER") }}</h2>
|
||||||
<div>
|
<div>
|
||||||
{% if filters.party[0] == filters.party_name[0] %}
|
{% if filters.party[0] == filters.party_name[0] %}
|
||||||
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{ filters.party_name[0] }}</b></h5>
|
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{ filters.party_name[0] }}</b></h5>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
"autoname": "Prompt",
|
"autoname": "Prompt",
|
||||||
"creation": "2020-05-22 16:46:18.712954",
|
"creation": "2020-05-22 16:46:18.712954",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
"fieldname": "frequency",
|
"fieldname": "frequency",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Frequency",
|
"label": "Frequency",
|
||||||
"options": "Weekly\nMonthly\nQuarterly"
|
"options": "Daily\nWeekly\nBiweekly\nMonthly\nQuarterly"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
@@ -401,7 +402,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-08-04 18:21:12.603623",
|
"modified": "2025-10-07 12:19:20.719898",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Statement Of Accounts",
|
"name": "Process Statement Of Accounts",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.desk.reportview import get_match_cond
|
from frappe.desk.reportview import get_match_cond
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_days, add_months, format_date, getdate, today
|
from frappe.utils import add_days, add_months, add_to_date, format_date, getdate, today
|
||||||
from frappe.utils.jinja import validate_template
|
from frappe.utils.jinja import validate_template
|
||||||
from frappe.utils.pdf import get_pdf
|
from frappe.utils.pdf import get_pdf
|
||||||
from frappe.www.printview import get_print_style
|
from frappe.www.printview import get_print_style
|
||||||
@@ -55,7 +55,7 @@ class ProcessStatementOfAccounts(Document):
|
|||||||
enable_auto_email: DF.Check
|
enable_auto_email: DF.Check
|
||||||
filter_duration: DF.Int
|
filter_duration: DF.Int
|
||||||
finance_book: DF.Link | None
|
finance_book: DF.Link | None
|
||||||
frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
|
frequency: DF.Literal["Daily", "Weekly", "Biweekly", "Monthly", "Quarterly"]
|
||||||
from_date: DF.Date | None
|
from_date: DF.Date | None
|
||||||
ignore_cr_dr_notes: DF.Check
|
ignore_cr_dr_notes: DF.Check
|
||||||
ignore_exchange_rate_revaluation_journals: DF.Check
|
ignore_exchange_rate_revaluation_journals: DF.Check
|
||||||
@@ -529,8 +529,9 @@ def send_emails(document_name, from_scheduler=False, posting_date=None):
|
|||||||
|
|
||||||
if doc.enable_auto_email and from_scheduler:
|
if doc.enable_auto_email and from_scheduler:
|
||||||
new_to_date = getdate(posting_date or today())
|
new_to_date = getdate(posting_date or today())
|
||||||
if doc.frequency == "Weekly":
|
if doc.frequency in ("Daily", "Weekly", "Biweekly"):
|
||||||
new_to_date = add_days(new_to_date, 7)
|
frequency = {"Daily": 1, "Weekly": 7, "Biweekly": 14}
|
||||||
|
new_to_date = add_days(new_to_date, frequency[doc.frequency])
|
||||||
else:
|
else:
|
||||||
new_to_date = add_months(new_to_date, 1 if doc.frequency == "Monthly" else 3)
|
new_to_date = add_months(new_to_date, 1 if doc.frequency == "Monthly" else 3)
|
||||||
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
|
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
|
||||||
|
|||||||
@@ -6,228 +6,304 @@
|
|||||||
.print-format td {
|
.print-format td {
|
||||||
vertical-align:middle !important;
|
vertical-align:middle !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="header-html" class="hidden-pdf">
|
<div id="header-html" class="hidden-pdf">
|
||||||
{% if letter_head.content %}
|
{% if letter_head.content %}
|
||||||
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
||||||
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div id="footer-html" class="visible-pdf">
|
|
||||||
{% if letter_head.footer %}
|
|
||||||
<div class="letter-head-footer">
|
|
||||||
<hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
|
|
||||||
{{ letter_head.footer }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
|
||||||
<h4 class="text-center">
|
|
||||||
{{ filters.customer_name }}
|
|
||||||
</h4>
|
|
||||||
<h6 class="text-center">
|
|
||||||
{% if (filters.tax_id) %}
|
|
||||||
{{ _("Tax Id: ") }}{{ filters.tax_id }}
|
|
||||||
{% endif %}
|
|
||||||
</h6>
|
|
||||||
<h5 class="text-center">
|
|
||||||
{{ _(filters.ageing_based_on) }}
|
|
||||||
{{ _("Until") }}
|
|
||||||
{{ frappe.format(filters.report_date, 'Date') }}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="clearfix">
|
|
||||||
<div class="pull-left">
|
|
||||||
{% if(filters.payment_terms) %}
|
|
||||||
<strong>{{ _("Payment Terms") }}:</strong> {{ filters.payment_terms }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="pull-right">
|
|
||||||
{% if(filters.credit_limit) %}
|
|
||||||
<strong>{{ _("Credit Limit") }}:</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if(filters.show_future_payments) %}
|
|
||||||
{% set balance_row = data.slice(-1).pop() %}
|
|
||||||
{% for i in report.columns %}
|
|
||||||
{% if i.fieldname == 'age' %}
|
|
||||||
{% set elem = i %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% set start = report.columns.findIndex(elem) %}
|
|
||||||
{% set range1 = report.columns[start].label %}
|
|
||||||
{% set range2 = report.columns[start+1].label %}
|
|
||||||
{% set range3 = report.columns[start+2].label %}
|
|
||||||
{% set range4 = report.columns[start+3].label %}
|
|
||||||
{% set range5 = report.columns[start+4].label %}
|
|
||||||
{% set 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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>{{ _("Total Outstanding") }}</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{{ format_number(balance_row["age"], null, 2) }}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{{ frappe.utils.fmt_money(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">
|
|
||||||
{{ frappe.utils.fmt_money(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">
|
|
||||||
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) }}</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table class="table table-bordered">
|
</div>
|
||||||
|
<div id="footer-html" class="visible-pdf">
|
||||||
|
{% if letter_head.footer %}
|
||||||
|
<div class="letter-head-footer">
|
||||||
|
<hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
|
||||||
|
{{ letter_head.footer }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="text-center" style="margin-top:0">{{ _("STATEMENT OF ACCOUNTS") }}</h2>
|
||||||
|
<h4 class="text-center">
|
||||||
|
{{ filters.customer_name }}
|
||||||
|
</h4>
|
||||||
|
<h6 class="text-center">
|
||||||
|
{% if (filters.tax_id) %}
|
||||||
|
{{ _("Tax Id: {0}").format(filters.tax_id) }}
|
||||||
|
{% endif %}
|
||||||
|
</h6>
|
||||||
|
<h5 class="text-center">
|
||||||
|
{{ _("{0} until {1}").format(
|
||||||
|
_(filters.ageing_based_on),
|
||||||
|
frappe.format(filters.report_date, 'Date')
|
||||||
|
) }}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="clearfix">
|
||||||
|
<div class="pull-left">
|
||||||
|
{% if(filters.payment_terms) %}
|
||||||
|
<strong>{{ _("Payment Terms:") }}</strong> {{ filters.payment_terms }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
{% if(filters.credit_limit) %}
|
||||||
|
<strong>{{ _("Credit Limit:") }}</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if(filters.show_future_payments)%}
|
||||||
|
{% set balance_row = data[-1] %}
|
||||||
|
|
||||||
|
{% set ns = namespace(idx=None) %}
|
||||||
|
{% for i in report.columns %}
|
||||||
|
{% if i.fieldname == "age" and ns.idx is none %}
|
||||||
|
{% set ns.idx = loop.index0 %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% set age = report.columns[ns.idx].label %}
|
||||||
|
{% set range1 = report.columns[ns.idx+1].label %}
|
||||||
|
{% set range2 = report.columns[ns.idx+2].label %}
|
||||||
|
{% set range3 = report.columns[ns.idx+3].label %}
|
||||||
|
{% set range4 = report.columns[ns.idx+4].label %}
|
||||||
|
{% set range5 = report.columns[ns.idx+5].label %}
|
||||||
|
|
||||||
|
{% if(balance_row) %}
|
||||||
|
<table class="table table-bordered table-condensed">
|
||||||
|
<caption class="text-right">{{ _("Amount in {0}").format(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>
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
<th>{{ _(" ") }}</th>
|
||||||
<th style="width: 10%">{{ _("Date") }}</th>
|
<th>{{ _(age) }}</th>
|
||||||
<th style="width: 4%">{{ _("Age (Days)") }}</th>
|
<th>{{ _(range1) }}</th>
|
||||||
|
<th>{{ _(range2) }}</th>
|
||||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
<th>{{ _(range3) }}</th>
|
||||||
<th style="width: 14%">{{ _("Reference") }}</th>
|
<th>{{ _(range4) }}</th>
|
||||||
<th style="width: 10%">{{ _("Sales Person") }}</th>
|
<th>{{ _(range5) }}</th>
|
||||||
{% else %}
|
<th>{{ _("Total") }}</th>
|
||||||
<th style="width: 24%">{{ _("Reference") }}</th>
|
|
||||||
{% endif %}
|
|
||||||
{% if not(filters.show_future_payments) %}
|
|
||||||
<th style="width: 20%">
|
|
||||||
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
|
||||||
{{ _("Remarks") }}
|
|
||||||
{% else %}
|
|
||||||
{{ _("Party") }}
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
{% endif %}
|
|
||||||
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
|
|
||||||
{% if not(filters.show_future_payments) %}
|
|
||||||
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
|
|
||||||
<th style="width: 10%; text-align: right">
|
|
||||||
{% if report.report_name == "Accounts Receivable" %}
|
|
||||||
{{ _('Credit Note') }}
|
|
||||||
{% else %}
|
|
||||||
{{ _('Debit Note') }}
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
{% endif %}
|
|
||||||
<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>
|
|
||||||
{% endif %}
|
|
||||||
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
|
|
||||||
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
|
|
||||||
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<th style="width: 40%">
|
|
||||||
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
|
||||||
{{ _("Remarks")}}
|
|
||||||
{% else %}
|
|
||||||
{{ _("Party") }}
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
|
|
||||||
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
|
|
||||||
<th style="width: 15%">
|
|
||||||
{% if report.report_name == "Accounts Receivable Summary" %}
|
|
||||||
{{ _('Credit Note Amount') }}
|
|
||||||
{% else %}
|
|
||||||
{{ _('Debit Note Amount') }}
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for i in range(data|length) %}
|
<tr>
|
||||||
<tr>
|
<td>{{ _("Total Outstanding") }}</td>
|
||||||
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
<td class="text-right">
|
||||||
{% if(data[i]["party"]) %}
|
{{ frappe.utils.flt(balance_row["age"], 2) }}
|
||||||
<td>{{ frappe.format((data[i]["posting_date"]), 'Date') }}</td>
|
</td>
|
||||||
<td style="text-align: right">{{ data[i]["age"] }}</td>
|
<td class="text-right">
|
||||||
<td>
|
{{ frappe.utils.fmt_money(balance_row["range1"], currency=balance_row["currency"]) }}
|
||||||
{% if not(filters.show_future_payments) %}
|
</td>
|
||||||
{{ data[i]["voucher_type"] }}
|
<td class="text-right">
|
||||||
<br>
|
{{ frappe.utils.fmt_money(balance_row["range2"], currency=balance_row["currency"]) }}
|
||||||
{% endif %}
|
</td>
|
||||||
{{ data[i]["voucher_no"] }}
|
<td class="text-right">
|
||||||
</td>
|
{{ frappe.utils.fmt_money(balance_row["range3"], currency=balance_row["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(balance_row["range4"], currency=balance_row["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(balance_row["range5"], currency=balance_row["currency"]) }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(frappe.utils.flt(balance_row["outstanding"]), currency=balance_row["currency"]) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<td>{{ _("Future Payments") }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="text-right">
|
||||||
|
{{ frappe.utils.fmt_money(frappe.utils.flt(balance_row["future_amount"]), currency=balance_row["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">
|
||||||
|
{{ frappe.utils.fmt_money(frappe.utils.flt(balance_row["outstanding"] - balance_row["future_amount"]), currency=balance_row["currency"]) }}</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
</table>
|
||||||
<td>{{ data[i]["sales_person"] }}</td>
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||||
|
<th style="width: 10%">{{ _("Date") }}</th>
|
||||||
|
<th style="width: 4%">{{ _("Age (Days)") }}</th>
|
||||||
|
|
||||||
|
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||||
|
<th style="width: 14%">{{ _("Reference") }}</th>
|
||||||
|
<th style="width: 10%">{{ _("Sales Person") }}</th>
|
||||||
|
{% else %}
|
||||||
|
<th style="width: 24%">{{ _("Reference") }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% if not(filters.show_future_payments) and filters.show_remarks %}
|
||||||
|
<th style="width: 20%">
|
||||||
|
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||||
|
{{ _("Remarks") }}
|
||||||
|
{% else %}
|
||||||
|
{{ _("Party") }}
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
{% endif %}
|
||||||
|
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
|
||||||
|
<th style="width: 10%; text-align: right">
|
||||||
|
{% if report.report_name == "Accounts Receivable" %}
|
||||||
|
{{ _("Credit Note") }}
|
||||||
|
{% else %}
|
||||||
|
{{ _("Debit Note") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
{% endif %}
|
||||||
|
<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>
|
||||||
|
{% endif %}
|
||||||
|
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
|
||||||
|
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
|
||||||
|
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<th style="width: 40%">
|
||||||
|
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||||
|
{{ _("Remarks")}}
|
||||||
|
{% else %}
|
||||||
|
{{ _("Party") }}
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
|
||||||
|
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
|
||||||
|
<th style="width: 15%">
|
||||||
|
{% if report.report_name == "Accounts Receivable Summary" %}
|
||||||
|
{{ _("Credit Note Amount") }}
|
||||||
|
{% else %}
|
||||||
|
{{ _("Debit Note Amount") }}
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for i in range(data|length) %}
|
||||||
|
<tr>
|
||||||
|
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||||
|
{% if(data[i]["party"]) %}
|
||||||
|
<td>{{ frappe.format(data[i]["posting_date"], 'Date') }}</td>
|
||||||
|
<td style="text-align: right">{{ data[i]["age"] }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
{{ data[i]["voucher_type"] }}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{{ data[i]["voucher_no"] }}
|
||||||
|
</td>
|
||||||
|
|
||||||
{% if not (filters.show_future_payments) %}
|
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||||
|
<td>{{ data[i]["sales_person"] }}</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not (filters.show_future_payments) and filters.show_remarks %}
|
||||||
|
<td>
|
||||||
|
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
|
||||||
|
{{ data[i]["party"] }}
|
||||||
|
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||||
|
<br> {{ data[i]["customer_name"] }}
|
||||||
|
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
|
||||||
|
<br> {{ data[i]["supplier_name"] }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
{% if data[i]["remarks"] %}
|
||||||
|
{{ _("Remarks") }}:
|
||||||
|
{{ data[i]["remarks"] }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||||
|
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=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>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<td></td>
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
|
||||||
|
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=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>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if(data[i]["party"] or " ") %}
|
||||||
|
{% if not(data[i]["is_total_row"]) %}
|
||||||
<td>
|
<td>
|
||||||
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
|
{% if(not(filters.customer | filters.supplier)) %}
|
||||||
{{ data[i]["party"] }}
|
{{ data[i]["party"] }}
|
||||||
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||||
<br> {{ data[i]["customer_name"] }}
|
<br> {{ data[i]["customer_name"] }}
|
||||||
@@ -235,132 +311,73 @@
|
|||||||
<br> {{ data[i]["supplier_name"] }}
|
<br> {{ data[i]["supplier_name"] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<br>{{ _("Remarks") }}:
|
||||||
{% if data[i]["remarks"] %}
|
{{ data[i]["remarks"] }}
|
||||||
{{ _("Remarks") }}:
|
|
||||||
{{ data[i]["remarks"] }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<td style="text-align: right">
|
|
||||||
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
|
||||||
|
|
||||||
{% if not(filters.show_future_payments) %}
|
|
||||||
<td style="text-align: right">
|
|
||||||
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
|
||||||
<td style="text-align: right">
|
|
||||||
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
|
||||||
{% endif %}
|
|
||||||
<td style="text-align: right">
|
|
||||||
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=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>
|
|
||||||
{% endif %}
|
|
||||||
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<td></td>
|
<td><b>{{ _("Total") }}</b></td>
|
||||||
{% if not(filters.show_future_payments) %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
<td></td>
|
|
||||||
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
|
|
||||||
<td style="text-align: right">
|
|
||||||
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
|
|
||||||
|
|
||||||
{% if not(filters.show_future_payments) %}
|
|
||||||
<td style="text-align: right">
|
|
||||||
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
|
|
||||||
{% endif %}
|
|
||||||
<td style="text-align: right">
|
|
||||||
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=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>
|
|
||||||
{% endif %}
|
|
||||||
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if(data[i]["party"] or " ") %}
|
|
||||||
{% if not(data[i]["is_total_row"]) %}
|
|
||||||
<td>
|
|
||||||
{% if(not(filters.customer | filters.supplier)) %}
|
|
||||||
{{ data[i]["party"] }}
|
|
||||||
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
|
||||||
<br> {{ data[i]["customer_name"] }}
|
|
||||||
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
|
|
||||||
<br> {{ data[i]["supplier_name"] }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<br>{{ _("Remarks") }}:
|
|
||||||
{{ data[i]["remarks"] }}
|
|
||||||
</td>
|
|
||||||
{% else %}
|
|
||||||
<td><b>{{ _("Total") }}</b></td>
|
|
||||||
{% endif %}
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
|
||||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||||
|
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
{% endif %}
|
||||||
{% endfor %}
|
</tr>
|
||||||
<td></td>
|
{% endfor %}
|
||||||
<td></td>
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
{% if (filters.show_future_payments) or filters.show_remarks %}
|
||||||
<td></td>
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
{% if not(filters.show_future_payments) %}
|
||||||
<td></td>
|
<td></td>
|
||||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
|
||||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
|
||||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
|
||||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
|
||||||
</tbody>
|
{% else %}
|
||||||
</table>
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
|
||||||
<br>
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
|
||||||
{% if ageing %}
|
<td></td>
|
||||||
<h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
|
<td></td>
|
||||||
{{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}}
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="future_amount"), currency=data[0]["currency"]) }}</b></td>
|
||||||
</h4>
|
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="remaining_balance"), currency=data[0]["currency"]) }}</b></td>
|
||||||
<table class="table table-bordered">
|
{% endif %}
|
||||||
<thead>
|
</tbody>
|
||||||
<tr>
|
</table>
|
||||||
<th style="width: 25%">0 - 30 Days</th>
|
<br>
|
||||||
<th style="width: 25%">30 - 60 Days</th>
|
{% if ageing %}
|
||||||
<th style="width: 25%">60 - 90 Days</th>
|
<h4 class="text-center">
|
||||||
<th style="width: 25%">90 - 120 Days</th>
|
{{ _("Ageing Report based on {0} up to {1}").format(
|
||||||
<th style="width: 20%">Above 120 Days</th>
|
ageing.ageing_based_on,
|
||||||
</tr>
|
frappe.format(filters.report_date, "Date")
|
||||||
</thead>
|
) }}
|
||||||
<tbody>
|
</h4>
|
||||||
<tr>
|
<table class="table table-bordered">
|
||||||
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
|
<thead>
|
||||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
|
<tr>
|
||||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
|
<th style="width: 25%">{{ _("0 - 30 Days") }}</th>
|
||||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
|
<th style="width: 25%">{{ _("30 - 60 Days") }}</th>
|
||||||
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
|
<th style="width: 25%">{{ _("60 - 90 Days") }}</th>
|
||||||
</tr>
|
<th style="width: 25%">{{ _("90 - 120 Days") }}</th>
|
||||||
</tbody>
|
<th style="width: 20%">{{ _("Above 120 Days") }}</th>
|
||||||
</table>
|
</tr>
|
||||||
{% endif %}
|
</thead>
|
||||||
{% if terms_and_conditions %}
|
<tbody>
|
||||||
<div>
|
<tr>
|
||||||
{{ terms_and_conditions }}
|
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
|
||||||
</div>
|
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
|
||||||
{% endif %}
|
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
|
||||||
<p class="text-right text-muted">{{ _("Printed On ") }}{{ frappe.utils.now() }}</p>
|
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
|
||||||
|
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
{% if terms_and_conditions %}
|
||||||
|
<div>
|
||||||
|
{{ terms_and_conditions }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-right text-muted">{{ _("Printed on {0}").format(frappe.utils.now()) }}</p>
|
||||||
|
|||||||
@@ -215,16 +215,22 @@ def start_repost(account_repost_doc=str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def get_allowed_types_from_settings(child_doc: bool = False):
|
def get_allowed_types_from_settings(child_doc: bool = False):
|
||||||
repost_docs = [
|
# Avoid DISTINCT(...) here: Frappe applies a default ORDER BY which breaks on Postgres
|
||||||
x.document_type
|
# when used with SELECT DISTINCT.
|
||||||
for x in frappe.db.get_all(
|
repost_docs = frappe.db.get_all(
|
||||||
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
"Repost Allowed Types",
|
||||||
)
|
filters={"allowed": True},
|
||||||
]
|
pluck="document_type",
|
||||||
|
)
|
||||||
|
|
||||||
|
# De-dupe while preserving order (first occurrence wins)
|
||||||
|
repost_docs = list(dict.fromkeys(repost_docs))
|
||||||
result = repost_docs
|
result = repost_docs
|
||||||
|
|
||||||
if repost_docs and child_doc:
|
if repost_docs and child_doc:
|
||||||
result.extend(get_child_docs(repost_docs))
|
result.extend(get_child_docs(repost_docs))
|
||||||
|
# Keep uniqueness after extending
|
||||||
|
result = list(dict.fromkeys(result))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -291,8 +297,11 @@ def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters
|
|||||||
if txt:
|
if txt:
|
||||||
filters.update({"document_type": ("like", f"%{txt}%")})
|
filters.update({"document_type": ("like", f"%{txt}%")})
|
||||||
|
|
||||||
if allowed_types := frappe.db.get_all(
|
allowed_types = frappe.db.get_all(
|
||||||
"Repost Allowed Types", filters=filters, fields=["distinct(document_type)"], as_list=1
|
"Repost Allowed Types",
|
||||||
):
|
filters=filters,
|
||||||
return allowed_types
|
pluck="document_type",
|
||||||
return []
|
)
|
||||||
|
|
||||||
|
allowed_types = list(dict.fromkeys(allowed_types))
|
||||||
|
return [[dt] for dt in allowed_types]
|
||||||
|
|||||||
@@ -2974,6 +2974,60 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
||||||
self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
|
self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
|
||||||
|
|
||||||
|
def test_item_tax_template_change_with_grand_total_discount(self):
|
||||||
|
"""
|
||||||
|
Test that when item tax template changes due to discount on Grand Total,
|
||||||
|
the tax calculations are consistent.
|
||||||
|
"""
|
||||||
|
item = create_item("Test Item With Multiple Tax Templates")
|
||||||
|
|
||||||
|
item.set("taxes", [])
|
||||||
|
item.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
|
||||||
|
"minimum_net_rate": 0,
|
||||||
|
"maximum_net_rate": 500,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
item.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
|
||||||
|
"minimum_net_rate": 501,
|
||||||
|
"maximum_net_rate": 1000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(item=item.name, rate=700, do_not_save=True)
|
||||||
|
si.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Excise Duty - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Excise Duty",
|
||||||
|
"rate": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
si.insert()
|
||||||
|
|
||||||
|
self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
|
||||||
|
|
||||||
|
si.apply_discount_on = "Grand Total"
|
||||||
|
si.discount_amount = 300
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
# Verify template changed to 10%
|
||||||
|
self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
||||||
|
self.assertEqual(si.taxes[0].tax_amount, 70) # 10% of 700
|
||||||
|
self.assertEqual(si.grand_total, 470) # 700 + 70 - 300
|
||||||
|
|
||||||
|
si.submit()
|
||||||
|
|
||||||
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||||
def test_sales_invoice_with_discount_accounting_enabled(self):
|
def test_sales_invoice_with_discount_accounting_enabled(self):
|
||||||
discount_account = create_account(
|
discount_account = create_account(
|
||||||
|
|||||||
@@ -219,13 +219,18 @@ def get_net_profit(
|
|||||||
|
|
||||||
has_value = False
|
has_value = False
|
||||||
|
|
||||||
|
gross_income_roots = [row for row in (gross_income or []) if not flt(row.get("indent"))]
|
||||||
|
non_gross_income_roots = [row for row in (non_gross_income or []) if not flt(row.get("indent"))]
|
||||||
|
gross_expense_roots = [row for row in (gross_expense or []) if not flt(row.get("indent"))]
|
||||||
|
non_gross_expense_roots = [row for row in (non_gross_expense or []) if not flt(row.get("indent"))]
|
||||||
|
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
key = period if consolidated else period.key
|
key = period if consolidated else period.key
|
||||||
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 0
|
|
||||||
non_gross_income_for_period = flt(non_gross_income[0].get(key, 0)) if non_gross_income else 0
|
|
||||||
|
|
||||||
gross_expense_for_period = flt(gross_expense[0].get(key, 0)) if gross_expense else 0
|
gross_income_for_period = sum(flt(row.get(key, 0)) for row in gross_income_roots)
|
||||||
non_gross_expense_for_period = flt(non_gross_expense[0].get(key, 0)) if non_gross_expense else 0
|
non_gross_income_for_period = sum(flt(row.get(key, 0)) for row in non_gross_income_roots)
|
||||||
|
gross_expense_for_period = sum(flt(row.get(key, 0)) for row in gross_expense_roots)
|
||||||
|
non_gross_expense_for_period = sum(flt(row.get(key, 0)) for row in non_gross_expense_roots)
|
||||||
|
|
||||||
total_income = gross_income_for_period + non_gross_income_for_period
|
total_income = gross_income_for_period + non_gross_income_for_period
|
||||||
total_expense = gross_expense_for_period + non_gross_expense_for_period
|
total_expense = gross_expense_for_period + non_gross_expense_for_period
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
|
|||||||
{
|
{
|
||||||
"total_tax": total_tax,
|
"total_tax": total_tax,
|
||||||
"total_other_charges": total_other_charges,
|
"total_other_charges": total_other_charges,
|
||||||
"total": d.base_net_amount + total_tax,
|
"total": d.base_net_amount + total_tax + total_other_charges,
|
||||||
"currency": company_currency,
|
"currency": company_currency,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -511,6 +511,7 @@ def reconcile_against_document(
|
|||||||
doc.make_advance_gl_entries(entry=row)
|
doc.make_advance_gl_entries(entry=row)
|
||||||
else:
|
else:
|
||||||
_delete_pl_entries(voucher_type, voucher_no)
|
_delete_pl_entries(voucher_type, voucher_no)
|
||||||
|
_delete_adv_pl_entries(voucher_type, voucher_no)
|
||||||
gl_map = doc.build_gl_map()
|
gl_map = doc.build_gl_map()
|
||||||
# Make sure there is no overallocation
|
# Make sure there is no overallocation
|
||||||
from erpnext.accounts.general_ledger import process_debit_credit_difference
|
from erpnext.accounts.general_ledger import process_debit_credit_difference
|
||||||
|
|||||||
@@ -1297,6 +1297,55 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
pi = make_pi_from_po(po.name)
|
pi = make_pi_from_po(po.name)
|
||||||
self.assertEqual(pi.items[0].qty, 50)
|
self.assertEqual(pi.items[0].qty, 50)
|
||||||
|
|
||||||
|
def test_multiple_advances_against_purchase_order_are_allocated_across_partial_purchase_invoices(self):
|
||||||
|
# step - 1: create PO
|
||||||
|
po = create_purchase_order(qty=10, rate=10)
|
||||||
|
|
||||||
|
# step - 2: create first partial advance payment
|
||||||
|
pe1 = get_payment_entry("Purchase Order", po.name, bank_account="_Test Bank - _TC")
|
||||||
|
pe1.reference_no = "1"
|
||||||
|
pe1.reference_date = nowdate()
|
||||||
|
pe1.paid_amount = 50
|
||||||
|
pe1.references[0].allocated_amount = 50
|
||||||
|
pe1.save(ignore_permissions=True).submit()
|
||||||
|
|
||||||
|
# check first advance paid against PO
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.advance_paid, 50)
|
||||||
|
|
||||||
|
# step - 3: create first PI for partial qty and allocate first advance
|
||||||
|
pi_1 = make_pi_from_po(po.name)
|
||||||
|
pi_1.update_stock = 1
|
||||||
|
pi_1.allocate_advances_automatically = 1
|
||||||
|
pi_1.items[0].qty = 5
|
||||||
|
pi_1.save(ignore_permissions=True).submit()
|
||||||
|
|
||||||
|
# step - 4: create second advance payment for remaining
|
||||||
|
pe2 = get_payment_entry("Purchase Order", po.name, bank_account="_Test Bank - _TC")
|
||||||
|
pe2.reference_no = "2"
|
||||||
|
pe2.reference_date = nowdate()
|
||||||
|
pe2.paid_amount = 50
|
||||||
|
pe2.references[0].allocated_amount = 50
|
||||||
|
pe2.save(ignore_permissions=True).submit()
|
||||||
|
|
||||||
|
# check second advance paid against PO
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.advance_paid, 100)
|
||||||
|
|
||||||
|
# step - 5: create second PI for remaining qty and allocate second advance
|
||||||
|
pi_2 = make_pi_from_po(po.name)
|
||||||
|
pi_2.update_stock = 1
|
||||||
|
pi_2.allocate_advances_automatically = 1
|
||||||
|
pi_2.save(ignore_permissions=True).submit()
|
||||||
|
|
||||||
|
# check PO and PI status
|
||||||
|
po.reload()
|
||||||
|
pi_1.reload()
|
||||||
|
pi_2.reload()
|
||||||
|
self.assertEqual(pi_1.status, "Paid")
|
||||||
|
self.assertEqual(pi_2.status, "Paid")
|
||||||
|
self.assertEqual(po.status, "Completed")
|
||||||
|
|
||||||
|
|
||||||
def create_po_for_sc_testing():
|
def create_po_for_sc_testing():
|
||||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||||
|
|||||||
@@ -34,15 +34,6 @@ frappe.ui.form.on("Request for Quotation", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function (frm) {
|
|
||||||
if (!frm.doc.message_for_supplier) {
|
|
||||||
frm.set_value(
|
|
||||||
"message_for_supplier",
|
|
||||||
__("Please supply the specified items at the best possible rates")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
refresh: function (frm, cdt, cdn) {
|
refresh: function (frm, cdt, cdn) {
|
||||||
if (frm.doc.docstatus === 1) {
|
if (frm.doc.docstatus === 1) {
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
@@ -248,6 +239,25 @@ frappe.ui.form.on("Request for Quotation", {
|
|||||||
}
|
}
|
||||||
refresh_field("items");
|
refresh_field("items");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
email_template(frm) {
|
||||||
|
if (frm.doc.email_template) {
|
||||||
|
frappe.db
|
||||||
|
.get_value("Email Template", frm.doc.email_template, [
|
||||||
|
"use_html",
|
||||||
|
"response",
|
||||||
|
"response_html",
|
||||||
|
"subject",
|
||||||
|
])
|
||||||
|
.then((r) => {
|
||||||
|
frm.set_value(
|
||||||
|
"message_for_supplier",
|
||||||
|
r.message.use_html ? r.message.response_html : r.message.response
|
||||||
|
);
|
||||||
|
frm.set_value("subject", r.message.subject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
preview: (frm) => {
|
preview: (frm) => {
|
||||||
let dialog = new frappe.ui.Dialog({
|
let dialog = new frappe.ui.Dialog({
|
||||||
title: __("Preview Email"),
|
title: __("Preview Email"),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"send_attached_files",
|
"send_attached_files",
|
||||||
"send_document_print",
|
"send_document_print",
|
||||||
"sec_break_email_2",
|
"sec_break_email_2",
|
||||||
|
"subject",
|
||||||
"message_for_supplier",
|
"message_for_supplier",
|
||||||
"terms_section_break",
|
"terms_section_break",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:doc.suppliers.some((item) => item.send_email) && !(doc.docstatus == 1 && !doc.email_template)",
|
||||||
"fieldname": "supplier_response_section",
|
"fieldname": "supplier_response_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Email Details"
|
"label": "Email Details"
|
||||||
@@ -139,8 +141,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"fetch_from": "email_template.response",
|
"default": "Please supply the specified items at the best possible rates",
|
||||||
"fetch_if_empty": 1,
|
|
||||||
"fieldname": "message_for_supplier",
|
"fieldname": "message_for_supplier",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -251,7 +252,7 @@
|
|||||||
"label": "Preview Email"
|
"label": "Preview Email"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.__islocal",
|
"depends_on": "eval:doc.suppliers.some((item) => item.send_email)",
|
||||||
"fieldname": "sec_break_email_2",
|
"fieldname": "sec_break_email_2",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hide_border": 1
|
"hide_border": 1
|
||||||
@@ -315,6 +316,14 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Has Unit Price Items",
|
"label": "Has Unit Price Items",
|
||||||
"no_copy": 1
|
"no_copy": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Request for Quotation",
|
||||||
|
"fieldname": "subject",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Subject",
|
||||||
|
"not_nullable": 1,
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
@@ -322,7 +331,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-03-03 16:48:39.856779",
|
"modified": "2026-01-05 14:27:33.329810",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation",
|
"name": "Request for Quotation",
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
send_attached_files: DF.Check
|
send_attached_files: DF.Check
|
||||||
send_document_print: DF.Check
|
send_document_print: DF.Check
|
||||||
status: DF.Literal["", "Draft", "Submitted", "Cancelled"]
|
status: DF.Literal["", "Draft", "Submitted", "Cancelled"]
|
||||||
|
subject: DF.Data
|
||||||
suppliers: DF.Table[RequestforQuotationSupplier]
|
suppliers: DF.Table[RequestforQuotationSupplier]
|
||||||
tc_name: DF.Link | None
|
tc_name: DF.Link | None
|
||||||
terms: DF.TextEditor | None
|
terms: DF.TextEditor | None
|
||||||
@@ -66,6 +67,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
def before_validate(self):
|
def before_validate(self):
|
||||||
self.set_has_unit_price_items()
|
self.set_has_unit_price_items()
|
||||||
self.flags.allow_zero_qty = self.has_unit_price_items
|
self.flags.allow_zero_qty = self.has_unit_price_items
|
||||||
|
self.set_data_for_supplier()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_duplicate_supplier()
|
self.validate_duplicate_supplier()
|
||||||
@@ -90,6 +92,19 @@ class RequestforQuotation(BuyingController):
|
|||||||
not row.qty for row in self.get("items") if (row.item_code and not row.qty)
|
not row.qty for row in self.get("items") if (row.item_code and not row.qty)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_data_for_supplier(self):
|
||||||
|
if self.email_template:
|
||||||
|
data = frappe.get_value(
|
||||||
|
"Email Template",
|
||||||
|
self.email_template,
|
||||||
|
["use_html", "response", "response_html", "subject"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
if not self.message_for_supplier:
|
||||||
|
self.message_for_supplier = data.response_html if data.use_html else data.response
|
||||||
|
if not self.subject:
|
||||||
|
self.subject = data.subject
|
||||||
|
|
||||||
def validate_duplicate_supplier(self):
|
def validate_duplicate_supplier(self):
|
||||||
supplier_list = [d.supplier for d in self.suppliers]
|
supplier_list = [d.supplier for d in self.suppliers]
|
||||||
if len(supplier_list) != len(set(supplier_list)):
|
if len(supplier_list) != len(set(supplier_list)):
|
||||||
@@ -283,12 +298,6 @@ class RequestforQuotation(BuyingController):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.email_template:
|
|
||||||
return
|
|
||||||
|
|
||||||
email_template = frappe.get_doc("Email Template", self.email_template)
|
|
||||||
message = frappe.render_template(email_template.response_, doc_args)
|
|
||||||
subject = frappe.render_template(email_template.subject, doc_args)
|
|
||||||
fixed_procurement_email = frappe.db.get_single_value("Buying Settings", "fixed_email")
|
fixed_procurement_email = frappe.db.get_single_value("Buying Settings", "fixed_email")
|
||||||
if fixed_procurement_email:
|
if fixed_procurement_email:
|
||||||
sender = frappe.db.get_value("Email Account", fixed_procurement_email, "email_id")
|
sender = frappe.db.get_value("Email Account", fixed_procurement_email, "email_id")
|
||||||
@@ -296,7 +305,12 @@ class RequestforQuotation(BuyingController):
|
|||||||
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
|
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
|
||||||
|
|
||||||
if preview:
|
if preview:
|
||||||
return {"message": message, "subject": subject}
|
return {
|
||||||
|
"message": self.message_for_supplier,
|
||||||
|
"subject": self.subject
|
||||||
|
or frappe.get_value("Email Template", self.email_template, "subject")
|
||||||
|
or _("Request for Quotation"),
|
||||||
|
}
|
||||||
|
|
||||||
attachments = []
|
attachments = []
|
||||||
if self.send_attached_files:
|
if self.send_attached_files:
|
||||||
@@ -316,7 +330,15 @@ class RequestforQuotation(BuyingController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.send_email(data, sender, subject, message, attachments)
|
self.send_email(
|
||||||
|
data,
|
||||||
|
sender,
|
||||||
|
self.subject
|
||||||
|
or frappe.get_value("Email Template", self.email_template, "subject")
|
||||||
|
or _("Request for Quotation"),
|
||||||
|
self.message_for_supplier,
|
||||||
|
attachments,
|
||||||
|
)
|
||||||
|
|
||||||
def send_email(self, data, sender, subject, message, attachments):
|
def send_email(self, data, sender, subject, message, attachments):
|
||||||
make(
|
make(
|
||||||
|
|||||||
@@ -80,20 +80,21 @@
|
|||||||
"fieldname": "email_id",
|
"fieldname": "email_id",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Email Id",
|
"label": "Email ID",
|
||||||
"no_copy": 1
|
"no_copy": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-04 22:01:43.832942",
|
"modified": "2026-01-05 14:08:27.274538",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation Supplier",
|
"name": "Request for Quotation Supplier",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -184,9 +184,8 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
msg = ""
|
msg = ""
|
||||||
if self.get("update_outstanding_for_self"):
|
if self.get("update_outstanding_for_self"):
|
||||||
msg = (
|
msg = _(
|
||||||
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, "
|
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck the '{2}' checkbox."
|
||||||
"uncheck '{2}' checkbox. <br><br>Or"
|
|
||||||
).format(
|
).format(
|
||||||
frappe.bold(document_type),
|
frappe.bold(document_type),
|
||||||
get_link_to_form(self.doctype, self.get("return_against")),
|
get_link_to_form(self.doctype, self.get("return_against")),
|
||||||
@@ -197,8 +196,8 @@ class AccountsController(TransactionBase):
|
|||||||
abs(flt(self.rounded_total) or flt(self.grand_total)) > flt(against_voucher_outstanding)
|
abs(flt(self.rounded_total) or flt(self.grand_total)) > flt(against_voucher_outstanding)
|
||||||
):
|
):
|
||||||
self.update_outstanding_for_self = 1
|
self.update_outstanding_for_self = 1
|
||||||
msg = (
|
msg = _(
|
||||||
"The outstanding amount {} in {} is lesser than {}. Updating the outstanding to this invoice. <br><br>And"
|
"The outstanding amount {0} in {1} is lesser than {2}. Updating the outstanding to this invoice."
|
||||||
).format(
|
).format(
|
||||||
against_voucher_outstanding,
|
against_voucher_outstanding,
|
||||||
get_link_to_form(self.doctype, self.get("return_against")),
|
get_link_to_form(self.doctype, self.get("return_against")),
|
||||||
@@ -206,11 +205,11 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if msg:
|
if msg:
|
||||||
msg += " you can use {} tool to reconcile against {} later.".format(
|
msg += "<br><br>" + _("You can use {0} to reconcile against {1} later.").format(
|
||||||
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
|
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
|
||||||
get_link_to_form(self.doctype, self.get("return_against")),
|
get_link_to_form(self.doctype, self.get("return_against")),
|
||||||
)
|
)
|
||||||
frappe.msgprint(_(msg))
|
frappe.msgprint(msg)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not self.get("is_return") and not self.get("is_debit_note"):
|
if not self.get("is_return") and not self.get("is_debit_note"):
|
||||||
|
|||||||
@@ -46,17 +46,23 @@ class calculate_taxes_and_totals:
|
|||||||
items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
|
items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self, ignore_tax_template_validation=False):
|
||||||
if not len(self.doc.items):
|
if not len(self.doc.items):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.discount_amount_applied = False
|
self.discount_amount_applied = False
|
||||||
|
self.need_recomputation = False
|
||||||
|
self.ignore_tax_template_validation = ignore_tax_template_validation
|
||||||
|
|
||||||
self._calculate()
|
self._calculate()
|
||||||
|
|
||||||
if self.doc.meta.get_field("discount_amount"):
|
if self.doc.meta.get_field("discount_amount"):
|
||||||
self.set_discount_amount()
|
self.set_discount_amount()
|
||||||
self.apply_discount_amount()
|
self.apply_discount_amount()
|
||||||
|
|
||||||
|
if not ignore_tax_template_validation and self.need_recomputation:
|
||||||
|
return self.calculate(ignore_tax_template_validation=True)
|
||||||
|
|
||||||
# Update grand total as per cash and non trade discount
|
# Update grand total as per cash and non trade discount
|
||||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||||
self.doc.grand_total -= self.doc.discount_amount
|
self.doc.grand_total -= self.doc.discount_amount
|
||||||
@@ -100,6 +106,9 @@ class calculate_taxes_and_totals:
|
|||||||
self.doc.base_tax_withholding_net_total = sum_base_net_amount
|
self.doc.base_tax_withholding_net_total = sum_base_net_amount
|
||||||
|
|
||||||
def validate_item_tax_template(self):
|
def validate_item_tax_template(self):
|
||||||
|
if self.ignore_tax_template_validation:
|
||||||
|
return
|
||||||
|
|
||||||
if self.doc.get("is_return") and self.doc.get("return_against"):
|
if self.doc.get("is_return") and self.doc.get("return_against"):
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -141,6 +150,10 @@ class calculate_taxes_and_totals:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# For correct tax_amount calculation re-computation is required
|
||||||
|
if self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total":
|
||||||
|
self.need_recomputation = True
|
||||||
|
|
||||||
def update_item_tax_map(self):
|
def update_item_tax_map(self):
|
||||||
for item in self.doc.items:
|
for item in self.doc.items:
|
||||||
item.item_tax_rate = get_item_tax_map(
|
item.item_tax_rate = get_item_tax_map(
|
||||||
|
|||||||
@@ -559,6 +559,7 @@ accounting_dimension_doctypes = [
|
|||||||
"Payment Request",
|
"Payment Request",
|
||||||
"Asset Movement Item",
|
"Asset Movement Item",
|
||||||
"Asset Depreciation Schedule",
|
"Asset Depreciation Schedule",
|
||||||
|
"Advance Taxes and Charges",
|
||||||
]
|
]
|
||||||
|
|
||||||
get_matching_queries = (
|
get_matching_queries = (
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ class WorkOrder(Document):
|
|||||||
def validate_work_order_against_so(self):
|
def validate_work_order_against_so(self):
|
||||||
# already ordered qty
|
# already ordered qty
|
||||||
ordered_qty_against_so = frappe.db.sql(
|
ordered_qty_against_so = frappe.db.sql(
|
||||||
"""select sum(qty) from `tabWork Order`
|
"""select sum(qty - process_loss_qty) from `tabWork Order`
|
||||||
where production_item = %s and sales_order = %s and docstatus < 2 and status != 'Closed' and name != %s""",
|
where production_item = %s and sales_order = %s and docstatus < 2 and status != 'Closed' and name != %s""",
|
||||||
(self.production_item, self.sales_order, self.name),
|
(self.production_item, self.sales_order, self.name),
|
||||||
)[0][0]
|
)[0][0]
|
||||||
|
|||||||
@@ -427,3 +427,4 @@ erpnext.patches.v15_0.toggle_legacy_controller_for_period_closing
|
|||||||
execute:frappe.db.set_single_value("Accounts Settings", "show_party_balance", 1)
|
execute:frappe.db.set_single_value("Accounts Settings", "show_party_balance", 1)
|
||||||
execute:frappe.db.set_single_value("Accounts Settings", "show_account_balance", 1)
|
execute:frappe.db.set_single_value("Accounts Settings", "show_account_balance", 1)
|
||||||
erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter #2025-12-11
|
erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter #2025-12-11
|
||||||
|
erpnext.patches.v15_0.create_accounting_dimensions_in_advance_taxes_and_charges
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
|
create_accounting_dimensions_for_doctype,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
create_accounting_dimensions_for_doctype(doctype="Advance Taxes and Charges")
|
||||||
@@ -8,12 +8,24 @@ def execute():
|
|||||||
|
|
||||||
|
|
||||||
def update_delivery_note():
|
def update_delivery_note():
|
||||||
DN = frappe.qb.DocType("Delivery Note")
|
# Postgres doesn't support UPDATE ... JOIN. Use UPDATE ... FROM instead.
|
||||||
DNI = frappe.qb.DocType("Delivery Note Item")
|
frappe.db.multisql(
|
||||||
|
{
|
||||||
frappe.qb.update(DNI).join(DN).on(DN.name == DNI.parent).set(DNI.against_pick_list, DN.pick_list).where(
|
"mariadb": """
|
||||||
IfNull(DN.pick_list, "") != ""
|
UPDATE `tabDelivery Note Item` dni
|
||||||
).run()
|
JOIN `tabDelivery Note` dn ON dn.`name` = dni.`parent`
|
||||||
|
SET dni.`against_pick_list` = dn.`pick_list`
|
||||||
|
WHERE COALESCE(dn.`pick_list`, '') <> ''
|
||||||
|
""",
|
||||||
|
"postgres": """
|
||||||
|
UPDATE "tabDelivery Note Item" dni
|
||||||
|
SET against_pick_list = dn.pick_list
|
||||||
|
FROM "tabDelivery Note" dn
|
||||||
|
WHERE dn.name = dni.parent
|
||||||
|
AND COALESCE(dn.pick_list, '') <> ''
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_pick_list_items():
|
def update_pick_list_items():
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
|
|
||||||
barcode(doc, cdt, cdn) {
|
barcode(doc, cdt, cdn) {
|
||||||
let row = locals[cdt][cdn];
|
let row = locals[cdt][cdn];
|
||||||
if (row.barcode) {
|
if (row.barcode && !frappe.flags.trigger_from_barcode_scanner) {
|
||||||
erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
|
erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
|
||||||
frappe.model.set_value(cdt, cdn, {
|
frappe.model.set_value(cdt, cdn, {
|
||||||
"item_code": r.message.item_code,
|
"item_code": r.message.item_code,
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
|
|
||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
() => this.set_selector_trigger_flag(data),
|
() => this.set_selector_trigger_flag(data),
|
||||||
() => this.set_barcode_uom(row, uom),
|
|
||||||
() => this.set_serial_no(row, serial_no),
|
() => this.set_serial_no(row, serial_no),
|
||||||
() => this.set_batch_no(row, batch_no),
|
() => this.set_batch_no(row, batch_no),
|
||||||
() => this.set_barcode(row, barcode),
|
() => this.set_barcode(row, barcode),
|
||||||
@@ -148,6 +147,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
this.show_scan_message(row.idx, !is_new_row, qty);
|
this.show_scan_message(row.idx, !is_new_row, qty);
|
||||||
}),
|
}),
|
||||||
() => this.clean_up(),
|
() => this.clean_up(),
|
||||||
|
() => this.set_barcode_uom(row, uom),
|
||||||
() => this.revert_selector_flag(),
|
() => this.revert_selector_flag(),
|
||||||
() => resolve(row),
|
() => resolve(row),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1869,7 +1869,7 @@ def get_work_order_items(sales_order, for_raw_material_request=0):
|
|||||||
if not for_raw_material_request:
|
if not for_raw_material_request:
|
||||||
total_work_order_qty = flt(
|
total_work_order_qty = flt(
|
||||||
qb.from_(wo)
|
qb.from_(wo)
|
||||||
.select(Sum(wo.qty))
|
.select(Sum(wo.qty - wo.process_loss_qty))
|
||||||
.where(
|
.where(
|
||||||
(wo.production_item == i.item_code)
|
(wo.production_item == i.item_code)
|
||||||
& (wo.sales_order == so.name)
|
& (wo.sales_order == so.name)
|
||||||
|
|||||||
@@ -606,6 +606,9 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
|
|
||||||
async on_cart_update(args) {
|
async on_cart_update(args) {
|
||||||
frappe.dom.freeze();
|
frappe.dom.freeze();
|
||||||
|
if (this.frm.doc.set_warehouse !== this.settings.warehouse) {
|
||||||
|
this.frm.set_value("set_warehouse", this.settings.warehouse);
|
||||||
|
}
|
||||||
let item_row = undefined;
|
let item_row = undefined;
|
||||||
try {
|
try {
|
||||||
let { field, value, item } = args;
|
let { field, value, item } = args;
|
||||||
|
|||||||
@@ -73,6 +73,18 @@ frappe.query_reports["Sales Analytics"] = {
|
|||||||
default: "Monthly",
|
default: "Monthly",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "curves",
|
||||||
|
label: __("Curves"),
|
||||||
|
fieldtype: "Select",
|
||||||
|
options: [
|
||||||
|
{ value: "all", label: __("All") },
|
||||||
|
{ value: "non-zeros", label: __("Non-Zeros") },
|
||||||
|
{ value: "total", label: __("Total Only") },
|
||||||
|
],
|
||||||
|
default: "all",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldname: "show_aggregate_value_from_subsidiary_companies",
|
fieldname: "show_aggregate_value_from_subsidiary_companies",
|
||||||
label: __("Show Aggregate Value from Subsidiary Companies"),
|
label: __("Show Aggregate Value from Subsidiary Companies"),
|
||||||
|
|||||||
@@ -460,7 +460,31 @@ class Analytics:
|
|||||||
labels = [d.get("label") for d in self.columns[3 : length - 1]]
|
labels = [d.get("label") for d in self.columns[3 : length - 1]]
|
||||||
else:
|
else:
|
||||||
labels = [d.get("label") for d in self.columns[1 : length - 1]]
|
labels = [d.get("label") for d in self.columns[1 : length - 1]]
|
||||||
self.chart = {"data": {"labels": labels, "datasets": []}, "type": "line"}
|
|
||||||
|
datasets = []
|
||||||
|
for curve in self.data:
|
||||||
|
data = {
|
||||||
|
"name": curve.get("entity_name", curve["entity"]),
|
||||||
|
"values": [curve.get(scrub(label), 0) for label in labels],
|
||||||
|
}
|
||||||
|
if self.filters.curves == "non-zeros" and not sum(data["values"]):
|
||||||
|
continue
|
||||||
|
elif self.filters.curves == "total" and "indent" in curve:
|
||||||
|
if curve["indent"] == 0:
|
||||||
|
datasets.append(data)
|
||||||
|
elif self.filters.curves == "total":
|
||||||
|
if datasets:
|
||||||
|
a = [
|
||||||
|
data["values"][idx] + datasets[0]["values"][idx] for idx in range(len(data["values"]))
|
||||||
|
]
|
||||||
|
datasets[0]["values"] = a
|
||||||
|
else:
|
||||||
|
datasets.append(data)
|
||||||
|
datasets[0]["name"] = _("Total")
|
||||||
|
else:
|
||||||
|
datasets.append(data)
|
||||||
|
|
||||||
|
self.chart = {"data": {"labels": labels, "datasets": datasets}, "type": "line"}
|
||||||
|
|
||||||
if self.filters["value_quantity"] == "Value":
|
if self.filters["value_quantity"] == "Value":
|
||||||
self.chart["fieldtype"] = "Currency"
|
self.chart["fieldtype"] = "Currency"
|
||||||
|
|||||||
@@ -11,7 +11,16 @@ from frappe.cache_manager import clear_defaults_cache
|
|||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
from frappe.desk.page.setup_wizard.setup_wizard import make_records
|
from frappe.desk.page.setup_wizard.setup_wizard import make_records
|
||||||
from frappe.utils import add_months, cint, formatdate, get_first_day, get_link_to_form, get_timestamp, today
|
from frappe.utils import (
|
||||||
|
add_months,
|
||||||
|
cint,
|
||||||
|
formatdate,
|
||||||
|
get_first_day,
|
||||||
|
get_last_day,
|
||||||
|
get_link_to_form,
|
||||||
|
get_timestamp,
|
||||||
|
today,
|
||||||
|
)
|
||||||
from frappe.utils.nestedset import NestedSet, rebuild_tree
|
from frappe.utils.nestedset import NestedSet, rebuild_tree
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.account import get_account_currency
|
from erpnext.accounts.doctype.account.account import get_account_currency
|
||||||
@@ -762,31 +771,41 @@ def install_country_fixtures(company, country):
|
|||||||
|
|
||||||
|
|
||||||
def update_company_current_month_sales(company):
|
def update_company_current_month_sales(company):
|
||||||
from_date = get_first_day(today())
|
"""Update Company's Total Monthly Sales.
|
||||||
to_date = get_first_day(add_months(from_date, 1))
|
|
||||||
|
|
||||||
results = frappe.db.sql(
|
Postgres compatibility:
|
||||||
"""
|
- Avoid MariaDB-only DATE_FORMAT().
|
||||||
SELECT
|
- Use a date range for the current month instead (portable + index-friendly).
|
||||||
SUM(base_grand_total) AS total,
|
"""
|
||||||
DATE_FORMAT(posting_date, '%%m-%%Y') AS month_year
|
|
||||||
FROM
|
# Local imports so you don't have to touch file-level imports
|
||||||
`tabSales Invoice`
|
from frappe.query_builder.functions import Sum
|
||||||
WHERE
|
|
||||||
posting_date >= %s
|
start_date = get_first_day(today())
|
||||||
AND posting_date < %s
|
end_date = get_last_day(today())
|
||||||
AND docstatus = 1
|
|
||||||
AND company = %s
|
si = frappe.qb.DocType("Sales Invoice")
|
||||||
GROUP BY
|
|
||||||
month_year
|
total_monthly_sales = (
|
||||||
""",
|
frappe.qb.from_(si)
|
||||||
(from_date, to_date, company),
|
.select(Sum(si.base_grand_total))
|
||||||
as_dict=True,
|
.where(
|
||||||
|
(si.docstatus == 1)
|
||||||
|
& (si.company == company)
|
||||||
|
& (si.posting_date >= start_date)
|
||||||
|
& (si.posting_date <= end_date)
|
||||||
|
)
|
||||||
|
).run(pluck=True)[0] or 0
|
||||||
|
|
||||||
|
# Fieldname in standard ERPNext is `total_monthly_sales`
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Company",
|
||||||
|
company,
|
||||||
|
"total_monthly_sales",
|
||||||
|
total_monthly_sales,
|
||||||
|
update_modified=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
monthly_total = results[0]["total"] if len(results) > 0 else 0
|
|
||||||
frappe.db.set_value("Company", company, "total_monthly_sales", monthly_total)
|
|
||||||
|
|
||||||
|
|
||||||
def update_company_monthly_sales(company):
|
def update_company_monthly_sales(company):
|
||||||
"""Cache past year monthly sales of every company based on sales invoices"""
|
"""Cache past year monthly sales of every company based on sales invoices"""
|
||||||
|
|||||||
@@ -977,7 +977,7 @@ frappe.tour["Item"] = [
|
|||||||
fieldname: "valuation_rate",
|
fieldname: "valuation_rate",
|
||||||
title: "Valuation Rate",
|
title: "Valuation Rate",
|
||||||
description: __(
|
description: __(
|
||||||
"There are two options to maintain valuation of stock. FIFO (first in - first out) and Moving Average. To understand this topic in detail please visit <a href='https://docs.erpnext.com/docs/v13/user/manual/en/stock/articles/item-valuation-fifo-and-moving-average' target='_blank'>Item Valuation, FIFO and Moving Average.</a>"
|
"There are two options to maintain valuation of stock. FIFO (first in - first out) and Moving Average. To understand this topic in detail please visit <a href='https://docs.frappe.io/erpnext/user/manual/en/calculation-of-valuation-rate-in-fifo-and-moving-average' target='_blank'>Item Valuation, FIFO and Moving Average.</a>"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1004,7 +1004,8 @@ def make_material_request(**args):
|
|||||||
mr = frappe.new_doc("Material Request")
|
mr = frappe.new_doc("Material Request")
|
||||||
mr.material_request_type = args.material_request_type or "Purchase"
|
mr.material_request_type = args.material_request_type or "Purchase"
|
||||||
mr.company = args.company or "_Test Company"
|
mr.company = args.company or "_Test Company"
|
||||||
mr.customer = args.customer or "_Test Customer"
|
if mr.material_request_type == "Customer Provided":
|
||||||
|
mr.customer = args.customer or "_Test Customer"
|
||||||
mr.append(
|
mr.append(
|
||||||
"items",
|
"items",
|
||||||
{
|
{
|
||||||
@@ -1013,6 +1014,7 @@ def make_material_request(**args):
|
|||||||
"uom": args.uom or "_Test UOM",
|
"uom": args.uom or "_Test UOM",
|
||||||
"conversion_factor": args.conversion_factor or 1,
|
"conversion_factor": args.conversion_factor or 1,
|
||||||
"schedule_date": args.schedule_date or today(),
|
"schedule_date": args.schedule_date or today(),
|
||||||
|
"from_warehouse": args.from_warehouse,
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4529,6 +4529,154 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(sles, [1500.0, 1500.0])
|
self.assertEqual(sles, [1500.0, 1500.0])
|
||||||
|
|
||||||
|
def test_do_not_use_batchwise_valuation_with_fifo(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Item Do Not Use Batchwise Valuation with FIFO",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"batch_number_series": "BN-TESTDNUBVWF-.#####",
|
||||||
|
"valuation_method": "FIFO",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
doc = frappe.new_doc("Batch")
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
"batch_id": "BN-TESTDNUBVWF-00001",
|
||||||
|
"item": item_code,
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
doc.db_set("use_batchwise_valuation", 0)
|
||||||
|
doc.reload()
|
||||||
|
|
||||||
|
self.assertTrue(doc.use_batchwise_valuation == 0)
|
||||||
|
|
||||||
|
doc = frappe.new_doc("Batch")
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
"batch_id": "BN-TESTDNUBVWF-00002",
|
||||||
|
"item": item_code,
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
self.assertTrue(doc.use_batchwise_valuation == 1)
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
target=warehouse,
|
||||||
|
batch_no="BN-TESTDNUBVWF-00001",
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
se1 = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=10,
|
||||||
|
rate=200,
|
||||||
|
target=warehouse,
|
||||||
|
batch_no="BN-TESTDNUBVWF-00001",
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_queue = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"is_cancelled": 0,
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"voucher_no": se1.name,
|
||||||
|
},
|
||||||
|
"stock_queue",
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_queue = frappe.parse_json(stock_queue)
|
||||||
|
|
||||||
|
self.assertEqual(stock_queue, [[10, 100.0], [10, 200.0]])
|
||||||
|
|
||||||
|
se2 = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=10,
|
||||||
|
rate=2,
|
||||||
|
target=warehouse,
|
||||||
|
batch_no="BN-TESTDNUBVWF-00002",
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_queue = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"is_cancelled": 0,
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"voucher_no": se2.name,
|
||||||
|
},
|
||||||
|
"stock_queue",
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_queue = frappe.parse_json(stock_queue)
|
||||||
|
self.assertEqual(stock_queue, [[10, 100.0], [10, 200.0]])
|
||||||
|
|
||||||
|
se3 = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=20,
|
||||||
|
source=warehouse,
|
||||||
|
batch_no="BN-TESTDNUBVWF-00001",
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
ste_details = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"is_cancelled": 0,
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"voucher_no": se3.name,
|
||||||
|
},
|
||||||
|
["stock_queue", "stock_value_difference"],
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_queue = frappe.parse_json(ste_details.stock_queue)
|
||||||
|
self.assertEqual(stock_queue, [])
|
||||||
|
self.assertEqual(ste_details.stock_value_difference, 3000 * -1)
|
||||||
|
|
||||||
|
se4 = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=20,
|
||||||
|
rate=0,
|
||||||
|
target=warehouse,
|
||||||
|
batch_no="BN-TESTDNUBVWF-00001",
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
se4.items[0].basic_rate = 0.0
|
||||||
|
se4.items[0].allow_zero_valuation_rate = 1
|
||||||
|
se4.submit()
|
||||||
|
|
||||||
|
stock_queue = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"is_cancelled": 0,
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"voucher_no": se4.name,
|
||||||
|
},
|
||||||
|
"stock_queue",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(frappe.parse_json(stock_queue), [[20, 0.0]])
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -713,17 +713,16 @@ class SerialandBatchBundle(Document):
|
|||||||
is_packed_item = True
|
is_packed_item = True
|
||||||
|
|
||||||
stock_queue = []
|
stock_queue = []
|
||||||
batches = []
|
batches = frappe.get_all(
|
||||||
if prev_sle and prev_sle.stock_queue:
|
"Batch",
|
||||||
batches = frappe.get_all(
|
filters={
|
||||||
"Batch",
|
"name": ("in", [d.batch_no for d in self.entries if d.batch_no]),
|
||||||
filters={
|
"use_batchwise_valuation": 0,
|
||||||
"name": ("in", [d.batch_no for d in self.entries if d.batch_no]),
|
},
|
||||||
"use_batchwise_valuation": 0,
|
pluck="name",
|
||||||
},
|
)
|
||||||
pluck="name",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if prev_sle and prev_sle.stock_queue and parse_json(prev_sle.stock_queue):
|
||||||
if batches and valuation_method == "FIFO":
|
if batches and valuation_method == "FIFO":
|
||||||
stock_queue = parse_json(prev_sle.stock_queue)
|
stock_queue = parse_json(prev_sle.stock_queue)
|
||||||
|
|
||||||
@@ -750,7 +749,7 @@ class SerialandBatchBundle(Document):
|
|||||||
if d.qty:
|
if d.qty:
|
||||||
d.stock_value_difference = flt(d.qty) * d.incoming_rate
|
d.stock_value_difference = flt(d.qty) * d.incoming_rate
|
||||||
|
|
||||||
if stock_queue and valuation_method == "FIFO" and d.batch_no in batches:
|
if valuation_method == "FIFO" and d.batch_no in batches and d.incoming_rate is not None:
|
||||||
stock_queue.append([d.qty, d.incoming_rate])
|
stock_queue.append([d.qty, d.incoming_rate])
|
||||||
d.stock_queue = json.dumps(stock_queue)
|
d.stock_queue = json.dumps(stock_queue)
|
||||||
|
|
||||||
|
|||||||
@@ -646,7 +646,7 @@ class StockEntry(StockController):
|
|||||||
"Material Transfer for Manufacture",
|
"Material Transfer for Manufacture",
|
||||||
]
|
]
|
||||||
|
|
||||||
validate_for_manufacture = any([d.bom_no for d in self.get("items")])
|
has_bom = any([d.bom_no for d in self.get("items")])
|
||||||
|
|
||||||
if self.purpose in source_mandatory and self.purpose not in target_mandatory:
|
if self.purpose in source_mandatory and self.purpose not in target_mandatory:
|
||||||
self.to_warehouse = None
|
self.to_warehouse = None
|
||||||
@@ -675,7 +675,7 @@ class StockEntry(StockController):
|
|||||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||||
|
|
||||||
if self.purpose == "Manufacture":
|
if self.purpose == "Manufacture":
|
||||||
if validate_for_manufacture:
|
if has_bom:
|
||||||
if d.is_finished_item or d.is_scrap_item:
|
if d.is_finished_item or d.is_scrap_item:
|
||||||
d.s_warehouse = None
|
d.s_warehouse = None
|
||||||
if not d.t_warehouse:
|
if not d.t_warehouse:
|
||||||
@@ -685,6 +685,17 @@ class StockEntry(StockController):
|
|||||||
if not d.s_warehouse:
|
if not d.s_warehouse:
|
||||||
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
|
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
|
||||||
|
|
||||||
|
if self.purpose == "Disassemble":
|
||||||
|
if has_bom:
|
||||||
|
if d.is_finished_item:
|
||||||
|
d.t_warehouse = None
|
||||||
|
if not d.s_warehouse:
|
||||||
|
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
|
||||||
|
else:
|
||||||
|
d.s_warehouse = None
|
||||||
|
if not d.t_warehouse:
|
||||||
|
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||||
|
|
||||||
if cstr(d.s_warehouse) == cstr(d.t_warehouse) and self.purpose not in [
|
if cstr(d.s_warehouse) == cstr(d.t_warehouse) and self.purpose not in [
|
||||||
"Material Transfer for Manufacture",
|
"Material Transfer for Manufacture",
|
||||||
"Material Transfer",
|
"Material Transfer",
|
||||||
@@ -1907,9 +1918,12 @@ class StockEntry(StockController):
|
|||||||
def get_items_for_disassembly(self):
|
def get_items_for_disassembly(self):
|
||||||
"""Get items for Disassembly Order"""
|
"""Get items for Disassembly Order"""
|
||||||
|
|
||||||
if not self.work_order:
|
if self.work_order:
|
||||||
frappe.throw(_("The Work Order is mandatory for Disassembly Order"))
|
return self._add_items_for_disassembly_from_work_order()
|
||||||
|
|
||||||
|
return self._add_items_for_disassembly_from_bom()
|
||||||
|
|
||||||
|
def _add_items_for_disassembly_from_work_order(self):
|
||||||
items = self.get_items_from_manufacture_entry()
|
items = self.get_items_from_manufacture_entry()
|
||||||
|
|
||||||
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse")
|
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse")
|
||||||
@@ -1941,6 +1955,23 @@ class StockEntry(StockController):
|
|||||||
child_row.t_warehouse = row.s_warehouse
|
child_row.t_warehouse = row.s_warehouse
|
||||||
child_row.is_finished_item = 0 if row.is_finished_item else 1
|
child_row.is_finished_item = 0 if row.is_finished_item else 1
|
||||||
|
|
||||||
|
def _add_items_for_disassembly_from_bom(self):
|
||||||
|
if not self.bom_no or not self.fg_completed_qty:
|
||||||
|
frappe.throw(_("BOM and Finished Good Quantity is mandatory for Disassembly"))
|
||||||
|
|
||||||
|
# Raw Materials
|
||||||
|
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
|
||||||
|
|
||||||
|
for item_row in item_dict.values():
|
||||||
|
item_row["to_warehouse"] = self.to_warehouse
|
||||||
|
item_row["from_warehouse"] = ""
|
||||||
|
item_row["is_finished_item"] = 0
|
||||||
|
|
||||||
|
self.add_to_stock_entry_detail(item_dict)
|
||||||
|
|
||||||
|
# Finished goods
|
||||||
|
self.load_items_from_bom()
|
||||||
|
|
||||||
def get_items_from_manufacture_entry(self):
|
def get_items_from_manufacture_entry(self):
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
"Stock Entry",
|
"Stock Entry",
|
||||||
@@ -2164,6 +2195,7 @@ class StockEntry(StockController):
|
|||||||
expense_account = item.get("expense_account")
|
expense_account = item.get("expense_account")
|
||||||
if not expense_account:
|
if not expense_account:
|
||||||
expense_account = frappe.get_cached_value("Company", self.company, "stock_adjustment_account")
|
expense_account = frappe.get_cached_value("Company", self.company, "stock_adjustment_account")
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
"to_warehouse": to_warehouse,
|
"to_warehouse": to_warehouse,
|
||||||
"from_warehouse": "",
|
"from_warehouse": "",
|
||||||
@@ -2176,6 +2208,15 @@ class StockEntry(StockController):
|
|||||||
"is_finished_item": 1,
|
"is_finished_item": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.purpose == "Disassemble":
|
||||||
|
args.update(
|
||||||
|
{
|
||||||
|
"from_warehouse": self.from_warehouse,
|
||||||
|
"to_warehouse": "",
|
||||||
|
"qty": flt(self.fg_completed_qty),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.work_order
|
self.work_order
|
||||||
and self.pro_doc.has_batch_no
|
and self.pro_doc.has_batch_no
|
||||||
@@ -2807,7 +2848,7 @@ class StockEntry(StockController):
|
|||||||
stock_entries_child_list.append(d.ste_detail)
|
stock_entries_child_list.append(d.ste_detail)
|
||||||
transferred_qty = frappe.get_all(
|
transferred_qty = frappe.get_all(
|
||||||
"Stock Entry Detail",
|
"Stock Entry Detail",
|
||||||
fields=["sum(qty) as qty"],
|
fields=["sum(transfer_qty) as qty"],
|
||||||
filters={
|
filters={
|
||||||
"against_stock_entry": d.against_stock_entry,
|
"against_stock_entry": d.against_stock_entry,
|
||||||
"ste_detail": d.ste_detail,
|
"ste_detail": d.ste_detail,
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ from erpnext.stock.doctype.item.test_item import (
|
|||||||
make_item_variant,
|
make_item_variant,
|
||||||
set_item_variant_settings,
|
set_item_variant_settings,
|
||||||
)
|
)
|
||||||
|
from erpnext.stock.doctype.material_request.material_request import (
|
||||||
|
make_in_transit_stock_entry,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.material_request.test_material_request import (
|
||||||
|
get_in_transit_warehouse,
|
||||||
|
make_material_request,
|
||||||
|
)
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||||
get_batch_from_bundle,
|
get_batch_from_bundle,
|
||||||
get_serial_nos_from_bundle,
|
get_serial_nos_from_bundle,
|
||||||
@@ -2127,6 +2134,104 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
self.assertEqual(se.purpose, "Repack")
|
self.assertEqual(se.purpose, "Repack")
|
||||||
self.assertRaises(frappe.ValidationError, se.submit)
|
self.assertRaises(frappe.ValidationError, se.submit)
|
||||||
|
|
||||||
|
def test_transferred_qty_in_material_transfer(self):
|
||||||
|
item_code = "_Test Item"
|
||||||
|
source_warehouse = "_Test Warehouse - _TC"
|
||||||
|
target_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
|
||||||
|
if not frappe.db.get_value("UOM Conversion Detail", {"parent": item_code, "uom": "Box"}):
|
||||||
|
item_doc = frappe.get_doc("Item", item_code)
|
||||||
|
item_doc.append("uoms", {"uom": "Box", "conversion_factor": 12})
|
||||||
|
item_doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
make_stock_entry(item_code=item_code, target=source_warehouse, qty=12, rate=100)
|
||||||
|
|
||||||
|
# Create a Material Request for Material Transfer
|
||||||
|
material_request = make_material_request(
|
||||||
|
material_request_type="Material Transfer",
|
||||||
|
qty=1,
|
||||||
|
item_code=item_code,
|
||||||
|
uom="Box",
|
||||||
|
conversion_factor=12,
|
||||||
|
from_warehouse=source_warehouse,
|
||||||
|
warehouse=target_warehouse,
|
||||||
|
)
|
||||||
|
in_transit_wh = get_in_transit_warehouse(material_request.company)
|
||||||
|
|
||||||
|
# Create first Stock Entry (Source -> In-Transit)
|
||||||
|
stock_entry_1 = make_in_transit_stock_entry(material_request.name, in_transit_wh)
|
||||||
|
stock_entry_1.items[0].update(
|
||||||
|
{
|
||||||
|
"qty": 1,
|
||||||
|
"s_warehouse": source_warehouse,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
stock_entry_1.save().submit()
|
||||||
|
|
||||||
|
# Validate transfer status after first transfer
|
||||||
|
material_request.reload()
|
||||||
|
self.assertEqual(material_request.transfer_status, "In Transit")
|
||||||
|
|
||||||
|
# Create final Stock Entry (In-Transit -> Target)
|
||||||
|
end_transit_1 = make_stock_in_entry(stock_entry_1.name)
|
||||||
|
end_transit_1.save().submit()
|
||||||
|
end_transit_1.reload()
|
||||||
|
|
||||||
|
# Validate quantities
|
||||||
|
stock_entry_1.reload()
|
||||||
|
self.assertEqual(stock_entry_1.items[0].qty, 1)
|
||||||
|
self.assertEqual(stock_entry_1.items[0].transfer_qty, 12)
|
||||||
|
self.assertEqual(stock_entry_1.items[0].transferred_qty, 12)
|
||||||
|
|
||||||
|
# Validate transfer status after final transfer
|
||||||
|
material_request.reload()
|
||||||
|
self.assertEqual(material_request.transfer_status, "Completed")
|
||||||
|
|
||||||
|
def test_disassemble_entry_without_wo(self):
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
|
fg_item = make_item("_Disassemble Mobile", properties={"is_stock_item": 1}).name
|
||||||
|
rm_item1 = make_item("_Disassemble Temper Glass", properties={"is_stock_item": 1}).name
|
||||||
|
rm_item2 = make_item("_Disassemble Battery", properties={"is_stock_item": 1}).name
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
# Stock up the FG item (what we'll disassemble)
|
||||||
|
make_stock_entry(item_code=fg_item, target=warehouse, qty=5, purpose="Material Receipt")
|
||||||
|
|
||||||
|
bom_no = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2]).name
|
||||||
|
|
||||||
|
se = make_stock_entry(item_code=fg_item, qty=1, purpose="Disassemble", do_not_save=True)
|
||||||
|
se.from_bom = 1
|
||||||
|
se.use_multi_level_bom = 1
|
||||||
|
se.bom_no = bom_no
|
||||||
|
se.fg_completed_qty = 1
|
||||||
|
se.from_warehouse = warehouse
|
||||||
|
se.to_warehouse = warehouse
|
||||||
|
|
||||||
|
se.get_items()
|
||||||
|
|
||||||
|
# Verify FG as source (being consumed)
|
||||||
|
fg_items = [d for d in se.items if d.is_finished_item]
|
||||||
|
self.assertEqual(len(fg_items), 1)
|
||||||
|
self.assertEqual(fg_items[0].item_code, fg_item)
|
||||||
|
self.assertEqual(fg_items[0].qty, 1)
|
||||||
|
self.assertEqual(fg_items[0].s_warehouse, warehouse)
|
||||||
|
self.assertFalse(fg_items[0].t_warehouse)
|
||||||
|
|
||||||
|
# Verify RM as target (being received)
|
||||||
|
rm_items = {d.item_code: d for d in se.items if not d.is_finished_item}
|
||||||
|
self.assertEqual(len(rm_items), 2)
|
||||||
|
self.assertIn(rm_item1, rm_items)
|
||||||
|
self.assertIn(rm_item2, rm_items)
|
||||||
|
self.assertEqual(rm_items[rm_item1].qty, 1)
|
||||||
|
self.assertEqual(rm_items[rm_item2].qty, 1)
|
||||||
|
self.assertEqual(rm_items[rm_item1].t_warehouse, warehouse)
|
||||||
|
self.assertFalse(rm_items[rm_item1].s_warehouse)
|
||||||
|
|
||||||
|
se.calculate_rate_and_amount()
|
||||||
|
se.save()
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(**args):
|
def make_serialized_item(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class StockReconciliation(StockController):
|
|||||||
self.validate_duplicate_serial_and_batch_bundle("items")
|
self.validate_duplicate_serial_and_batch_bundle("items")
|
||||||
self.remove_items_with_no_change()
|
self.remove_items_with_no_change()
|
||||||
self.validate_data()
|
self.validate_data()
|
||||||
|
self.change_row_indexes()
|
||||||
self.validate_expense_account()
|
self.validate_expense_account()
|
||||||
self.validate_customer_provided_item()
|
self.validate_customer_provided_item()
|
||||||
self.set_zero_value_for_customer_provided_items()
|
self.set_zero_value_for_customer_provided_items()
|
||||||
@@ -557,8 +558,7 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
elif len(items) != len(self.items):
|
elif len(items) != len(self.items):
|
||||||
self.items = items
|
self.items = items
|
||||||
for i, item in enumerate(self.items):
|
self.change_idx = True
|
||||||
item.idx = i + 1
|
|
||||||
frappe.msgprint(_("Removed items with no change in quantity or value."))
|
frappe.msgprint(_("Removed items with no change in quantity or value."))
|
||||||
|
|
||||||
def calculate_difference_amount(self, item, item_dict):
|
def calculate_difference_amount(self, item, item_dict):
|
||||||
@@ -575,14 +575,14 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
def validate_data(self):
|
def validate_data(self):
|
||||||
def _get_msg(row_num, msg):
|
def _get_msg(row_num, msg):
|
||||||
return _("Row # {0}:").format(row_num + 1) + " " + msg
|
return _("Row #{0}:").format(row_num) + " " + msg
|
||||||
|
|
||||||
self.validation_messages = []
|
self.validation_messages = []
|
||||||
item_warehouse_combinations = []
|
item_warehouse_combinations = []
|
||||||
|
|
||||||
default_currency = frappe.db.get_default("currency")
|
default_currency = frappe.db.get_default("currency")
|
||||||
|
|
||||||
for row_num, row in enumerate(self.items):
|
for row in self.items:
|
||||||
# find duplicates
|
# find duplicates
|
||||||
key = [row.item_code, row.warehouse]
|
key = [row.item_code, row.warehouse]
|
||||||
for field in ["serial_no", "batch_no"]:
|
for field in ["serial_no", "batch_no"]:
|
||||||
@@ -595,7 +595,7 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
if key in item_warehouse_combinations:
|
if key in item_warehouse_combinations:
|
||||||
self.validation_messages.append(
|
self.validation_messages.append(
|
||||||
_get_msg(row_num, _("Same item and warehouse combination already entered."))
|
_get_msg(row.idx, _("Same item and warehouse combination already entered."))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
item_warehouse_combinations.append(key)
|
item_warehouse_combinations.append(key)
|
||||||
@@ -605,29 +605,29 @@ class StockReconciliation(StockController):
|
|||||||
if row.serial_no and not row.qty:
|
if row.serial_no and not row.qty:
|
||||||
self.validation_messages.append(
|
self.validation_messages.append(
|
||||||
_get_msg(
|
_get_msg(
|
||||||
row_num,
|
row.idx,
|
||||||
f"Quantity should not be zero for the {bold(row.item_code)} since serial nos are specified",
|
f"Quantity should not be zero for the {bold(row.item_code)} since serial nos are specified",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# validate warehouse
|
# validate warehouse
|
||||||
if not frappe.db.get_value("Warehouse", row.warehouse):
|
if not frappe.db.get_value("Warehouse", row.warehouse):
|
||||||
self.validation_messages.append(_get_msg(row_num, _("Warehouse not found in the system")))
|
self.validation_messages.append(_get_msg(row.idx, _("Warehouse not found in the system")))
|
||||||
|
|
||||||
# if both not specified
|
# if both not specified
|
||||||
if row.qty in ["", None] and row.valuation_rate in ["", None]:
|
if row.qty in ["", None] and row.valuation_rate in ["", None]:
|
||||||
self.validation_messages.append(
|
self.validation_messages.append(
|
||||||
_get_msg(row_num, _("Please specify either Quantity or Valuation Rate or both"))
|
_get_msg(row.idx, _("Please specify either Quantity or Valuation Rate or both"))
|
||||||
)
|
)
|
||||||
|
|
||||||
# do not allow negative quantity
|
# do not allow negative quantity
|
||||||
if flt(row.qty) < 0:
|
if flt(row.qty) < 0:
|
||||||
self.validation_messages.append(_get_msg(row_num, _("Negative Quantity is not allowed")))
|
self.validation_messages.append(_get_msg(row.idx, _("Negative Quantity is not allowed")))
|
||||||
|
|
||||||
# do not allow negative valuation
|
# do not allow negative valuation
|
||||||
if flt(row.valuation_rate) < 0:
|
if flt(row.valuation_rate) < 0:
|
||||||
self.validation_messages.append(
|
self.validation_messages.append(
|
||||||
_get_msg(row_num, _("Negative Valuation Rate is not allowed"))
|
_get_msg(row.idx, _("Negative Valuation Rate is not allowed"))
|
||||||
)
|
)
|
||||||
|
|
||||||
if row.qty and row.valuation_rate in ["", None]:
|
if row.qty and row.valuation_rate in ["", None]:
|
||||||
@@ -659,6 +659,11 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
raise frappe.ValidationError(self.validation_messages)
|
raise frappe.ValidationError(self.validation_messages)
|
||||||
|
|
||||||
|
def change_row_indexes(self):
|
||||||
|
if getattr(self, "change_idx", False):
|
||||||
|
for i, item in enumerate(self.items):
|
||||||
|
item.idx = i + 1
|
||||||
|
|
||||||
def validate_item(self, item_code, row):
|
def validate_item(self, item_code, row):
|
||||||
from erpnext.stock.doctype.item.item import (
|
from erpnext.stock.doctype.item.item import (
|
||||||
validate_cancelled_item,
|
validate_cancelled_item,
|
||||||
|
|||||||
@@ -42,9 +42,37 @@ def get_data(report_filters):
|
|||||||
gl_data = voucher_wise_gl_data.get(key) or {}
|
gl_data = voucher_wise_gl_data.get(key) or {}
|
||||||
d.account_value = gl_data.get("account_value", 0)
|
d.account_value = gl_data.get("account_value", 0)
|
||||||
d.difference_value = d.stock_value - d.account_value
|
d.difference_value = d.stock_value - d.account_value
|
||||||
|
d.ledger_type = "Stock Ledger Entry"
|
||||||
if abs(d.difference_value) > 0.1:
|
if abs(d.difference_value) > 0.1:
|
||||||
data.append(d)
|
data.append(d)
|
||||||
|
|
||||||
|
if key in voucher_wise_gl_data:
|
||||||
|
del voucher_wise_gl_data[key]
|
||||||
|
|
||||||
|
if voucher_wise_gl_data:
|
||||||
|
data += get_gl_ledgers_with_no_stock_ledger_entries(voucher_wise_gl_data)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_gl_ledgers_with_no_stock_ledger_entries(voucher_wise_gl_data):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for key in voucher_wise_gl_data:
|
||||||
|
gl_data = voucher_wise_gl_data.get(key) or {}
|
||||||
|
data.append(
|
||||||
|
{
|
||||||
|
"name": gl_data.get("name"),
|
||||||
|
"ledger_type": "GL Entry",
|
||||||
|
"voucher_type": gl_data.get("voucher_type"),
|
||||||
|
"voucher_no": gl_data.get("voucher_no"),
|
||||||
|
"posting_date": gl_data.get("posting_date"),
|
||||||
|
"stock_value": 0,
|
||||||
|
"account_value": gl_data.get("account_value", 0),
|
||||||
|
"difference_value": gl_data.get("account_value", 0) * -1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -89,6 +117,7 @@ def get_gl_data(report_filters, filters):
|
|||||||
"voucher_type",
|
"voucher_type",
|
||||||
"voucher_no",
|
"voucher_no",
|
||||||
"sum(debit_in_account_currency) - sum(credit_in_account_currency) as account_value",
|
"sum(debit_in_account_currency) - sum(credit_in_account_currency) as account_value",
|
||||||
|
"posting_date",
|
||||||
],
|
],
|
||||||
group_by="voucher_type, voucher_no",
|
group_by="voucher_type, voucher_no",
|
||||||
)
|
)
|
||||||
@@ -106,10 +135,15 @@ def get_columns(filters):
|
|||||||
{
|
{
|
||||||
"label": _("Stock Ledger ID"),
|
"label": _("Stock Ledger ID"),
|
||||||
"fieldname": "name",
|
"fieldname": "name",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"options": "Stock Ledger Entry",
|
"options": "ledger_type",
|
||||||
"width": "80",
|
"width": "80",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": _("Ledger Type"),
|
||||||
|
"fieldname": "ledger_type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date"},
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date"},
|
||||||
{"label": _("Posting Time"), "fieldname": "posting_time", "fieldtype": "Time"},
|
{"label": _("Posting Time"), "fieldname": "posting_time", "fieldtype": "Time"},
|
||||||
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": "110"},
|
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": "110"},
|
||||||
|
|||||||
Reference in New Issue
Block a user