Merge pull request #40137 from ljain112/fix-tax-breakup
fix: default taxable value for item not found in item list
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 295cd98..5258214 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -11,6 +11,10 @@
from frappe.model.document import Document
from frappe.utils import cstr
+from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
+ get_allowed_types_from_settings,
+)
+
class AccountingDimension(Document):
# begin: auto-generated types
@@ -106,6 +110,7 @@
doc_count = len(get_accounting_dimensions())
count = 0
+ repostable_doctypes = get_allowed_types_from_settings()
for doctype in doclist:
@@ -121,6 +126,7 @@
"options": doc.document_type,
"insert_after": insert_after_field,
"owner": "Administrator",
+ "allow_on_submit": 1 if doctype in repostable_doctypes else 0,
}
meta = frappe.get_meta(doctype, cached=False)
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 07fb5e8..72469ea 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -14,6 +14,25 @@
refresh: function(frm) {
erpnext.toggle_naming_series();
+ if (frm.doc.repost_required && frm.doc.docstatus===1) {
+ frm.set_intro(__("Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update."));
+ frm.add_custom_button(__('Repost Accounting Entries'),
+ () => {
+ frm.call({
+ doc: frm.doc,
+ method: 'repost_accounting_entries',
+ freeze: true,
+ freeze_message: __('Reposting...'),
+ callback: (r) => {
+ if (!r.exc) {
+ frappe.msgprint(__('Accounting Entries are reposted.'));
+ frm.refresh();
+ }
+ }
+ });
+ }).removeClass('btn-default').addClass('btn-warning');
+ }
+
if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = {
@@ -184,7 +203,6 @@
$.each(r, function(i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
frappe.model.set_value(row.doctype, row.name, "account", d.account)
- frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
});
refresh_field("accounts");
}
@@ -193,7 +211,6 @@
onload() {
this.load_defaults();
this.setup_queries();
- this.setup_balance_formatter();
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}
@@ -292,19 +309,6 @@
}
- setup_balance_formatter() {
- const formatter = function(value, df, options, doc) {
- var currency = frappe.meta.get_field_currency(df, doc);
- var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
- return "<div style='text-align: right'>"
- + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
- + " " + dr_or_cr
- + "</div>";
- };
- this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
- this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
- }
-
reference_name(doc, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn);
@@ -400,23 +404,22 @@
if(!d.account && d.party_type && d.party) {
if(!frm.doc.company) frappe.throw(__("Please select Company"));
return frm.call({
- method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance",
+ method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_currency",
child: d,
args: {
company: frm.doc.company,
party_type: d.party_type,
party: d.party,
- cost_center: d.cost_center
}
});
}
},
cost_center: function(frm, dt, dn) {
- erpnext.journal_entry.set_account_balance(frm, dt, dn);
+ erpnext.journal_entry.set_account_details(frm, dt, dn);
},
account: function(frm, dt, dn) {
- erpnext.journal_entry.set_account_balance(frm, dt, dn);
+ erpnext.journal_entry.set_account_details(frm, dt, dn);
},
debit_in_account_currency: function(frm, cdt, cdn) {
@@ -600,14 +603,14 @@
});
$.extend(erpnext.journal_entry, {
- set_account_balance: function(frm, dt, dn) {
+ set_account_details: function(frm, dt, dn) {
var d = locals[dt][dn];
if(d.account) {
if(!frm.doc.company) frappe.throw(__("Please select Company first"));
if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
return frappe.call({
- method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
+ method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type",
args: {
account: d.account,
date: frm.doc.posting_date,
@@ -615,7 +618,6 @@
debit: flt(d.debit_in_account_currency),
credit: flt(d.credit_in_account_currency),
exchange_rate: d.exchange_rate,
- cost_center: d.cost_center
},
callback: function(r) {
if(r.message) {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 906760e..a0fcaf9 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -64,7 +64,8 @@
"stock_entry",
"subscription_section",
"auto_repeat",
- "amended_from"
+ "amended_from",
+ "repost_required"
],
"fields": [
{
@@ -543,6 +544,15 @@
"label": "Is System Generated",
"no_copy": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "repost_required",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Repost Required",
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
@@ -558,6 +568,7 @@
}
],
"modified": "2023-11-23 12:11:04.128015",
+
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 18cf4ed..2a02cd7 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -13,6 +13,10 @@
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
get_party_account_based_on_invoice_discounting,
)
+from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
+ validate_docs_for_deferred_accounting,
+ validate_docs_for_voucher_types,
+)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
@@ -140,7 +144,6 @@
self.set_print_format_fields()
self.validate_credit_debit_note()
self.validate_empty_accounts_table()
- self.set_account_and_party_balance()
self.validate_inter_company_accounts()
self.validate_depr_entry_voucher_type()
@@ -150,6 +153,10 @@
if not self.title:
self.title = self.get_title()
+ def validate_for_repost(self):
+ validate_docs_for_voucher_types(["Journal Entry"])
+ validate_docs_for_deferred_accounting([self.name], [])
+
def submit(self):
if len(self.accounts) > 100:
msgprint(_("The task has been enqueued as a background job."), alert=True)
@@ -173,6 +180,15 @@
self.update_inter_company_jv()
self.update_invoice_discounting()
+ def on_update_after_submit(self):
+ if hasattr(self, "repost_required"):
+ self.needs_repost = self.check_if_fields_updated(
+ fields_to_check=[], child_tables={"accounts": []}
+ )
+ if self.needs_repost:
+ self.validate_for_repost()
+ self.db_set("repost_required", self.needs_repost)
+
def on_cancel(self):
# References for this Journal are removed on the `on_cancel` event in accounts_controller
super(JournalEntry, self).on_cancel()
@@ -1152,21 +1168,6 @@
if not self.get("accounts"):
frappe.throw(_("Accounts table cannot be blank."))
- def set_account_and_party_balance(self):
- account_balance = {}
- party_balance = {}
- for d in self.get("accounts"):
- if d.account not in account_balance:
- account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date)
-
- if (d.party_type, d.party) not in party_balance:
- party_balance[(d.party_type, d.party)] = get_balance_on(
- party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company
- )
-
- d.account_balance = account_balance[d.account]
- d.party_balance = party_balance[(d.party_type, d.party)]
-
@frappe.whitelist()
def get_default_bank_cash_account(
@@ -1334,8 +1335,6 @@
"account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"),
"account_currency": args.get("party_account_currency")
or get_account_currency(args.get("party_account")),
- "balance": get_balance_on(args.get("party_account")),
- "party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")),
"exchange_rate": exchange_rate,
args.get("amount_field_party"): args.get("amount"),
"is_advance": args.get("is_advance"),
@@ -1483,30 +1482,23 @@
@frappe.whitelist()
-def get_party_account_and_balance(company, party_type, party, cost_center=None):
+def get_party_account_and_currency(company, party_type, party):
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
account = get_party_account(party_type, party, company)
- account_balance = get_balance_on(account=account, cost_center=cost_center)
- party_balance = get_balance_on(
- party_type=party_type, party=party, company=company, cost_center=cost_center
- )
-
return {
"account": account,
- "balance": account_balance,
- "party_balance": party_balance,
"account_currency": frappe.get_cached_value("Account", account, "account_currency"),
}
@frappe.whitelist()
-def get_account_balance_and_party_type(
- account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None
+def get_account_details_and_party_type(
+ account, date, company, debit=None, credit=None, exchange_rate=None
):
- """Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
+ """Returns dict of account details and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
@@ -1526,7 +1518,6 @@
party_type = ""
grid_values = {
- "balance": get_balance_on(account, date, cost_center=cost_center),
"party_type": party_type,
"account_type": account_details.account_type,
"account_currency": account_details.account_currency or company_currency,
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index a6e920b..798d3bb 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -166,43 +166,37 @@
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.submit()
- gl_entries = frappe.db.sql(
- """select account, account_currency, debit, credit,
- debit_in_account_currency, credit_in_account_currency
- from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
- order by account asc""",
- jv.name,
- as_dict=1,
- )
+ self.voucher_no = jv.name
- self.assertTrue(gl_entries)
+ self.fields = [
+ "account",
+ "account_currency",
+ "debit",
+ "debit_in_account_currency",
+ "credit",
+ "credit_in_account_currency",
+ ]
- expected_values = {
- "_Test Bank USD - _TC": {
- "account_currency": "USD",
- "debit": 5000,
- "debit_in_account_currency": 100,
- "credit": 0,
- "credit_in_account_currency": 0,
- },
- "_Test Bank - _TC": {
+ self.expected_gle = [
+ {
+ "account": "_Test Bank - _TC",
"account_currency": "INR",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 5000,
},
- }
+ {
+ "account": "_Test Bank USD - _TC",
+ "account_currency": "USD",
+ "debit": 5000,
+ "debit_in_account_currency": 100,
+ "credit": 0,
+ "credit_in_account_currency": 0,
+ },
+ ]
- for field in (
- "account_currency",
- "debit",
- "debit_in_account_currency",
- "credit",
- "credit_in_account_currency",
- ):
- for i, gle in enumerate(gl_entries):
- self.assertEqual(expected_values[gle.account][field], gle[field])
+ self.check_gl_entries()
# cancel
jv.cancel()
@@ -228,43 +222,37 @@
rjv.posting_date = nowdate()
rjv.submit()
- gl_entries = frappe.db.sql(
- """select account, account_currency, debit, credit,
- debit_in_account_currency, credit_in_account_currency
- from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
- order by account asc""",
- rjv.name,
- as_dict=1,
- )
+ self.voucher_no = rjv.name
- self.assertTrue(gl_entries)
+ self.fields = [
+ "account",
+ "account_currency",
+ "debit",
+ "credit",
+ "debit_in_account_currency",
+ "credit_in_account_currency",
+ ]
- expected_values = {
- "_Test Bank USD - _TC": {
+ self.expected_gle = [
+ {
+ "account": "_Test Bank USD - _TC",
"account_currency": "USD",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 100,
},
- "Sales - _TC": {
+ {
+ "account": "Sales - _TC",
"account_currency": "INR",
"debit": 5000,
"debit_in_account_currency": 5000,
"credit": 0,
"credit_in_account_currency": 0,
},
- }
+ ]
- for field in (
- "account_currency",
- "debit",
- "debit_in_account_currency",
- "credit",
- "credit_in_account_currency",
- ):
- for i, gle in enumerate(gl_entries):
- self.assertEqual(expected_values[gle.account][field], gle[field])
+ self.check_gl_entries()
def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD
@@ -344,23 +332,25 @@
jv.insert()
jv.submit()
- expected_values = {
- "_Test Cash - _TC": {"cost_center": cost_center},
- "_Test Bank - _TC": {"cost_center": cost_center},
- }
+ self.voucher_no = jv.name
- gl_entries = frappe.db.sql(
- """select account, cost_center, debit, credit
- from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
- order by account asc""",
- jv.name,
- as_dict=1,
- )
+ self.fields = [
+ "account",
+ "cost_center",
+ ]
- self.assertTrue(gl_entries)
+ self.expected_gle = [
+ {
+ "account": "_Test Bank - _TC",
+ "cost_center": cost_center,
+ },
+ {
+ "account": "_Test Cash - _TC",
+ "cost_center": cost_center,
+ },
+ ]
- for gle in gl_entries:
- self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+ self.check_gl_entries()
def test_jv_with_project(self):
from erpnext.projects.doctype.project.test_project import make_project
@@ -387,23 +377,22 @@
jv.insert()
jv.submit()
- expected_values = {
- "_Test Cash - _TC": {"project": project_name},
- "_Test Bank - _TC": {"project": project_name},
- }
+ self.voucher_no = jv.name
- gl_entries = frappe.db.sql(
- """select account, project, debit, credit
- from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
- order by account asc""",
- jv.name,
- as_dict=1,
- )
+ self.fields = ["account", "project"]
- self.assertTrue(gl_entries)
+ self.expected_gle = [
+ {
+ "account": "_Test Bank - _TC",
+ "project": project_name,
+ },
+ {
+ "account": "_Test Cash - _TC",
+ "project": project_name,
+ },
+ ]
- for gle in gl_entries:
- self.assertEqual(expected_values[gle.account]["project"], gle.project)
+ self.check_gl_entries()
def test_jv_account_and_party_balance_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
@@ -426,6 +415,79 @@
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
self.assertEqual(expected_account_balance, account_balance)
+ def test_repost_accounting_entries(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+
+ # Configure Repost Accounting Ledger for JVs
+ settings = frappe.get_doc("Repost Accounting Ledger Settings")
+ if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]:
+ settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True})
+ settings.save()
+
+ # Create JV with defaut cost center - _Test Cost Center
+ jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
+ jv.multi_currency = 0
+ jv.submit()
+
+ # Check GL entries before reposting
+ self.voucher_no = jv.name
+
+ self.fields = [
+ "account",
+ "debit_in_account_currency",
+ "credit_in_account_currency",
+ "cost_center",
+ ]
+
+ self.expected_gle = [
+ {
+ "account": "_Test Bank - _TC",
+ "debit_in_account_currency": 0,
+ "credit_in_account_currency": 100,
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ {
+ "account": "_Test Cash - _TC",
+ "debit_in_account_currency": 100,
+ "credit_in_account_currency": 0,
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ ]
+
+ self.check_gl_entries()
+
+ # Change cost center for bank account - _Test Cost Center for BS Account
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+ jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC"
+ jv.save()
+
+ # Check if repost flag gets set on update after submit
+ self.assertTrue(jv.repost_required)
+ jv.repost_accounting_entries()
+
+ # Check GL entries after reposting
+ jv.load_from_db()
+ self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC"
+ self.check_gl_entries()
+
+ def check_gl_entries(self):
+ gl = frappe.qb.DocType("GL Entry")
+ query = frappe.qb.from_(gl)
+ for field in self.fields:
+ query = query.select(gl[field])
+
+ query = query.where(
+ (gl.voucher_type == "Journal Entry")
+ & (gl.voucher_no == self.voucher_no)
+ & (gl.is_cancelled == 0)
+ ).orderby(gl.account)
+
+ gl_entries = query.run(as_dict=True)
+
+ for i in range(len(self.expected_gle)):
+ for field in self.fields:
+ self.assertEqual(self.expected_gle[i][field], gl_entries[i][field])
+
def make_journal_entry(
account1,
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index 99e66e6..a61f8bf 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -9,12 +9,10 @@
"field_order": [
"account",
"account_type",
- "balance",
"col_break1",
"bank_account",
"party_type",
"party",
- "party_balance",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -64,17 +62,7 @@
"print_hide": 1
},
{
- "fieldname": "balance",
- "fieldtype": "Currency",
- "label": "Account Balance",
- "no_copy": 1,
- "oldfieldname": "balance",
- "oldfieldtype": "Data",
- "options": "account_currency",
- "print_hide": 1,
- "read_only": 1
- },
- {
+ "allow_on_submit": 1,
"default": ":Company",
"description": "If Income or Expense",
"fieldname": "cost_center",
@@ -108,14 +96,6 @@
"options": "party_type"
},
{
- "fieldname": "party_balance",
- "fieldtype": "Currency",
- "label": "Party Balance",
- "options": "account_currency",
- "print_hide": 1,
- "read_only": 1
- },
- {
"fieldname": "currency_section",
"fieldtype": "Section Break",
"label": "Currency"
@@ -223,6 +203,7 @@
"no_copy": 1
},
{
+ "allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
@@ -286,7 +267,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-12-03 23:21:22.205409",
+ "modified": "2024-02-05 01:10:50.224840",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 62e2181..832b116 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -149,7 +149,7 @@
},
refresh: function(frm) {
- erpnext.hide_company();
+ erpnext.hide_company(frm);
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
frm.events.show_general_ledger(frm);
diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
index ad2889d..b92579e 100644
--- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
+++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
@@ -654,7 +654,7 @@
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
- "fieldtype": "Small Text",
+ "fieldtype": "Text",
"hidden": 1,
"in_list_view": 1,
"label": "Serial No",
@@ -853,7 +853,7 @@
],
"istable": 1,
"links": [],
- "modified": "2024-02-04 16:36:25.665743",
+ "modified": "2024-02-25 15:50:17.140269",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",
diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
index 55a577b..c24db1d 100644
--- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
+++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
@@ -72,7 +72,7 @@
rate_with_margin: DF.Currency
sales_order: DF.Link | None
serial_and_batch_bundle: DF.Link | None
- serial_no: DF.SmallText | None
+ serial_no: DF.Text | None
service_end_date: DF.Date | None
service_start_date: DF.Date | None
service_stop_date: DF.Date | None
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index a48f5ea..d12a43c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -22,6 +22,8 @@
"is_paid",
"is_return",
"return_against",
+ "update_billed_amount_in_purchase_order",
+ "update_billed_amount_in_purchase_receipt",
"apply_tds",
"tax_withholding_category",
"amended_from",
@@ -413,6 +415,20 @@
"search_index": 1
},
{
+ "default": "0",
+ "depends_on": "eval: doc.is_return",
+ "fieldname": "update_billed_amount_in_purchase_order",
+ "fieldtype": "Check",
+ "label": "Update Billed Amount in Purchase Order"
+ },
+ {
+ "default": "1",
+ "depends_on": "eval: doc.is_return",
+ "fieldname": "update_billed_amount_in_purchase_receipt",
+ "fieldtype": "Check",
+ "label": "Update Billed Amount in Purchase Receipt"
+ },
+ {
"fieldname": "section_addresses",
"fieldtype": "Section Break",
"label": "Supplier Address"
@@ -1613,7 +1629,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2024-01-26 10:46:00.469053",
+ "modified": "2024-02-25 11:20:28.366808",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index c68ff83..e74807e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -193,6 +193,7 @@
supplied_items: DF.Table[PurchaseReceiptItemSupplied]
supplier: DF.Link
supplier_address: DF.Link | None
+ supplier_group: DF.Link | None
supplier_name: DF.Data | None
supplier_warehouse: DF.Link | None
tax_category: DF.Link | None
@@ -214,6 +215,8 @@
total_qty: DF.Float
total_taxes_and_charges: DF.Currency
unrealized_profit_loss_account: DF.Link | None
+ update_billed_amount_in_purchase_order: DF.Check
+ update_billed_amount_in_purchase_receipt: DF.Check
update_stock: DF.Check
use_company_roundoff_cost_center: DF.Check
use_transaction_date_exchange_rate: DF.Check
@@ -679,6 +682,11 @@
super(PurchaseInvoice, self).on_submit()
self.check_prev_docstatus()
+
+ if self.is_return and not self.update_billed_amount_in_purchase_order:
+ # NOTE status updating bypassed for is_return
+ self.status_updater = []
+
self.update_status_updater_args()
self.update_prevdoc_status()
@@ -724,6 +732,7 @@
"cash_bank_account",
"write_off_account",
"unrealized_profit_loss_account",
+ "is_opening",
]
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
@@ -1426,6 +1435,10 @@
self.check_on_hold_or_closed_status()
+ if self.is_return and not self.update_billed_amount_in_purchase_order:
+ # NOTE status updating bypassed for is_return
+ self.status_updater = []
+
self.update_status_updater_args()
self.update_prevdoc_status()
@@ -1520,6 +1533,9 @@
frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}").format(pi))
def update_billing_status_in_pr(self, update_modified=True):
+ if self.is_return and not self.update_billed_amount_in_purchase_receipt:
+ return
+
updated_pr = []
po_details = []
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 3a930e0..3352e0d 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -447,7 +447,11 @@
# Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO
if self.update_stock == 1:
- self.make_bundle_using_old_serial_batch_fields()
+ for table_name in ["items", "packed_items"]:
+ if not self.get(table_name):
+ continue
+
+ self.make_bundle_using_old_serial_batch_fields(table_name)
self.update_stock_ledger()
# this sequence because outstanding may get -ve
@@ -723,6 +727,7 @@
"write_off_account",
"loyalty_redemption_account",
"unrealized_profit_loss_account",
+ "is_opening",
]
child_tables = {
"items": ("income_account", "expense_account", "discount_account"),
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index d06c786..e7536e9 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -625,7 +625,7 @@
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
- "fieldtype": "Small Text",
+ "fieldtype": "Text",
"label": "Serial No",
"oldfieldname": "serial_no",
"oldfieldtype": "Small Text"
@@ -926,7 +926,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2024-02-04 11:52:16.106541",
+ "modified": "2024-02-25 15:56:44.828634",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
index c71d08e..9be1b42 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
@@ -75,7 +75,7 @@
sales_invoice_item: DF.Data | None
sales_order: DF.Link | None
serial_and_batch_bundle: DF.Link | None
- serial_no: DF.SmallText | None
+ serial_no: DF.Text | None
service_end_date: DF.Date | None
service_start_date: DF.Date | None
service_stop_date: DF.Date | None
diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
index 8eda441..f79a848 100644
--- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
+++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
@@ -107,7 +107,7 @@
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
- "fieldtype": "Small Text",
+ "fieldtype": "Text",
"hidden": 1,
"label": "Serial No",
"print_hide": 1
@@ -178,7 +178,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-02-04 16:41:09.239762",
+ "modified": "2024-02-25 15:57:35.007501",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Stock Item",
diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py
index d2b075c..0f06cc7 100644
--- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py
+++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py
@@ -24,7 +24,7 @@
parentfield: DF.Data
parenttype: DF.Data
serial_and_batch_bundle: DF.Link | None
- serial_no: DF.SmallText | None
+ serial_no: DF.Text | None
stock_qty: DF.Float
stock_uom: DF.Link
use_serial_batch_fields: DF.Check
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index a30de68..d262783 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -1072,6 +1072,38 @@
frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated"
)
+ def test_po_billed_amount_against_return_entry(self):
+ from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note
+
+ # Create a Purchase Order and Fully Bill it
+ po = create_purchase_order()
+ pi = make_pi_from_po(po.name)
+ pi.insert()
+ pi.submit()
+
+ # Debit Note - 50% Qty & enable updating PO billed amount
+ pi_return = make_debit_note(pi.name)
+ pi_return.items[0].qty = -5
+ pi_return.update_billed_amount_in_purchase_order = 1
+ pi_return.submit()
+
+ # Check if the billed amount reduced
+ po.reload()
+ self.assertEqual(po.per_billed, 50)
+
+ pi_return.reload()
+ pi_return.cancel()
+
+ # Debit Note - 50% Qty & disable updating PO billed amount
+ pi_return = make_debit_note(pi.name)
+ pi_return.items[0].qty = -5
+ pi_return.update_billed_amount_in_purchase_order = 0
+ pi_return.submit()
+
+ # Check if the billed amount stayed the same
+ po.reload()
+ self.assertEqual(po.per_billed, 100)
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index eec996c..1a9dbab 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -206,10 +206,30 @@
contact.save(ignore_permissions=True)
+ if rfq_supplier.supplier:
+ self.update_user_in_supplier(rfq_supplier.supplier, user.name)
+
if not rfq_supplier.contact:
# return contact to later update, RFQ supplier row's contact
return contact.name
+ def update_user_in_supplier(self, supplier, user):
+ """Update user in Supplier."""
+ if not frappe.db.exists("Portal User", {"parent": supplier, "user": user}):
+ supplier_doc = frappe.get_doc("Supplier", supplier)
+ supplier_doc.append(
+ "portal_users",
+ {
+ "user": user,
+ },
+ )
+
+ supplier_doc.flags.ignore_validate = True
+ supplier_doc.flags.ignore_mandatory = True
+ supplier_doc.flags.ignore_permissions = True
+
+ supplier_doc.save()
+
def create_user(self, rfq_supplier, link):
user = frappe.get_doc(
{
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index 05a604f..fe1466c 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -149,6 +149,33 @@
get_pdf(rfq.name, rfq.get("suppliers")[0].supplier)
self.assertEqual(frappe.local.response.type, "pdf")
+ def test_portal_user_with_new_supplier(self):
+ supplier_doc = frappe.get_doc(
+ {
+ "doctype": "Supplier",
+ "supplier_name": "Test Supplier for RFQ",
+ "supplier_group": "_Test Supplier Group",
+ }
+ ).insert()
+
+ self.assertFalse(supplier_doc.portal_users)
+
+ rfq = make_request_for_quotation(
+ supplier_data=[
+ {
+ "supplier": supplier_doc.name,
+ "supplier_name": supplier_doc.supplier_name,
+ "email_id": "123_testrfquser@example.com",
+ }
+ ],
+ do_not_submit=True,
+ )
+ for rfq_supplier in rfq.suppliers:
+ rfq.update_supplier_contact(rfq_supplier, rfq.get_link())
+
+ supplier_doc.reload()
+ self.assertTrue(supplier_doc.portal_users[0].user)
+
def make_request_for_quotation(**args) -> "RequestforQuotation":
"""
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 32a5a61..9896cad 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -2453,27 +2453,20 @@
doc_before_update = self.get_doc_before_save()
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
- # Check if opening entry check updated
- needs_repost = doc_before_update.get("is_opening") != self.is_opening
+ # Parent Level Accounts excluding party account
+ fields_to_check += accounting_dimensions
+ for field in fields_to_check:
+ if doc_before_update.get(field) != self.get(field):
+ return True
- if not needs_repost:
- # Parent Level Accounts excluding party account
- fields_to_check += accounting_dimensions
- for field in fields_to_check:
- if doc_before_update.get(field) != self.get(field):
- needs_repost = 1
- break
+ # Check for child tables
+ for table in child_tables:
+ if check_if_child_table_updated(
+ doc_before_update.get(table), self.get(table), child_tables[table]
+ ):
+ return True
- if not needs_repost:
- # Check for child tables
- for table in child_tables:
- needs_repost = check_if_child_table_updated(
- doc_before_update.get(table), self.get(table), child_tables[table]
- )
- if needs_repost:
- break
-
- return needs_repost
+ return False
@frappe.whitelist()
def repost_accounting_entries(self):
@@ -3502,15 +3495,12 @@
def check_if_child_table_updated(
child_table_before_update, child_table_after_update, fields_to_check
):
- accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
- # Check if any field affecting accounting entry is altered
- for index, item in enumerate(child_table_after_update):
- for field in fields_to_check:
- if child_table_before_update[index].get(field) != item.get(field):
- return True
+ fields_to_check = list(fields_to_check) + get_accounting_dimensions() + ["cost_center", "project"]
- for dimension in accounting_dimensions:
- if child_table_before_update[index].get(dimension) != item.get(dimension):
+ # Check if any field affecting accounting entry is altered
+ for index, item in enumerate(child_table_before_update):
+ for field in fields_to_check:
+ if child_table_after_update[index].get(field) != item.get(field):
return True
return False
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index c8d40ed..359d721 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -704,6 +704,9 @@
def get_serial_and_batch_bundle(child, parent):
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
+ if child.get("use_serial_batch_fields"):
+ return
+
if not frappe.db.get_single_value(
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
):
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index fdbfd10..a67fbdc 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -158,7 +158,7 @@
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)
- def make_bundle_using_old_serial_batch_fields(self):
+ def make_bundle_using_old_serial_batch_fields(self, table_name=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
@@ -169,7 +169,9 @@
if frappe.flags.in_test and frappe.flags.use_serial_and_batch_fields:
return
- table_name = "items"
+ if not table_name:
+ table_name = "items"
+
if self.doctype == "Asset Capitalization":
table_name = "stock_items"
@@ -192,6 +194,12 @@
qty = row.qty
type_of_transaction = "Inward"
warehouse = row.warehouse
+ elif table_name == "packed_items":
+ qty = row.qty
+ warehouse = row.warehouse
+ type_of_transaction = "Outward"
+ if self.is_return:
+ type_of_transaction = "Inward"
else:
qty = row.stock_qty if self.doctype != "Stock Entry" else row.transfer_qty
type_of_transaction = get_type_of_transaction(self, row)
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index ec7d71e..dc5a329 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -10,7 +10,6 @@
from frappe.utils import add_months, formatdate, getdate, sbool, today
from plaid.errors import ItemError
-from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
@@ -90,9 +89,15 @@
bank = json.loads(bank)
result = []
- default_gl_account = get_default_bank_cash_account(company, "Bank")
- if not default_gl_account:
- frappe.throw(_("Please setup a default bank account for company {0}").format(company))
+ parent_gl_account = frappe.db.get_all(
+ "Account", {"company": company, "account_type": "Bank", "is_group": 1, "disabled": 0}
+ )
+ if not parent_gl_account:
+ frappe.throw(
+ _(
+ "Please setup and enable a group account with the Account Type - {0} for the company {1}"
+ ).format(frappe.bold("Bank"), company)
+ )
for account in response["accounts"]:
acc_type = frappe.db.get_value("Bank Account Type", account["type"])
@@ -108,11 +113,22 @@
if not existing_bank_account:
try:
+ gl_account = frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_name": account["name"] + " - " + response["institution"]["name"],
+ "parent_account": parent_gl_account[0].name,
+ "account_type": "Bank",
+ "company": company,
+ }
+ )
+ gl_account.insert(ignore_if_duplicate=True)
+
new_account = frappe.get_doc(
{
"doctype": "Bank Account",
"bank": bank["bank_name"],
- "account": default_gl_account.account,
+ "account": gl_account.name,
"account_name": account["name"],
"account_type": account.get("type", ""),
"account_subtype": account.get("subtype", ""),
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
index 6716853..666def3 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
@@ -7,7 +7,6 @@
import frappe
from frappe.utils.response import json_handler
-from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import (
add_account_subtype,
add_account_type,
@@ -72,14 +71,6 @@
bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler)
company = frappe.db.get_single_value("Global Defaults", "default_company")
- if frappe.db.get_value("Company", company, "default_bank_account") is None:
- frappe.db.set_value(
- "Company",
- company,
- "default_bank_account",
- get_default_bank_cash_account(company, "Cash").get("account"),
- )
-
add_bank_accounts(bank_accounts, bank, company)
transactions = {
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
index 7b26a14..716af85 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
@@ -18,7 +18,7 @@
},
refresh: function(frm) {
- erpnext.hide_company();
+ erpnext.hide_company(frm);
if (frm.doc.customer && frm.doc.docstatus === 1 && frm.doc.to_date > frappe.datetime.get_today()) {
frm.add_custom_button(__("Sales Order"), function() {
frappe.model.open_mapped_doc({
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index c9c474d..667ece2 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -518,6 +518,12 @@
}
});
+frappe.ui.form.on("Production Plan Sub Assembly Item", {
+ fg_warehouse(frm, cdt, cdn) {
+ erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "sub_assembly_items", "fg_warehouse");
+ },
+})
+
frappe.tour['Production Plan'] = [
{
fieldname: "get_items_from",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 54c3893..84bbad5 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -421,9 +421,11 @@
"fieldtype": "Column Break"
},
{
+ "description": "When a parent warehouse is chosen, the system conducts stock checks against the associated child warehouses",
"fieldname": "sub_assembly_warehouse",
"fieldtype": "Link",
"label": "Sub Assembly Warehouse",
+ "mandatory_depends_on": "eval:doc.skip_available_sub_assembly_item === 1",
"options": "Warehouse"
},
{
@@ -437,7 +439,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-02-11 15:42:47.642481",
+ "modified": "2024-02-27 13:34:20.692211",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 517b2b0..c852f84 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -894,8 +894,8 @@
sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items:
- if self.skip_available_sub_assembly_item and not row.warehouse:
- frappe.throw(_("Row #{0}: Please select the FG Warehouse in Assembly Items").format(row.idx))
+ if self.skip_available_sub_assembly_item and not self.sub_assembly_warehouse:
+ frappe.throw(_("Row #{0}: Please select the Sub Assembly Warehouse").format(row.idx))
if not row.item_code:
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
@@ -905,15 +905,24 @@
bom_data = []
- warehouse = (
- (self.sub_assembly_warehouse or row.warehouse)
- if self.skip_available_sub_assembly_item
- else None
- )
+ warehouse = (self.sub_assembly_warehouse) if self.skip_available_sub_assembly_item else None
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
sub_assembly_items_store.extend(bom_data)
+ if not sub_assembly_items_store and self.skip_available_sub_assembly_item:
+ message = (
+ _(
+ "As there are sufficient Sub Assembly Items, Work Order is not required for Warehouse {0}."
+ ).format(self.sub_assembly_warehouse)
+ + "<br><br>"
+ )
+ message += _(
+ "If you still want to proceed, please disable 'Skip Available Sub Assembly Items' checkbox."
+ )
+
+ frappe.msgprint(message, title=_("Note"))
+
if self.combine_sub_items:
# Combine subassembly items
sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
@@ -926,15 +935,19 @@
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
"Modify bom_data, set additional details."
+ is_group_warehouse = frappe.db.get_value("Warehouse", self.sub_assembly_warehouse, "is_group")
+
for data in bom_data:
data.qty = data.stock_qty
data.production_plan_item = row.name
- data.fg_warehouse = self.sub_assembly_warehouse or row.warehouse
data.schedule_date = row.planned_start_date
data.type_of_manufacturing = manufacturing_type or (
"Subcontract" if data.is_sub_contracted_item else "In House"
)
+ if not is_group_warehouse:
+ data.fg_warehouse = self.sub_assembly_warehouse
+
def set_default_supplier_for_subcontracting_order(self):
items = [
d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract"
@@ -1478,7 +1491,7 @@
so_item_details = frappe._dict()
sub_assembly_items = {}
- if doc.get("skip_available_sub_assembly_item"):
+ if doc.get("skip_available_sub_assembly_item") and doc.get("sub_assembly_items"):
for d in doc.get("sub_assembly_items"):
sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty"))
@@ -1690,34 +1703,37 @@
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
if warehouse:
- bin_dict = get_bin_details(d, company, for_warehouse=warehouse)
+ bin_details = get_bin_details(d, company, for_warehouse=warehouse)
- if bin_dict and bin_dict[0].projected_qty > 0:
- if bin_dict[0].projected_qty > stock_qty:
- continue
- else:
- stock_qty = stock_qty - bin_dict[0].projected_qty
+ for _bin_dict in bin_details:
+ if _bin_dict.projected_qty > 0:
+ if _bin_dict.projected_qty > stock_qty:
+ stock_qty = 0
+ continue
+ else:
+ stock_qty = stock_qty - _bin_dict.projected_qty
- bom_data.append(
- frappe._dict(
- {
- "parent_item_code": parent_item_code,
- "description": d.description,
- "production_item": d.item_code,
- "item_name": d.item_name,
- "stock_uom": d.stock_uom,
- "uom": d.stock_uom,
- "bom_no": d.value,
- "is_sub_contracted_item": d.is_sub_contracted_item,
- "bom_level": indent,
- "indent": indent,
- "stock_qty": stock_qty,
- }
+ if stock_qty > 0:
+ bom_data.append(
+ frappe._dict(
+ {
+ "parent_item_code": parent_item_code,
+ "description": d.description,
+ "production_item": d.item_code,
+ "item_name": d.item_name,
+ "stock_uom": d.stock_uom,
+ "uom": d.stock_uom,
+ "bom_no": d.value,
+ "is_sub_contracted_item": d.is_sub_contracted_item,
+ "bom_level": indent,
+ "indent": indent,
+ "stock_qty": stock_qty,
+ }
+ )
)
- )
- if d.value:
- get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
+ if d.value:
+ get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
def set_default_warehouses(row, default_warehouses):
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 53537f9..0bf3705 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1205,6 +1205,7 @@
ignore_existing_ordered_qty=1,
do_not_submit=1,
skip_available_sub_assembly_item=1,
+ sub_assembly_warehouse="_Test Warehouse - _TC",
warehouse="_Test Warehouse - _TC",
)
@@ -1338,6 +1339,7 @@
ignore_existing_ordered_qty=1,
do_not_submit=1,
skip_available_sub_assembly_item=1,
+ sub_assembly_warehouse="_Test Warehouse - _TC",
warehouse="_Test Warehouse - _TC",
)
@@ -1590,6 +1592,48 @@
for row in work_orders:
self.assertEqual(row.qty, wo_qty[row.name])
+ def test_parent_warehouse_for_sub_assembly_items(self):
+ from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ parent_warehouse = "_Test Warehouse Group - _TC"
+ sub_warehouse = create_warehouse("Sub Warehouse", company="_Test Company")
+
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+ sf_item = make_item(properties={"is_stock_item": 1}).name
+ rm_item = make_item(properties={"is_stock_item": 1}).name
+
+ bom_tree = {fg_item: {sf_item: {rm_item: {}}}}
+ create_nested_bom(bom_tree, prefix="")
+
+ pln = create_production_plan(
+ item_code=fg_item,
+ planned_qty=10,
+ warehouse="_Test Warehouse - _TC",
+ sub_assembly_warehouse=parent_warehouse,
+ skip_available_sub_assembly_item=1,
+ do_not_submit=1,
+ skip_getting_mr_items=1,
+ )
+
+ pln.get_sub_assembly_items()
+
+ for row in pln.sub_assembly_items:
+ self.assertFalse(row.fg_warehouse)
+ self.assertEqual(row.production_item, sf_item)
+ self.assertEqual(row.qty, 10.0)
+
+ make_stock_entry(item_code=sf_item, qty=5, target=sub_warehouse, rate=100)
+
+ pln.sub_assembly_items = []
+ pln.get_sub_assembly_items()
+
+ self.assertEqual(pln.sub_assembly_warehouse, parent_warehouse)
+ for row in pln.sub_assembly_items:
+ self.assertFalse(row.fg_warehouse)
+ self.assertEqual(row.production_item, sf_item)
+ self.assertEqual(row.qty, 5.0)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
index 0688278..78a3897 100644
--- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
@@ -11,6 +11,7 @@
"bom_no",
"column_break_6",
"planned_qty",
+ "stock_uom",
"warehouse",
"planned_start_date",
"section_break_9",
@@ -18,7 +19,6 @@
"ordered_qty",
"column_break_17",
"description",
- "stock_uom",
"produced_qty",
"reference_section",
"sales_order",
@@ -65,6 +65,7 @@
"width": "100px"
},
{
+ "columns": 1,
"fieldname": "planned_qty",
"fieldtype": "Float",
"in_list_view": 1,
@@ -80,6 +81,7 @@
"fieldtype": "Column Break"
},
{
+ "columns": 2,
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
@@ -141,8 +143,10 @@
"width": "200px"
},
{
+ "columns": 1,
"fieldname": "stock_uom",
"fieldtype": "Link",
+ "in_list_view": 1,
"label": "UOM",
"oldfieldname": "stock_uom",
"oldfieldtype": "Data",
@@ -216,7 +220,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-11-25 14:15:40.061514",
+ "modified": "2024-02-27 13:24:43.571844",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Item",
diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
index aff740b..7965965 100644
--- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
@@ -101,7 +101,6 @@
"columns": 1,
"fieldname": "bom_level",
"fieldtype": "Int",
- "in_list_view": 1,
"label": "Level (BOM)",
"read_only": 1
},
@@ -149,8 +148,10 @@
"label": "Indent"
},
{
+ "columns": 2,
"fieldname": "fg_warehouse",
"fieldtype": "Link",
+ "in_list_view": 1,
"label": "Target Warehouse",
"options": "Warehouse"
},
@@ -170,6 +171,7 @@
"options": "Supplier"
},
{
+ "columns": 1,
"fieldname": "schedule_date",
"fieldtype": "Datetime",
"in_list_view": 1,
@@ -207,7 +209,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-03 13:33:42.959387",
+ "modified": "2024-02-27 13:45:17.422435",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 5d30323..815b01d 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -354,6 +354,7 @@
execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
erpnext.patches.v14_0.update_total_asset_cost_field
erpnext.patches.v15_0.create_advance_payment_status
+erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py
new file mode 100644
index 0000000..e75610d
--- /dev/null
+++ b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py
@@ -0,0 +1,14 @@
+import frappe
+
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+ get_accounting_dimensions,
+)
+from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
+ get_allowed_types_from_settings,
+)
+
+
+def execute():
+ for dt in get_allowed_types_from_settings():
+ for dimension in get_accounting_dimensions():
+ frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 775bdb4..faaa38d 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -397,7 +397,7 @@
refresh() {
erpnext.toggle_naming_series();
- erpnext.hide_company();
+ erpnext.hide_company(this.frm);
this.set_dynamic_labels();
this.setup_sms();
this.setup_quality_inspection();
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index aee761f..61b8f35 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -26,14 +26,14 @@
}
},
- hide_company: function() {
- if(cur_frm.fields_dict.company) {
+ hide_company: function(frm) {
+ if(frm?.fields_dict.company) {
var companies = Object.keys(locals[":Company"] || {});
if(companies.length === 1) {
- if(!cur_frm.doc.company) cur_frm.set_value("company", companies[0]);
- cur_frm.toggle_display("company", false);
+ if(!frm.doc.company) frm.set_value("company", companies[0]);
+ frm.toggle_display("company", false);
} else if(erpnext.last_selected_company) {
- if(!cur_frm.doc.company) cur_frm.set_value("company", erpnext.last_selected_company);
+ if(!frm.doc.company) frm.set_value("company", erpnext.last_selected_company);
}
}
},
diff --git a/erpnext/selling/doctype/installation_note/installation_note.json b/erpnext/selling/doctype/installation_note/installation_note.json
index 18c7d08..1e22f44 100644
--- a/erpnext/selling/doctype/installation_note/installation_note.json
+++ b/erpnext/selling/doctype/installation_note/installation_note.json
@@ -24,6 +24,7 @@
"inst_time",
"status",
"company",
+ "project",
"amended_from",
"remarks",
"item_details",
@@ -225,13 +226,19 @@
"oldfieldtype": "Table",
"options": "Installation Note Item",
"reqd": 1
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
}
],
"icon": "fa fa-wrench",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-06-03 16:31:08.386961",
+ "modified": "2024-02-04 18:20:12.020313",
"modified_by": "Administrator",
"module": "Selling",
"name": "Installation Note",
@@ -264,4 +271,4 @@
"states": [],
"timeline_field": "customer",
"title_field": "customer_name"
-}
\ No newline at end of file
+}
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index 63711c5..588b698 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -389,7 +389,7 @@
df: {
label: p.mode_of_payment,
fieldtype: 'Currency',
- placeholder: __('Enter {0} amount.', [p.mode_of_payment]),
+ placeholder: __('Enter {0} amount.', [__(p.mode_of_payment)]),
onchange: function() {
const current_value = frappe.model.get_value(p.doctype, p.name, 'amount');
if (current_value != this.value) {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 4eacbc1..a3903a3 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -399,7 +399,12 @@
elif self.issue_credit_note:
self.make_return_invoice()
- self.make_bundle_using_old_serial_batch_fields()
+ for table_name in ["items", "packed_items"]:
+ if not self.get(table_name):
+ continue
+
+ self.make_bundle_using_old_serial_batch_fields(table_name)
+
# Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO
self.update_stock_ledger()
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 459e7e7..293ef9f 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1078,6 +1078,8 @@
self.assertEqual(si2.items[1].qty, 1)
def test_delivery_note_bundle_with_batched_item(self):
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
+
batched_bundle = make_item("_Test Batched bundle", {"is_stock_item": 0})
batched_item = make_item(
"_Test Batched Item",
@@ -1099,6 +1101,8 @@
batch_no = get_batch_from_bundle(dn.packed_items[0].serial_and_batch_bundle)
self.assertTrue(batch_no)
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
@@ -1551,6 +1555,53 @@
self.assertEqual(so.items[0].rate, rate)
self.assertEqual(dn.items[0].rate, so.items[0].rate)
+ def test_use_serial_batch_fields_for_packed_items(self):
+ bundle_item = make_item("Test _Packed Product Bundle Item ", {"is_stock_item": 0})
+ serial_item = make_item(
+ "Test _Packed Serial Item ",
+ {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-TESTSERIAL-.#####"},
+ )
+ batch_item = make_item(
+ "Test _Packed Batch Item ",
+ {
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "batch_no_series": "BATCH-TESTSERIAL-.#####",
+ "create_new_batch": 1,
+ },
+ )
+ make_product_bundle(parent=bundle_item.name, items=[serial_item.name, batch_item.name])
+
+ item_details = {}
+ for item in [serial_item, batch_item]:
+ se = make_stock_entry(
+ item_code=item.name, target="_Test Warehouse - _TC", qty=5, basic_rate=100
+ )
+ item_details[item.name] = se.items[0].serial_and_batch_bundle
+
+ dn = create_delivery_note(item_code=bundle_item.name, qty=1, do_not_submit=True)
+ serial_no = ""
+ for row in dn.packed_items:
+ row.use_serial_batch_fields = 1
+
+ if row.item_code == serial_item.name:
+ serial_and_batch_bundle = item_details[serial_item.name]
+ row.serial_no = get_serial_nos_from_bundle(serial_and_batch_bundle)[3]
+ serial_no = row.serial_no
+ else:
+ serial_and_batch_bundle = item_details[batch_item.name]
+ row.batch_no = get_batch_from_bundle(serial_and_batch_bundle)
+
+ dn.submit()
+ dn.load_from_db()
+
+ for row in dn.packed_items:
+ self.assertTrue(row.serial_no or row.batch_no)
+ self.assertTrue(row.serial_and_batch_bundle)
+
+ if row.serial_no:
+ self.assertEqual(row.serial_no, serial_no)
+
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
index 9b2b5da..736eb9d 100644
--- a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
+++ b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
@@ -1,187 +1,77 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-02-22 01:28:02",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2013-02-22 01:28:02",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "receipt_document_type",
+ "receipt_document",
+ "supplier",
+ "col_break1",
+ "posting_date",
+ "grand_total"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "receipt_document_type",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Receipt Document Type",
- "length": 0,
- "no_copy": 0,
- "options": "\nPurchase Invoice\nPurchase Receipt",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "receipt_document_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Receipt Document Type",
+ "options": "\nPurchase Invoice\nPurchase Receipt",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "receipt_document",
- "fieldtype": "Dynamic Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Receipt Document",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "purchase_receipt_no",
- "oldfieldtype": "Link",
- "options": "receipt_document_type",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "220px",
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "receipt_document",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Receipt Document",
+ "oldfieldname": "purchase_receipt_no",
+ "oldfieldtype": "Link",
+ "options": "receipt_document_type",
+ "print_width": "220px",
+ "reqd": 1,
"width": "220px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "supplier",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Supplier",
- "length": 0,
- "no_copy": 0,
- "options": "Supplier",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Supplier",
+ "options": "Supplier",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "col_break1",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "col_break1",
+ "fieldtype": "Column Break",
"width": "50%"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Posting Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Posting Date",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "grand_total",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Grand Total",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "grand_total",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Grand Total",
+ "options": "Company:company:default_currency",
+ "read_only": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Landed Cost Purchase Receipt",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_order": "ASC",
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2024-02-26 18:41:06.281750",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Landed Cost Purchase Receipt",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index c7239b5..c705d59 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -198,12 +198,14 @@
"fieldname": "rate",
"fieldtype": "Currency",
"label": "Rate",
+ "options": "Company:company:default_currency",
"print_hide": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
+ "options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -489,7 +491,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-02-08 16:30:56.137858",
+ "modified": "2024-02-26 18:30:03.684872",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py
index c115e33..c5fed0d 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -227,6 +227,9 @@
bin = get_packed_item_bin_qty(packing_item.item_code, pi_row.warehouse)
pi_row.actual_qty = flt(bin.get("actual_qty"))
pi_row.projected_qty = flt(bin.get("projected_qty"))
+ pi_row.use_serial_batch_fields = frappe.db.get_single_value(
+ "Stock Settings", "use_serial_batch_fields"
+ )
def update_packed_item_price_data(pi_row, item_data, doc):
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index daa0166..aa17ab4 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -2237,6 +2237,10 @@
create_stock_reconciliation,
)
+ frappe.db.set_single_value(
+ "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0
+ )
+
item_code = make_item(
"_Test Use Serial Fields Item Serial Item",
properties={"has_serial_no": 1, "serial_no_series": "SNU-TSFISI-.#####"},
@@ -2317,6 +2321,10 @@
serial_no_status = frappe.db.get_value("Serial No", sn, "status")
self.assertTrue(serial_no_status != "Active")
+ frappe.db.set_single_value(
+ "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 1
+ )
+
def test_sle_qty_after_transaction(self):
item = make_item(
"_Test Item Qty After Transaction",
@@ -2416,7 +2424,9 @@
},
).name
- frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+ frappe.db.set_single_value(
+ "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0
+ )
pr = make_purchase_receipt(
item_code=item_code,
@@ -2428,7 +2438,44 @@
batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
self.assertEqual(pr.items[0].batch_no, batch_no)
- frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
+ frappe.db.set_single_value(
+ "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 1
+ )
+
+ def test_pr_billed_amount_against_return_entry(self):
+ from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note
+ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
+ make_purchase_invoice as make_pi_from_pr,
+ )
+
+ # Create a Purchase Receipt and Fully Bill it
+ pr = make_purchase_receipt(qty=10)
+ pi = make_pi_from_pr(pr.name)
+ pi.insert()
+ pi.submit()
+
+ # Debit Note - 50% Qty & enable updating PR billed amount
+ pi_return = make_debit_note(pi.name)
+ pi_return.items[0].qty = -5
+ pi_return.update_billed_amount_in_purchase_receipt = 1
+ pi_return.submit()
+
+ # Check if the billed amount reduced
+ pr.reload()
+ self.assertEqual(pr.per_billed, 50)
+
+ pi_return.reload()
+ pi_return.cancel()
+
+ # Debit Note - 50% Qty & disable updating PR billed amount
+ pi_return = make_debit_note(pi.name)
+ pi_return.items[0].qty = -5
+ pi_return.update_billed_amount_in_purchase_receipt = 0
+ pi_return.submit()
+
+ # Check if the billed amount stayed the same
+ pr.reload()
+ self.assertEqual(pr.per_billed, 100)
def prepare_data_for_internal_transfer():
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index eb4df29..b6e4d6f 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -257,9 +257,9 @@
if sn_obj.batch_avg_rate.get(d.batch_no):
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
- available_qty = flt(sn_obj.available_qty.get(d.batch_no))
+ available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty"))
if self.docstatus == 1:
- available_qty += flt(d.qty)
+ available_qty += flt(d.qty, d.precision("qty"))
if not allow_negative_stock:
self.validate_negative_batch(d.batch_no, available_qty)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 7f79f04..1b7089b 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -943,7 +943,7 @@
if (this.frm.doc.docstatus===1 && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
this.show_general_ledger();
}
- erpnext.hide_company();
+ erpnext.hide_company(this.frm);
erpnext.utils.add_item(this.frm);
}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 832894b..399e698 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1033,7 +1033,7 @@
already_picked_serial_nos = []
for row in self.items:
- if row.use_serial_batch_fields and (row.serial_no or row.batch_no):
+ if row.use_serial_batch_fields:
continue
if not row.s_warehouse:
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index c7b3daa..48fc31a 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -294,7 +294,7 @@
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
- "fieldtype": "Small Text",
+ "fieldtype": "Text",
"label": "Serial No",
"no_copy": 1,
"oldfieldname": "serial_no",
@@ -610,7 +610,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-02-04 16:16:47.606270",
+ "modified": "2024-02-25 15:58:40.982582",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
index 47c443c..bd3dda1 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
@@ -54,7 +54,7 @@
sample_quantity: DF.Int
sco_rm_detail: DF.Data | None
serial_and_batch_bundle: DF.Link | None
- serial_no: DF.SmallText | None
+ serial_no: DF.Text | None
set_basic_rate_manually: DF.Check
ste_detail: DF.Data | None
stock_uom: DF.Link
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index fb8089e..51036ad 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -51,6 +51,7 @@
"use_naming_series",
"naming_series_prefix",
"use_serial_batch_fields",
+ "do_not_update_serial_batch_on_creation_of_auto_bundle",
"stock_planning_tab",
"auto_material_request",
"auto_indent",
@@ -428,6 +429,14 @@
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial / Batch Fields"
+ },
+ {
+ "default": "1",
+ "depends_on": "use_serial_batch_fields",
+ "description": "If enabled, do not update serial / batch values in the stock transactions on creation of auto Serial \n / Batch Bundle. ",
+ "fieldname": "do_not_update_serial_batch_on_creation_of_auto_bundle",
+ "fieldtype": "Check",
+ "label": "Do Not Update Serial / Batch on Creation of Auto Bundle"
}
],
"icon": "icon-cog",
@@ -435,7 +444,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2024-02-24 12:50:51.740097",
+ "modified": "2024-02-25 16:32:01.084453",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index c4960aa..d975c29 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -39,6 +39,7 @@
clean_description_html: DF.Check
default_warehouse: DF.Link | None
disable_serial_no_and_batch_selector: DF.Check
+ do_not_update_serial_batch_on_creation_of_auto_bundle: DF.Check
enable_stock_reservation: DF.Check
item_group: DF.Link | None
item_naming_by: DF.Literal["Item Code", "Naming Series"]
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 52c5b00..24dd9d1 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -142,7 +142,9 @@
"serial_and_batch_bundle": sn_doc.name,
}
- if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
+ if not frappe.db.get_single_value(
+ "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle"
+ ):
if sn_doc.has_serial_no:
values_to_update["serial_no"] = ",".join(cstr(d.serial_no) for d in sn_doc.entries)
elif sn_doc.has_batch_no and len(sn_doc.entries) == 1: