diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js
index 320e1cab7c3..7d63b257faf 100644
--- a/erpnext/accounts/doctype/account/account.js
+++ b/erpnext/accounts/doctype/account/account.js
@@ -117,9 +117,6 @@ frappe.ui.form.on('Account', {
args: {
old: frm.doc.name,
new: data.name,
- is_group: frm.doc.is_group,
- root_type: frm.doc.root_type,
- company: frm.doc.company
},
callback: function(r) {
if(!r.exc) {
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index bbe4c54a71a..22ddc2ffae3 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -18,6 +18,10 @@ class BalanceMismatchError(frappe.ValidationError):
pass
+class InvalidAccountMergeError(frappe.ValidationError):
+ pass
+
+
class Account(NestedSet):
nsm_parent_field = "parent_account"
@@ -444,24 +448,35 @@ def update_account_number(name, account_name, account_number=None, from_descenda
@frappe.whitelist()
-def merge_account(old, new, is_group, root_type, company):
+def merge_account(old, new):
# Validate properties before merging
- if not frappe.db.exists("Account", new):
+ new_account = frappe.get_cached_doc("Account", new)
+ old_account = frappe.get_cached_doc("Account", old)
+
+ if not new_account:
throw(_("Account {0} does not exist").format(new))
- val = list(frappe.db.get_value("Account", new, ["is_group", "root_type", "company"]))
-
- if val != [cint(is_group), root_type, company]:
+ if (
+ cint(new_account.is_group),
+ new_account.root_type,
+ new_account.company,
+ cstr(new_account.account_currency),
+ ) != (
+ cint(old_account.is_group),
+ old_account.root_type,
+ old_account.company,
+ cstr(old_account.account_currency),
+ ):
throw(
- _(
- """Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""
- )
+ msg=_(
+ """Merging is only possible if following properties are same in both records. Is Group, Root Type, Company and Account Currency"""
+ ),
+ title=("Invalid Accounts"),
+ exc=InvalidAccountMergeError,
)
- if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
- frappe.db.set_value(
- "Account", new, "parent_account", frappe.db.get_value("Account", old, "parent_account")
- )
+ if old_account.is_group and new_account.parent_account == old:
+ new_account.db_set("parent_account", frappe.get_cached_value("Account", old, "parent_account"))
frappe.rename_doc("Account", old, new, merge=1, force=1)
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index 8ae90ceb383..d537adfcbfd 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -56,36 +56,41 @@ frappe.treeview_settings["Account"] = {
accounts = nodes;
}
- const get_balances = frappe.call({
- method: 'erpnext.accounts.utils.get_account_balances',
- args: {
- accounts: accounts,
- company: cur_tree.args.company
- },
- });
+ frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
+ if(value) {
- get_balances.then(r => {
- if (!r.message || r.message.length == 0) return;
+ const get_balances = frappe.call({
+ method: 'erpnext.accounts.utils.get_account_balances',
+ args: {
+ accounts: accounts,
+ company: cur_tree.args.company
+ },
+ });
- for (let account of r.message) {
+ get_balances.then(r => {
+ if (!r.message || r.message.length == 0) return;
- const node = cur_tree.nodes && cur_tree.nodes[account.value];
- if (!node || node.is_root) continue;
+ for (let account of r.message) {
- // show Dr if positive since balance is calculated as debit - credit else show Cr
- const balance = account.balance_in_account_currency || account.balance;
- const dr_or_cr = balance > 0 ? "Dr": "Cr";
- const format = (value, currency) => format_currency(Math.abs(value), currency);
+ const node = cur_tree.nodes && cur_tree.nodes[account.value];
+ if (!node || node.is_root) continue;
- if (account.balance!==undefined) {
- node.parent && node.parent.find('.balance-area').remove();
- $(''
- + (account.balance_in_account_currency ?
- (format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
- + format(account.balance, account.company_currency)
- + " " + dr_or_cr
- + '').insertBefore(node.$ul);
- }
+ // show Dr if positive since balance is calculated as debit - credit else show Cr
+ const balance = account.balance_in_account_currency || account.balance;
+ const dr_or_cr = balance > 0 ? "Dr": "Cr";
+ const format = (value, currency) => format_currency(Math.abs(value), currency);
+
+ if (account.balance!==undefined) {
+ node.parent && node.parent.find('.balance-area').remove();
+ $(''
+ + (account.balance_in_account_currency ?
+ (format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
+ + format(account.balance, account.company_currency)
+ + " " + dr_or_cr
+ + '').insertBefore(node.$ul);
+ }
+ }
+ });
}
});
},
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index 62303bd723f..30eebef7fba 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -7,7 +7,11 @@ import unittest
import frappe
from frappe.test_runner import make_test_records
-from erpnext.accounts.doctype.account.account import merge_account, update_account_number
+from erpnext.accounts.doctype.account.account import (
+ InvalidAccountMergeError,
+ merge_account,
+ update_account_number,
+)
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
test_dependencies = ["Company"]
@@ -47,49 +51,53 @@ class TestAccount(unittest.TestCase):
frappe.delete_doc("Account", "1211-11-4 - 6 - Debtors 1 - Test - - _TC")
def test_merge_account(self):
- if not frappe.db.exists("Account", "Current Assets - _TC"):
- acc = frappe.new_doc("Account")
- acc.account_name = "Current Assets"
- acc.is_group = 1
- acc.parent_account = "Application of Funds (Assets) - _TC"
- acc.company = "_Test Company"
- acc.insert()
- if not frappe.db.exists("Account", "Securities and Deposits - _TC"):
- acc = frappe.new_doc("Account")
- acc.account_name = "Securities and Deposits"
- acc.parent_account = "Current Assets - _TC"
- acc.is_group = 1
- acc.company = "_Test Company"
- acc.insert()
- if not frappe.db.exists("Account", "Earnest Money - _TC"):
- acc = frappe.new_doc("Account")
- acc.account_name = "Earnest Money"
- acc.parent_account = "Securities and Deposits - _TC"
- acc.company = "_Test Company"
- acc.insert()
- if not frappe.db.exists("Account", "Cash In Hand - _TC"):
- acc = frappe.new_doc("Account")
- acc.account_name = "Cash In Hand"
- acc.is_group = 1
- acc.parent_account = "Current Assets - _TC"
- acc.company = "_Test Company"
- acc.insert()
- if not frappe.db.exists("Account", "Accumulated Depreciation - _TC"):
- acc = frappe.new_doc("Account")
- acc.account_name = "Accumulated Depreciation"
- acc.parent_account = "Fixed Assets - _TC"
- acc.company = "_Test Company"
- acc.account_type = "Accumulated Depreciation"
- acc.insert()
+ create_account(
+ account_name="Current Assets",
+ is_group=1,
+ parent_account="Application of Funds (Assets) - _TC",
+ company="_Test Company",
+ )
+
+ create_account(
+ account_name="Securities and Deposits",
+ is_group=1,
+ parent_account="Current Assets - _TC",
+ company="_Test Company",
+ )
+
+ create_account(
+ account_name="Earnest Money",
+ parent_account="Securities and Deposits - _TC",
+ company="_Test Company",
+ )
+
+ create_account(
+ account_name="Cash In Hand",
+ is_group=1,
+ parent_account="Current Assets - _TC",
+ company="_Test Company",
+ )
+
+ create_account(
+ account_name="Receivable INR",
+ parent_account="Current Assets - _TC",
+ company="_Test Company",
+ account_currency="INR",
+ )
+
+ create_account(
+ account_name="Receivable USD",
+ parent_account="Current Assets - _TC",
+ company="_Test Company",
+ account_currency="USD",
+ )
- doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
self.assertEqual(parent, "Securities and Deposits - _TC")
- merge_account(
- "Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company
- )
+ merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC")
+
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
# Parent account of the child account changes after merging
@@ -98,30 +106,28 @@ class TestAccount(unittest.TestCase):
# Old account doesn't exist after merging
self.assertFalse(frappe.db.exists("Account", "Securities and Deposits - _TC"))
- doc = frappe.get_doc("Account", "Current Assets - _TC")
-
# Raise error as is_group property doesn't match
self.assertRaises(
- frappe.ValidationError,
+ InvalidAccountMergeError,
merge_account,
"Current Assets - _TC",
"Accumulated Depreciation - _TC",
- doc.is_group,
- doc.root_type,
- doc.company,
)
- doc = frappe.get_doc("Account", "Capital Stock - _TC")
-
# Raise error as root_type property doesn't match
self.assertRaises(
- frappe.ValidationError,
+ InvalidAccountMergeError,
merge_account,
"Capital Stock - _TC",
"Softwares - _TC",
- doc.is_group,
- doc.root_type,
- doc.company,
+ )
+
+ # Raise error as currency doesn't match
+ self.assertRaises(
+ InvalidAccountMergeError,
+ merge_account,
+ "Receivable INR - _TC",
+ "Receivable USD - _TC",
)
def test_account_sync(self):
@@ -400,11 +406,20 @@ def create_account(**kwargs):
"Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")}
)
if account:
- return account
+ account = frappe.get_doc("Account", account)
+ account.update(
+ dict(
+ is_group=kwargs.get("is_group", 0),
+ parent_account=kwargs.get("parent_account"),
+ )
+ )
+ account.save()
+ return account.name
else:
account = frappe.get_doc(
dict(
doctype="Account",
+ is_group=kwargs.get("is_group", 0),
account_name=kwargs.get("account_name"),
account_type=kwargs.get("account_type"),
parent_account=kwargs.get("parent_account"),
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 467c68f1021..3ab9d2b60d5 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -66,7 +66,9 @@
"report_settings_sb",
"banking_tab",
"enable_party_matching",
- "enable_fuzzy_matching"
+ "enable_fuzzy_matching",
+ "tab_break_dpet",
+ "show_balance_in_coa"
],
"fields": [
{
@@ -416,6 +418,17 @@
"fieldname": "ignore_account_closing_balance",
"fieldtype": "Check",
"label": "Ignore Account Closing Balance"
+ },
+ {
+ "fieldname": "tab_break_dpet",
+ "fieldtype": "Tab Break",
+ "label": "Chart Of Accounts"
+ },
+ {
+ "default": "1",
+ "fieldname": "show_balance_in_coa",
+ "fieldtype": "Check",
+ "label": "Show Balances in Chart Of Accounts"
}
],
"icon": "icon-cog",
diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
index 18e5a1ac85b..6e89d039932 100644
--- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
+++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
@@ -49,9 +49,6 @@ def start_merge(docname):
merge_account(
row.account,
ledger_merge.account,
- ledger_merge.is_group,
- ledger_merge.root_type,
- ledger_merge.company,
)
row.db_set("merged", 1)
frappe.db.commit()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 0e2de4ac395..c5501a58306 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1455,6 +1455,14 @@ def get_outstanding_reference_documents(args):
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
)
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
+ elif args.get(date_fields[0]):
+ # if only from date is supplied
+ condition += " and {0} >= '{1}'".format(fieldname, args.get(date_fields[0]))
+ posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0])))
+ elif args.get(date_fields[1]):
+ # if only to date is supplied
+ condition += " and {0} <= '{1}'".format(fieldname, args.get(date_fields[1]))
+ posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1])))
if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index af1c06643a1..d984d86af25 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -33,7 +33,7 @@ class PeriodClosingVoucher(AccountsController):
def on_cancel(self):
self.validate_future_closing_vouchers()
self.db_set("gle_processing_status", "In Progress")
- self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
gle_count = frappe.db.count(
"GL Entry",
{"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index e7a7ae24ceb..d3058e44509 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -1,5 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
# For license information, please see license.txt
+
+
import collections
import frappe
@@ -43,7 +45,6 @@ class POSInvoice(SalesInvoice):
self.validate_debit_to_acc()
self.validate_write_off_account()
self.validate_change_amount()
- self.validate_duplicate_serial_and_batch_no()
self.validate_change_account()
self.validate_item_cost_centers()
self.validate_warehouse()
@@ -56,6 +57,7 @@ class POSInvoice(SalesInvoice):
self.validate_payment_amount()
self.validate_loyalty_transaction()
self.validate_company_with_pos_company()
+ self.validate_duplicate_serial_no()
if self.coupon_code:
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
@@ -156,27 +158,18 @@ class POSInvoice(SalesInvoice):
title=_("Item Unavailable"),
)
- def validate_duplicate_serial_and_batch_no(self):
+ def validate_duplicate_serial_no(self):
serial_nos = []
- batch_nos = []
for row in self.get("items"):
if row.serial_no:
serial_nos = row.serial_no.split("\n")
- if row.batch_no and not row.serial_no:
- batch_nos.append(row.batch_no)
-
if serial_nos:
for key, value in collections.Counter(serial_nos).items():
if value > 1:
frappe.throw(_("Duplicate Serial No {0} found").format("key"))
- if batch_nos:
- for key, value in collections.Counter(batch_nos).items():
- if value > 1:
- frappe.throw(_("Duplicate Batch No {0} found").format("key"))
-
def validate_pos_reserved_batch_qty(self, item):
filters = {"item_code": item.item_code, "warehouse": item.warehouse, "batch_no": item.batch_no}
@@ -525,7 +518,7 @@ class POSInvoice(SalesInvoice):
selling_price_list = (
customer_price_list or customer_group_price_list or profile.get("selling_price_list")
)
- if customer_currency != profile.get("currency"):
+ if customer_currency and customer_currency != profile.get("currency"):
self.set("currency", customer_currency)
else:
@@ -683,7 +676,7 @@ def get_bundle_availability(bundle_item_code, warehouse):
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
available_qty = item_bin_qty - item_pos_reserved_qty
- max_available_bundles = available_qty / item.stock_qty
+ max_available_bundles = available_qty / item.qty
if bundle_bin_qty > max_available_bundles and frappe.get_value(
"Item", item.item_code, "is_stock_item"
):
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 3132fdd259a..bd2fee3b0ad 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -464,6 +464,37 @@ class TestPOSInvoice(unittest.TestCase):
pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
+ def test_pos_invoice_with_duplicate_serial_no(self):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+
+ se = make_serialized_item(
+ company="_Test Company",
+ target_warehouse="Stores - _TC",
+ cost_center="Main - _TC",
+ expense_account="Cost of Goods Sold - _TC",
+ )
+
+ serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+
+ pos = create_pos_invoice(
+ company="_Test Company",
+ debit_to="Debtors - _TC",
+ account_for_change_amount="Cash - _TC",
+ warehouse="Stores - _TC",
+ income_account="Sales - _TC",
+ expense_account="Cost of Goods Sold - _TC",
+ cost_center="Main - _TC",
+ item=se.get("items")[0].item_code,
+ rate=1000,
+ qty=2,
+ do_not_save=1,
+ )
+
+ pos.get("items")[0].has_serial_no = 1
+ pos.get("items")[0].serial_no = serial_nos[0] + "\n" + serial_nos[0]
+ self.assertRaises(frappe.ValidationError, pos.submit)
+
def test_invalid_serial_no_validation(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
index 45373741f39..e711ae0de2b 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
@@ -49,6 +49,7 @@
"column_break_21",
"start_date",
"section_break_33",
+ "pdf_name",
"subject",
"column_break_28",
"cc_to",
@@ -273,7 +274,7 @@
"fieldname": "help_text",
"fieldtype": "HTML",
"label": "Help Text",
- "options": "
\n
Statement Of Accounts for {{ customer.name }}Hello {{ customer.name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}. Statement Of Accounts for {{ customer.customer_name }}Hello {{ customer.customer_name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}. {{ _("Printed On ") }}{{ frappe.utils.now() }}
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index c281040aaf2..fb0d8d152f0 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -1,9 +1,42 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe import unittest +import frappe +from frappe.utils import add_days, getdate, today + +from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import ( + send_emails, +) +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + class TestProcessStatementOfAccounts(unittest.TestCase): - pass + def setUp(self): + self.si = create_sales_invoice() + self.process_soa = create_process_soa() + + def test_auto_email_for_process_soa_ar(self): + send_emails(self.process_soa.name, from_scheduler=True) + self.process_soa.load_from_db() + self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7))) + + def tearDown(self): + frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + + +def create_process_soa(): + frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + process_soa = frappe.new_doc("Process Statement Of Accounts") + soa_dict = { + "name": "Test Process SOA", + "company": "_Test Company", + } + process_soa.update(soa_dict) + process_soa.set("customers", [{"customer": "_Test Customer"}]) + process_soa.enable_auto_email = 1 + process_soa.frequency = "Weekly" + process_soa.report = "Accounts Receivable" + process_soa.save() + return process_soa diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0f8e77952cf..30265aeb50e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1153,7 +1153,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True) item.enable_deferred_expense = 1 - item.deferred_expense_account = deferred_account + item.item_defaults[0].deferred_expense_account = deferred_account item.save() pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index 6fdcf263a55..fd95c1fe0e5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -15,9 +15,11 @@ def get_data(): }, "internal_links": { "Sales Order": ["items", "sales_order"], - "Delivery Note": ["items", "delivery_note"], "Timesheet": ["timesheets", "time_sheet"], }, + "internal_and_external_links": { + "Delivery Note": ["items", "delivery_note"], + }, "transactions": [ { "label": _("Payment"), diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index eee99dcfde0..e0a7ff002bb 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1801,6 +1801,10 @@ class TestSalesInvoice(unittest.TestCase): ) def test_outstanding_amount_after_advance_payment_entry_cancellation(self): + """Test impact of advance PE submission/cancellation on SI and SO.""" + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + + sales_order = make_sales_order(item_code="138-CMS Shoe", qty=1, price_list_rate=500) pe = frappe.get_doc( { "doctype": "Payment Entry", @@ -1820,10 +1824,25 @@ class TestSalesInvoice(unittest.TestCase): "paid_to": "_Test Cash - _TC", } ) + pe.append( + "references", + { + "reference_doctype": "Sales Order", + "reference_name": sales_order.name, + "total_amount": sales_order.grand_total, + "outstanding_amount": sales_order.grand_total, + "allocated_amount": 300, + }, + ) pe.insert() pe.submit() + sales_order.reload() + self.assertEqual(sales_order.advance_paid, 300) + si = frappe.copy_doc(test_records[0]) + si.items[0].sales_order = sales_order.name + si.items[0].so_detail = sales_order.get("items")[0].name si.is_pos = 0 si.append( "advances", @@ -1831,6 +1850,7 @@ class TestSalesInvoice(unittest.TestCase): "doctype": "Sales Invoice Advance", "reference_type": "Payment Entry", "reference_name": pe.name, + "reference_row": pe.references[0].name, "advance_amount": 300, "allocated_amount": 300, "remarks": pe.remarks, @@ -1839,7 +1859,13 @@ class TestSalesInvoice(unittest.TestCase): si.insert() si.submit() - si.load_from_db() + si.reload() + pe.reload() + sales_order.reload() + + # Check if SO is unlinked/replaced by SI in PE & if SO advance paid is 0 + self.assertEqual(pe.references[0].reference_name, si.name) + self.assertEqual(sales_order.advance_paid, 0.0) # check outstanding after advance allocation self.assertEqual( @@ -1847,11 +1873,9 @@ class TestSalesInvoice(unittest.TestCase): flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")), ) - # added to avoid Document has been modified exception - pe = frappe.get_doc("Payment Entry", pe.name) pe.cancel() + si.reload() - si.load_from_db() # check outstanding after advance cancellation self.assertEqual( flt(si.outstanding_amount), @@ -2322,7 +2346,7 @@ class TestSalesInvoice(unittest.TestCase): item = create_item("_Test Item for Deferred Accounting") item.enable_deferred_revenue = 1 - item.deferred_revenue_account = deferred_account + item.item_defaults[0].deferred_revenue_account = deferred_account item.no_of_months = 12 item.save() @@ -3102,7 +3126,7 @@ class TestSalesInvoice(unittest.TestCase): item = create_item("_Test Item for Deferred Accounting") item.enable_deferred_expense = 1 - item.deferred_revenue_account = deferred_account + item.item_defaults[0].deferred_revenue_account = deferred_account item.save() si = create_sales_invoice( diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index cb84cf4fc0a..3cf93cc8659 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -24,7 +24,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): def tearDown(self): frappe.db.rollback() - def test_accounts_receivable_with_supplier(self): + def test_accounts_payable_for_foreign_currency_supplier(self): pi = self.create_purchase_invoice(do_not_submit=True) pi.currency = "USD" pi.conversion_rate = 80 diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index cb8ec876e9e..bb00d616dbc 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -38,32 +38,31 @@ frappe.query_reports["Accounts Receivable"] = { } }, { - "fieldname": "customer", - "label": __("Customer"), + "fieldname": "party_type", + "label": __("Party Type"), "fieldtype": "Link", - "options": "Customer", + "options": "Party Type", + "Default": "Customer", + get_query: () => { + return { + filters: { + 'account_type': 'Receivable' + } + }; + }, on_change: () => { - var customer = frappe.query_report.get_filter_value('customer'); - var company = frappe.query_report.get_filter_value('company'); - if (customer) { - frappe.db.get_value('Customer', customer, ["customer_name", "payment_terms"], function(value) { - frappe.query_report.set_filter_value('customer_name', value["customer_name"]); - frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]); - }); + frappe.query_report.set_filter_value('party', ""); + let party_type = frappe.query_report.get_filter_value('party_type'); + frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); - frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company}, - ["credit_limit"], function(value) { - if (value) { - frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]); - } - }, "Customer"); - } else { - frappe.query_report.set_filter_value('customer_name', ""); - frappe.query_report.set_filter_value('credit_limit', ""); - frappe.query_report.set_filter_value('payment_terms', ""); - } } }, + { + "fieldname":"party", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "options": "party_type", + }, { "fieldname": "party_account", "label": __("Receivable Account"), @@ -174,24 +173,6 @@ frappe.query_reports["Accounts Receivable"] = { "fieldname": "show_remarks", "label": __("Show Remarks"), "fieldtype": "Check", - }, - { - "fieldname": "customer_name", - "label": __("Customer Name"), - "fieldtype": "Data", - "hidden": 1 - }, - { - "fieldname": "payment_terms", - "label": __("Payment Tems"), - "fieldtype": "Data", - "hidden": 1 - }, - { - "fieldname": "credit_limit", - "label": __("Credit Limit"), - "fieldtype": "Currency", - "hidden": 1 } ], diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 14f8993727a..79424023652 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -769,15 +769,12 @@ class ReceivablePayableReport(object): self.or_filters = [] for party_type in self.party_type: - party_type_field = scrub(party_type) - self.or_filters.append(self.ple.party_type == party_type) + self.add_common_filters() - self.add_common_filters(party_type_field=party_type_field) - - if party_type_field == "customer": + if self.account_type == "Receivable": self.add_customer_filters() - elif party_type_field == "supplier": + elif self.account_type == "Payable": self.add_supplier_filters() if self.filters.cost_center: @@ -793,16 +790,13 @@ class ReceivablePayableReport(object): ] self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list)) - def add_common_filters(self, party_type_field): + def add_common_filters(self): if self.filters.company: self.qb_selection_filter.append(self.ple.company == self.filters.company) if self.filters.finance_book: self.qb_selection_filter.append(self.ple.finance_book == self.filters.finance_book) - if self.filters.get(party_type_field): - self.qb_selection_filter.append(self.ple.party == self.filters.get(party_type_field)) - if self.filters.get("party_type"): self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type) @@ -969,6 +963,20 @@ class ReceivablePayableReport(object): fieldtype="Link", options="Contact", ) + if self.filters.party_type == "Customer": + self.add_column( + _("Customer Name"), + fieldname="customer_name", + fieldtype="Link", + options="Customer", + ) + elif self.filters.party_type == "Supplier": + self.add_column( + _("Supplier Name"), + fieldname="supplier_name", + fieldtype="Link", + options="Supplier", + ) self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data") self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data") diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 0c7d931d2d5..b98916ee443 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -568,3 +568,40 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): row.account_currency, ], ) + + def test_usd_customer_filter(self): + filters = { + "company": self.company, + "party_type": "Customer", + "party": self.customer, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + + si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si.currency = "USD" + si.conversion_rate = 80 + si.debit_to = self.debtors_usd + si.save().submit() + name = si.name + + # check invoice grand total and invoiced column's value for 3 payment terms + report = execute(filters) + + expected = { + "voucher_type": si.doctype, + "voucher_no": si.name, + "party_account": self.debtors_usd, + "customer_name": self.customer, + "invoiced": 100.0, + "outstanding": 100.0, + "account_currency": "USD", + } + self.assertEqual(len(report[1]), 1) + report_output = report[1][0] + for field in expected: + with self.subTest(field=field): + self.assertEqual(report_output.get(field), expected.get(field)) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py index 28d0c20a918..7b1a9027780 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py @@ -81,7 +81,7 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin): self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) item.enable_deferred_revenue = 1 - item.deferred_revenue_account = self.deferred_revenue_account + item.item_defaults[0].deferred_revenue_account = self.deferred_revenue_account item.no_of_months = 3 item.save() @@ -150,7 +150,7 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin): self.create_item("_Test Office Desk", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) item.enable_deferred_expense = 1 - item.deferred_expense_account = self.deferred_expense_account + item.item_defaults[0].deferred_expense_account = self.deferred_expense_account item.no_of_months_exp = 3 item.save() diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 23403a4b15b..d670a356975 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -272,20 +272,19 @@ def get_conditions(filters): if match_conditions: conditions.append(match_conditions) - if filters.get("include_dimensions"): - accounting_dimensions = get_accounting_dimensions(as_list=False) + accounting_dimensions = get_accounting_dimensions(as_list=False) - if accounting_dimensions: - for dimension in accounting_dimensions: - if not dimension.disabled: - if filters.get(dimension.fieldname): - if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): - filters[dimension.fieldname] = get_dimension_with_children( - dimension.document_type, filters.get(dimension.fieldname) - ) - conditions.append("{0} in %({0})s".format(dimension.fieldname)) - else: - conditions.append("{0} in %({0})s".format(dimension.fieldname)) + if accounting_dimensions: + for dimension in accounting_dimensions: + if not dimension.disabled: + if filters.get(dimension.fieldname): + if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): + filters[dimension.fieldname] = get_dimension_with_children( + dimension.document_type, filters.get(dimension.fieldname) + ) + conditions.append("{0} in %({0})s".format(dimension.fieldname)) + else: + conditions.append("{0} in %({0})s".format(dimension.fieldname)) return "and {}".format(" and ".join(conditions)) if conditions else "" diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index e45c3adcb6d..6e233c802f0 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -99,6 +99,12 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "label": __("Include Default Book Entries"), "fieldtype": "Check", "default": 1 + }, + { + "fieldname": "show_net_values", + "label": __("Show net values in opening and closing columns"), + "fieldtype": "Check", + "default": 1 } ], "formatter": erpnext.financial_statements.formatter, diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 376571f0346..2a8aa0c202f 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -120,7 +120,9 @@ def get_data(filters): ignore_opening_entries=True, ) - calculate_values(accounts, gl_entries_by_account, opening_balances) + calculate_values( + accounts, gl_entries_by_account, opening_balances, filters.get("show_net_values") + ) accumulate_values_into_parents(accounts, accounts_by_name) data = prepare_data(accounts, filters, parent_children_map, company_currency) @@ -310,7 +312,7 @@ def get_opening_balance( return gle -def calculate_values(accounts, gl_entries_by_account, opening_balances): +def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net_values): init = { "opening_debit": 0.0, "opening_credit": 0.0, @@ -335,7 +337,8 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances): d["closing_debit"] = d["opening_debit"] + d["debit"] d["closing_credit"] = d["opening_credit"] + d["credit"] - prepare_opening_closing(d) + if show_net_values: + prepare_opening_closing(d) def calculate_total_row(accounts, company_currency): @@ -375,7 +378,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency): for d in accounts: # Prepare opening closing for group account - if parent_children_map.get(d.account): + if parent_children_map.get(d.account) and filters.get("show_net_values"): prepare_opening_closing(d) has_value = False diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 8f0ef869ad3..971932e415a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -560,6 +560,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): """ jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0] + # Update Advance Paid in SO/PO since they might be getting unlinked + if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"): + frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid() + if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0: # adjust the unreconciled balance amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) @@ -625,6 +629,13 @@ def update_reference_in_payment_entry( if d.voucher_detail_no: existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0] + + # Update Advance Paid in SO/PO since they are getting unlinked + if existing_row.get("reference_doctype") in ("Sales Order", "Purchase Order"): + frappe.get_doc( + existing_row.reference_doctype, existing_row.reference_name + ).set_total_advance_paid() + original_row = existing_row.as_dict().copy() existing_row.update(reference_details) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 645abf25a8f..bacd98bea77 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1171,6 +1171,7 @@ "depends_on": "is_internal_supplier", "fieldname": "set_from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set From Warehouse", "options": "Warehouse" }, @@ -1271,7 +1272,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-05-24 11:16:41.195340", + "modified": "2023-09-13 16:21:07.361700", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index c645b04e129..fe1b9702539 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -878,6 +878,7 @@ "depends_on": "eval:parent.is_internal_supplier", "fieldname": "from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "From Warehouse", "options": "Warehouse" }, @@ -902,7 +903,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-29 16:47:41.364387", + "modified": "2023-09-13 16:22:40.825092", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index b9fc344647b..7c7467e6f64 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -195,6 +195,9 @@ class TestSupplier(FrappeTestCase): def create_supplier(**args): args = frappe._dict(args) + if not args.supplier_name: + args.supplier_name = frappe.generate_hash() + if frappe.db.exists("Supplier", args.supplier_name): return frappe.get_doc("Supplier", args.supplier_name) @@ -202,6 +205,7 @@ def create_supplier(**args): { "doctype": "Supplier", "supplier_name": args.supplier_name, + "default_currency": args.default_currency, "supplier_group": args.supplier_group or "Services", "supplier_type": args.supplier_type or "Company", "tax_withholding_category": args.tax_withholding_category, diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py index 21241e08603..07187352eb7 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py @@ -7,7 +7,7 @@ import copy import frappe from frappe import _ from frappe.query_builder.functions import Coalesce, Sum -from frappe.utils import date_diff, flt, getdate +from frappe.utils import cint, date_diff, flt, getdate def execute(filters=None): @@ -47,8 +47,10 @@ def get_data(filters): mr.transaction_date.as_("date"), mr_item.schedule_date.as_("required_date"), mr_item.item_code.as_("item_code"), - Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"), - Coalesce(mr_item.stock_uom, "").as_("uom"), + Sum(Coalesce(mr_item.qty, 0)).as_("qty"), + Sum(Coalesce(mr_item.stock_qty, 0)).as_("stock_qty"), + Coalesce(mr_item.uom, "").as_("uom"), + Coalesce(mr_item.stock_uom, "").as_("stock_uom"), Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"), Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"), (Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))).as_( @@ -96,7 +98,7 @@ def get_conditions(filters, query, mr, mr_item): def update_qty_columns(row_to_update, data_row): - fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"] + fields = ["qty", "stock_qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"] for field in fields: row_to_update[field] += flt(data_row[field]) @@ -104,16 +106,20 @@ def update_qty_columns(row_to_update, data_row): def prepare_data(data, filters): """Prepare consolidated Report data and Chart data""" material_request_map, item_qty_map = {}, {} + precision = cint(frappe.db.get_default("float_precision")) or 2 for row in data: # item wise map for charts if not row["item_code"] in item_qty_map: item_qty_map[row["item_code"]] = { - "qty": row["qty"], - "ordered_qty": row["ordered_qty"], - "received_qty": row["received_qty"], - "qty_to_receive": row["qty_to_receive"], - "qty_to_order": row["qty_to_order"], + "qty": flt(row["stock_qty"], precision), + "stock_qty": flt(row["stock_qty"], precision), + "stock_uom": row["stock_uom"], + "uom": row["uom"], + "ordered_qty": flt(row["ordered_qty"], precision), + "received_qty": flt(row["received_qty"], precision), + "qty_to_receive": flt(row["qty_to_receive"], precision), + "qty_to_order": flt(row["qty_to_order"], precision), } else: item_entry = item_qty_map[row["item_code"]] @@ -200,21 +206,34 @@ def get_columns(filters): {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100}, {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 200}, { - "label": _("Stock UOM"), + "label": _("UOM"), "fieldname": "uom", "fieldtype": "Data", "width": 100, }, + { + "label": _("Stock UOM"), + "fieldname": "stock_uom", + "fieldtype": "Data", + "width": 100, + }, ] ) columns.extend( [ { - "label": _("Stock Qty"), + "label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", - "width": 120, + "width": 140, + "convertible": "qty", + }, + { + "label": _("Qty in Stock UOM"), + "fieldname": "stock_qty", + "fieldtype": "Float", + "width": 140, "convertible": "qty", }, { diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index df77e5260f5..bd2354e7452 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -168,7 +168,6 @@ class AccountsController(TransactionBase): self.validate_value("base_grand_total", ">=", 0) validate_return(self) - self.set_total_in_words() self.validate_all_documents_schedule() @@ -207,6 +206,8 @@ class AccountsController(TransactionBase): if self.doctype != "Material Request" and not self.ignore_pricing_rule: apply_pricing_rule_on_transaction(self) + self.set_total_in_words() + def before_cancel(self): validate_einvoice_fields(self) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 1de39967730..01990a3a268 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -162,10 +162,13 @@ class BuyingController(SubcontractingController): purchase_doc_field = ( "purchase_receipt" if self.doctype == "Purchase Receipt" else "purchase_invoice" ) - not_cancelled_asset = [ - d.name - for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1}) - ] + not_cancelled_asset = [] + if self.return_against: + not_cancelled_asset = [ + d.name + for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1}) + ] + if self.is_return and len(not_cancelled_asset): frappe.throw( _( diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index 019a5f9ee4f..8eebfdb83af 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -312,7 +312,7 @@ class TestWebsiteItem(unittest.TestCase): # check if stock details are fetched and item not in stock with warehouse set data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertFalse(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"][0][0], 0) + self.assertEqual(data.product_info["stock_qty"], 0) # disable show stock availability setup_e_commerce_settings({"show_stock_availability": 0}) @@ -355,7 +355,7 @@ class TestWebsiteItem(unittest.TestCase): # check if stock details are fetched and item is in stock with warehouse set data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertTrue(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"][0][0], 2) + self.assertEqual(data.product_info["stock_qty"], 2) # unset warehouse frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "") @@ -364,7 +364,7 @@ class TestWebsiteItem(unittest.TestCase): # (even though it has stock in some warehouse) data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertFalse(bool(data.product_info["in_stock"])) - self.assertFalse(bool(data.product_info["stock_qty"])) + self.assertFalse(data.product_info["stock_qty"]) # disable show stock availability setup_e_commerce_settings({"show_stock_availability": 0}) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js index 7b7193e833a..b6595cce8a9 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.js +++ b/erpnext/e_commerce/doctype/website_item/website_item.js @@ -5,12 +5,6 @@ frappe.ui.form.on('Website Item', { onload: (frm) => { // should never check Private frm.fields_dict["website_image"].df.is_private = 0; - - frm.set_query("website_warehouse", () => { - return { - filters: {"is_group": 0} - }; - }); }, refresh: (frm) => { diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json index 6556eabf4ab..6f551a0b42d 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -135,7 +135,7 @@ "fieldtype": "Column Break" }, { - "description": "Show Stock availability based on this warehouse.", + "description": "Show Stock availability based on this warehouse. If the parent warehouse is selected, then the system will display the consolidated available quantity of all child warehouses.", "fieldname": "website_warehouse", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -348,7 +348,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2022-09-30 04:01:52.090732", + "modified": "2023-09-12 14:19:22.822689", "modified_by": "Administrator", "module": "E-commerce", "name": "Website Item", diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py index e6a595a0344..975f87608a6 100644 --- a/erpnext/e_commerce/product_data_engine/query.py +++ b/erpnext/e_commerce/product_data_engine/query.py @@ -259,6 +259,10 @@ class ProductQuery: ) def get_stock_availability(self, item): + from erpnext.templates.pages.wishlist import ( + get_stock_availability as get_stock_availability_from_template, + ) + """Modify item object and add stock details.""" item.in_stock = False warehouse = item.get("website_warehouse") @@ -274,11 +278,7 @@ class ProductQuery: else: item.in_stock = True elif warehouse: - # stock item and has warehouse - actual_qty = frappe.db.get_value( - "Bin", {"item_code": item.item_code, "warehouse": item.get("website_warehouse")}, "actual_qty" - ) - item.in_stock = bool(flt(actual_qty)) + item.in_stock = get_stock_availability_from_template(item.item_code, warehouse) def get_cart_items(self): customer = get_customer(silent=True) diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py index 4f9088e8c08..030b439ae4b 100644 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ b/erpnext/e_commerce/shopping_cart/cart.py @@ -111,8 +111,8 @@ def place_order(): item_stock = get_web_item_qty_in_stock(item.item_code, "website_warehouse") if not cint(item_stock.in_stock): throw(_("{0} Not in Stock").format(item.item_code)) - if item.qty > item_stock.stock_qty[0][0]: - throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code)) + if item.qty > item_stock.stock_qty: + throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty, item.item_code)) sales_order.flags.ignore_permissions = True sales_order.insert() @@ -150,6 +150,10 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False): empty_card = True else: + warehouse = frappe.get_cached_value( + "Website Item", {"item_code": item_code}, "website_warehouse" + ) + quotation_items = quotation.get("items", {"item_code": item_code}) if not quotation_items: quotation.append( @@ -159,11 +163,13 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False): "item_code": item_code, "qty": qty, "additional_notes": additional_notes, + "warehouse": warehouse, }, ) else: quotation_items[0].qty = qty quotation_items[0].additional_notes = additional_notes + quotation_items[0].warehouse = warehouse apply_cart_settings(quotation=quotation) @@ -322,6 +328,10 @@ def decorate_quotation_doc(doc): fields = fields[2:] d.update(frappe.db.get_value("Website Item", {"item_code": item_code}, fields, as_dict=True)) + website_warehouse = frappe.get_cached_value( + "Website Item", {"item_code": item_code}, "website_warehouse" + ) + d.warehouse = website_warehouse return doc @@ -634,7 +644,6 @@ def get_applicable_shipping_rules(party=None, quotation=None): shipping_rules = get_shipping_rules(quotation) if shipping_rules: - rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label") # we need this in sorted order as per the position of the rule in the settings page return [[rule, rule] for rule in shipping_rules] diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py index 4466c457436..88356f5e909 100644 --- a/erpnext/e_commerce/variant_selector/utils.py +++ b/erpnext/e_commerce/variant_selector/utils.py @@ -104,6 +104,8 @@ def get_attributes_and_values(item_code): @frappe.whitelist(allow_guest=True) def get_next_attribute_and_values(item_code, selected_attributes): + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + """Find the count of Items that match the selected attributes. Also, find the attribute values that are not applicable for further searching. If less than equal to 10 items are found, return item_codes of those items. @@ -168,7 +170,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): product_info = None product_id = "" - website_warehouse = "" + warehouse = "" if exact_match or filtered_items: if exact_match and len(exact_match) == 1: product_id = exact_match[0] @@ -176,16 +178,19 @@ def get_next_attribute_and_values(item_code, selected_attributes): product_id = list(filtered_items)[0] if product_id: - website_warehouse = frappe.get_cached_value( + warehouse = frappe.get_cached_value( "Website Item", {"item_code": product_id}, "website_warehouse" ) available_qty = 0.0 - if website_warehouse: - available_qty = flt( - frappe.db.get_value( - "Bin", {"item_code": product_id, "warehouse": website_warehouse}, "actual_qty" - ) + if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: + warehouses = get_child_warehouses(warehouse) + else: + warehouses = [warehouse] if warehouse else [] + + for warehouse in warehouses: + available_qty += flt( + frappe.db.get_value("Bin", {"item_code": product_id, "warehouse": warehouse}, "actual_qty") ) return { diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.js b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.js index 82a2d802b80..53581339fae 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.js +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.js @@ -6,7 +6,14 @@ frappe.ui.form.on('Loan Repayment', { // refresh: function(frm) { - // } + // }, + + setup: function(frm) { + if (frappe.meta.has_field("Loan Repayment", "repay_from_salary")) { + frm.add_fetch("against_loan", "repay_from_salary", "repay_from_salary"); + } + }, + onload: function(frm) { frm.set_query('against_loan', function() { return { diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index f0e113b0e7b..0d16c7f0c50 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -80,6 +80,12 @@ class LoanRepayment(AccountsController): if amounts.get("due_date"): self.due_date = amounts.get("due_date") + if hasattr(self, "repay_from_salary") and hasattr(self, "payroll_payable_account"): + if self.repay_from_salary and not self.payroll_payable_account: + frappe.throw(_("Please set Payroll Payable Account in Loan Repayment")) + elif not self.repay_from_salary and self.payroll_payable_account: + self.repay_from_salary = 1 + def check_future_entries(self): future_repayment_date = frappe.db.get_value( "Loan Repayment", diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 09bf1d8a736..d07bf0fa66b 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -10,22 +10,25 @@ "warehouse", "item_name", "material_request_type", - "actual_qty", - "ordered_qty", + "quantity", "required_bom_qty", "column_break_4", - "quantity", + "schedule_date", "uom", "conversion_factor", - "projected_qty", - "reserved_qty_for_production", - "safety_stock", "item_details", "description", "min_order_qty", "section_break_8", "sales_order", - "requested_qty" + "bin_qty_section", + "actual_qty", + "requested_qty", + "reserved_qty_for_production", + "column_break_yhelv", + "ordered_qty", + "projected_qty", + "safety_stock" ], "fields": [ { @@ -65,7 +68,7 @@ "fieldtype": "Column Break" }, { - "columns": 1, + "columns": 2, "fieldname": "quantity", "fieldtype": "Float", "in_list_view": 1, @@ -80,12 +83,12 @@ "read_only": 1 }, { - "columns": 2, + "columns": 1, "default": "0", "fieldname": "actual_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Available Qty", + "label": "Qty In Stock", "no_copy": 1, "read_only": 1 }, @@ -176,11 +179,27 @@ "fieldtype": "Float", "label": "Conversion Factor", "read_only": 1 + }, + { + "columns": 1, + "fieldname": "schedule_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Required By" + }, + { + "fieldname": "bin_qty_section", + "fieldtype": "Section Break", + "label": "BIN Qty" + }, + { + "fieldname": "column_break_yhelv", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2023-05-03 12:43:29.895754", + "modified": "2023-09-12 12:09:08.358326", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 46c554c1e80..72438ddceea 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -476,6 +476,15 @@ frappe.ui.form.on("Material Request Plan Item", { } }) } + }, + + material_request_type(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + + if (row.from_warehouse && + row.material_request_type !== "Material Transfer") { + frappe.model.set_value(cdt, cdn, 'from_warehouse', ''); + } } }); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a494550423f..fcaae79d6c5 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -40,6 +40,12 @@ class ProductionPlan(Document): self._rename_temporary_references() validate_uom_is_integer(self, "stock_uom", "planned_qty") self.validate_sales_orders() + self.validate_material_request_type() + + def validate_material_request_type(self): + for row in self.get("mr_items"): + if row.from_warehouse and row.material_request_type != "Material Transfer": + row.from_warehouse = "" @frappe.whitelist() def validate_sales_orders(self, sales_order=None): @@ -725,7 +731,7 @@ class ProductionPlan(Document): # key for Sales Order:Material Request Type:Customer key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "") - schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) + schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days)) if not key in material_request_map: # make a new MR for the combination @@ -749,7 +755,9 @@ class ProductionPlan(Document): "items", { "item_code": item.item_code, - "from_warehouse": item.from_warehouse, + "from_warehouse": item.from_warehouse + if material_request_type == "Material Transfer" + else None, "qty": item.quantity, "schedule_date": schedule_date, "warehouse": item.warehouse, diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2871a29d768..78080645050 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2,7 +2,7 @@ # See license.txt import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_to_date, flt, now_datetime, nowdate +from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.production_plan import ( @@ -58,6 +58,9 @@ class TestProductionPlan(FrappeTestCase): pln = create_production_plan(item_code="Test Production Item 1") self.assertTrue(len(pln.mr_items), 2) + for row in pln.mr_items: + row.schedule_date = add_to_date(nowdate(), days=10) + pln.make_material_request() pln.reload() self.assertTrue(pln.status, "Material Requested") @@ -71,6 +74,13 @@ class TestProductionPlan(FrappeTestCase): self.assertTrue(len(material_requests), 2) + for row in material_requests: + mr_schedule_date = getdate(frappe.db.get_value("Material Request", row[0], "schedule_date")) + + expected_date = getdate(add_to_date(nowdate(), days=10)) + + self.assertEqual(mr_schedule_date, expected_date) + pln.make_work_order() work_orders = frappe.get_all( "Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1 @@ -1077,6 +1087,41 @@ class TestProductionPlan(FrappeTestCase): ) self.assertEqual(reserved_qty_after_mr, before_qty) + def test_from_warehouse_for_purchase_material_request(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + from erpnext.stock.utils import get_or_make_bin + + create_item("RM-TEST-123 For Purchase", valuation_rate=100) + bin_name = get_or_make_bin("RM-TEST-123 For Purchase", "_Test Warehouse - _TC") + t_warehouse = create_warehouse("_Test Store - _TC") + make_stock_entry( + item_code="Raw Material Item 1", + qty=5, + rate=100, + target=t_warehouse, + ) + + plan = create_production_plan(item_code="Test Production Item 1", do_not_save=1) + mr_items = get_items_for_material_requests( + plan.as_dict(), warehouses=[{"warehouse": t_warehouse}] + ) + + for d in mr_items: + plan.append("mr_items", d) + + plan.save() + + for row in plan.mr_items: + if row.material_request_type == "Material Transfer": + self.assertEqual(row.from_warehouse, t_warehouse) + + row.material_request_type = "Purchase" + + plan.save() + + for row in plan.mr_items: + self.assertFalse(row.from_warehouse) + def test_skip_available_qty_for_sub_assembly_items(self): from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cde484eb0fa..19f8dab9a17 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -339,5 +339,6 @@ erpnext.patches.v14_0.update_closing_balances #15-07-2023 execute:frappe.defaults.clear_default("fiscal_year") execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0) erpnext.patches.v14_0.correct_asset_value_if_je_with_workflow +erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/delete_education_doctypes.py b/erpnext/patches/v14_0/delete_education_doctypes.py index 76b2300fd2a..55b64eaabd8 100644 --- a/erpnext/patches/v14_0/delete_education_doctypes.py +++ b/erpnext/patches/v14_0/delete_education_doctypes.py @@ -46,6 +46,17 @@ def execute(): for doctype in doctypes: frappe.delete_doc("DocType", doctype, ignore_missing=True) + titles = [ + "Fees", + "Student Admission", + "Grant Application", + "Chapter", + "Certification Application", + ] + items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name") + for item in items: + frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True) + frappe.delete_doc("Module Def", "Education", ignore_missing=True, force=True) click.secho( diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py index 2c699e4a9f2..896a4409507 100644 --- a/erpnext/patches/v14_0/delete_healthcare_doctypes.py +++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py @@ -41,7 +41,7 @@ def execute(): for card in cards: frappe.delete_doc("Number Card", card, ignore_missing=True, force=True) - titles = ["Lab Test", "Prescription", "Patient Appointment"] + titles = ["Lab Test", "Prescription", "Patient Appointment", "Patient"] items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name") for item in items: frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True) diff --git a/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py b/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py new file mode 100644 index 00000000000..44b830babb2 --- /dev/null +++ b/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py @@ -0,0 +1,39 @@ +import frappe + + +def execute(): + try: + item_dict = get_deferred_accounts() + add_to_item_defaults(item_dict) + except Exception: + frappe.db.rollback() + frappe.log_error("Failed to migrate deferred accounts in Item Defaults.") + + +def get_deferred_accounts(): + item = frappe.qb.DocType("Item") + return ( + frappe.qb.from_(item) + .select(item.name, item.deferred_expense_account, item.deferred_revenue_account) + .where((item.enable_deferred_expense == 1) | (item.enable_deferred_revenue == 1)) + .run(as_dict=True) + ) + + +def add_to_item_defaults(item_dict): + for item in item_dict: + add_company_wise_item_default(item, "deferred_expense_account") + add_company_wise_item_default(item, "deferred_revenue_account") + + +def add_company_wise_item_default(item, account_type): + company = frappe.get_cached_value("Account", item[account_type], "company") + if company and item[account_type]: + item_defaults = frappe.get_cached_value("Item", item["name"], "item_defaults") + for item_row in item_defaults: + if item_row.company == company: + frappe.set_value("Item Default", item_row.name, account_type, item[account_type]) + break + else: + item_defaults.append({"company": company, account_type: item[account_type]}) + frappe.set_value("Item", item["name"], "item_defaults", item_defaults) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index d80133c988a..082ba915207 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -84,6 +84,7 @@ class Project(Document): issue=task_details.issue, is_group=task_details.is_group, color=task_details.color, + template_task=task_details.name, ) ).insert() @@ -103,9 +104,13 @@ class Project(Document): return date def dependency_mapping(self, template_tasks, project_tasks): - for template_task in template_tasks: - project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0] - project_task = frappe.get_doc("Task", project_task.name) + for project_task in project_tasks: + if project_task.get("template_task"): + template_task = frappe.get_doc("Task", project_task.template_task) + else: + template_task = list(filter(lambda x: x.subject == project_task.subject, template_tasks))[0] + template_task = frappe.get_doc("Task", template_task.name) + self.check_depends_on_value(template_task, project_task, project_tasks) self.check_for_parent_tasks(template_task, project_task, project_tasks) @@ -117,6 +122,7 @@ class Project(Document): filter(lambda x: x.subject == child_task_subject, project_tasks) ) if len(corresponding_project_task): + project_task.reload() # reload, as it might have been updated in the previous iteration project_task.append("depends_on", {"task": corresponding_project_task[0].name}) project_task.save() diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 8a599cef753..e49fecd1f47 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import unittest - import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.project_template.test_project_template import make_project_template @@ -15,7 +14,7 @@ test_records = frappe.get_test_records("Project") test_ignore = ["Sales Order"] -class TestProject(unittest.TestCase): +class TestProject(FrappeTestCase): def test_project_with_template_having_no_parent_and_depend_tasks(self): project_name = "Test Project with Template - No Parent and Dependend Tasks" frappe.db.sql(""" delete from tabTask where project = %s """, project_name) @@ -155,6 +154,50 @@ class TestProject(unittest.TestCase): so.reload() self.assertFalse(so.project) + def test_project_with_template_tasks_having_common_name(self): + # Step - 1: Create Template Parent Tasks + template_parent_task1 = create_task(subject="Parent Task - 1", is_template=1, is_group=1) + template_parent_task2 = create_task(subject="Parent Task - 2", is_template=1, is_group=1) + template_parent_task3 = create_task(subject="Parent Task - 1", is_template=1, is_group=1) + + # Step - 2: Create Template Child Tasks + template_task1 = create_task( + subject="Task - 1", is_template=1, parent_task=template_parent_task1.name + ) + template_task2 = create_task( + subject="Task - 2", is_template=1, parent_task=template_parent_task2.name + ) + template_task3 = create_task( + subject="Task - 1", is_template=1, parent_task=template_parent_task3.name + ) + + # Step - 3: Create Project Template + template_tasks = [ + template_parent_task1, + template_task1, + template_parent_task2, + template_task2, + template_parent_task3, + template_task3, + ] + project_template = make_project_template( + "Project template with common Task Subject", template_tasks + ) + + # Step - 4: Create Project against the Project Template + project = get_project("Project with common Task Subject", project_template) + project_tasks = frappe.get_all( + "Task", {"project": project.name}, ["subject", "parent_task", "is_group"] + ) + + # Test - 1: No. of Project Tasks should be equal to No. of Template Tasks + self.assertEquals(len(project_tasks), len(template_tasks)) + + # Test - 2: All child Project Tasks should have Parent Task linked + for pt in project_tasks: + if not pt.is_group: + self.assertIsNotNone(pt.parent_task) + def get_project(name, template): diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 141a99e612b..33a8799f96c 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -52,13 +52,15 @@ "company", "lft", "rgt", - "old_parent" + "old_parent", + "template_task" ], "fields": [ { "fieldname": "subject", "fieldtype": "Data", "in_global_search": 1, + "in_list_view": 1, "in_standard_filter": 1, "label": "Subject", "reqd": 1, @@ -138,6 +140,7 @@ "fieldname": "parent_task", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_list_view": 1, "label": "Parent Task", "options": "Task", "search_index": 1 @@ -382,6 +385,12 @@ "fieldtype": "Date", "label": "Completed On", "mandatory_depends_on": "eval: doc.status == \"Completed\"" + }, + { + "fieldname": "template_task", + "fieldtype": "Data", + "hidden": 1, + "label": "Template Task" } ], "icon": "fa fa-check", @@ -389,7 +398,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2022-06-23 16:58:47.005241", + "modified": "2023-09-06 13:52:05.861175", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b6b6e2e5ad6..fe24b18098a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -119,19 +119,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } }); - if(this.frm.fields_dict["items"].grid.get_field('batch_no')) { - this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) { + if(this.frm.fields_dict['items'].grid.get_field('batch_no')) { + this.frm.set_query('batch_no', 'items', function(doc, cdt, cdn) { return me.set_query_for_batch(doc, cdt, cdn); }); - - let batch_field = this.frm.get_docfield('items', 'batch_no'); - if (batch_field) { - batch_field.get_route_options_for_new_doc = (row) => { - return { - 'item': row.doc.item_code - } - }; - } } if( @@ -196,14 +187,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe }); } - let batch_no_field = this.frm.get_docfield("items", "batch_no"); - if (batch_no_field) { - batch_no_field.get_route_options_for_new_doc = function(row) { - return { - "item": row.doc.item_code - } - }; - } if (this.frm.fields_dict["items"].grid.get_field('blanket_order')) { this.frm.set_query("blanket_order", "items", function(doc, cdt, cdn) { @@ -257,6 +240,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } ]); } + + if(this.frm.fields_dict['items'].grid.get_field('batch_no')) { + let batch_field = this.frm.get_docfield('items', 'batch_no'); + if (batch_field) { + batch_field.get_route_options_for_new_doc = (row) => { + return { + 'item': row.doc.item_code + } + }; + } + } } is_return() { diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 608e23a8268..799ad555a52 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1998,6 +1998,61 @@ class TestSalesOrder(FrappeTestCase): self.assertEqual(len(dn.packed_items), 1) self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2") + @change_settings("Selling Settings", {"editable_bundle_item_rates": 1}) + def test_expired_rate_for_packed_item(self): + bundle = "_Test Product Bundle 1" + packed_item = "_Packed Item 1" + + # test Update Items with product bundle + for product_bundle in [bundle]: + if not frappe.db.exists("Item", product_bundle): + bundle_item = make_item(product_bundle, {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + for product_bundle in [packed_item]: + if not frappe.db.exists("Item", product_bundle): + make_item(product_bundle, {"is_stock_item": 0, "stock_uom": "Nos"}) + + make_product_bundle(bundle, [packed_item], 1) + + for scenario in [ + {"valid_upto": add_days(nowdate(), -1), "expected_rate": 0.0}, + {"valid_upto": add_days(nowdate(), 1), "expected_rate": 111.0}, + ]: + with self.subTest(scenario=scenario): + frappe.get_doc( + { + "doctype": "Item Price", + "item_code": packed_item, + "selling": 1, + "price_list": "_Test Price List", + "valid_from": add_days(nowdate(), -1), + "valid_upto": scenario.get("valid_upto"), + "price_list_rate": 111, + } + ).save() + + so = frappe.new_doc("Sales Order") + so.transaction_date = nowdate() + so.delivery_date = nowdate() + so.set_warehouse = "" + so.company = "_Test Company" + so.customer = "_Test Customer" + so.currency = "INR" + so.selling_price_list = "_Test Price List" + so.append("items", {"item_code": bundle, "qty": 1}) + so.save() + + self.assertEqual(len(so.items), 1) + self.assertEqual(len(so.packed_items), 1) + self.assertEqual(so.items[0].item_code, bundle) + self.assertEqual(so.packed_items[0].item_code, packed_item) + self.assertEqual(so.items[0].rate, scenario.get("expected_rate")) + self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate")) + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index ff0e1b66bf8..11f2cafc35d 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1252,6 +1252,7 @@ "depends_on": "eval: doc.is_internal_customer", "fieldname": "set_target_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "in_standard_filter": 1, "label": "Set Target Warehouse", "no_copy": 1, @@ -1399,7 +1400,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-06-16 14:58:55.066602", + "modified": "2023-09-04 14:15:28.363184", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 3a056500b54..ba6e247d2e4 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -138,6 +138,7 @@ class DeliveryNote(SellingController): self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("uom", "qty") self.validate_with_previous_doc() + self.validate_duplicate_serial_nos() from erpnext.stock.doctype.packed_item.packed_item import make_packing_list @@ -412,6 +413,21 @@ class DeliveryNote(SellingController): pluck="name", ) + def validate_duplicate_serial_nos(self): + serial_nos = [] + for item in self.items: + if not item.serial_no: + continue + + for serial_no in item.serial_no.split("\n"): + if serial_no in serial_nos: + frappe.throw( + _("Row #{0}: Serial No {1} is already selected.").format(item.idx, serial_no), + title=_("Duplicate Serial No"), + ) + else: + serial_nos.append(serial_no) + def update_billed_amount_based_on_so(so_detail, update_modified=True): from frappe.query_builder.functions import Sum diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py index e66c23324da..d4a574da73f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py @@ -11,10 +11,12 @@ def get_data(): }, "internal_links": { "Sales Order": ["items", "against_sales_order"], - "Sales Invoice": ["items", "against_sales_invoice"], "Material Request": ["items", "material_request"], "Purchase Order": ["items", "purchase_order"], }, + "internal_and_external_links": { + "Sales Invoice": ["items", "against_sales_invoice"], + }, "transactions": [ {"label": _("Related"), "items": ["Sales Invoice", "Packing Slip", "Delivery Trip"]}, {"label": _("Reference"), "items": ["Sales Order", "Shipment", "Quality Inspection"]}, diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 2565d1b76d1..2acfd84d944 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1211,6 +1211,38 @@ class TestDeliveryNote(FrappeTestCase): self.assertTrue(return_dn.docstatus == 1) + def test_duplicate_serial_no_in_delivery_note(self): + # Step - 1: Create Serial Item + serial_item = make_item( + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": frappe.generate_hash("", 10) + ".###", + } + ).name + + # Step - 2: Inward Stock + se = make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=4) + + # Step - 3: Create Delivery Note with Duplicare Serial Nos + serial_nos = se.items[0].serial_no.split("\n") + dn = create_delivery_note( + item_code=serial_item, + warehouse="_Test Warehouse - _TC", + qty=2, + do_not_save=True, + ) + dn.items[0].serial_no = "\n".join(serial_nos[:2]) + dn.append("items", dn.items[0].as_dict()) + + # Test - 1: ValidationError should be raised + self.assertRaises(frappe.ValidationError, dn.save) + + # Step - 4: Submit Delivery Note with unique Serial Nos + dn.items[1].serial_no = "\n".join(serial_nos[2:]) + dn.save() + dn.submit() + def tearDown(self): frappe.db.rollback() frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 86f9af25e7a..b306a41bb83 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -350,18 +350,20 @@ $.extend(erpnext.item, { } } - frm.fields_dict['deferred_revenue_account'].get_query = function() { + frm.fields_dict["item_defaults"].grid.get_field("deferred_revenue_account").get_query = function(doc, cdt, cdn) { return { filters: { + "company": locals[cdt][cdn].company, 'root_type': 'Liability', "is_group": 0 } } } - frm.fields_dict['deferred_expense_account'].get_query = function() { + frm.fields_dict["item_defaults"].grid.get_field("deferred_expense_account").get_query = function(doc, cdt, cdn) { return { filters: { + "company": locals[cdt][cdn].company, 'root_type': 'Asset', "is_group": 0 } diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 7f4ba032e86..7d0a387f43e 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -70,6 +70,13 @@ "variant_based_on", "attributes", "accounting", + "deferred_accounting_section", + "enable_deferred_expense", + "no_of_months_exp", + "column_break_9s9o", + "enable_deferred_revenue", + "no_of_months", + "section_break_avcp", "item_defaults", "purchasing_tab", "purchase_uom", @@ -85,10 +92,6 @@ "delivered_by_supplier", "column_break2", "supplier_items", - "deferred_expense_section", - "enable_deferred_expense", - "deferred_expense_account", - "no_of_months_exp", "foreign_trade_details", "country_of_origin", "column_break_59", @@ -99,10 +102,6 @@ "is_sales_item", "column_break3", "max_discount", - "deferred_revenue", - "enable_deferred_revenue", - "deferred_revenue_account", - "no_of_months", "customer_details", "customer_items", "item_tax_section_break", @@ -657,20 +656,6 @@ "oldfieldname": "max_discount", "oldfieldtype": "Currency" }, - { - "collapsible": 1, - "fieldname": "deferred_revenue", - "fieldtype": "Section Break", - "label": "Deferred Revenue" - }, - { - "depends_on": "enable_deferred_revenue", - "fieldname": "deferred_revenue_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Revenue Account", - "options": "Account" - }, { "default": "0", "fieldname": "enable_deferred_revenue", @@ -681,21 +666,7 @@ "depends_on": "enable_deferred_revenue", "fieldname": "no_of_months", "fieldtype": "Int", - "label": "No of Months" - }, - { - "collapsible": 1, - "fieldname": "deferred_expense_section", - "fieldtype": "Section Break", - "label": "Deferred Expense" - }, - { - "depends_on": "enable_deferred_expense", - "fieldname": "deferred_expense_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Expense Account", - "options": "Account" + "label": "No of Months (Revenue)" }, { "default": "0", @@ -904,6 +875,20 @@ "fieldname": "accounting", "fieldtype": "Tab Break", "label": "Accounting" + }, + { + "fieldname": "column_break_9s9o", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_avcp", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "deferred_accounting_section", + "fieldtype": "Section Break", + "label": "Deferred Accounting" } ], "icon": "fa fa-tag", @@ -912,7 +897,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-07-14 17:18:18.658942", + "modified": "2023-09-11 13:46:32.688051", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json index 042d398256a..28956612762 100644 --- a/erpnext/stock/doctype/item_default/item_default.json +++ b/erpnext/stock/doctype/item_default/item_default.json @@ -19,7 +19,11 @@ "selling_defaults", "selling_cost_center", "column_break_12", - "income_account" + "income_account", + "deferred_accounting_defaults_section", + "deferred_expense_account", + "column_break_kwad", + "deferred_revenue_account" ], "fields": [ { @@ -108,11 +112,34 @@ "fieldtype": "Link", "label": "Default Provisional Account", "options": "Account" + }, + { + "fieldname": "deferred_accounting_defaults_section", + "fieldtype": "Section Break", + "label": "Deferred Accounting Defaults" + }, + { + "depends_on": "eval: parent.enable_deferred_expense", + "fieldname": "deferred_expense_account", + "fieldtype": "Link", + "label": "Deferred Expense Account", + "options": "Account" + }, + { + "depends_on": "eval: parent.enable_deferred_revenue", + "fieldname": "deferred_revenue_account", + "fieldtype": "Link", + "label": "Deferred Revenue Account", + "options": "Account" + }, + { + "fieldname": "column_break_kwad", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2022-04-10 20:18:54.148195", + "modified": "2023-09-04 12:33:14.607267", "modified_by": "Administrator", "module": "Stock", "name": "Item Default", diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index b096b024f44..2632501b718 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -218,7 +218,8 @@ frappe.ui.form.on('Material Request', { plc_conversion_rate: 1, rate: item.rate, uom: item.uom, - conversion_factor: item.conversion_factor + conversion_factor: item.conversion_factor, + project: item.project, }, overwrite_warehouse: overwrite_warehouse }, diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index ffec57ca1df..25c765bbced 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -296,6 +296,7 @@ "depends_on": "eval:doc.material_request_type == 'Material Transfer'", "fieldname": "set_from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set Source Warehouse", "options": "Warehouse" }, @@ -356,7 +357,7 @@ "idx": 70, "is_submittable": 1, "links": [], - "modified": "2023-07-25 17:19:31.662662", + "modified": "2023-09-15 12:07:24.789471", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index dbd8de4fcb0..a9e9ad1a639 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -207,6 +207,9 @@ def update_packed_item_price_data(pi_row, item_data, doc): "conversion_rate": doc.get("conversion_rate"), } ) + if not row_data.get("transaction_date"): + row_data.update({"transaction_date": doc.get("transaction_date")}) + rate = get_price_list_rate(row_data, item_doc).get("price_list_rate") pi_row.rate = rate or item_data.get("valuation_rate") or 0.0 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 4f6c6364a47..1873efc711a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -605,7 +605,7 @@ class PurchaseReceipt(BuyingController): account=provisional_account, cost_center=item.cost_center, debit=0.0, - credit=multiplication_factor * item.amount, + credit=multiplication_factor * item.base_amount, remarks=remarks, against_account=expense_account, account_currency=credit_currency, @@ -619,7 +619,7 @@ class PurchaseReceipt(BuyingController): gl_entries=gl_entries, account=expense_account, cost_center=item.cost_center, - debit=multiplication_factor * item.amount, + debit=multiplication_factor * item.base_amount, credit=0.0, remarks=remarks, against_account=provisional_account, diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c9433cf5106..2f46809f49d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2024,6 +2024,49 @@ class TestPurchaseReceipt(FrappeTestCase): ste7.reload() self.assertEqual(ste7.items[0].valuation_rate, valuation_rate) + def test_purchase_receipt_provisional_accounting(self): + # Step - 1: Create Supplier with Default Currency as USD + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + + supplier = create_supplier(default_currency="USD") + + # Step - 2: Setup Company for Provisional Accounting + from erpnext.accounts.doctype.account.test_account import create_account + + provisional_account = create_account( + account_name="Provision Account", + parent_account="Current Liabilities - _TC", + company="_Test Company", + ) + company = frappe.get_doc("Company", "_Test Company") + company.enable_provisional_accounting_for_non_stock_items = 1 + company.default_provisional_account = provisional_account + company.save() + + # Step - 3: Create Non-Stock Item + item = make_item(properties={"is_stock_item": 0}) + + # Step - 4: Create Purchase Receipt + pr = make_purchase_receipt( + qty=2, + item_code=item.name, + company=company.name, + supplier=supplier.name, + currency=supplier.default_currency, + ) + + # Test - 1: Total and Base Total should not be the same as the currency is different + self.assertNotEqual(flt(pr.total, 2), flt(pr.base_total, 2)) + self.assertEqual(flt(pr.total * pr.conversion_rate, 2), flt(pr.base_total, 2)) + + # Test - 2: Sum of Debit or Credit should be equal to Purchase Receipt Base Total + amount = frappe.db.get_value("GL Entry", {"docstatus": 1, "voucher_no": pr.name}, ["sum(debit)"]) + expected_amount = pr.base_total + self.assertEqual(amount, expected_amount) + + company.enable_provisional_accounting_for_non_stock_items = 0 + company.save() + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index eea28791a9f..05fa2324dd4 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -6,6 +6,14 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type; frappe.ui.form.on("Quality Inspection", { setup: function(frm) { + frm.set_query("reference_name", function() { + return { + filters: { + "docstatus": ["!=", 2], + } + } + }); + frm.set_query("batch_no", function() { return { filters: { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index dd08ef4d1e3..6dd0a58645c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -101,15 +101,6 @@ frappe.ui.form.on('Stock Entry', { } }); - let batch_field = frm.get_docfield('items', 'batch_no'); - if (batch_field) { - batch_field.get_route_options_for_new_doc = (row) => { - return { - 'item': row.doc.item_code - } - }; - } - frm.add_fetch("bom_no", "inspection_required", "inspection_required"); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); @@ -345,6 +336,15 @@ frappe.ui.form.on('Stock Entry', { if(!check_should_not_attach_bom_items(frm.doc.bom_no)) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); } + + let batch_field = frm.get_docfield('items', 'batch_no'); + if (batch_field) { + batch_field.get_route_options_for_new_doc = (row) => { + return { + 'item': row.doc.item_code + } + }; + } }, get_items_from_transit_entry: function(frm) { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f3adefb3e74..13f484b1c85 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -729,7 +729,11 @@ def get_default_discount_account(args, item): def get_default_deferred_account(args, item, fieldname=None): if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): return ( - item.get(fieldname) + frappe.get_cached_value( + "Item Default", + {"parent": args.item_code, "company": args.get("company")}, + fieldname, + ) or args.get(fieldname) or frappe.get_cached_value("Company", args.company, "default_" + fieldname) ) @@ -1388,6 +1392,9 @@ def get_default_bom(item_code=None): @frappe.whitelist() def get_valuation_rate(item_code, company, warehouse=None): + if frappe.get_cached_value("Warehouse", warehouse, "is_group"): + return {"valuation_rate": 0.0} + item = get_item_defaults(item_code, company) item_group = get_item_group_defaults(item_code, company) brand = get_brand_defaults(item_code, company) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js index 31f389f236e..94e0b2dce3b 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js @@ -2,24 +2,24 @@ // For license information, please see license.txt /* eslint-disable */ -const DIFFERNCE_FIELD_NAMES = [ - "difference_in_qty", - "fifo_qty_diff", - "fifo_value_diff", - "fifo_valuation_diff", - "valuation_diff", - "fifo_difference_diff", - "diff_value_diff" +const DIFFERENCE_FIELD_NAMES = [ + 'difference_in_qty', + 'fifo_qty_diff', + 'fifo_value_diff', + 'fifo_valuation_diff', + 'valuation_diff', + 'fifo_difference_diff', + 'diff_value_diff' ]; -frappe.query_reports["Stock Ledger Invariant Check"] = { - "filters": [ +frappe.query_reports['Stock Ledger Invariant Check'] = { + 'filters': [ { - "fieldname": "item_code", - "fieldtype": "Link", - "label": "Item", - "mandatory": 1, - "options": "Item", + 'fieldname': 'item_code', + 'fieldtype': 'Link', + 'label': 'Item', + 'mandatory': 1, + 'options': 'Item', get_query: function() { return { filters: {is_stock_item: 1, has_serial_no: 0} @@ -27,18 +27,61 @@ frappe.query_reports["Stock Ledger Invariant Check"] = { } }, { - "fieldname": "warehouse", - "fieldtype": "Link", - "label": "Warehouse", - "mandatory": 1, - "options": "Warehouse", + 'fieldname': 'warehouse', + 'fieldtype': 'Link', + 'label': 'Warehouse', + 'mandatory': 1, + 'options': 'Warehouse', } ], + formatter (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { - value = "" + value + ""; + if (DIFFERENCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { + value = '' + value + ''; } return value; }, + + get_datatable_options(options) { + return Object.assign(options, { + checkboxColumn: true, + }); + }, + + onload(report) { + report.page.add_inner_button(__('Create Reposting Entry'), () => { + let message = ` ++ Reposting Entry will change the value of + accounts Stock In Hand, and Stock Expenses + in the Trial Balance report and will also change + the Balance Value in the Stock Balance report. +
+Are you sure you want to create a Reposting Entry?
++ Reposting Entries will change the value of + accounts Stock In Hand, and Stock Expenses + in the Trial Balance report and will also change + the Balance Value in the Stock Balance report. +
+Are you sure you want to create Reposting Entries?
+