mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 03:01:22 +00:00
Merge pull request #54282 from frappe/version-15-hotfix
This commit is contained in:
@@ -839,7 +839,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
paid_amount: function (frm) {
|
paid_amount: function (frm) {
|
||||||
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
|
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
|
||||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||||
if (frm.doc.paid_amount) {
|
if (!frm.doc.received_amount) {
|
||||||
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||||
frm.set_value("received_amount", frm.doc.paid_amount);
|
frm.set_value("received_amount", frm.doc.paid_amount);
|
||||||
} else if (company_currency == frm.doc.paid_to_account_currency) {
|
} else if (company_currency == frm.doc.paid_to_account_currency) {
|
||||||
@@ -860,7 +860,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
|
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (frm.doc.received_amount) {
|
if (!frm.doc.paid_amount) {
|
||||||
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||||
frm.set_value("paid_amount", frm.doc.received_amount);
|
frm.set_value("paid_amount", frm.doc.received_amount);
|
||||||
if (frm.doc.target_exchange_rate) {
|
if (frm.doc.target_exchange_rate) {
|
||||||
|
|||||||
@@ -731,7 +731,6 @@
|
|||||||
"label": "Valuation Rate",
|
"label": "Valuation Rate",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"precision": "6",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -984,7 +983,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-10-14 13:01:54.441511",
|
"modified": "2026-04-07 15:41:45.687554",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
|||||||
@@ -166,13 +166,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.toggle_get_items();
|
||||||
// Show buttons only when pos view is active
|
|
||||||
if (cint(doc.docstatus == 0) && cur_frm.page.current_view_name !== "pos" && !doc.is_return) {
|
|
||||||
this.frm.cscript.sales_order_btn();
|
|
||||||
this.frm.cscript.delivery_note_btn();
|
|
||||||
this.frm.cscript.quotation_btn();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set_default_print_format();
|
this.set_default_print_format();
|
||||||
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
|
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
|
||||||
@@ -258,6 +252,93 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggle_get_items() {
|
||||||
|
const buttons = ["Sales Order", "Quotation", "Timesheet", "Delivery Note"];
|
||||||
|
|
||||||
|
buttons.forEach((label) => {
|
||||||
|
this.frm.remove_custom_button(label, "Get Items From");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cint(this.frm.doc.docstatus) !== 0 || this.frm.page.current_view_name === "pos") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.frm.doc.is_return) {
|
||||||
|
this.frm.cscript.sales_order_btn();
|
||||||
|
this.frm.cscript.quotation_btn();
|
||||||
|
this.frm.cscript.timesheet_btn();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frm.cscript.delivery_note_btn();
|
||||||
|
}
|
||||||
|
|
||||||
|
timesheet_btn() {
|
||||||
|
var me = this;
|
||||||
|
|
||||||
|
me.frm.add_custom_button(
|
||||||
|
__("Timesheet"),
|
||||||
|
function () {
|
||||||
|
let d = new frappe.ui.Dialog({
|
||||||
|
title: __("Fetch Timesheet"),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: __("From"),
|
||||||
|
fieldname: "from_time",
|
||||||
|
fieldtype: "Date",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Item Code"),
|
||||||
|
fieldname: "item_code",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Item",
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.item_query",
|
||||||
|
filters: {
|
||||||
|
is_sales_item: 1,
|
||||||
|
customer: me.frm.doc.customer,
|
||||||
|
has_variants: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Column Break",
|
||||||
|
fieldname: "col_break_1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("To"),
|
||||||
|
fieldname: "to_time",
|
||||||
|
fieldtype: "Date",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Project"),
|
||||||
|
fieldname: "project",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Project",
|
||||||
|
default: me.frm.doc.project,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primary_action: function () {
|
||||||
|
const data = d.get_values();
|
||||||
|
me.frm.events.add_timesheet_data(me.frm, {
|
||||||
|
from_time: data.from_time,
|
||||||
|
to_time: data.to_time,
|
||||||
|
project: data.project,
|
||||||
|
item_code: data.item_code,
|
||||||
|
});
|
||||||
|
d.hide();
|
||||||
|
},
|
||||||
|
primary_action_label: __("Get Timesheets"),
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
},
|
||||||
|
__("Get Items From")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
sales_order_btn() {
|
sales_order_btn() {
|
||||||
var me = this;
|
var me = this;
|
||||||
this.$sales_order_btn = this.frm.add_custom_button(
|
this.$sales_order_btn = this.frm.add_custom_button(
|
||||||
@@ -322,6 +403,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
|||||||
this.$delivery_note_btn = this.frm.add_custom_button(
|
this.$delivery_note_btn = this.frm.add_custom_button(
|
||||||
__("Delivery Note"),
|
__("Delivery Note"),
|
||||||
function () {
|
function () {
|
||||||
|
if (!me.frm.doc.customer) {
|
||||||
|
frappe.throw({
|
||||||
|
title: __("Mandatory"),
|
||||||
|
message: __("Please Select a Customer"),
|
||||||
|
});
|
||||||
|
}
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
|
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
|
||||||
source_doctype: "Delivery Note",
|
source_doctype: "Delivery Note",
|
||||||
@@ -334,7 +421,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
|||||||
var filters = {
|
var filters = {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
company: me.frm.doc.company,
|
company: me.frm.doc.company,
|
||||||
is_return: 0,
|
is_return: me.frm.doc.is_return,
|
||||||
};
|
};
|
||||||
if (me.frm.doc.customer) filters["customer"] = me.frm.doc.customer;
|
if (me.frm.doc.customer) filters["customer"] = me.frm.doc.customer;
|
||||||
return {
|
return {
|
||||||
@@ -594,6 +681,14 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
|||||||
|
|
||||||
this.calculate_taxes_and_totals();
|
this.calculate_taxes_and_totals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply_tds(frm) {
|
||||||
|
this.frm.clear_table("tax_withholding_entries");
|
||||||
|
}
|
||||||
|
|
||||||
|
is_return() {
|
||||||
|
this.toggle_get_items();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// for backward compatibility: combine new and previous states
|
// for backward compatibility: combine new and previous states
|
||||||
@@ -1039,71 +1134,6 @@ frappe.ui.form.on("Sales Invoice", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
if (frm.doc.docstatus === 0 && !frm.doc.is_return) {
|
|
||||||
frm.add_custom_button(
|
|
||||||
__("Timesheet"),
|
|
||||||
function () {
|
|
||||||
let d = new frappe.ui.Dialog({
|
|
||||||
title: __("Fetch Timesheet"),
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: __("From"),
|
|
||||||
fieldname: "from_time",
|
|
||||||
fieldtype: "Date",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Item Code"),
|
|
||||||
fieldname: "item_code",
|
|
||||||
fieldtype: "Link",
|
|
||||||
options: "Item",
|
|
||||||
get_query: () => {
|
|
||||||
return {
|
|
||||||
query: "erpnext.controllers.queries.item_query",
|
|
||||||
filters: {
|
|
||||||
is_sales_item: 1,
|
|
||||||
customer: frm.doc.customer,
|
|
||||||
has_variants: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: "Column Break",
|
|
||||||
fieldname: "col_break_1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("To"),
|
|
||||||
fieldname: "to_time",
|
|
||||||
fieldtype: "Date",
|
|
||||||
reqd: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Project"),
|
|
||||||
fieldname: "project",
|
|
||||||
fieldtype: "Link",
|
|
||||||
options: "Project",
|
|
||||||
default: frm.doc.project,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
primary_action: function () {
|
|
||||||
const data = d.get_values();
|
|
||||||
frm.events.add_timesheet_data(frm, {
|
|
||||||
from_time: data.from_time,
|
|
||||||
to_time: data.to_time,
|
|
||||||
project: data.project,
|
|
||||||
item_code: data.item_code,
|
|
||||||
});
|
|
||||||
d.hide();
|
|
||||||
},
|
|
||||||
primary_action_label: __("Get Timesheets"),
|
|
||||||
});
|
|
||||||
d.show();
|
|
||||||
},
|
|
||||||
__("Get Items From")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frm.doc.is_debit_note) {
|
if (frm.doc.is_debit_note) {
|
||||||
frm.set_df_property("return_against", "label", __("Adjustment Against"));
|
frm.set_df_property("return_against", "label", __("Adjustment Against"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2917,7 +2917,7 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
# Check if adjustment entry is created
|
# Check if adjustment entry is created
|
||||||
self.assertTrue(
|
self.assertFalse(
|
||||||
frappe.db.exists(
|
frappe.db.exists(
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ frappe.ui.form.on("Asset Movement", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: (frm) => {
|
refresh: (frm) => {
|
||||||
frm.trigger("set_required_fields");
|
frm.trigger("set_required_fields");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
user.save(ignore_permissions=True)
|
user.save(ignore_permissions=True)
|
||||||
update_password_link = user.reset_password()
|
update_password_link = user._reset_password()
|
||||||
|
|
||||||
return user, update_password_link
|
return user, update_password_link
|
||||||
|
|
||||||
|
|||||||
@@ -356,38 +356,43 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
|
def get_delivery_notes_to_be_billed(
|
||||||
doctype = "Delivery Note"
|
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict, as_dict: bool = False
|
||||||
|
):
|
||||||
|
DeliveryNote = frappe.qb.DocType("Delivery Note")
|
||||||
|
|
||||||
fields = get_fields(doctype, ["name", "customer", "posting_date"])
|
fields = get_fields(doctype, ["name", "customer", "posting_date"])
|
||||||
|
|
||||||
return frappe.db.sql(
|
original_dn = (
|
||||||
"""
|
frappe.qb.from_(DeliveryNote)
|
||||||
select {fields}
|
.select(DeliveryNote.name)
|
||||||
from `tabDelivery Note`
|
.where((DeliveryNote.docstatus == 1) & (DeliveryNote.is_return == 0) & (DeliveryNote.per_billed > 0))
|
||||||
where `tabDelivery Note`.`{key}` like {txt} and
|
)
|
||||||
`tabDelivery Note`.docstatus = 1
|
|
||||||
and status not in ('Stopped', 'Closed') {fcond}
|
query = (
|
||||||
and (
|
frappe.qb.from_(DeliveryNote)
|
||||||
(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
|
.select(*[DeliveryNote[f] for f in fields])
|
||||||
or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100)
|
.where(
|
||||||
or (
|
(DeliveryNote.docstatus == 1)
|
||||||
`tabDelivery Note`.is_return = 1
|
& (DeliveryNote.status.notin(["Stopped", "Closed"]))
|
||||||
and return_against in (select name from `tabDelivery Note` where per_billed < 100)
|
& (DeliveryNote[searchfield].like(f"%{txt}%"))
|
||||||
|
& (
|
||||||
|
((DeliveryNote.is_return == 0) & (DeliveryNote.per_billed < 100))
|
||||||
|
| ((DeliveryNote.grand_total == 0) & (DeliveryNote.per_billed < 100))
|
||||||
|
| (
|
||||||
|
(DeliveryNote.is_return == 1)
|
||||||
|
& (DeliveryNote.per_billed < 100)
|
||||||
|
& (DeliveryNote.return_against.isin(original_dn))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
{mcond} order by `tabDelivery Note`.`{key}` asc limit {page_len} offset {start}
|
)
|
||||||
""".format(
|
|
||||||
fields=", ".join([f"`tabDelivery Note`.{f}" for f in fields]),
|
|
||||||
key=searchfield,
|
|
||||||
fcond=get_filters_cond(doctype, filters, []),
|
|
||||||
mcond=get_match_cond(doctype),
|
|
||||||
start=start,
|
|
||||||
page_len=page_len,
|
|
||||||
txt="%(txt)s",
|
|
||||||
),
|
|
||||||
{"txt": ("%%%s%%" % txt)},
|
|
||||||
as_dict=as_dict,
|
|
||||||
)
|
)
|
||||||
|
if filters and isinstance(filters, dict):
|
||||||
|
for key, value in filters.items():
|
||||||
|
query = query.where(DeliveryNote[key] == value)
|
||||||
|
|
||||||
|
query = query.orderby(DeliveryNote[searchfield], order=Order.asc).limit(page_len).offset(start)
|
||||||
|
return query.run(as_dict=as_dict)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -616,11 +616,11 @@ class SellingController(StockController):
|
|||||||
if allow_at_arms_length_price:
|
if allow_at_arms_length_price:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rate = flt(
|
rate = flt(flt(d.incoming_rate) * flt(d.conversion_factor or 1.0))
|
||||||
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
|
|
||||||
d.precision("rate"),
|
if flt(d.rate, d.precision("incoming_rate")) != flt(
|
||||||
)
|
rate, d.precision("incoming_rate")
|
||||||
if d.rate != rate:
|
):
|
||||||
d.rate = rate
|
d.rate = rate
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_(
|
_(
|
||||||
|
|||||||
@@ -186,8 +186,11 @@ class calculate_taxes_and_totals:
|
|||||||
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
|
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
|
||||||
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
|
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
do_not_round_fields = ["valuation_rate", "incoming_rate"]
|
||||||
|
|
||||||
for item in self.doc.items:
|
for item in self.doc.items:
|
||||||
self.doc.round_floats_in(item)
|
self.doc.round_floats_in(item, do_not_round_fields=do_not_round_fields)
|
||||||
|
|
||||||
if item.discount_percentage == 100:
|
if item.discount_percentage == 100:
|
||||||
item.rate = 0.0
|
item.rate = 0.0
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ def get_join(filters):
|
|||||||
join = """JOIN `tabOpportunity Lost Reason Detail`
|
join = """JOIN `tabOpportunity Lost Reason Detail`
|
||||||
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
|
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
|
||||||
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and
|
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and
|
||||||
`tabOpportunity Lost Reason Detail`.lost_reason = '{}'
|
`tabOpportunity Lost Reason Detail`.lost_reason=%(lost_reason)s
|
||||||
""".format(filters.get("lost_reason"))
|
"""
|
||||||
|
|
||||||
return join
|
return join
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import escape_html
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from lxml.etree import Element
|
from lxml.etree import Element
|
||||||
@@ -63,14 +64,16 @@ class CodeList(Document):
|
|||||||
|
|
||||||
def from_genericode(self, root: "Element"):
|
def from_genericode(self, root: "Element"):
|
||||||
"""Extract Code List details from genericode XML"""
|
"""Extract Code List details from genericode XML"""
|
||||||
self.title = root.find(".//Identification/ShortName").text
|
self.title = escape_html(root.find(".//Identification/ShortName").text)
|
||||||
self.version = root.find(".//Identification/Version").text
|
self.version = root.find(".//Identification/Version").text
|
||||||
self.canonical_uri = root.find(".//CanonicalUri").text
|
self.canonical_uri = root.find(".//CanonicalUri").text
|
||||||
# optionals
|
# optionals
|
||||||
self.description = getattr(root.find(".//Identification/LongName"), "text", None)
|
self.description = escape_html(getattr(root.find(".//Identification/LongName"), "text", None))
|
||||||
self.publisher = getattr(root.find(".//Identification/Agency/ShortName"), "text", None)
|
self.publisher = escape_html(getattr(root.find(".//Identification/Agency/ShortName"), "text", None))
|
||||||
if not self.publisher:
|
if not self.publisher:
|
||||||
self.publisher = getattr(root.find(".//Identification/Agency/LongName"), "text", None)
|
self.publisher = escape_html(
|
||||||
|
getattr(root.find(".//Identification/Agency/LongName"), "text", None)
|
||||||
|
)
|
||||||
self.publisher_id = getattr(root.find(".//Identification/Agency/Identifier"), "text", None)
|
self.publisher_id = getattr(root.find(".//Identification/Agency/Identifier"), "text", None)
|
||||||
self.url = getattr(root.find(".//Identification/LocationUri"), "text", None)
|
self.url = getattr(root.find(".//Identification/LocationUri"), "text", None)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import json
|
|||||||
import frappe
|
import frappe
|
||||||
import requests
|
import requests
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.utils import escape_html
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
URL_PREFIXES = ("http://", "https://")
|
URL_PREFIXES = ("http://", "https://")
|
||||||
@@ -32,7 +33,12 @@ def import_genericode():
|
|||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
# Parse the xml content
|
# Parse the xml content
|
||||||
parser = etree.XMLParser(remove_blank_text=True)
|
parser = etree.XMLParser(
|
||||||
|
remove_blank_text=True,
|
||||||
|
resolve_entities=False,
|
||||||
|
load_dtd=False,
|
||||||
|
no_network=True,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
root = etree.fromstring(content, parser=parser)
|
root = etree.fromstring(content, parser=parser)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -104,7 +110,7 @@ def get_genericode_columns_and_examples(root):
|
|||||||
|
|
||||||
# Get column names
|
# Get column names
|
||||||
for column in root.findall(".//Column"):
|
for column in root.findall(".//Column"):
|
||||||
column_id = column.get("Id")
|
column_id = escape_html(column.get("Id"))
|
||||||
columns.append(column_id)
|
columns.append(column_id)
|
||||||
example_values[column_id] = []
|
example_values[column_id] = []
|
||||||
filterable_columns[column_id] = set()
|
filterable_columns[column_id] = set()
|
||||||
@@ -112,7 +118,7 @@ def get_genericode_columns_and_examples(root):
|
|||||||
# Get all values and count unique occurrences
|
# Get all values and count unique occurrences
|
||||||
for row in root.findall(".//SimpleCodeList/Row"):
|
for row in root.findall(".//SimpleCodeList/Row"):
|
||||||
for value in row.findall("Value"):
|
for value in row.findall("Value"):
|
||||||
column_id = value.get("ColumnRef")
|
column_id = escape_html(value.get("ColumnRef"))
|
||||||
if column_id not in columns:
|
if column_id not in columns:
|
||||||
# Handle undeclared column
|
# Handle undeclared column
|
||||||
columns.append(column_id)
|
columns.append(column_id)
|
||||||
@@ -123,7 +129,7 @@ def get_genericode_columns_and_examples(root):
|
|||||||
if simple_value is None:
|
if simple_value is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
filterable_columns[column_id].add(simple_value.text)
|
filterable_columns[column_id].add(escape_html(simple_value.text))
|
||||||
|
|
||||||
# Get example values (up to 3) and filter columns with cardinality <= 5
|
# Get example values (up to 3) and filter columns with cardinality <= 5
|
||||||
for row in root.findall(".//SimpleCodeList/Row")[:3]:
|
for row in root.findall(".//SimpleCodeList/Row")[:3]:
|
||||||
@@ -133,7 +139,7 @@ def get_genericode_columns_and_examples(root):
|
|||||||
if simple_value is None:
|
if simple_value is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
example_values[column_id].append(simple_value.text)
|
example_values[column_id].append(escape_html(simple_value.text))
|
||||||
|
|
||||||
filterable_columns = {k: list(v) for k, v in filterable_columns.items() if len(v) <= 5}
|
filterable_columns = {k: list(v) for k, v in filterable_columns.items() if len(v) <= 5}
|
||||||
|
|
||||||
|
|||||||
@@ -760,6 +760,8 @@ frappe.ui.form.on("BOM Item", "sourced_by_supplier", function (frm, cdt, cdn) {
|
|||||||
if (d.sourced_by_supplier) {
|
if (d.sourced_by_supplier) {
|
||||||
d.rate = 0;
|
d.rate = 0;
|
||||||
refresh_field("rate", d.name, d.parentfield);
|
refresh_field("rate", d.name, d.parentfield);
|
||||||
|
} else {
|
||||||
|
get_bom_material_detail(frm.doc, cdt, cdn, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Operation",
|
"label": "Operation",
|
||||||
"options": "Operation"
|
"options": "Operation",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-08-04 16:15:11.425349",
|
"modified": "2026-04-13 12:17:33.776504",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Sub Operation",
|
"name": "Sub Operation",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class SubOperation(Document):
|
|||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
description: DF.SmallText | None
|
description: DF.SmallText | None
|
||||||
operation: DF.Link | None
|
operation: DF.Link
|
||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ frappe.listview_settings["Workstation"] = {
|
|||||||
Setup: "blue",
|
Setup: "blue",
|
||||||
};
|
};
|
||||||
|
|
||||||
return [__(doc.status), color_map[doc.status], true];
|
return [__(doc.status), color_map[doc.status], "status,=," + doc.status];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -432,3 +432,4 @@ erpnext.patches.v16_0.set_ordered_qty_in_quotation_item
|
|||||||
erpnext.patches.v15_0.replace_http_with_https_in_sales_partner
|
erpnext.patches.v15_0.replace_http_with_https_in_sales_partner
|
||||||
erpnext.patches.v16_0.add_portal_redirects
|
erpnext.patches.v16_0.add_portal_redirects
|
||||||
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
|
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
|
||||||
|
erpnext.patches.v16_0.depends_on_inv_dimensions
|
||||||
|
|||||||
89
erpnext/patches/v16_0/depends_on_inv_dimensions.py
Normal file
89
erpnext/patches/v16_0/depends_on_inv_dimensions.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def get_inventory_dimensions():
|
||||||
|
return frappe.get_all(
|
||||||
|
"Inventory Dimension",
|
||||||
|
fields=[
|
||||||
|
"target_fieldname as fieldname",
|
||||||
|
"source_fieldname",
|
||||||
|
"reference_document as doctype",
|
||||||
|
"reqd",
|
||||||
|
"mandatory_depends_on",
|
||||||
|
],
|
||||||
|
order_by="creation",
|
||||||
|
distinct=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_display_depends_on(doctype, fieldname):
|
||||||
|
if doctype not in [
|
||||||
|
"Stock Entry Detail",
|
||||||
|
"Sales Invoice Item",
|
||||||
|
"Delivery Note Item",
|
||||||
|
"Purchase Invoice Item",
|
||||||
|
"Purchase Receipt Item",
|
||||||
|
]:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
fieldname_start_with = "to"
|
||||||
|
display_depends_on = ""
|
||||||
|
|
||||||
|
if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
|
||||||
|
display_depends_on = "eval:parent.is_internal_supplier == 1"
|
||||||
|
fieldname_start_with = "from"
|
||||||
|
elif doctype != "Stock Entry Detail":
|
||||||
|
display_depends_on = "eval:parent.is_internal_customer == 1"
|
||||||
|
elif doctype == "Stock Entry Detail":
|
||||||
|
display_depends_on = "eval:doc.t_warehouse"
|
||||||
|
|
||||||
|
return f"{fieldname_start_with}_{fieldname}", display_depends_on
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for dimension in get_inventory_dimensions():
|
||||||
|
if frappe.db.exists(
|
||||||
|
"Custom Field", {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail"}
|
||||||
|
):
|
||||||
|
frappe.set_value(
|
||||||
|
"Custom Field",
|
||||||
|
{"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail"},
|
||||||
|
"depends_on",
|
||||||
|
"eval:doc.s_warehouse",
|
||||||
|
)
|
||||||
|
if frappe.db.exists(
|
||||||
|
"Custom Field", {"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail", "reqd": 1}
|
||||||
|
):
|
||||||
|
frappe.set_value(
|
||||||
|
"Custom Field",
|
||||||
|
{"fieldname": dimension.source_fieldname, "dt": "Stock Entry Detail", "reqd": 1},
|
||||||
|
{"mandatory_depends_on": "eval:doc.s_warehouse", "reqd": 0},
|
||||||
|
)
|
||||||
|
if frappe.db.exists(
|
||||||
|
"Custom Field",
|
||||||
|
{
|
||||||
|
"fieldname": f"to_{dimension.fieldname}",
|
||||||
|
"dt": "Stock Entry Detail",
|
||||||
|
"depends_on": "eval:parent.purpose != 'Material Issue'",
|
||||||
|
},
|
||||||
|
):
|
||||||
|
frappe.set_value(
|
||||||
|
"Custom Field",
|
||||||
|
{
|
||||||
|
"fieldname": f"to_{dimension.fieldname}",
|
||||||
|
"dt": "Stock Entry Detail",
|
||||||
|
"depends_on": "eval:parent.purpose != 'Material Issue'",
|
||||||
|
},
|
||||||
|
"depends_on",
|
||||||
|
"eval:doc.t_warehouse",
|
||||||
|
)
|
||||||
|
fieldname, display_depends_on = get_display_depends_on(dimension.doctype, dimension.fieldname)
|
||||||
|
if display_depends_on and frappe.db.exists(
|
||||||
|
"Custom Field", {"fieldname": fieldname, "dt": dimension.doctype}
|
||||||
|
):
|
||||||
|
frappe.set_value(
|
||||||
|
"Custom Field",
|
||||||
|
{"fieldname": fieldname, "dt": dimension.doctype},
|
||||||
|
"mandatory_depends_on",
|
||||||
|
display_depends_on if dimension.reqd else dimension.mandatory_depends_on,
|
||||||
|
)
|
||||||
@@ -759,7 +759,6 @@
|
|||||||
"label": "Incoming Rate",
|
"label": "Incoming Rate",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"precision": "6",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -952,7 +951,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-31 18:51:32.651562",
|
"modified": "2026-04-07 15:44:20.892151",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note Item",
|
"name": "Delivery Note Item",
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"dimension_details_tab",
|
"dimension_details_tab",
|
||||||
"dimension_name",
|
"dimension_name",
|
||||||
"reference_document",
|
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"disabled",
|
"reference_document",
|
||||||
"field_mapping_section",
|
"field_mapping_section",
|
||||||
"source_fieldname",
|
"source_fieldname",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
@@ -93,12 +92,6 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Apply to All Inventory Documents"
|
"label": "Apply to All Inventory Documents"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "disabled",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Disabled"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "target_fieldname",
|
"fieldname": "target_fieldname",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@@ -159,6 +152,7 @@
|
|||||||
"label": "Conditional Rule Examples"
|
"label": "Conditional Rule Examples"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||||
"description": "To apply condition on parent field use parent.field_name and to apply condition on child table use doc.field_name. Here field_name could be based on the actual column name of the respective field.",
|
"description": "To apply condition on parent field use parent.field_name and to apply condition on child table use doc.field_name. Here field_name could be based on the actual column name of the respective field.",
|
||||||
"fieldname": "mandatory_depends_on",
|
"fieldname": "mandatory_depends_on",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
@@ -188,7 +182,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-07 15:51:29.329064",
|
"modified": "2026-04-08 10:10:16.884388",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Inventory Dimension",
|
"name": "Inventory Dimension",
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ class InventoryDimension(Document):
|
|||||||
apply_to_all_doctypes: DF.Check
|
apply_to_all_doctypes: DF.Check
|
||||||
condition: DF.Code | None
|
condition: DF.Code | None
|
||||||
dimension_name: DF.Data
|
dimension_name: DF.Data
|
||||||
disabled: DF.Check
|
|
||||||
document_type: DF.Link | None
|
document_type: DF.Link | None
|
||||||
fetch_from_parent: DF.Literal[None]
|
fetch_from_parent: DF.Literal[None]
|
||||||
istable: DF.Check
|
istable: DF.Check
|
||||||
@@ -75,7 +74,6 @@ class InventoryDimension(Document):
|
|||||||
|
|
||||||
old_doc = self._doc_before_save
|
old_doc = self._doc_before_save
|
||||||
allow_to_edit_fields = [
|
allow_to_edit_fields = [
|
||||||
"disabled",
|
|
||||||
"fetch_from_parent",
|
"fetch_from_parent",
|
||||||
"type_of_transaction",
|
"type_of_transaction",
|
||||||
"condition",
|
"condition",
|
||||||
@@ -119,6 +117,7 @@ class InventoryDimension(Document):
|
|||||||
def reset_value(self):
|
def reset_value(self):
|
||||||
if self.apply_to_all_doctypes:
|
if self.apply_to_all_doctypes:
|
||||||
self.type_of_transaction = ""
|
self.type_of_transaction = ""
|
||||||
|
self.mandatory_depends_on = ""
|
||||||
|
|
||||||
self.istable = 0
|
self.istable = 0
|
||||||
for field in ["document_type", "condition"]:
|
for field in ["document_type", "condition"]:
|
||||||
@@ -183,8 +182,12 @@ class InventoryDimension(Document):
|
|||||||
label=_(label),
|
label=_(label),
|
||||||
depends_on="eval:doc.s_warehouse" if doctype == "Stock Entry Detail" else "",
|
depends_on="eval:doc.s_warehouse" if doctype == "Stock Entry Detail" else "",
|
||||||
search_index=1,
|
search_index=1,
|
||||||
reqd=self.reqd,
|
reqd=1
|
||||||
mandatory_depends_on=self.mandatory_depends_on,
|
if self.reqd and not self.mandatory_depends_on and doctype != "Stock Entry Detail"
|
||||||
|
else 0,
|
||||||
|
mandatory_depends_on="eval:doc.s_warehouse"
|
||||||
|
if self.reqd and doctype == "Stock Entry Detail"
|
||||||
|
else self.mandatory_depends_on,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -296,12 +299,13 @@ class InventoryDimension(Document):
|
|||||||
options=self.reference_document,
|
options=self.reference_document,
|
||||||
label=label,
|
label=label,
|
||||||
depends_on=display_depends_on,
|
depends_on=display_depends_on,
|
||||||
|
mandatory_depends_on=display_depends_on if self.reqd else self.mandatory_depends_on,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def field_exists(doctype, fieldname) -> str or None:
|
def field_exists(doctype, fieldname) -> str | None:
|
||||||
return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
|
return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
|
||||||
|
|
||||||
|
|
||||||
@@ -374,7 +378,6 @@ def get_document_wise_inventory_dimensions(doctype) -> dict:
|
|||||||
"type_of_transaction",
|
"type_of_transaction",
|
||||||
"fetch_from_parent",
|
"fetch_from_parent",
|
||||||
],
|
],
|
||||||
filters={"disabled": 0},
|
|
||||||
or_filters={"document_type": doctype, "apply_to_all_doctypes": 1},
|
or_filters={"document_type": doctype, "apply_to_all_doctypes": 1},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -397,7 +400,6 @@ def get_inventory_dimensions():
|
|||||||
"reference_document as doctype",
|
"reference_document as doctype",
|
||||||
"validate_negative_stock",
|
"validate_negative_stock",
|
||||||
],
|
],
|
||||||
filters={"disabled": 0},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
frappe.local.inventory_dimensions = dimensions
|
frappe.local.inventory_dimensions = dimensions
|
||||||
|
|||||||
@@ -220,9 +220,9 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
doc = create_inventory_dimension(
|
doc = create_inventory_dimension(
|
||||||
reference_document="Pallet",
|
reference_document="Pallet",
|
||||||
type_of_transaction="Outward",
|
type_of_transaction="Outward",
|
||||||
dimension_name="Pallet",
|
dimension_name="Pallet 75",
|
||||||
apply_to_all_doctypes=0,
|
apply_to_all_doctypes=0,
|
||||||
document_type="Stock Entry Detail",
|
document_type="Delivery Note Item",
|
||||||
)
|
)
|
||||||
|
|
||||||
doc.reqd = 1
|
doc.reqd = 1
|
||||||
@@ -230,7 +230,7 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
frappe.db.get_value(
|
frappe.db.get_value(
|
||||||
"Custom Field", {"fieldname": "pallet", "dt": "Stock Entry Detail", "reqd": 1}, "name"
|
"Custom Field", {"fieldname": "pallet_75", "dt": "Delivery Note Item", "reqd": 1}, "name"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -734,7 +734,6 @@
|
|||||||
"oldfieldname": "valuation_rate",
|
"oldfieldname": "valuation_rate",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"precision": "6",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_width": "80px",
|
"print_width": "80px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
@@ -1149,7 +1148,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-10-14 12:59:20.384056",
|
"modified": "2026-04-07 15:41:47.032889",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ frappe.ui.form.on("Quality Inspection", {
|
|||||||
if (doc.reference_type && doc.reference_name) {
|
if (doc.reference_type && doc.reference_name) {
|
||||||
let filters = {
|
let filters = {
|
||||||
from: doctype,
|
from: doctype,
|
||||||
|
parent_doctype: doc.reference_type,
|
||||||
inspection_type: doc.inspection_type,
|
inspection_type: doc.inspection_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -364,10 +364,11 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
from frappe.desk.reportview import get_match_cond
|
from frappe.desk.reportview import get_match_cond
|
||||||
|
|
||||||
from_doctype = cstr(filters.get("from"))
|
from_doctype = cstr(filters.get("from"))
|
||||||
|
parent_doctype = cstr(filters.get("parent_doctype"))
|
||||||
if not from_doctype or not frappe.db.exists("DocType", from_doctype):
|
if not from_doctype or not frappe.db.exists("DocType", from_doctype):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
mcond = get_match_cond(from_doctype)
|
mcond = get_match_cond(parent_doctype or from_doctype)
|
||||||
cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')"
|
cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')"
|
||||||
|
|
||||||
if filters.get("parent"):
|
if filters.get("parent"):
|
||||||
|
|||||||
@@ -1793,6 +1793,47 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
|||||||
elif s.id_plant == plant_b.name:
|
elif s.id_plant == plant_b.name:
|
||||||
self.assertEqual(s.actual_qty, 3)
|
self.assertEqual(s.actual_qty, 3)
|
||||||
|
|
||||||
|
def test_serial_no_status_with_backdated_stock_reco(self):
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
|
item_code = self.make_item(
|
||||||
|
"Test Item",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "SERIAL.###",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
reco = create_stock_reconciliation(
|
||||||
|
item_code=item_code,
|
||||||
|
posting_date=add_days(nowdate(), -2),
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=1,
|
||||||
|
rate=80,
|
||||||
|
purpose="Opening Stock",
|
||||||
|
)
|
||||||
|
|
||||||
|
serial_no = get_serial_nos_from_bundle(reco.items[0].serial_and_batch_bundle)[0]
|
||||||
|
|
||||||
|
create_delivery_note(
|
||||||
|
item_code=item_code, warehouse=warehouse, qty=1, rate=100, posting_date=nowdate()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(frappe.get_value("Serial No", serial_no, "status"), "Delivered")
|
||||||
|
|
||||||
|
reco = create_stock_reconciliation(
|
||||||
|
item_code=item_code,
|
||||||
|
posting_date=add_days(nowdate(), -1),
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=1,
|
||||||
|
rate=90,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(frappe.get_value("Serial No", serial_no, "status"), "Delivered")
|
||||||
|
|
||||||
|
|
||||||
def create_batch_item_with_batch(item_name, batch_id):
|
def create_batch_item_with_batch(item_name, batch_id):
|
||||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||||
|
|||||||
@@ -101,49 +101,23 @@ class Warehouse(NestedSet):
|
|||||||
def warn_about_multiple_warehouse_account(self):
|
def warn_about_multiple_warehouse_account(self):
|
||||||
"If Warehouse value is split across multiple accounts, warn."
|
"If Warehouse value is split across multiple accounts, warn."
|
||||||
|
|
||||||
def get_accounts_where_value_is_booked(name):
|
if not frappe.db.count("Stock Ledger Entry", {"warehouse": self.name}):
|
||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
|
||||||
gle = frappe.qb.DocType("GL Entry")
|
|
||||||
ac = frappe.qb.DocType("Account")
|
|
||||||
|
|
||||||
return (
|
|
||||||
frappe.qb.from_(sle)
|
|
||||||
.join(gle)
|
|
||||||
.on(sle.voucher_no == gle.voucher_no)
|
|
||||||
.join(ac)
|
|
||||||
.on(ac.name == gle.account)
|
|
||||||
.select(gle.account)
|
|
||||||
.distinct()
|
|
||||||
.where((sle.warehouse == name) & (ac.account_type == "Stock"))
|
|
||||||
.orderby(sle.creation)
|
|
||||||
.run(as_dict=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.is_new():
|
|
||||||
return
|
return
|
||||||
|
|
||||||
old_wh_account = frappe.db.get_value("Warehouse", self.name, "account")
|
doc_before_save = self.get_doc_before_save()
|
||||||
|
old_wh_account = doc_before_save.account if doc_before_save else None
|
||||||
|
|
||||||
# WH account is being changed or set get all accounts against which wh value is booked
|
if self.is_new() or (self.account and old_wh_account == self.account):
|
||||||
if self.account != old_wh_account:
|
return
|
||||||
accounts = get_accounts_where_value_is_booked(self.name)
|
|
||||||
accounts = [d.account for d in accounts]
|
|
||||||
|
|
||||||
if not accounts or (len(accounts) == 1 and self.account in accounts):
|
frappe.msgprint(
|
||||||
# if same singular account has stock value booked ignore
|
title=_("Warning: Account changed for warehouse"),
|
||||||
return
|
indicator="orange",
|
||||||
|
msg=_(
|
||||||
warning = _("Warehouse's Stock Value has already been booked in the following accounts:")
|
"Stock entries exist with the old account. Changing the account may lead to a mismatch between the warehouse closing balance and the account closing balance. The overall closing balance will still match, but not for the specific account."
|
||||||
account_str = "<br>" + ", ".join(frappe.bold(ac) for ac in accounts)
|
),
|
||||||
reason = "<br><br>" + _(
|
alert=True,
|
||||||
"Booking stock value across multiple accounts will make it harder to track stock and account value."
|
)
|
||||||
)
|
|
||||||
|
|
||||||
frappe.msgprint(
|
|
||||||
warning + account_str + reason,
|
|
||||||
title=_("Multiple Warehouse Accounts"),
|
|
||||||
indicator="orange",
|
|
||||||
)
|
|
||||||
|
|
||||||
def check_if_sle_exists(self):
|
def check_if_sle_exists(self):
|
||||||
return frappe.db.exists("Stock Ledger Entry", {"warehouse": self.name})
|
return frappe.db.exists("Stock Ledger Entry", {"warehouse": self.name})
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ def get_item_warehouse_batch_map(filters, float_precision):
|
|||||||
)
|
)
|
||||||
|
|
||||||
qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision)
|
qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision)
|
||||||
qty_dict.bal_value += flt(d.stock_value_difference, float_precision)
|
qty_dict.bal_value += flt(d.stock_value_difference)
|
||||||
|
|
||||||
return iwb_map
|
return iwb_map
|
||||||
|
|
||||||
|
|||||||
@@ -549,6 +549,16 @@ class update_entries_after:
|
|||||||
previous_sle = get_previous_sle_of_current_voucher(args)
|
previous_sle = get_previous_sle_of_current_voucher(args)
|
||||||
if previous_sle:
|
if previous_sle:
|
||||||
self.prev_sle_dict[(args.get("item_code"), args.get("warehouse"))] = previous_sle
|
self.prev_sle_dict[(args.get("item_code"), args.get("warehouse"))] = previous_sle
|
||||||
|
else:
|
||||||
|
self.prev_sle_dict[(args.get("item_code"), args.get("warehouse"))] = frappe._dict(
|
||||||
|
{
|
||||||
|
"qty_after_transaction": 0.0,
|
||||||
|
"valuation_rate": 0.0,
|
||||||
|
"stock_value": 0.0,
|
||||||
|
"prev_stock_value": 0.0,
|
||||||
|
"stock_queue": [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
warehouse_dict.previous_sle = previous_sle
|
warehouse_dict.previous_sle = previous_sle
|
||||||
|
|
||||||
@@ -1063,34 +1073,6 @@ class update_entries_after:
|
|||||||
sabb_doc.voucher_no = None
|
sabb_doc.voucher_no = None
|
||||||
sabb_doc.cancel()
|
sabb_doc.cancel()
|
||||||
|
|
||||||
if sle.serial_and_batch_bundle and frappe.get_cached_value("Item", sle.item_code, "has_serial_no"):
|
|
||||||
self.update_serial_no_status(sle)
|
|
||||||
|
|
||||||
def update_serial_no_status(self, sle):
|
|
||||||
from erpnext.stock.serial_batch_bundle import get_serial_nos
|
|
||||||
|
|
||||||
serial_nos = get_serial_nos(sle.serial_and_batch_bundle)
|
|
||||||
if not serial_nos:
|
|
||||||
return
|
|
||||||
|
|
||||||
warehouse = None
|
|
||||||
status = "Inactive"
|
|
||||||
|
|
||||||
if sle.actual_qty > 0:
|
|
||||||
warehouse = sle.warehouse
|
|
||||||
status = "Active"
|
|
||||||
|
|
||||||
sn_table = frappe.qb.DocType("Serial No")
|
|
||||||
|
|
||||||
query = (
|
|
||||||
frappe.qb.update(sn_table)
|
|
||||||
.set(sn_table.warehouse, warehouse)
|
|
||||||
.set(sn_table.status, status)
|
|
||||||
.where(sn_table.name.isin(serial_nos))
|
|
||||||
)
|
|
||||||
|
|
||||||
query.run()
|
|
||||||
|
|
||||||
def calculate_valuation_for_serial_batch_bundle(self, sle):
|
def calculate_valuation_for_serial_batch_bundle(self, sle):
|
||||||
if not frappe.db.exists("Serial and Batch Bundle", sle.serial_and_batch_bundle):
|
if not frappe.db.exists("Serial and Batch Bundle", sle.serial_and_batch_bundle):
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user