Merge pull request #34174 from Vishnu7025/coa-import-currency
fix: currency in coa import
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index e23265b..fde4de8 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -395,6 +395,7 @@
class GrossProfitGenerator(object):
def __init__(self, filters=None):
+ self.sle = {}
self.data = []
self.average_buying_rate = {}
self.filters = frappe._dict(filters)
@@ -404,7 +405,6 @@
if filters.group_by == "Invoice":
self.group_items_by_invoice()
- self.load_stock_ledger_entries()
self.load_product_bundle()
self.load_non_stock_items()
self.get_returned_invoice_items()
@@ -633,7 +633,7 @@
return flt(row.qty) * item_rate
else:
- my_sle = self.sle.get((item_code, row.warehouse))
+ my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
if (row.update_stock or row.dn_detail) and my_sle:
parenttype, parent = row.parenttype, row.parent
if row.dn_detail:
@@ -651,7 +651,7 @@
dn["item_row"],
dn["warehouse"],
)
- my_sle = self.sle.get((item_code, warehouse))
+ my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
@@ -667,15 +667,12 @@
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
from frappe.query_builder.functions import Sum
- delivery_note = frappe.qb.DocType("Delivery Note")
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
query = (
- frappe.qb.from_(delivery_note)
- .inner_join(delivery_note_item)
- .on(delivery_note.name == delivery_note_item.parent)
+ frappe.qb.from_(delivery_note_item)
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
- .where(delivery_note.docstatus == 1)
+ .where(delivery_note_item.docstatus == 1)
.where(delivery_note_item.item_code == item_code)
.where(delivery_note_item.against_sales_order == sales_order)
.where(delivery_note_item.so_detail == so_detail)
@@ -947,24 +944,36 @@
"Item", item_code, ["item_name", "description", "item_group", "brand"]
)
- def load_stock_ledger_entries(self):
- res = frappe.db.sql(
- """select item_code, voucher_type, voucher_no,
- voucher_detail_no, stock_value, warehouse, actual_qty as qty
- from `tabStock Ledger Entry`
- where company=%(company)s and is_cancelled = 0
- order by
- item_code desc, warehouse desc, posting_date desc,
- posting_time desc, creation desc""",
- self.filters,
- as_dict=True,
- )
- self.sle = {}
- for r in res:
- if (r.item_code, r.warehouse) not in self.sle:
- self.sle[(r.item_code, r.warehouse)] = []
+ def get_stock_ledger_entries(self, item_code, warehouse):
+ if item_code and warehouse:
+ if (item_code, warehouse) not in self.sle:
+ sle = qb.DocType("Stock Ledger Entry")
+ res = (
+ qb.from_(sle)
+ .select(
+ sle.item_code,
+ sle.voucher_type,
+ sle.voucher_no,
+ sle.voucher_detail_no,
+ sle.stock_value,
+ sle.warehouse,
+ sle.actual_qty.as_("qty"),
+ )
+ .where(
+ (sle.company == self.filters.company)
+ & (sle.item_code == item_code)
+ & (sle.warehouse == warehouse)
+ & (sle.is_cancelled == 0)
+ )
+ .orderby(sle.item_code)
+ .orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc)
+ .run(as_dict=True)
+ )
- self.sle[(r.item_code, r.warehouse)].append(r)
+ self.sle[(item_code, warehouse)] = res
+
+ return self.sle[(item_code, warehouse)]
+ return []
def load_product_bundle(self):
self.product_bundles = {}
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
index c28b2b3..3d2dff1 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
@@ -43,9 +43,9 @@
if(frm.doc.depreciation_method != "Manual") return;
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
- $.each(frm.doc.schedules || [], function(i, row) {
+
+ $.each(frm.doc.depreciation_schedule || [], function(i, row) {
accumulated_depreciation += flt(row.depreciation_amount);
- frappe.model.set_value(row.doctype, row.name,
- "accumulated_depreciation_amount", accumulated_depreciation);
+ frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation);
})
};
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index 898c482..d38508d 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -10,7 +10,9 @@
"asset",
"naming_series",
"column_break_2",
+ "gross_purchase_amount",
"opening_accumulated_depreciation",
+ "number_of_depreciations_booked",
"finance_book",
"finance_book_id",
"depreciation_details_section",
@@ -148,18 +150,36 @@
"read_only": 1
},
{
- "depends_on": "opening_accumulated_depreciation",
"fieldname": "opening_accumulated_depreciation",
"fieldtype": "Currency",
+ "hidden": 1,
"label": "Opening Accumulated Depreciation",
"options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "gross_purchase_amount",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Gross Purchase Amount",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "number_of_depreciations_booked",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "Number of Depreciations Booked",
+ "print_hide": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-01-16 21:08:21.421260",
+ "modified": "2023-02-26 16:37:23.734806",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Depreciation Schedule",
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 6f02662..b75fbcb 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -4,7 +4,15 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import add_days, add_months, cint, flt, get_last_day, is_last_day_of_the_month
+from frappe.utils import (
+ add_days,
+ add_months,
+ cint,
+ flt,
+ get_last_day,
+ getdate,
+ is_last_day_of_the_month,
+)
class AssetDepreciationSchedule(Document):
@@ -83,15 +91,58 @@
date_of_return=None,
update_asset_finance_book_row=True,
):
+ have_asset_details_been_modified = self.have_asset_details_been_modified(asset_doc)
+ not_manual_depr_or_have_manual_depr_details_been_modified = (
+ self.not_manual_depr_or_have_manual_depr_details_been_modified(row)
+ )
+
self.set_draft_asset_depr_schedule_details(asset_doc, row)
- self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
- self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
+
+ if self.should_prepare_depreciation_schedule(
+ have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
+ ):
+ self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
+ self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
+
+ def have_asset_details_been_modified(self, asset_doc):
+ return (
+ asset_doc.gross_purchase_amount != self.gross_purchase_amount
+ or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
+ or asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked
+ )
+
+ def not_manual_depr_or_have_manual_depr_details_been_modified(self, row):
+ return (
+ self.depreciation_method != "Manual"
+ or row.total_number_of_depreciations != self.total_number_of_depreciations
+ or row.frequency_of_depreciation != self.frequency_of_depreciation
+ or getdate(row.depreciation_start_date) != self.get("depreciation_schedule")[0].schedule_date
+ or row.expected_value_after_useful_life != self.expected_value_after_useful_life
+ )
+
+ def should_prepare_depreciation_schedule(
+ self, have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
+ ):
+ if not self.get("depreciation_schedule"):
+ return True
+
+ old_asset_depr_schedule_doc = self.get_doc_before_save()
+
+ if self.docstatus != 0 and not old_asset_depr_schedule_doc:
+ return True
+
+ if have_asset_details_been_modified or not_manual_depr_or_have_manual_depr_details_been_modified:
+ return True
+
+ return False
def set_draft_asset_depr_schedule_details(self, asset_doc, row):
self.asset = asset_doc.name
self.finance_book = row.finance_book
self.finance_book_id = row.idx
self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation
+ self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked
+ self.gross_purchase_amount = asset_doc.gross_purchase_amount
self.depreciation_method = row.depreciation_method
self.total_number_of_depreciations = row.total_number_of_depreciations
self.frequency_of_depreciation = row.frequency_of_depreciation
@@ -102,7 +153,7 @@
def make_depr_schedule(
self, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
):
- if row.depreciation_method != "Manual" and not self.get("depreciation_schedule"):
+ if not self.get("depreciation_schedule"):
self.depreciation_schedule = []
if not asset_doc.available_for_use_date:
@@ -293,7 +344,9 @@
ignore_booked_entry=False,
):
straight_line_idx = [
- d.idx for d in self.get("depreciation_schedule") if d.depreciation_method == "Straight Line"
+ d.idx
+ for d in self.get("depreciation_schedule")
+ if d.depreciation_method == "Straight Line" or d.depreciation_method == "Manual"
]
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index fc6793a..9fcb769 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -252,7 +252,6 @@
child.parent = par.name and par.docstatus = 1
and par.is_return = 1 and par.return_against = %s
group by item_code
- for update
""".format(
column, doc.doctype, doc.doctype
),
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js
index 7beecac..e7f67ca 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js
@@ -25,8 +25,9 @@
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
+
if (column.id == "item") {
- if (data["enough_parts_to_build"] > 0) {
+ if (data["in_stock_qty"] >= data["required_qty"]) {
value = `<a style='color:green' href="/app/item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
} else {
value = `<a style='color:red' href="/app/item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
index 371ecbc..5c46bf3 100644
--- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
+++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
@@ -27,7 +27,13 @@
records = (
frappe.qb.from_(asset)
- .select(asset.name, asset.opening_accumulated_depreciation, asset.docstatus)
+ .select(
+ asset.name,
+ asset.opening_accumulated_depreciation,
+ asset.gross_purchase_amount,
+ asset.number_of_depreciations_booked,
+ asset.docstatus,
+ )
.where(asset.calculate_depreciation == 1)
.where(asset.docstatus < 2)
).run(as_dict=True)
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index fb64772..ee07525 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -309,9 +309,12 @@
make_work_order() {
var me = this;
- this.frm.call({
- doc: this.frm.doc,
- method: 'get_work_order_items',
+ me.frm.call({
+ method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
+ args: {
+ sales_order: this.frm.docname,
+ },
+ freeze: true,
callback: function(r) {
if(!r.message) {
frappe.msgprint({
@@ -321,14 +324,7 @@
});
return;
}
- else if(!r.message) {
- frappe.msgprint({
- title: __('Work Order not created'),
- message: __('Work Order already created for all items with BOM'),
- indicator: 'orange'
- });
- return;
- } else {
+ else {
const fields = [{
label: 'Items',
fieldtype: 'Table',
@@ -429,9 +425,9 @@
make_raw_material_request() {
var me = this;
this.frm.call({
- doc: this.frm.doc,
- method: 'get_work_order_items',
+ method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
args: {
+ sales_order: this.frm.docname,
for_raw_material_request: 1
},
callback: function(r) {
@@ -450,6 +446,7 @@
}
make_raw_material_request_dialog(r) {
+ var me = this;
var fields = [
{fieldtype:'Check', fieldname:'include_exploded_items',
label: __('Include Exploded Items')},
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index ca6a51a..385d0f3 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -6,11 +6,12 @@
import frappe
import frappe.utils
-from frappe import _
+from frappe import _, qb
from frappe.contacts.doctype.address.address import get_company_address
from frappe.desk.notifications import clear_doctype_notifications
from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values
+from frappe.query_builder.functions import Sum
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
@@ -414,51 +415,6 @@
self.indicator_color = "green"
self.indicator_title = _("Paid")
- @frappe.whitelist()
- def get_work_order_items(self, for_raw_material_request=0):
- """Returns items with BOM that already do not have a linked work order"""
- items = []
- item_codes = [i.item_code for i in self.items]
- product_bundle_parents = [
- pb.new_item_code
- for pb in frappe.get_all(
- "Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
- )
- ]
-
- for table in [self.items, self.packed_items]:
- for i in table:
- bom = get_default_bom(i.item_code)
- stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
-
- if not for_raw_material_request:
- total_work_order_qty = flt(
- frappe.db.sql(
- """select sum(qty) from `tabWork Order`
- where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2""",
- (i.item_code, self.name, i.name),
- )[0][0]
- )
- pending_qty = stock_qty - total_work_order_qty
- else:
- pending_qty = stock_qty
-
- if pending_qty and i.item_code not in product_bundle_parents:
- items.append(
- dict(
- name=i.name,
- item_code=i.item_code,
- description=i.description,
- bom=bom or "",
- warehouse=i.warehouse,
- pending_qty=pending_qty,
- required_qty=pending_qty if for_raw_material_request else 0,
- sales_order_item=i.name,
- )
- )
-
- return items
-
def on_recurring(self, reference_doc, auto_repeat_doc):
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
@@ -1350,3 +1306,57 @@
return
frappe.db.set_value("Sales Order Item", sales_order_item, "produced_qty", total_produced_qty)
+
+
+@frappe.whitelist()
+def get_work_order_items(sales_order, for_raw_material_request=0):
+ """Returns items with BOM that already do not have a linked work order"""
+ if sales_order:
+ so = frappe.get_doc("Sales Order", sales_order)
+
+ wo = qb.DocType("Work Order")
+
+ items = []
+ item_codes = [i.item_code for i in so.items]
+ product_bundle_parents = [
+ pb.new_item_code
+ for pb in frappe.get_all(
+ "Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
+ )
+ ]
+
+ for table in [so.items, so.packed_items]:
+ for i in table:
+ bom = get_default_bom(i.item_code)
+ stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
+
+ if not for_raw_material_request:
+ total_work_order_qty = flt(
+ qb.from_(wo)
+ .select(Sum(wo.qty))
+ .where(
+ (wo.production_item == i.item_code)
+ & (wo.sales_order == so.name) * (wo.sales_order_item == i.name)
+ & (wo.docstatus.lte(2))
+ )
+ .run()[0][0]
+ )
+ pending_qty = stock_qty - total_work_order_qty
+ else:
+ pending_qty = stock_qty
+
+ if pending_qty and i.item_code not in product_bundle_parents:
+ items.append(
+ dict(
+ name=i.name,
+ item_code=i.item_code,
+ description=i.description,
+ bom=bom or "",
+ warehouse=i.warehouse,
+ pending_qty=pending_qty,
+ required_qty=pending_qty if for_raw_material_request else 0,
+ sales_order_item=i.name,
+ )
+ )
+
+ return items
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index d4d7c58..627914f 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1217,6 +1217,8 @@
self.assertTrue(si.get("payment_schedule"))
def test_make_work_order(self):
+ from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
+
# Make a new Sales Order
so = make_sales_order(
**{
@@ -1230,7 +1232,7 @@
# Raise Work Orders
po_items = []
so_item_name = {}
- for item in so.get_work_order_items():
+ for item in get_work_order_items(so.name):
po_items.append(
{
"warehouse": item.get("warehouse"),
@@ -1448,6 +1450,7 @@
from erpnext.controllers.item_variant import create_variant
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+ from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
make_item( # template item
"Test-WO-Tshirt",
@@ -1487,7 +1490,7 @@
]
}
)
- wo_items = so.get_work_order_items()
+ wo_items = get_work_order_items(so.name)
self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
@@ -1497,6 +1500,8 @@
self.assertEqual(wo_items[1].get("bom"), template_bom.name)
def test_request_for_raw_materials(self):
+ from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
+
item = make_item(
"_Test Finished Item",
{
@@ -1529,7 +1534,7 @@
so = make_sales_order(**{"item_list": [{"item_code": item.item_code, "qty": 1, "rate": 1000}]})
so.submit()
mr_dict = frappe._dict()
- items = so.get_work_order_items(1)
+ items = get_work_order_items(so.name, 1)
mr_dict["items"] = items
mr_dict["include_exploded_items"] = 0
mr_dict["ignore_existing_ordered_qty"] = 1
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 8ff01f5..5ce6e9c 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -418,8 +418,6 @@
callback: function(r) {
if(r.message) {
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
- } else {
- frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
}
}
});
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 5bcb05a..9a9ddf4 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -33,6 +33,9 @@
'Material Request': () => {
open_form(frm, "Material Request", "Material Request Item", "items");
},
+ 'Stock Entry': () => {
+ open_form(frm, "Stock Entry", "Stock Entry Detail", "items");
+ },
};
},
@@ -893,6 +896,9 @@
new_child_doc.item_name = frm.doc.item_name;
new_child_doc.uom = frm.doc.stock_uom;
new_child_doc.description = frm.doc.description;
+ if (!new_child_doc.qty) {
+ new_child_doc.qty = 1.0;
+ }
frappe.run_serially([
() => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc),
diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js
index 12cf6cf..ce489ff 100644
--- a/erpnext/stock/doctype/item_price/item_price.js
+++ b/erpnext/stock/doctype/item_price/item_price.js
@@ -2,7 +2,18 @@
// License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Item Price", {
- onload: function (frm) {
+ setup(frm) {
+ frm.set_query("item_code", function() {
+ return {
+ filters: {
+ "disabled": 0,
+ "has_variants": 0
+ }
+ };
+ });
+ },
+
+ onload(frm) {
// Fetch price list details
frm.add_fetch("price_list", "buying", "buying");
frm.add_fetch("price_list", "selling", "selling");
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index bcd31ad..54d1ae6 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -3,7 +3,7 @@
import frappe
-from frappe import _
+from frappe import _, bold
from frappe.model.document import Document
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Cast_
@@ -21,6 +21,7 @@
self.update_price_list_details()
self.update_item_details()
self.check_duplicates()
+ self.validate_item_template()
def validate_item(self):
if not frappe.db.exists("Item", self.item_code):
@@ -49,6 +50,12 @@
"Item", self.item_code, ["item_name", "description"]
)
+ def validate_item_template(self):
+ if frappe.get_cached_value("Item", self.item_code, "has_variants"):
+ msg = f"Item Price cannot be created for the template item {bold(self.item_code)}"
+
+ frappe.throw(_(msg))
+
def check_duplicates(self):
item_price = frappe.qb.DocType("Item Price")
diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py
index 30d933e..8fd4938 100644
--- a/erpnext/stock/doctype/item_price/test_item_price.py
+++ b/erpnext/stock/doctype/item_price/test_item_price.py
@@ -16,6 +16,28 @@
frappe.db.sql("delete from `tabItem Price`")
make_test_records_for_doctype("Item Price", force=True)
+ def test_template_item_price(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ item = make_item(
+ "Test Template Item 1",
+ {
+ "has_variants": 1,
+ "variant_based_on": "Manufacturer",
+ },
+ )
+
+ doc = frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": "_Test Price List",
+ "item_code": item.name,
+ "price_list_rate": 100,
+ }
+ )
+
+ self.assertRaises(frappe.ValidationError, doc.save)
+
def test_duplicate_item(self):
doc = frappe.copy_doc(test_records[0])
self.assertRaises(ItemPriceDuplicateItem, doc.save)
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index b3af309..111a0861 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -55,7 +55,6 @@
self.get_items_from_purchase_receipts()
self.set_applicable_charges_on_item()
- self.validate_applicable_charges_for_item()
def check_mandatory(self):
if not self.get("purchase_receipts"):
@@ -115,6 +114,13 @@
total_item_cost += item.get(based_on_field)
for item in self.get("items"):
+ if not total_item_cost and not item.get(based_on_field):
+ frappe.throw(
+ _(
+ "It's not possible to distribute charges equally when total amount is zero, please set 'Distribute Charges Based On' as 'Quantity'"
+ )
+ )
+
item.applicable_charges = flt(
flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
item.precision("applicable_charges"),
@@ -162,6 +168,7 @@
)
def on_submit(self):
+ self.validate_applicable_charges_for_item()
self.update_landed_cost()
def on_cancel(self):
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 979b5c4..00fa168 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -175,6 +175,59 @@
)
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
+ def test_landed_cost_voucher_for_zero_purchase_rate(self):
+ "Test impact of LCV on future stock balances."
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ item = make_item("LCV Stock Item", {"is_stock_item": 1})
+ warehouse = "Stores - _TC"
+
+ pr = make_purchase_receipt(
+ item_code=item.name,
+ warehouse=warehouse,
+ qty=10,
+ rate=0,
+ posting_date=add_days(frappe.utils.nowdate(), -2),
+ )
+
+ self.assertEqual(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
+ "stock_value_difference",
+ ),
+ 0,
+ )
+
+ lcv = make_landed_cost_voucher(
+ company=pr.company,
+ receipt_document_type="Purchase Receipt",
+ receipt_document=pr.name,
+ charges=100,
+ distribute_charges_based_on="Distribute Manually",
+ do_not_save=True,
+ )
+
+ lcv.get_items_from_purchase_receipts()
+ lcv.items[0].applicable_charges = 100
+ lcv.save()
+ lcv.submit()
+
+ self.assertTrue(
+ frappe.db.exists(
+ "Stock Ledger Entry",
+ {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
+ )
+ )
+ self.assertEqual(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
+ "stock_value_difference",
+ ),
+ 100,
+ )
+
def test_landed_cost_voucher_against_purchase_invoice(self):
pi = make_purchase_invoice(
@@ -516,7 +569,7 @@
lcv = frappe.new_doc("Landed Cost Voucher")
lcv.company = args.company or "_Test Company"
- lcv.distribute_charges_based_on = "Amount"
+ lcv.distribute_charges_based_on = args.distribute_charges_based_on or "Amount"
lcv.set(
"purchase_receipts",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index e6025ab..c8a4bd3 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -473,7 +473,7 @@
)
divisional_loss = flt(
- valuation_amount_as_per_doc - stock_value_diff, d.precision("base_net_amount")
+ valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount")
)
if divisional_loss:
@@ -1134,13 +1134,25 @@
account.expense_account, {"amount": 0.0, "base_amount": 0.0}
)
- item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][
- "amount"
- ] += (account.amount * item.get(based_on_field) / total_item_cost)
+ if total_item_cost > 0:
+ item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
+ account.expense_account
+ ]["amount"] += (
+ account.amount * item.get(based_on_field) / total_item_cost
+ )
- item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][
- "base_amount"
- ] += (account.base_amount * item.get(based_on_field) / total_item_cost)
+ item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
+ account.expense_account
+ ]["base_amount"] += (
+ account.base_amount * item.get(based_on_field) / total_item_cost
+ )
+ else:
+ item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
+ account.expense_account
+ ]["amount"] += item.applicable_charges
+ item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
+ account.expense_account
+ ]["base_amount"] += item.applicable_charges
return item_account_wise_cost