mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-21 10:26:30 +00:00
Merge branch 'develop' of https://github.com/frappe/erpnext into finance-book-filter
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user