Merge branch 'develop' of https://github.com/frappe/erpnext into finance-book-filter

This commit is contained in:
Khushi Rawat
2024-09-13 13:26:45 +05:30
10 changed files with 519 additions and 361 deletions

View File

@@ -1791,6 +1791,79 @@ class TestPaymentEntry(FrappeTestCase):
# 'Is Opening' should always be 'No' for normal advance payments
self.assertEqual(gl_with_opening_set, [])
@change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1})
def test_delete_linked_exchange_gain_loss_journal(self):
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
)
debtors = create_account(
account_name="Debtors USD",
parent_account="Accounts Receivable - _TC",
company="_Test Company",
account_currency="USD",
account_type="Receivable",
)
# create a customer
customer = make_customer(customer="_Test Party USD")
cust_doc = frappe.get_doc("Customer", customer)
cust_doc.default_currency = "USD"
test_account_details = {
"company": "_Test Company",
"account": debtors,
}
cust_doc.append("accounts", test_account_details)
cust_doc.save()
# create a sales invoice
si = create_sales_invoice(
customer=customer,
currency="USD",
conversion_rate=83.970000000,
debit_to=debtors,
do_not_save=1,
)
si.party_account_currency = "USD"
si.save()
si.submit()
# create a payment entry for the invoice
pe = get_payment_entry("Sales Invoice", si.name)
pe.reference_no = "1"
pe.reference_date = frappe.utils.nowdate()
pe.paid_amount = 100
pe.source_exchange_rate = 90
pe.append(
"deductions",
{
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 2710,
},
)
pe.save()
pe.submit()
# check creation of journal entry
jv = frappe.get_all(
"Journal Entry Account",
{"reference_type": pe.doctype, "reference_name": pe.name, "docstatus": 1},
pluck="parent",
)
self.assertTrue(jv)
# check cancellation of payment entry and journal entry
pe.cancel()
self.assertTrue(pe.docstatus == 2)
self.assertTrue(frappe.db.get_value("Journal Entry", {"name": jv[0]}, "docstatus") == 2)
# check deletion of payment entry and journal entry
pe.delete()
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, pe.doctype, pe.name)
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, "Journal Entry", jv[0])
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")

View File

@@ -5,6 +5,7 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase, change_settings
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -14,7 +15,7 @@ from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.get_item_details import get_item_details
class TestPricingRule(unittest.TestCase):
class TestPricingRule(FrappeTestCase):
def setUp(self):
delete_existing_pricing_rules()
setup_pricing_rule_data()

View File

@@ -769,40 +769,74 @@ def cancel_exchange_gain_loss_journal(
Cancel Exchange Gain/Loss for Sales/Purchase Invoice, if they have any.
"""
if parent_doc.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]:
journals = frappe.db.get_all(
"Journal Entry Account",
filters={
"reference_type": parent_doc.doctype,
"reference_name": parent_doc.name,
"docstatus": 1,
},
fields=["parent"],
as_list=1,
gain_loss_journals = get_linked_exchange_gain_loss_journal(
referenced_dt=parent_doc.doctype, referenced_dn=parent_doc.name, je_docstatus=1
)
if journals:
gain_loss_journals = frappe.db.get_all(
"Journal Entry",
filters={
"name": ["in", [x[0] for x in journals]],
"voucher_type": "Exchange Gain Or Loss",
"docstatus": 1,
},
as_list=1,
)
for doc in gain_loss_journals:
gain_loss_je = frappe.get_doc("Journal Entry", doc[0])
if referenced_dt and referenced_dn:
references = [(x.reference_type, x.reference_name) for x in gain_loss_je.accounts]
if (
len(references) == 2
and (referenced_dt, referenced_dn) in references
and (parent_doc.doctype, parent_doc.name) in references
):
# only cancel JE generated against parent_doc and referenced_dn
gain_loss_je.cancel()
else:
for doc in gain_loss_journals:
gain_loss_je = frappe.get_doc("Journal Entry", doc)
if referenced_dt and referenced_dn:
references = [(x.reference_type, x.reference_name) for x in gain_loss_je.accounts]
if (
len(references) == 2
and (referenced_dt, referenced_dn) in references
and (parent_doc.doctype, parent_doc.name) in references
):
# only cancel JE generated against parent_doc and referenced_dn
gain_loss_je.cancel()
else:
gain_loss_je.cancel()
def delete_exchange_gain_loss_journal(
parent_doc: dict | object, referenced_dt: str | None = None, referenced_dn: str | None = None
) -> None:
"""
Delete Exchange Gain/Loss for Sales/Purchase Invoice, if they have any.
"""
if parent_doc.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]:
gain_loss_journals = get_linked_exchange_gain_loss_journal(
referenced_dt=parent_doc.doctype, referenced_dn=parent_doc.name, je_docstatus=2
)
for doc in gain_loss_journals:
gain_loss_je = frappe.get_doc("Journal Entry", doc)
if referenced_dt and referenced_dn:
references = [(x.reference_type, x.reference_name) for x in gain_loss_je.accounts]
if (
len(references) == 2
and (referenced_dt, referenced_dn) in references
and (parent_doc.doctype, parent_doc.name) in references
):
# only delete JE generated against parent_doc and referenced_dn
gain_loss_je.delete()
else:
gain_loss_je.delete()
def get_linked_exchange_gain_loss_journal(referenced_dt: str, referenced_dn: str, je_docstatus: int) -> list:
"""
Get all the linked exchange gain/loss journal entries for a given document.
"""
gain_loss_journals = []
if journals := frappe.db.get_all(
"Journal Entry Account",
{
"reference_type": referenced_dt,
"reference_name": referenced_dn,
"docstatus": je_docstatus,
},
pluck="parent",
):
gain_loss_journals = frappe.db.get_all(
"Journal Entry",
{
"name": ["in", journals],
"voucher_type": "Exchange Gain Or Loss",
"is_system_generated": 1,
"docstatus": je_docstatus,
},
pluck="name",
)
return gain_loss_journals
def cancel_common_party_journal(self):

View File

@@ -346,12 +346,17 @@ class AccountsController(TransactionBase):
repost_doc.save(ignore_permissions=True)
def on_trash(self):
from erpnext.accounts.utils import delete_exchange_gain_loss_journal
self._remove_references_in_repost_doctypes()
self._remove_references_in_unreconcile()
self.remove_serial_and_batch_bundle()
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
# delete linked exchange gain/loss journal
delete_exchange_gain_loss_journal(self)
ple = frappe.qb.DocType("Payment Ledger Entry")
frappe.qb.from_(ple).delete().where(
(ple.voucher_type == self.doctype) & (ple.voucher_no == self.name)

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: info@erpnext.com\n"
"POT-Creation-Date: 2024-09-01 09:35+0000\n"
"PO-Revision-Date: 2024-09-07 18:35\n"
"PO-Revision-Date: 2024-09-10 18:59\n"
"Last-Translator: info@erpnext.com\n"
"Language-Team: Persian\n"
"MIME-Version: 1.0\n"
@@ -715,7 +715,7 @@ msgstr ""
#. Header text in the Stock Workspace
#: selling/workspace/selling/selling.json stock/workspace/stock/stock.json
msgid "<span class=\"h4\"><b>Quick Access</b></span>"
msgstr ""
msgstr "<span class=\"h4\"><b>دسترسی سریع</b></span>"
#. Header text in the Assets Workspace
#. Header text in the Quality Workspace
@@ -748,7 +748,7 @@ msgstr ""
#. Header text in the Settings Workspace
#: setup/workspace/settings/settings.json
msgid "<span class=\"h4\"><b>Settings</b></span>"
msgstr ""
msgstr "<span class=\"h4\"><b>تنظیمات</b></span>"
#. Header text in the Accounting Workspace
#. Header text in the Payables Workspace
@@ -757,7 +757,7 @@ msgstr ""
#: accounts/workspace/payables/payables.json
#: accounts/workspace/receivables/receivables.json
msgid "<span class=\"h4\"><b>Shortcuts</b></span>"
msgstr ""
msgstr "<span class=\"h4\"><b>میانبرها</b></span>"
#. Header text in the Settings Workspace
#: setup/workspace/settings/settings.json
@@ -785,7 +785,7 @@ msgstr ""
#: quality_management/workspace/quality/quality.json
#: setup/workspace/home/home.json support/workspace/support/support.json
msgid "<span class=\"h4\"><b>Your Shortcuts</b></span>"
msgstr ""
msgstr "<span class=\"h4\"><b>میانبرهای شما</b></span>"
#. Content of the 'html_19' (HTML) field in DocType 'Inventory Dimension'
#: stock/doctype/inventory_dimension/inventory_dimension.json
@@ -1020,7 +1020,7 @@ msgstr "در بالا"
#: accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html:99
#: accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html:351
msgid "Above 120 Days"
msgstr ""
msgstr "بالای 120 روز"
#. Name of a role
#: setup/doctype/department/department.json
@@ -3632,7 +3632,7 @@ msgstr "گروهی از آیتم‌ها را در یک آیتم دیگر جمع
#: setup/setup_wizard/data/industry_type.txt:4
msgid "Agriculture"
msgstr ""
msgstr "کشاورزی"
#. Name of a role
#: assets/doctype/location/location.json
@@ -3646,7 +3646,7 @@ msgstr "کاربر کشاورزی"
#: setup/setup_wizard/data/industry_type.txt:5
msgid "Airline"
msgstr ""
msgstr "شرکت هواپیمایی"
#. Label of the algorithm (Select) field in DocType 'Bisect Accounting
#. Statements'
@@ -4914,7 +4914,7 @@ msgstr "قابل اجرا در حساب"
#. Label of the to_designation (Link) field in DocType 'Authorization Rule'
#: setup/doctype/authorization_rule/authorization_rule.json
msgid "Applicable To (Designation)"
msgstr "قابل اجرا برای (تعیین)"
msgstr "قابل اجرا برای (نقش سازمانی)"
#. Label of the to_emp (Link) field in DocType 'Authorization Rule'
#: setup/doctype/authorization_rule/authorization_rule.json
@@ -5158,7 +5158,7 @@ msgstr "درخواست برای سند"
#. Label of a Link in the CRM Workspace
#: crm/doctype/appointment/appointment.json crm/workspace/crm/crm.json
msgid "Appointment"
msgstr "وقت ملاقات"
msgstr "قرار ملاقات"
#. Name of a DocType
#: crm/doctype/appointment_booking_settings/appointment_booking_settings.json
@@ -7930,7 +7930,7 @@ msgstr "Biot"
#: setup/setup_wizard/data/industry_type.txt:9
msgid "Biotechnology"
msgstr ""
msgstr "بیوتکنولوژی"
#. Name of a DocType
#: accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.json
@@ -8048,7 +8048,7 @@ msgstr "آبی"
#. Accounts'
#: accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
msgid "Body"
msgstr "بدن"
msgstr "بدنه"
#. Label of the body_text (Text Editor) field in DocType 'Dunning'
#. Label of the body_text (Text Editor) field in DocType 'Dunning Letter Text'
@@ -9756,7 +9756,7 @@ msgstr "سفارش پرداخت / ارسال سفارش / سفارش جدید"
#: setup/setup_wizard/data/industry_type.txt:12
msgid "Chemical"
msgstr ""
msgstr "شیمیایی"
#. Option for the 'Salary Mode' (Select) field in DocType 'Employee'
#: setup/doctype/employee/employee.json
@@ -19255,7 +19255,7 @@ msgstr "لاگ درون‌بُرد برون‌بُرد"
#: setup/setup_wizard/operations/install_fixtures.py:286
msgid "External"
msgstr ""
msgstr "بیرونی"
#. Label of the external_work_history (Table) field in DocType 'Employee'
#: setup/doctype/employee/employee.json
@@ -51167,7 +51167,7 @@ msgstr " شناسه مالیاتی:"
#: accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html:32
msgid "Tax Id: {0}"
msgstr ""
msgstr "شناسه مالیاتی: {0}"
#. Label of a Card Break in the Accounting Workspace
#: accounts/workspace/accounting/accounting.json
@@ -53861,7 +53861,7 @@ msgstr "تعداد کل پیش بینی شده"
#: buying/report/item_wise_purchase_history/item_wise_purchase_history.py:272
msgid "Total Purchase Amount"
msgstr ""
msgstr "کل مبلغ خرید"
#. Label of the total_purchase_cost (Currency) field in DocType 'Project'
#: projects/doctype/project/project.json

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: info@erpnext.com\n"
"POT-Creation-Date: 2024-09-01 09:35+0000\n"
"PO-Revision-Date: 2024-09-07 18:35\n"
"PO-Revision-Date: 2024-09-10 18:59\n"
"Last-Translator: info@erpnext.com\n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
@@ -28131,7 +28131,7 @@ msgstr "Maskin"
#: public/js/plant_floor_visual/visual_plant.js:70
msgid "Machine Type"
msgstr "Naskin Typ"
msgstr "Maskin Typ"
#. Option for the 'Stop Reason' (Select) field in DocType 'Downtime Entry'
#: manufacturing/doctype/downtime_entry/downtime_entry.json

File diff suppressed because it is too large Load Diff

View File

@@ -1268,6 +1268,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"Purchase Receipt": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"],
"Purchase Invoice": ["purchase_order_item", "pr_detail", "po_detail"],
"Sales Order": ["prevdoc_docname", "quotation_item"],
"Purchase Order": ["supplier_quotation_item"],
};
const mappped_fields = mapped_item_field_map[this.frm.doc.doctype] || [];
@@ -1511,6 +1512,31 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
}
batch_no(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.use_serial_batch_fields && row.batch_no) {
var params = this._get_args(row);
params.batch_no = row.batch_no;
params.uom = row.uom;
frappe.call({
method: "erpnext.stock.get_item_details.get_batch_based_item_price",
args: {
params: params,
item_code: row.item_code,
},
callback: function(r) {
if (!r.exc && r.message) {
row.price_list_rate = r.message;
row.rate = r.message;
refresh_field("rate", row.name, row.parentfield);
refresh_field("price_list_rate", row.name, row.parentfield);
}
}
})
}
}
toggle_item_grid_columns(company_currency) {
const me = this;
// toggle columns

View File

@@ -1,4 +1,4 @@
<div>
<a href={{ route }}>{{ title }}</a>
<a href={{ route }}>{{ title or name }}</a>
</div>
<!-- this is a sample default list template -->

View File

@@ -10,7 +10,7 @@ from frappe.model import child_table_fields, default_fields
from frappe.model.meta import get_field_precision
from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import add_days, add_months, cint, cstr, flt, getdate
from frappe.utils import add_days, add_months, cint, cstr, flt, getdate, parse_json
from erpnext import get_company_currency
from erpnext.accounts.doctype.pricing_rule.pricing_rule import (
@@ -902,7 +902,7 @@ def insert_item_price(args):
)
def get_item_price(args, item_code, ignore_party=False) -> list[dict]:
def get_item_price(args, item_code, ignore_party=False, force_batch_no=False) -> list[dict]:
"""
Get name, price_list_rate from Item Price based on conditions
Check if the desired qty is within the increment of the packing list.
@@ -919,7 +919,6 @@ def get_item_price(args, item_code, ignore_party=False) -> list[dict]:
(ip.item_code == item_code)
& (ip.price_list == args.get("price_list"))
& (IfNull(ip.uom, "").isin(["", args.get("uom")]))
& (IfNull(ip.batch_no, "").isin(["", args.get("batch_no")]))
)
.orderby(ip.valid_from, order=frappe.qb.desc)
.orderby(IfNull(ip.batch_no, ""), order=frappe.qb.desc)
@@ -927,6 +926,11 @@ def get_item_price(args, item_code, ignore_party=False) -> list[dict]:
.limit(1)
)
if force_batch_no:
query = query.where(ip.batch_no == args.get("batch_no"))
else:
query = query.where(IfNull(ip.batch_no, "").isin(["", args.get("batch_no")]))
if not ignore_party:
if args.get("customer"):
query = query.where(ip.customer == args.get("customer"))
@@ -944,6 +948,21 @@ def get_item_price(args, item_code, ignore_party=False) -> list[dict]:
return query.run(as_dict=True)
@frappe.whitelist()
def get_batch_based_item_price(params, item_code) -> float:
if isinstance(params, str):
params = parse_json(params)
item_price = get_item_price(params, item_code, force_batch_no=True)
if not item_price:
item_price = get_item_price(params, item_code, ignore_party=True, force_batch_no=True)
if item_price and item_price[0].uom == params.get("uom"):
return item_price[0].price_list_rate
return 0.0
def get_price_list_rate_for(args, item_code):
"""
:param customer: link to Customer DocType