mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-16 16:15:02 +00:00
Merge pull request #40919 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -135,6 +135,7 @@ class GLEntry(Document):
|
||||
and self.company == dimension.company
|
||||
and dimension.mandatory_for_pl
|
||||
and not dimension.disabled
|
||||
and not self.is_cancelled
|
||||
):
|
||||
if not self.get(dimension.fieldname):
|
||||
frappe.throw(
|
||||
@@ -148,6 +149,7 @@ class GLEntry(Document):
|
||||
and self.company == dimension.company
|
||||
and dimension.mandatory_for_bs
|
||||
and not dimension.disabled
|
||||
and not self.is_cancelled
|
||||
):
|
||||
if not self.get(dimension.fieldname):
|
||||
frappe.throw(
|
||||
|
||||
0
erpnext/accounts/doctype/ledger_health/__init__.py
Normal file
0
erpnext/accounts/doctype/ledger_health/__init__.py
Normal file
8
erpnext/accounts/doctype/ledger_health/ledger_health.js
Normal file
8
erpnext/accounts/doctype/ledger_health/ledger_health.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Ledger Health", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
70
erpnext/accounts/doctype/ledger_health/ledger_health.json
Normal file
70
erpnext/accounts/doctype/ledger_health/ledger_health.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2024-03-26 17:01:47.443986",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"checked_on",
|
||||
"debit_credit_mismatch",
|
||||
"general_and_payment_ledger_mismatch"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher No"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "debit_credit_mismatch",
|
||||
"fieldtype": "Check",
|
||||
"label": "Debit-Credit mismatch"
|
||||
},
|
||||
{
|
||||
"fieldname": "checked_on",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Checked On"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "general_and_payment_ledger_mismatch",
|
||||
"fieldtype": "Check",
|
||||
"label": "General and Payment Ledger mismatch"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-09 11:16:07.044484",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Ledger Health",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
25
erpnext/accounts/doctype/ledger_health/ledger_health.py
Normal file
25
erpnext/accounts/doctype/ledger_health/ledger_health.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LedgerHealth(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
checked_on: DF.Datetime | None
|
||||
debit_credit_mismatch: DF.Check
|
||||
general_and_payment_ledger_mismatch: DF.Check
|
||||
name: DF.Int | None
|
||||
voucher_no: DF.Data | None
|
||||
voucher_type: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
109
erpnext/accounts/doctype/ledger_health/test_ledger_health.py
Normal file
109
erpnext/accounts/doctype/ledger_health/test_ledger_health.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.accounts.utils import run_ledger_health_checks
|
||||
|
||||
|
||||
class TestLedgerHealth(AccountsTestMixin, FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.configure_monitoring_tool()
|
||||
self.clear_old_entries()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def configure_monitoring_tool(self):
|
||||
monitor_settings = frappe.get_doc("Ledger Health Monitor")
|
||||
monitor_settings.enable_health_monitor = True
|
||||
monitor_settings.enable_for_last_x_days = 60
|
||||
monitor_settings.debit_credit_mismatch = True
|
||||
monitor_settings.general_and_payment_ledger_mismatch = True
|
||||
exists = [x for x in monitor_settings.companies if x.company == self.company]
|
||||
if not exists:
|
||||
monitor_settings.append("companies", {"company": self.company})
|
||||
monitor_settings.save()
|
||||
|
||||
def clear_old_entries(self):
|
||||
super().clear_old_entries()
|
||||
lh = qb.DocType("Ledger Health")
|
||||
qb.from_(lh).delete().run()
|
||||
|
||||
def create_journal(self):
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.company = self.company
|
||||
je.voucher_type = "Journal Entry"
|
||||
je.posting_date = nowdate()
|
||||
je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"debit_in_account_currency": 10000,
|
||||
},
|
||||
)
|
||||
je.append("accounts", {"account": self.income_account, "credit_in_account_currency": 10000})
|
||||
je.save().submit()
|
||||
self.je = je
|
||||
|
||||
def test_debit_credit_mismatch(self):
|
||||
self.create_journal()
|
||||
|
||||
# manually cause debit-credit mismatch
|
||||
gle = frappe.db.get_all(
|
||||
"GL Entry", filters={"voucher_no": self.je.name, "account": self.income_account}
|
||||
)[0]
|
||||
frappe.db.set_value("GL Entry", gle.name, "credit", 8000)
|
||||
|
||||
run_ledger_health_checks()
|
||||
expected = {
|
||||
"voucher_type": self.je.doctype,
|
||||
"voucher_no": self.je.name,
|
||||
"debit_credit_mismatch": True,
|
||||
"general_and_payment_ledger_mismatch": False,
|
||||
}
|
||||
actual = frappe.db.get_all(
|
||||
"Ledger Health",
|
||||
fields=[
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"debit_credit_mismatch",
|
||||
"general_and_payment_ledger_mismatch",
|
||||
],
|
||||
)
|
||||
self.assertEqual(len(actual), 1)
|
||||
self.assertEqual(expected, actual[0])
|
||||
|
||||
def test_gl_and_pl_mismatch(self):
|
||||
self.create_journal()
|
||||
|
||||
# manually cause GL and PL discrepancy
|
||||
ple = frappe.db.get_all("Payment Ledger Entry", filters={"voucher_no": self.je.name})[0]
|
||||
frappe.db.set_value("Payment Ledger Entry", ple.name, "amount", 11000)
|
||||
|
||||
run_ledger_health_checks()
|
||||
expected = {
|
||||
"voucher_type": self.je.doctype,
|
||||
"voucher_no": self.je.name,
|
||||
"debit_credit_mismatch": False,
|
||||
"general_and_payment_ledger_mismatch": True,
|
||||
}
|
||||
actual = frappe.db.get_all(
|
||||
"Ledger Health",
|
||||
fields=[
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"debit_credit_mismatch",
|
||||
"general_and_payment_ledger_mismatch",
|
||||
],
|
||||
)
|
||||
self.assertEqual(len(actual), 1)
|
||||
self.assertEqual(expected, actual[0])
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Ledger Health Monitor", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2024-03-27 09:38:07.427997",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enable_health_monitor",
|
||||
"monitor_section",
|
||||
"monitor_for_last_x_days",
|
||||
"debit_credit_mismatch",
|
||||
"general_and_payment_ledger_mismatch",
|
||||
"section_break_xdsp",
|
||||
"companies"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_health_monitor",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Health Monitor"
|
||||
},
|
||||
{
|
||||
"fieldname": "monitor_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Configuration"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "debit_credit_mismatch",
|
||||
"fieldtype": "Check",
|
||||
"label": "Debit-Credit Mismatch"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "general_and_payment_ledger_mismatch",
|
||||
"fieldtype": "Check",
|
||||
"label": "Discrepancy between General and Payment Ledger"
|
||||
},
|
||||
{
|
||||
"default": "60",
|
||||
"fieldname": "monitor_for_last_x_days",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Monitor for Last 'X' days",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_xdsp",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Companies"
|
||||
},
|
||||
{
|
||||
"fieldname": "companies",
|
||||
"fieldtype": "Table",
|
||||
"options": "Ledger Health Monitor Company"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 10:14:16.511681",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Ledger Health Monitor",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LedgerHealthMonitor(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.ledger_health_monitor_company.ledger_health_monitor_company import (
|
||||
LedgerHealthMonitorCompany,
|
||||
)
|
||||
|
||||
companies: DF.Table[LedgerHealthMonitorCompany]
|
||||
debit_credit_mismatch: DF.Check
|
||||
enable_health_monitor: DF.Check
|
||||
general_and_payment_ledger_mismatch: DF.Check
|
||||
monitor_for_last_x_days: DF.Int
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestLedgerHealthMonitor(FrappeTestCase):
|
||||
pass
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2024-03-27 10:04:45.727054",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 10:06:22.806155",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Ledger Health Monitor Company",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LedgerHealthMonitorCompany(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
company: DF.Link | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -338,7 +338,7 @@ class PaymentEntry(AccountsController):
|
||||
self,
|
||||
force: bool = False,
|
||||
update_ref_details_only_for: list | None = None,
|
||||
ref_exchange_rate: float | None = None,
|
||||
reference_exchange_details: dict | None = None,
|
||||
) -> None:
|
||||
for d in self.get("references"):
|
||||
if d.allocated_amount:
|
||||
@@ -352,8 +352,12 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
# Only update exchange rate when the reference is Journal Entry
|
||||
if ref_exchange_rate and d.reference_doctype == "Journal Entry":
|
||||
ref_details.update({"exchange_rate": ref_exchange_rate})
|
||||
if (
|
||||
reference_exchange_details
|
||||
and d.reference_doctype == reference_exchange_details.reference_doctype
|
||||
and d.reference_name == reference_exchange_details.reference_name
|
||||
):
|
||||
ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate})
|
||||
|
||||
for field, value in ref_details.items():
|
||||
if d.exchange_gain_loss:
|
||||
|
||||
@@ -3438,6 +3438,105 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
si.items[0].rate = 10
|
||||
si.save()
|
||||
|
||||
def test_taxes_merging_from_delivery_note(self):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
dn1 = create_delivery_note(do_not_submit=1)
|
||||
dn1.items[0].qty = 10
|
||||
dn1.items[0].rate = 100
|
||||
dn1.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "Actual",
|
||||
"account_head": "Freight and Forwarding Charges - _TC",
|
||||
"description": "movement charges",
|
||||
"tax_amount": 100,
|
||||
},
|
||||
)
|
||||
dn1.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "Actual",
|
||||
"account_head": "Marketing Expenses - _TC",
|
||||
"description": "marketing",
|
||||
"tax_amount": 150,
|
||||
},
|
||||
)
|
||||
dn1.save().submit()
|
||||
|
||||
dn2 = create_delivery_note(do_not_submit=1)
|
||||
dn2.items[0].qty = 5
|
||||
dn2.items[0].rate = 100
|
||||
dn2.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "Actual",
|
||||
"account_head": "Freight and Forwarding Charges - _TC",
|
||||
"description": "movement charges",
|
||||
"tax_amount": 20,
|
||||
},
|
||||
)
|
||||
dn2.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "Actual",
|
||||
"account_head": "Miscellaneous Expenses - _TC",
|
||||
"description": "marketing",
|
||||
"tax_amount": 60,
|
||||
},
|
||||
)
|
||||
dn2.save().submit()
|
||||
|
||||
# si = make_sales_invoice(dn1.name)
|
||||
si = create_sales_invoice(do_not_submit=True)
|
||||
si.customer = dn1.customer
|
||||
si.items.clear()
|
||||
|
||||
from frappe.model.mapper import map_docs
|
||||
|
||||
map_docs(
|
||||
method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
|
||||
source_names=frappe.json.dumps([dn1.name, dn2.name]),
|
||||
target_doc=si,
|
||||
args=frappe.json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}),
|
||||
)
|
||||
si.save().submit()
|
||||
|
||||
expected = [
|
||||
{
|
||||
"charge_type": "Actual",
|
||||
"account_head": "Freight and Forwarding Charges - _TC",
|
||||
"tax_amount": 120.0,
|
||||
"total": 1520.0,
|
||||
"base_total": 1520.0,
|
||||
},
|
||||
{
|
||||
"charge_type": "Actual",
|
||||
"account_head": "Marketing Expenses - _TC",
|
||||
"tax_amount": 150.0,
|
||||
"total": 1670.0,
|
||||
"base_total": 1670.0,
|
||||
},
|
||||
{
|
||||
"charge_type": "Actual",
|
||||
"account_head": "Miscellaneous Expenses - _TC",
|
||||
"tax_amount": 60.0,
|
||||
"total": 1610.0,
|
||||
"base_total": 1610.0,
|
||||
},
|
||||
]
|
||||
actual = [
|
||||
dict(
|
||||
charge_type=x.charge_type,
|
||||
account_head=x.account_head,
|
||||
tax_amount=x.tax_amount,
|
||||
total=x.total,
|
||||
base_total=x.base_total,
|
||||
)
|
||||
for x in si.taxes
|
||||
]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
|
||||
@@ -55,10 +55,10 @@
|
||||
</span>
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i].debit, filters.presentation_currency) %}
|
||||
{%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i].credit, filters.presentation_currency) %}
|
||||
{%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %}
|
||||
</td>
|
||||
{% } else { %}
|
||||
<td></td>
|
||||
|
||||
@@ -13,11 +13,13 @@ from frappe.query_builder import AliasedQuery, Criterion, Table
|
||||
from frappe.query_builder.functions import Round, Sum
|
||||
from frappe.query_builder.utils import DocType
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
cint,
|
||||
create_batch,
|
||||
cstr,
|
||||
flt,
|
||||
formatdate,
|
||||
get_datetime,
|
||||
get_number_format_info,
|
||||
getdate,
|
||||
now,
|
||||
@@ -682,7 +684,19 @@ def update_reference_in_payment_entry(
|
||||
payment_entry.setup_party_account_field()
|
||||
payment_entry.set_missing_values()
|
||||
if not skip_ref_details_update_for_pe:
|
||||
payment_entry.set_missing_ref_details(ref_exchange_rate=d.exchange_rate or None)
|
||||
reference_exchange_details = frappe._dict()
|
||||
if d.against_voucher_type == "Journal Entry" and d.exchange_rate:
|
||||
reference_exchange_details.update(
|
||||
{
|
||||
"reference_doctype": d.against_voucher_type,
|
||||
"reference_name": d.against_voucher,
|
||||
"exchange_rate": d.exchange_rate,
|
||||
}
|
||||
)
|
||||
payment_entry.set_missing_ref_details(
|
||||
update_ref_details_only_for=[(d.against_voucher_type, d.against_voucher)],
|
||||
reference_exchange_details=reference_exchange_details,
|
||||
)
|
||||
payment_entry.set_amounts()
|
||||
|
||||
payment_entry.make_exchange_gain_loss_journal(
|
||||
@@ -2072,3 +2086,44 @@ def create_gain_loss_journal(
|
||||
journal_entry.save()
|
||||
journal_entry.submit()
|
||||
return journal_entry.name
|
||||
|
||||
|
||||
def run_ledger_health_checks():
|
||||
health_monitor_settings = frappe.get_doc("Ledger Health Monitor")
|
||||
if health_monitor_settings.enable_health_monitor:
|
||||
period_end = getdate()
|
||||
period_start = add_days(period_end, -abs(health_monitor_settings.monitor_for_last_x_days))
|
||||
|
||||
run_date = get_datetime()
|
||||
|
||||
# Debit-Credit mismatch report
|
||||
if health_monitor_settings.debit_credit_mismatch:
|
||||
for x in health_monitor_settings.companies:
|
||||
filters = {"company": x.company, "from_date": period_start, "to_date": period_end}
|
||||
voucher_wise = frappe.get_doc("Report", "Voucher-wise Balance")
|
||||
res = voucher_wise.execute_script_report(filters=filters)
|
||||
for x in res[1]:
|
||||
doc = frappe.new_doc("Ledger Health")
|
||||
doc.voucher_type = x.voucher_type
|
||||
doc.voucher_no = x.voucher_no
|
||||
doc.debit_credit_mismatch = True
|
||||
doc.checked_on = run_date
|
||||
doc.save()
|
||||
|
||||
# General Ledger and Payment Ledger discrepancy
|
||||
if health_monitor_settings.general_and_payment_ledger_mismatch:
|
||||
for x in health_monitor_settings.companies:
|
||||
filters = {
|
||||
"company": x.company,
|
||||
"period_start_date": period_start,
|
||||
"period_end_date": period_end,
|
||||
}
|
||||
gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison")
|
||||
res = gl_pl_comparison.execute_script_report(filters=filters)
|
||||
for x in res[1]:
|
||||
doc = frappe.new_doc("Ledger Health")
|
||||
doc.voucher_type = x.voucher_type
|
||||
doc.voucher_no = x.voucher_no
|
||||
doc.general_and_payment_ledger_mismatch = True
|
||||
doc.checked_on = run_date
|
||||
doc.save()
|
||||
|
||||
@@ -437,6 +437,7 @@ class Asset(AccountsController):
|
||||
depreciation_amount,
|
||||
from_date,
|
||||
date_of_disposal,
|
||||
original_schedule_date=schedule_date,
|
||||
)
|
||||
|
||||
if depreciation_amount > 0:
|
||||
@@ -1118,14 +1119,20 @@ class Asset(AccountsController):
|
||||
return flt((100 * (1 - depreciation_rate)), float_precision)
|
||||
|
||||
def get_pro_rata_amt(
|
||||
self, row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False
|
||||
self,
|
||||
row,
|
||||
depreciation_amount,
|
||||
from_date,
|
||||
to_date,
|
||||
has_wdv_or_dd_non_yearly_pro_rata=False,
|
||||
original_schedule_date=None,
|
||||
):
|
||||
days = date_diff(to_date, from_date)
|
||||
months = month_diff(to_date, from_date)
|
||||
if has_wdv_or_dd_non_yearly_pro_rata:
|
||||
total_days = get_total_days(to_date, 12)
|
||||
total_days = get_total_days(original_schedule_date or to_date, 12)
|
||||
else:
|
||||
total_days = get_total_days(to_date, row.frequency_of_depreciation)
|
||||
total_days = get_total_days(original_schedule_date or to_date, row.frequency_of_depreciation)
|
||||
|
||||
return (depreciation_amount * flt(days)) / flt(total_days), days, months
|
||||
|
||||
@@ -1445,32 +1452,35 @@ def get_straight_line_or_manual_depr_amount(
|
||||
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
|
||||
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||
if row.daily_prorata_based:
|
||||
daily_depr_amount = (
|
||||
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||
) / date_diff(
|
||||
get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
|
||||
* row.frequency_of_depreciation,
|
||||
)
|
||||
),
|
||||
add_days(
|
||||
amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||
total_days = (
|
||||
date_diff(
|
||||
get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(
|
||||
row.total_number_of_depreciations
|
||||
- asset.number_of_depreciations_booked
|
||||
- number_of_pending_depreciations
|
||||
- 1
|
||||
)
|
||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
|
||||
* row.frequency_of_depreciation,
|
||||
)
|
||||
),
|
||||
1,
|
||||
),
|
||||
add_days(
|
||||
get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(
|
||||
row.total_number_of_depreciations
|
||||
- asset.number_of_depreciations_booked
|
||||
- number_of_pending_depreciations
|
||||
- 1
|
||||
)
|
||||
* row.frequency_of_depreciation,
|
||||
)
|
||||
),
|
||||
1,
|
||||
),
|
||||
)
|
||||
+ 1
|
||||
)
|
||||
daily_depr_amount = amount / total_days
|
||||
|
||||
to_date = get_last_day(
|
||||
add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
|
||||
@@ -1490,22 +1500,28 @@ def get_straight_line_or_manual_depr_amount(
|
||||
# if the Depreciation Schedule is being prepared for the first time
|
||||
else:
|
||||
if row.daily_prorata_based:
|
||||
daily_depr_amount = (
|
||||
|
||||
amount = (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(row.expected_value_after_useful_life)
|
||||
) / date_diff(
|
||||
get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
|
||||
* row.frequency_of_depreciation,
|
||||
)
|
||||
),
|
||||
add_days(
|
||||
get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1
|
||||
),
|
||||
)
|
||||
total_days = (
|
||||
date_diff(
|
||||
get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
|
||||
* row.frequency_of_depreciation,
|
||||
)
|
||||
),
|
||||
add_days(
|
||||
get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1
|
||||
),
|
||||
)
|
||||
+ 1
|
||||
)
|
||||
daily_depr_amount = amount / total_days
|
||||
|
||||
to_date = get_last_day(
|
||||
add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
|
||||
|
||||
@@ -219,7 +219,11 @@ class TestAsset(AssetSetup):
|
||||
asset.precision("gross_purchase_amount"),
|
||||
)
|
||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
|
||||
asset.finance_books[0],
|
||||
9000,
|
||||
get_last_day(add_months(purchase_date, 1)),
|
||||
date,
|
||||
original_schedule_date=get_last_day(nowdate()),
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
self.assertEquals(
|
||||
@@ -287,7 +291,11 @@ class TestAsset(AssetSetup):
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
|
||||
asset.finance_books[0],
|
||||
9000,
|
||||
get_last_day(add_months(purchase_date, 1)),
|
||||
date,
|
||||
original_schedule_date=get_last_day(nowdate()),
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
|
||||
@@ -349,7 +357,7 @@ class TestAsset(AssetSetup):
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]]
|
||||
expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1737.7, 37737.7]]
|
||||
|
||||
for i, schedule in enumerate(asset.schedules):
|
||||
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
|
||||
@@ -360,7 +368,7 @@ class TestAsset(AssetSetup):
|
||||
expected_gle = (
|
||||
(
|
||||
"_Test Accumulated Depreciations - _TC",
|
||||
37742.47,
|
||||
37737.7,
|
||||
0.0,
|
||||
),
|
||||
(
|
||||
@@ -371,7 +379,7 @@ class TestAsset(AssetSetup):
|
||||
(
|
||||
"_Test Gain/Loss on Asset Disposal - _TC",
|
||||
0.0,
|
||||
17742.47,
|
||||
17737.7,
|
||||
),
|
||||
("Debtors - _TC", 40000.0, 0.0),
|
||||
)
|
||||
@@ -691,18 +699,18 @@ class TestDepreciationMethods(AssetSetup):
|
||||
)
|
||||
|
||||
expected_schedules = [
|
||||
["2023-01-31", 1021.98, 1021.98],
|
||||
["2023-02-28", 923.08, 1945.06],
|
||||
["2023-03-31", 1021.98, 2967.04],
|
||||
["2023-04-30", 989.01, 3956.05],
|
||||
["2023-05-31", 1021.98, 4978.03],
|
||||
["2023-06-30", 989.01, 5967.04],
|
||||
["2023-07-31", 1021.98, 6989.02],
|
||||
["2023-08-31", 1021.98, 8011.0],
|
||||
["2023-09-30", 989.01, 9000.01],
|
||||
["2023-10-31", 1021.98, 10021.99],
|
||||
["2023-11-30", 989.01, 11011.0],
|
||||
["2023-12-31", 989.0, 12000.0],
|
||||
["2023-01-31", 1019.18, 1019.18],
|
||||
["2023-02-28", 920.55, 1939.73],
|
||||
["2023-03-31", 1019.18, 2958.91],
|
||||
["2023-04-30", 986.3, 3945.21],
|
||||
["2023-05-31", 1019.18, 4964.39],
|
||||
["2023-06-30", 986.3, 5950.69],
|
||||
["2023-07-31", 1019.18, 6969.87],
|
||||
["2023-08-31", 1019.18, 7989.05],
|
||||
["2023-09-30", 986.3, 8975.35],
|
||||
["2023-10-31", 1019.18, 9994.53],
|
||||
["2023-11-30", 986.3, 10980.83],
|
||||
["2023-12-31", 1019.17, 12000.0],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold, qb, throw
|
||||
@@ -1947,21 +1948,26 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
|
||||
def group_similar_items(self):
|
||||
group_item_qty = {}
|
||||
group_item_amount = {}
|
||||
grouped_items = {}
|
||||
# to update serial number in print
|
||||
count = 0
|
||||
|
||||
fields_to_group = frappe.get_hooks("fields_for_group_similar_items")
|
||||
fields_to_group = set(fields_to_group)
|
||||
|
||||
for item in self.items:
|
||||
group_item_qty[item.item_code] = group_item_qty.get(item.item_code, 0) + item.qty
|
||||
group_item_amount[item.item_code] = group_item_amount.get(item.item_code, 0) + item.amount
|
||||
item_values = grouped_items.setdefault(item.item_code, defaultdict(int))
|
||||
|
||||
for field in fields_to_group:
|
||||
item_values[field] += item.get(field, 0)
|
||||
|
||||
duplicate_list = []
|
||||
for item in self.items:
|
||||
if item.item_code in group_item_qty:
|
||||
if item.item_code in grouped_items:
|
||||
count += 1
|
||||
item.qty = group_item_qty[item.item_code]
|
||||
item.amount = group_item_amount[item.item_code]
|
||||
|
||||
for field in fields_to_group:
|
||||
item.set(field, grouped_items[item.item_code][field])
|
||||
|
||||
if item.qty:
|
||||
item.rate = flt(flt(item.amount) / flt(item.qty), item.precision("rate"))
|
||||
@@ -1969,7 +1975,7 @@ class AccountsController(TransactionBase):
|
||||
item.rate = 0
|
||||
|
||||
item.idx = count
|
||||
del group_item_qty[item.item_code]
|
||||
del grouped_items[item.item_code]
|
||||
else:
|
||||
duplicate_list.append(item)
|
||||
for item in duplicate_list:
|
||||
@@ -3313,6 +3319,37 @@ def check_if_child_table_updated(
|
||||
return False
|
||||
|
||||
|
||||
def merge_taxes(source_taxes, target_doc):
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||
update_item_wise_tax_detail,
|
||||
)
|
||||
|
||||
existing_taxes = target_doc.get("taxes") or []
|
||||
idx = 1
|
||||
for tax in source_taxes:
|
||||
found = False
|
||||
for t in existing_taxes:
|
||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||
update_item_wise_tax_detail(t, tax)
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
tax.charge_type = "Actual"
|
||||
tax.idx = idx
|
||||
idx += 1
|
||||
tax.included_in_print_rate = 0
|
||||
tax.dont_recompute_tax = 1
|
||||
tax.row_id = ""
|
||||
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||
tax.item_wise_tax_detail = tax.item_wise_tax_detail
|
||||
existing_taxes.append(tax)
|
||||
|
||||
target_doc.set("taxes", existing_taxes)
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def validate_regional(doc):
|
||||
pass
|
||||
|
||||
@@ -56,7 +56,8 @@ class TestAccountsController(FrappeTestCase):
|
||||
20 series - Sales Invoice against Journals
|
||||
30 series - Sales Invoice against Credit Notes
|
||||
40 series - Company default Cost center is unset
|
||||
50 series = Journals against Journals
|
||||
50 series - Journals against Journals
|
||||
60 series - Journals against Payment Entries
|
||||
90 series - Dimension inheritence
|
||||
"""
|
||||
|
||||
@@ -1574,3 +1575,70 @@ class TestAccountsController(FrappeTestCase):
|
||||
exc_je_for_je = self.get_journals_for(journal_as_payment.doctype, journal_as_payment.name)
|
||||
self.assertEqual(exc_je_for_si, [])
|
||||
self.assertEqual(exc_je_for_je, [])
|
||||
|
||||
def test_60_payment_entry_against_journal(self):
|
||||
# Invoices
|
||||
exc_rate1 = 75
|
||||
exc_rate2 = 77
|
||||
amount = 1
|
||||
je1 = self.create_journal_entry(
|
||||
acc1=self.debit_usd,
|
||||
acc1_exc_rate=exc_rate1,
|
||||
acc2=self.cash,
|
||||
acc1_amount=amount,
|
||||
acc2_amount=(amount * 75),
|
||||
acc2_exc_rate=1,
|
||||
)
|
||||
je1.accounts[0].party_type = "Customer"
|
||||
je1.accounts[0].party = self.customer
|
||||
je1 = je1.save().submit()
|
||||
|
||||
je2 = self.create_journal_entry(
|
||||
acc1=self.debit_usd,
|
||||
acc1_exc_rate=exc_rate2,
|
||||
acc2=self.cash,
|
||||
acc1_amount=amount,
|
||||
acc2_amount=(amount * exc_rate2),
|
||||
acc2_exc_rate=1,
|
||||
)
|
||||
je2.accounts[0].party_type = "Customer"
|
||||
je2.accounts[0].party = self.customer
|
||||
je2 = je2.save().submit()
|
||||
|
||||
# Payment
|
||||
pe = self.create_payment_entry(amount=2, source_exc_rate=exc_rate1).save().submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.receivable_payable_account = self.debit_usd
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(len(pr.invoices), 2)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [x.as_dict() for x in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
# There should be no outstanding in both currencies
|
||||
self.assert_ledger_outstanding(je1.doctype, je1.name, 0.0, 0.0)
|
||||
self.assert_ledger_outstanding(je2.doctype, je2.name, 0.0, 0.0)
|
||||
|
||||
# Exchange Gain/Loss Journal should've been created only for JE2
|
||||
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
|
||||
exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name)
|
||||
self.assertEqual(exc_je_for_je1, [])
|
||||
self.assertEqual(len(exc_je_for_je2), 1)
|
||||
|
||||
# Cancel Payment
|
||||
pe.reload()
|
||||
pe.cancel()
|
||||
|
||||
self.assert_ledger_outstanding(je1.doctype, je1.name, (amount * exc_rate1), amount)
|
||||
self.assert_ledger_outstanding(je2.doctype, je2.name, (amount * exc_rate2), amount)
|
||||
|
||||
# Exchange Gain/Loss Journal should've been cancelled
|
||||
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
|
||||
exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name)
|
||||
self.assertEqual(exc_je_for_je1, [])
|
||||
self.assertEqual(exc_je_for_je2, [])
|
||||
|
||||
@@ -449,6 +449,7 @@ scheduler_events = {
|
||||
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
|
||||
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
|
||||
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily",
|
||||
"erpnext.accounts.utils.run_ledger_health_checks",
|
||||
],
|
||||
"weekly": [
|
||||
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly",
|
||||
@@ -643,3 +644,5 @@ extend_bootinfo = [
|
||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
|
||||
"erpnext.startup.boot.bootinfo",
|
||||
]
|
||||
|
||||
fields_for_group_similar_items = ["qty", "amount"]
|
||||
|
||||
@@ -819,7 +819,7 @@ erpnext.utils.map_current_doc = function (opts) {
|
||||
|
||||
if (opts.source_doctype) {
|
||||
let data_fields = [];
|
||||
if (opts.source_doctype == "Purchase Receipt") {
|
||||
if (["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)) {
|
||||
data_fields.push({
|
||||
fieldname: "merge_taxes",
|
||||
fieldtype: "Check",
|
||||
@@ -845,7 +845,10 @@ erpnext.utils.map_current_doc = function (opts) {
|
||||
return;
|
||||
}
|
||||
opts.source_name = values;
|
||||
if (opts.allow_child_item_selection || opts.source_doctype == "Purchase Receipt") {
|
||||
if (
|
||||
opts.allow_child_item_selection ||
|
||||
["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)
|
||||
) {
|
||||
// args contains filtered child docnames
|
||||
opts.args = args;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges, merge_taxes
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
|
||||
@@ -623,7 +623,7 @@ def get_returned_qty_map(delivery_note):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_invoice(source_name, target_doc=None):
|
||||
def make_sales_invoice(source_name, target_doc=None, args=None):
|
||||
doc = frappe.get_doc("Delivery Note", source_name)
|
||||
|
||||
to_make_invoice_qty_map = {}
|
||||
@@ -637,6 +637,9 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
if len(target.get("items")) == 0:
|
||||
frappe.throw(_("All these items have already been Invoiced/Returned"))
|
||||
|
||||
if args and args.get("merge_taxes"):
|
||||
merge_taxes(source.get("taxes") or [], target)
|
||||
|
||||
target.run_method("calculate_taxes_and_totals")
|
||||
|
||||
# set company address
|
||||
@@ -701,7 +704,11 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
if not doc.get("is_return")
|
||||
else get_pending_qty(d) > 0,
|
||||
},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||
"Sales Taxes and Charges": {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"add_if_empty": True,
|
||||
"ignore": args.get("merge_taxes") if args else 0,
|
||||
},
|
||||
"Sales Team": {
|
||||
"doctype": "Sales Team",
|
||||
"field_map": {"incentives": "incentives"},
|
||||
|
||||
@@ -14,6 +14,7 @@ import erpnext
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.controllers.accounts_controller import merge_taxes
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction
|
||||
|
||||
@@ -974,37 +975,6 @@ def get_item_wise_returned_qty(pr_doc):
|
||||
)
|
||||
|
||||
|
||||
def merge_taxes(source_taxes, target_doc):
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||
update_item_wise_tax_detail,
|
||||
)
|
||||
|
||||
existing_taxes = target_doc.get("taxes") or []
|
||||
idx = 1
|
||||
for tax in source_taxes:
|
||||
found = False
|
||||
for t in existing_taxes:
|
||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||
update_item_wise_tax_detail(t, tax)
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
tax.charge_type = "Actual"
|
||||
tax.idx = idx
|
||||
idx += 1
|
||||
tax.included_in_print_rate = 0
|
||||
tax.dont_recompute_tax = 1
|
||||
tax.row_id = ""
|
||||
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||
tax.item_wise_tax_detail = tax.item_wise_tax_detail
|
||||
existing_taxes.append(tax)
|
||||
|
||||
target_doc.set("taxes", existing_taxes)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_invoice(source_name, target_doc=None, args=None):
|
||||
from erpnext.accounts.party import get_payment_terms_template
|
||||
|
||||
@@ -744,7 +744,9 @@ def get_items(
|
||||
warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False
|
||||
):
|
||||
ignore_empty_stock = cint(ignore_empty_stock)
|
||||
items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})]
|
||||
items = []
|
||||
if item_code and warehouse:
|
||||
items = get_item_and_warehouses(item_code, warehouse)
|
||||
|
||||
if not item_code:
|
||||
items = get_items_for_stock_reco(warehouse, company)
|
||||
@@ -789,6 +791,20 @@ def get_items(
|
||||
return res
|
||||
|
||||
|
||||
def get_item_and_warehouses(item_code, warehouse):
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
|
||||
items = []
|
||||
if frappe.get_cached_value("Warehouse", warehouse, "is_group"):
|
||||
childrens = get_descendants_of("Warehouse", warehouse, ignore_permissions=True, order_by="lft")
|
||||
for ch_warehouse in childrens:
|
||||
items.append(frappe._dict({"item_code": item_code, "warehouse": ch_warehouse}))
|
||||
else:
|
||||
items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})]
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def get_items_for_stock_reco(warehouse, company):
|
||||
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
|
||||
items = frappe.db.sql(
|
||||
@@ -803,7 +819,7 @@ def get_items_for_stock_reco(warehouse, company):
|
||||
and i.is_stock_item = 1
|
||||
and i.has_variants = 0
|
||||
and exists(
|
||||
select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse
|
||||
select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse and is_group = 0
|
||||
)
|
||||
""",
|
||||
as_dict=1,
|
||||
@@ -818,7 +834,7 @@ def get_items_for_stock_reco(warehouse, company):
|
||||
where
|
||||
i.name = id.parent
|
||||
and exists(
|
||||
select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse
|
||||
select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse and is_group = 0
|
||||
)
|
||||
and i.is_stock_item = 1
|
||||
and i.has_variants = 0
|
||||
@@ -880,7 +896,7 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None):
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": row[0],
|
||||
"warehouse": warehouse,
|
||||
"warehouse": row[3],
|
||||
"qty": row[8],
|
||||
"item_name": row[1],
|
||||
"batch_no": row[4],
|
||||
|
||||
@@ -117,34 +117,37 @@ def get_stock_ledger_entries(filters):
|
||||
if filters.get(field):
|
||||
query = query.where(sle[field] == filters.get(field))
|
||||
|
||||
return query.run(as_dict=True)
|
||||
return query
|
||||
|
||||
|
||||
def get_item_warehouse_batch_map(filters, float_precision):
|
||||
sle = get_stock_ledger_entries(filters)
|
||||
iwb_map = {}
|
||||
with frappe.db.unbuffered_cursor():
|
||||
sle = get_stock_ledger_entries(filters)
|
||||
sle = sle.run(as_dict=True, as_iterator=True)
|
||||
|
||||
from_date = getdate(filters["from_date"])
|
||||
to_date = getdate(filters["to_date"])
|
||||
iwb_map = {}
|
||||
|
||||
for d in sle:
|
||||
iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault(
|
||||
d.batch_no, frappe._dict({"opening_qty": 0.0, "in_qty": 0.0, "out_qty": 0.0, "bal_qty": 0.0})
|
||||
)
|
||||
qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no]
|
||||
if d.posting_date < from_date:
|
||||
qty_dict.opening_qty = flt(qty_dict.opening_qty, float_precision) + flt(
|
||||
d.actual_qty, float_precision
|
||||
from_date = getdate(filters["from_date"])
|
||||
to_date = getdate(filters["to_date"])
|
||||
|
||||
for d in sle:
|
||||
iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault(
|
||||
d.batch_no, frappe._dict({"opening_qty": 0.0, "in_qty": 0.0, "out_qty": 0.0, "bal_qty": 0.0})
|
||||
)
|
||||
elif d.posting_date >= from_date and d.posting_date <= to_date:
|
||||
if flt(d.actual_qty) > 0:
|
||||
qty_dict.in_qty = flt(qty_dict.in_qty, float_precision) + flt(d.actual_qty, float_precision)
|
||||
else:
|
||||
qty_dict.out_qty = flt(qty_dict.out_qty, float_precision) + abs(
|
||||
flt(d.actual_qty, float_precision)
|
||||
qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no]
|
||||
if d.posting_date < from_date:
|
||||
qty_dict.opening_qty = flt(qty_dict.opening_qty, float_precision) + flt(
|
||||
d.actual_qty, float_precision
|
||||
)
|
||||
elif d.posting_date >= from_date and d.posting_date <= to_date:
|
||||
if flt(d.actual_qty) > 0:
|
||||
qty_dict.in_qty = flt(qty_dict.in_qty, float_precision) + flt(d.actual_qty, float_precision)
|
||||
else:
|
||||
qty_dict.out_qty = flt(qty_dict.out_qty, float_precision) + abs(
|
||||
flt(d.actual_qty, float_precision)
|
||||
)
|
||||
|
||||
qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision)
|
||||
qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision)
|
||||
|
||||
return iwb_map
|
||||
|
||||
|
||||
@@ -9,9 +9,6 @@ frappe.query_reports["Item Prices"] = {
|
||||
fieldtype: "Select",
|
||||
options: "Enabled Items only\nDisabled Items only\nAll Items",
|
||||
default: "Enabled Items only",
|
||||
on_change: function (query_report) {
|
||||
query_report.trigger_refresh();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a>
|
||||
{{ _("Powered by {0}").format('<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">ERPNext</a>') }}
|
||||
|
||||
Reference in New Issue
Block a user