mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-02 11:49:10 +00:00
Merge pull request #31414 from frappe/version-13-hotfix
chore: weekly version-13 release
This commit is contained in:
@@ -36,10 +36,15 @@ class PricingRule(Document):
|
|||||||
self.margin_rate_or_amount = 0.0
|
self.margin_rate_or_amount = 0.0
|
||||||
|
|
||||||
def validate_duplicate_apply_on(self):
|
def validate_duplicate_apply_on(self):
|
||||||
field = apply_on_dict.get(self.apply_on)
|
if self.apply_on != "Transaction":
|
||||||
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
|
apply_on_table = apply_on_dict.get(self.apply_on)
|
||||||
if len(values) != len(set(values)):
|
if not apply_on_table:
|
||||||
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
return
|
||||||
|
|
||||||
|
apply_on_field = frappe.scrub(self.apply_on)
|
||||||
|
values = [d.get(apply_on_field) for d in self.get(apply_on_table) if d.get(apply_on_field)]
|
||||||
|
if len(values) != len(set(values)):
|
||||||
|
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
||||||
|
|
||||||
def validate_mandatory(self):
|
def validate_mandatory(self):
|
||||||
for apply_on, field in apply_on_dict.items():
|
for apply_on, field in apply_on_dict.items():
|
||||||
|
|||||||
@@ -161,17 +161,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
def check_conversion_rate(self):
|
|
||||||
default_currency = erpnext.get_company_currency(self.company)
|
|
||||||
if not default_currency:
|
|
||||||
throw(_("Please enter default currency in Company Master"))
|
|
||||||
if (
|
|
||||||
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
|
|
||||||
or not self.conversion_rate
|
|
||||||
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
|
|
||||||
):
|
|
||||||
throw(_("Conversion rate cannot be 0 or 1"))
|
|
||||||
|
|
||||||
def validate_credit_to_acc(self):
|
def validate_credit_to_acc(self):
|
||||||
if not self.credit_to:
|
if not self.credit_to:
|
||||||
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
|
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
|
||||||
|
|||||||
@@ -1582,6 +1582,26 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
company.enable_provisional_accounting_for_non_stock_items = 0
|
company.enable_provisional_accounting_for_non_stock_items = 0
|
||||||
company.save()
|
company.save()
|
||||||
|
|
||||||
|
def test_item_less_defaults(self):
|
||||||
|
|
||||||
|
pi = frappe.new_doc("Purchase Invoice")
|
||||||
|
pi.supplier = "_Test Supplier"
|
||||||
|
pi.company = "_Test Company"
|
||||||
|
pi.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_name": "Opening item",
|
||||||
|
"qty": 1,
|
||||||
|
"uom": "Tonne",
|
||||||
|
"stock_uom": "Kg",
|
||||||
|
"rate": 1000,
|
||||||
|
"expense_account": "Stock Received But Not Billed - _TC",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pi.save()
|
||||||
|
self.assertEqual(pi.items[0].conversion_factor, 1000)
|
||||||
|
|
||||||
|
|
||||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||||
gl_entries = frappe.db.sql(
|
gl_entries = frappe.db.sql(
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class SalesInvoice(SellingController):
|
|||||||
self.set_income_account_for_fixed_assets()
|
self.set_income_account_for_fixed_assets()
|
||||||
self.validate_item_cost_centers()
|
self.validate_item_cost_centers()
|
||||||
self.validate_income_account()
|
self.validate_income_account()
|
||||||
|
self.check_conversion_rate()
|
||||||
|
|
||||||
validate_inter_company_party(
|
validate_inter_company_party(
|
||||||
self.doctype, self.customer, self.company, self.inter_company_invoice_reference
|
self.doctype, self.customer, self.company, self.inter_company_invoice_reference
|
||||||
|
|||||||
@@ -1612,6 +1612,17 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(gle)
|
self.assertTrue(gle)
|
||||||
|
|
||||||
|
def test_invoice_exchange_rate(self):
|
||||||
|
si = create_sales_invoice(
|
||||||
|
customer="_Test Customer USD",
|
||||||
|
debit_to="_Test Receivable USD - _TC",
|
||||||
|
currency="USD",
|
||||||
|
conversion_rate=1,
|
||||||
|
do_not_save=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
|
||||||
def test_invalid_currency(self):
|
def test_invalid_currency(self):
|
||||||
# Customer currency = USD
|
# Customer currency = USD
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ class TestUtils(unittest.TestCase):
|
|||||||
stock_entry = {"item": item, "to_warehouse": "_Test Warehouse - _TC", "qty": 1, "rate": 10}
|
stock_entry = {"item": item, "to_warehouse": "_Test Warehouse - _TC", "qty": 1, "rate": 10}
|
||||||
|
|
||||||
se1 = make_stock_entry(posting_date="2022-01-01", **stock_entry)
|
se1 = make_stock_entry(posting_date="2022-01-01", **stock_entry)
|
||||||
se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)
|
|
||||||
se3 = make_stock_entry(posting_date="2022-03-01", **stock_entry)
|
se3 = make_stock_entry(posting_date="2022-03-01", **stock_entry)
|
||||||
|
se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)
|
||||||
|
|
||||||
for doc in (se1, se2, se3):
|
for doc in (se1, se2, se3):
|
||||||
vouchers.append((doc.doctype, doc.name))
|
vouchers.append((doc.doctype, doc.name))
|
||||||
|
|||||||
@@ -3,13 +3,23 @@
|
|||||||
|
|
||||||
|
|
||||||
from json import loads
|
from json import loads
|
||||||
from typing import List, Tuple
|
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe import _, throw
|
from frappe import _, throw
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
|
from frappe.utils import (
|
||||||
|
cint,
|
||||||
|
create_batch,
|
||||||
|
cstr,
|
||||||
|
flt,
|
||||||
|
formatdate,
|
||||||
|
get_number_format_info,
|
||||||
|
getdate,
|
||||||
|
now,
|
||||||
|
nowdate,
|
||||||
|
)
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -19,6 +29,9 @@ from erpnext.accounts.doctype.account.account import get_account_currency # noq
|
|||||||
from erpnext.stock import get_warehouse_account_map
|
from erpnext.stock import get_warehouse_account_map
|
||||||
from erpnext.stock.utils import get_stock_value_on
|
from erpnext.stock.utils import get_stock_value_on
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import RepostItemValuation
|
||||||
|
|
||||||
|
|
||||||
class FiscalYearError(frappe.ValidationError):
|
class FiscalYearError(frappe.ValidationError):
|
||||||
pass
|
pass
|
||||||
@@ -28,6 +41,9 @@ class PaymentEntryUnlinkError(frappe.ValidationError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
GL_REPOSTING_CHUNK = 100
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_fiscal_year(
|
def get_fiscal_year(
|
||||||
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
|
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
|
||||||
@@ -1122,38 +1138,55 @@ def update_gl_entries_after(
|
|||||||
|
|
||||||
|
|
||||||
def repost_gle_for_stock_vouchers(
|
def repost_gle_for_stock_vouchers(
|
||||||
stock_vouchers, posting_date, company=None, warehouse_account=None
|
stock_vouchers: List[Tuple[str, str]],
|
||||||
|
posting_date: str,
|
||||||
|
company: Optional[str] = None,
|
||||||
|
warehouse_account=None,
|
||||||
|
repost_doc: Optional["RepostItemValuation"] = None,
|
||||||
):
|
):
|
||||||
if not stock_vouchers:
|
if not stock_vouchers:
|
||||||
return
|
return
|
||||||
|
|
||||||
def _delete_gl_entries(voucher_type, voucher_no):
|
|
||||||
frappe.db.sql(
|
|
||||||
"""delete from `tabGL Entry`
|
|
||||||
where voucher_type=%s and voucher_no=%s""",
|
|
||||||
(voucher_type, voucher_no),
|
|
||||||
)
|
|
||||||
|
|
||||||
stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
|
|
||||||
|
|
||||||
if not warehouse_account:
|
if not warehouse_account:
|
||||||
warehouse_account = get_warehouse_account_map(company)
|
warehouse_account = get_warehouse_account_map(company)
|
||||||
|
|
||||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2
|
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2
|
||||||
|
|
||||||
gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
|
stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
|
||||||
for voucher_type, voucher_no in stock_vouchers:
|
if repost_doc and repost_doc.gl_reposting_index:
|
||||||
existing_gle = gle.get((voucher_type, voucher_no), [])
|
# Restore progress
|
||||||
voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no)
|
stock_vouchers = stock_vouchers[cint(repost_doc.gl_reposting_index) :]
|
||||||
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
|
|
||||||
if expected_gle:
|
for stock_vouchers_chunk in create_batch(stock_vouchers, GL_REPOSTING_CHUNK):
|
||||||
if not existing_gle or not compare_existing_and_expected_gle(
|
gle = get_voucherwise_gl_entries(stock_vouchers_chunk, posting_date)
|
||||||
existing_gle, expected_gle, precision
|
for voucher_type, voucher_no in stock_vouchers_chunk:
|
||||||
):
|
existing_gle = gle.get((voucher_type, voucher_no), [])
|
||||||
|
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
|
||||||
|
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
|
||||||
|
if expected_gle:
|
||||||
|
if not existing_gle or not compare_existing_and_expected_gle(
|
||||||
|
existing_gle, expected_gle, precision
|
||||||
|
):
|
||||||
|
_delete_gl_entries(voucher_type, voucher_no)
|
||||||
|
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
|
||||||
|
else:
|
||||||
_delete_gl_entries(voucher_type, voucher_no)
|
_delete_gl_entries(voucher_type, voucher_no)
|
||||||
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
|
|
||||||
else:
|
if not frappe.flags.in_test:
|
||||||
_delete_gl_entries(voucher_type, voucher_no)
|
frappe.db.commit()
|
||||||
|
|
||||||
|
if repost_doc:
|
||||||
|
repost_doc.db_set(
|
||||||
|
"gl_reposting_index", cint(repost_doc.gl_reposting_index) + len(stock_vouchers_chunk)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_gl_entries(voucher_type, voucher_no):
|
||||||
|
frappe.db.sql(
|
||||||
|
"""delete from `tabGL Entry`
|
||||||
|
where voucher_type=%s and voucher_no=%s""",
|
||||||
|
(voucher_type, voucher_no),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def sort_stock_vouchers_by_posting_date(
|
def sort_stock_vouchers_by_posting_date(
|
||||||
@@ -1167,6 +1200,9 @@ def sort_stock_vouchers_by_posting_date(
|
|||||||
.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
|
.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
|
||||||
.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
|
.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
|
||||||
.groupby(sle.voucher_type, sle.voucher_no)
|
.groupby(sle.voucher_type, sle.voucher_no)
|
||||||
|
.orderby(sle.posting_date)
|
||||||
|
.orderby(sle.posting_time)
|
||||||
|
.orderby(sle.creation)
|
||||||
).run(as_dict=True)
|
).run(as_dict=True)
|
||||||
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]
|
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ from erpnext.controllers.print_settings import (
|
|||||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
from erpnext.controllers.sales_and_purchase_return import validate_return
|
||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
from erpnext.setup.utils import get_exchange_rate
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
from erpnext.stock.doctype.item.item import get_uom_conv_factor
|
||||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||||
from erpnext.stock.get_item_details import (
|
from erpnext.stock.get_item_details import (
|
||||||
_get_item_tax_template,
|
_get_item_tax_template,
|
||||||
@@ -549,6 +550,15 @@ class AccountsController(TransactionBase):
|
|||||||
if ret.get("pricing_rules"):
|
if ret.get("pricing_rules"):
|
||||||
self.apply_pricing_rule_on_items(item, ret)
|
self.apply_pricing_rule_on_items(item, ret)
|
||||||
self.set_pricing_rule_details(item, ret)
|
self.set_pricing_rule_details(item, ret)
|
||||||
|
else:
|
||||||
|
# Transactions line item without item code
|
||||||
|
|
||||||
|
uom = item.get("uom")
|
||||||
|
stock_uom = item.get("stock_uom")
|
||||||
|
if bool(uom) != bool(stock_uom): # xor
|
||||||
|
item.stock_uom = item.uom = uom or stock_uom
|
||||||
|
|
||||||
|
item.conversion_factor = get_uom_conv_factor(item.get("uom"), item.get("stock_uom"))
|
||||||
|
|
||||||
if self.doctype == "Purchase Invoice":
|
if self.doctype == "Purchase Invoice":
|
||||||
self.set_expense_account(for_validate)
|
self.set_expense_account(for_validate)
|
||||||
@@ -1836,6 +1846,17 @@ class AccountsController(TransactionBase):
|
|||||||
jv.save()
|
jv.save()
|
||||||
jv.submit()
|
jv.submit()
|
||||||
|
|
||||||
|
def check_conversion_rate(self):
|
||||||
|
default_currency = erpnext.get_company_currency(self.company)
|
||||||
|
if not default_currency:
|
||||||
|
throw(_("Please enter default currency in Company Master"))
|
||||||
|
if (
|
||||||
|
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
|
||||||
|
or not self.conversion_rate
|
||||||
|
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
|
||||||
|
):
|
||||||
|
throw(_("Conversion rate cannot be 0 or 1"))
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_tax_rate(account_head):
|
def get_tax_rate(account_head):
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ class StockController(AccountsController):
|
|||||||
"against": warehouse_account[sle.warehouse]["account"],
|
"against": warehouse_account[sle.warehouse]["account"],
|
||||||
"cost_center": item_row.cost_center,
|
"cost_center": item_row.cost_center,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"credit": flt(sle.stock_value_difference, precision),
|
"debit": -1 * flt(sle.stock_value_difference, precision),
|
||||||
"project": item_row.get("project") or self.get("project"),
|
"project": item_row.get("project") or self.get("project"),
|
||||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"autoname": "autoincrement",
|
"autoname": "hash",
|
||||||
"creation": "2022-05-31 17:34:39.825537",
|
"creation": "2022-05-31 17:34:39.825537",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-06 14:50:35.161062",
|
"modified": "2022-06-20 15:10:15.826571",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Update Batch",
|
"name": "BOM Update Batch",
|
||||||
@@ -50,6 +50,5 @@
|
|||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC"
|
||||||
"states": []
|
|
||||||
}
|
}
|
||||||
@@ -572,7 +572,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
is_pos: cint(me.frm.doc.is_pos),
|
is_pos: cint(me.frm.doc.is_pos),
|
||||||
is_return: cint(me.frm.doc.is_return),
|
is_return: cint(me.frm.doc.is_return),
|
||||||
is_subcontracted: me.frm.doc.is_subcontracted,
|
is_subcontracted: me.frm.doc.is_subcontracted,
|
||||||
transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
|
|
||||||
ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,
|
ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,
|
||||||
doctype: me.frm.doc.doctype,
|
doctype: me.frm.doc.doctype,
|
||||||
name: me.frm.doc.name,
|
name: me.frm.doc.name,
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ def validate_eligibility(doc):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
invalid_company = not frappe.db.get_value("E Invoice User", {"company": doc.get("company")})
|
invalid_company = not frappe.db.get_value("E Invoice User", {"company": doc.get("company")})
|
||||||
|
invalid_company_gstin = not frappe.db.get_value(
|
||||||
|
"E Invoice User", {"gstin": doc.get("company_gstin")}
|
||||||
|
)
|
||||||
invalid_supply_type = doc.get("gst_category") not in [
|
invalid_supply_type = doc.get("gst_category") not in [
|
||||||
"Registered Regular",
|
"Registered Regular",
|
||||||
"Registered Composition",
|
"Registered Composition",
|
||||||
@@ -72,6 +75,7 @@ def validate_eligibility(doc):
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
invalid_company
|
invalid_company
|
||||||
|
or invalid_company_gstin
|
||||||
or invalid_supply_type
|
or invalid_supply_type
|
||||||
or company_transaction
|
or company_transaction
|
||||||
or no_taxes_applied
|
or no_taxes_applied
|
||||||
|
|||||||
@@ -377,6 +377,12 @@ def create_internal_customer(
|
|||||||
if not allowed_to_interact_with:
|
if not allowed_to_interact_with:
|
||||||
allowed_to_interact_with = represents_company
|
allowed_to_interact_with = represents_company
|
||||||
|
|
||||||
|
exisiting_representative = frappe.db.get_value(
|
||||||
|
"Customer", {"represents_company": represents_company}
|
||||||
|
)
|
||||||
|
if exisiting_representative:
|
||||||
|
return exisiting_representative
|
||||||
|
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
customer = frappe.get_doc(
|
customer = frappe.get_doc(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -331,7 +331,7 @@
|
|||||||
"show_seconds": 1
|
"show_seconds": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name",
|
"depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name",
|
||||||
"fieldname": "col_break98",
|
"fieldname": "col_break98",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"show_days": 1,
|
"show_days": 1,
|
||||||
@@ -357,7 +357,7 @@
|
|||||||
"show_seconds": 1
|
"show_seconds": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name",
|
"depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name",
|
||||||
"fieldname": "customer_group",
|
"fieldname": "customer_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@@ -1174,7 +1174,7 @@
|
|||||||
"idx": 82,
|
"idx": 82,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-11 20:35:32.635804",
|
"modified": "2022-06-15 20:35:32.635804",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation",
|
"name": "Quotation",
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class Quotation(SellingController):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
|
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
|
||||||
if not self.has_sales_order():
|
if not (self.is_fully_ordered() or self.is_partially_ordered()):
|
||||||
get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"])
|
get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"])
|
||||||
lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons]
|
lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons]
|
||||||
frappe.db.set(self, "status", "Lost")
|
frappe.db.set(self, "status", "Lost")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
|||||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||||
|
from erpnext.stock.get_item_details import get_default_bom
|
||||||
from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty
|
from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty
|
||||||
|
|
||||||
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||||
@@ -493,8 +494,9 @@ class SalesOrder(SellingController):
|
|||||||
|
|
||||||
for table in [self.items, self.packed_items]:
|
for table in [self.items, self.packed_items]:
|
||||||
for i in table:
|
for i in table:
|
||||||
bom = get_default_bom_item(i.item_code)
|
bom = get_default_bom(i.item_code)
|
||||||
stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
|
stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
|
||||||
|
|
||||||
if not for_raw_material_request:
|
if not for_raw_material_request:
|
||||||
total_work_order_qty = flt(
|
total_work_order_qty = flt(
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
@@ -508,32 +510,19 @@ class SalesOrder(SellingController):
|
|||||||
pending_qty = stock_qty
|
pending_qty = stock_qty
|
||||||
|
|
||||||
if pending_qty and i.item_code not in product_bundle_parents:
|
if pending_qty and i.item_code not in product_bundle_parents:
|
||||||
if bom:
|
items.append(
|
||||||
items.append(
|
dict(
|
||||||
dict(
|
name=i.name,
|
||||||
name=i.name,
|
item_code=i.item_code,
|
||||||
item_code=i.item_code,
|
description=i.description,
|
||||||
description=i.description,
|
bom=bom or "",
|
||||||
bom=bom,
|
warehouse=i.warehouse,
|
||||||
warehouse=i.warehouse,
|
pending_qty=pending_qty,
|
||||||
pending_qty=pending_qty,
|
required_qty=pending_qty if for_raw_material_request else 0,
|
||||||
required_qty=pending_qty if for_raw_material_request else 0,
|
sales_order_item=i.name,
|
||||||
sales_order_item=i.name,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
items.append(
|
|
||||||
dict(
|
|
||||||
name=i.name,
|
|
||||||
item_code=i.item_code,
|
|
||||||
description=i.description,
|
|
||||||
bom="",
|
|
||||||
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
|
return items
|
||||||
|
|
||||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||||
@@ -1237,13 +1226,6 @@ def update_status(status, name):
|
|||||||
so.update_status(status)
|
so.update_status(status)
|
||||||
|
|
||||||
|
|
||||||
def get_default_bom_item(item_code):
|
|
||||||
bom = frappe.get_all("BOM", dict(item=item_code, is_active=True), order_by="is_default desc")
|
|
||||||
bom = bom[0].name if bom else None
|
|
||||||
|
|
||||||
return bom
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_raw_material_request(items, company, sales_order, project=None):
|
def make_raw_material_request(items, company, sales_order, project=None):
|
||||||
if not frappe.has_permission("Sales Order", "write"):
|
if not frappe.has_permission("Sales Order", "write"):
|
||||||
|
|||||||
@@ -1374,6 +1374,59 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.fail("Can not cancel sales order with linked cancelled payment entry")
|
self.fail("Can not cancel sales order with linked cancelled payment entry")
|
||||||
|
|
||||||
|
def test_work_order_pop_up_from_sales_order(self):
|
||||||
|
"Test `get_work_order_items` in Sales Order picks the right BOM for items to manufacture."
|
||||||
|
|
||||||
|
from erpnext.controllers.item_variant import create_variant
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
|
make_item( # template item
|
||||||
|
"Test-WO-Tshirt",
|
||||||
|
{
|
||||||
|
"has_variant": 1,
|
||||||
|
"variant_based_on": "Item Attribute",
|
||||||
|
"attributes": [{"attribute": "Test Colour"}],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
make_item("Test-RM-Cotton") # RM for BOM
|
||||||
|
|
||||||
|
for colour in (
|
||||||
|
"Red",
|
||||||
|
"Green",
|
||||||
|
):
|
||||||
|
variant = create_variant("Test-WO-Tshirt", {"Test Colour": colour})
|
||||||
|
variant.save()
|
||||||
|
|
||||||
|
template_bom = make_bom(item="Test-WO-Tshirt", rate=100, raw_materials=["Test-RM-Cotton"])
|
||||||
|
red_var_bom = make_bom(item="Test-WO-Tshirt-R", rate=100, raw_materials=["Test-RM-Cotton"])
|
||||||
|
|
||||||
|
so = make_sales_order(
|
||||||
|
**{
|
||||||
|
"item_list": [
|
||||||
|
{
|
||||||
|
"item_code": "Test-WO-Tshirt-R",
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 1000,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_code": "Test-WO-Tshirt-G",
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 1000,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
wo_items = so.get_work_order_items()
|
||||||
|
|
||||||
|
self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
|
||||||
|
self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
|
||||||
|
|
||||||
|
# Must pick Template Item BOM for Test-WO-Tshirt-G as it has no BOM
|
||||||
|
self.assertEqual(wo_items[1].get("item_code"), "Test-WO-Tshirt-G")
|
||||||
|
self.assertEqual(wo_items[1].get("bom"), template_bom.name)
|
||||||
|
|
||||||
def test_request_for_raw_materials(self):
|
def test_request_for_raw_materials(self):
|
||||||
item = make_item(
|
item = make_item(
|
||||||
"_Test Finished Item",
|
"_Test Finished Item",
|
||||||
|
|||||||
@@ -1064,6 +1064,33 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(dn.items[0].rate, rate)
|
self.assertEqual(dn.items[0].rate, rate)
|
||||||
|
|
||||||
|
def test_internal_transfer_precision_gle(self):
|
||||||
|
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||||
|
|
||||||
|
item = make_item(properties={"valuation_method": "Moving Average"}).name
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
warehouse = "Stores - TCP1"
|
||||||
|
target = "Finished Goods - TCP1"
|
||||||
|
customer = create_internal_customer(represents_company=company)
|
||||||
|
|
||||||
|
# average rate = 128.015
|
||||||
|
rates = [101.45, 150.46, 138.25, 121.9]
|
||||||
|
|
||||||
|
for rate in rates:
|
||||||
|
make_stock_entry(item_code=item, target=warehouse, qty=1, rate=rate)
|
||||||
|
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item,
|
||||||
|
company=company,
|
||||||
|
customer=customer,
|
||||||
|
qty=4,
|
||||||
|
warehouse=warehouse,
|
||||||
|
target_warehouse=target,
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_delivery_note(**args):
|
def create_delivery_note(**args):
|
||||||
dn = frappe.new_doc("Delivery Note")
|
dn = frappe.new_doc("Delivery Note")
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
"items_to_be_repost",
|
"items_to_be_repost",
|
||||||
"affected_transactions",
|
"affected_transactions",
|
||||||
"distinct_item_and_warehouse",
|
"distinct_item_and_warehouse",
|
||||||
"current_index"
|
"current_index",
|
||||||
|
"gl_reposting_index"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -181,12 +182,20 @@
|
|||||||
"label": "Affected Transactions",
|
"label": "Affected Transactions",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "gl_reposting_index",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "GL reposting index",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-04-18 14:08:08.821602",
|
"modified": "2022-06-13 12:20:22.182322",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Repost Item Valuation",
|
"name": "Repost Item Valuation",
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class RepostItemValuation(Document):
|
|||||||
self.current_index = 0
|
self.current_index = 0
|
||||||
self.distinct_item_and_warehouse = None
|
self.distinct_item_and_warehouse = None
|
||||||
self.items_to_be_repost = None
|
self.items_to_be_repost = None
|
||||||
|
self.gl_reposting_index = 0
|
||||||
self.db_update()
|
self.db_update()
|
||||||
|
|
||||||
def deduplicate_similar_repost(self):
|
def deduplicate_similar_repost(self):
|
||||||
@@ -192,6 +193,7 @@ def repost_gl_entries(doc):
|
|||||||
directly_dependent_transactions + list(repost_affected_transaction),
|
directly_dependent_transactions + list(repost_affected_transaction),
|
||||||
doc.posting_date,
|
doc.posting_date,
|
||||||
doc.company,
|
doc.company,
|
||||||
|
repost_doc=doc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock, call
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
from frappe.utils.data import add_to_date, today
|
||||||
|
|
||||||
|
from erpnext.accounts.utils import repost_gle_for_stock_vouchers
|
||||||
from erpnext.controllers.stock_controller import create_item_wise_repost_entries
|
from erpnext.controllers.stock_controller import create_item_wise_repost_entries
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
@@ -13,10 +17,11 @@ from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import (
|
|||||||
in_configured_timeslot,
|
in_configured_timeslot,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
from erpnext.stock.tests.test_utils import StockTestMixin
|
||||||
from erpnext.stock.utils import PendingRepostingError
|
from erpnext.stock.utils import PendingRepostingError
|
||||||
|
|
||||||
|
|
||||||
class TestRepostItemValuation(FrappeTestCase):
|
class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.flags.dont_execute_stock_reposts = False
|
frappe.flags.dont_execute_stock_reposts = False
|
||||||
|
|
||||||
@@ -193,3 +198,77 @@ class TestRepostItemValuation(FrappeTestCase):
|
|||||||
[["a", "b"], ["c", "d"]],
|
[["a", "b"], ["c", "d"]],
|
||||||
sorted(frappe.parse_json(frappe.as_json(set([("a", "b"), ("c", "d")])))),
|
sorted(frappe.parse_json(frappe.as_json(set([("a", "b"), ("c", "d")])))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_gl_repost_progress(self):
|
||||||
|
from erpnext.accounts import utils
|
||||||
|
|
||||||
|
# lower numbers to simplify test
|
||||||
|
orig_chunk_size = utils.GL_REPOSTING_CHUNK
|
||||||
|
utils.GL_REPOSTING_CHUNK = 1
|
||||||
|
self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size)
|
||||||
|
|
||||||
|
doc = frappe.new_doc("Repost Item Valuation")
|
||||||
|
doc.db_set = MagicMock()
|
||||||
|
|
||||||
|
vouchers = []
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
posting_date = today()
|
||||||
|
|
||||||
|
for _ in range(3):
|
||||||
|
se = make_stock_entry(company=company, qty=1, rate=2, target="Stores - TCP1")
|
||||||
|
vouchers.append((se.doctype, se.name))
|
||||||
|
|
||||||
|
repost_gle_for_stock_vouchers(stock_vouchers=vouchers, posting_date=posting_date, repost_doc=doc)
|
||||||
|
self.assertIn(call("gl_reposting_index", 1), doc.db_set.mock_calls)
|
||||||
|
doc.db_set.reset_mock()
|
||||||
|
|
||||||
|
doc.gl_reposting_index = 1
|
||||||
|
repost_gle_for_stock_vouchers(stock_vouchers=vouchers, posting_date=posting_date, repost_doc=doc)
|
||||||
|
|
||||||
|
self.assertNotIn(call("gl_reposting_index", 1), doc.db_set.mock_calls)
|
||||||
|
|
||||||
|
def test_gl_complete_gl_reposting(self):
|
||||||
|
from erpnext.accounts import utils
|
||||||
|
|
||||||
|
# lower numbers to simplify test
|
||||||
|
orig_chunk_size = utils.GL_REPOSTING_CHUNK
|
||||||
|
utils.GL_REPOSTING_CHUNK = 2
|
||||||
|
self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size)
|
||||||
|
|
||||||
|
item = self.make_item().name
|
||||||
|
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
for _ in range(10):
|
||||||
|
make_stock_entry(item=item, company=company, qty=1, rate=10, target="Stores - TCP1")
|
||||||
|
|
||||||
|
# consume
|
||||||
|
consumption = make_stock_entry(item=item, company=company, qty=1, source="Stores - TCP1")
|
||||||
|
|
||||||
|
self.assertGLEs(
|
||||||
|
consumption,
|
||||||
|
[{"credit": 10, "debit": 0}],
|
||||||
|
gle_filters={"account": "Stock In Hand - TCP1"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# backdated receipt
|
||||||
|
backdated_receipt = make_stock_entry(
|
||||||
|
item=item,
|
||||||
|
company=company,
|
||||||
|
qty=1,
|
||||||
|
rate=50,
|
||||||
|
target="Stores - TCP1",
|
||||||
|
posting_date=add_to_date(today(), days=-1),
|
||||||
|
)
|
||||||
|
self.assertGLEs(
|
||||||
|
backdated_receipt,
|
||||||
|
[{"credit": 0, "debit": 50}],
|
||||||
|
gle_filters={"account": "Stock In Hand - TCP1"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# check that original consumption GLe is updated
|
||||||
|
self.assertGLEs(
|
||||||
|
consumption,
|
||||||
|
[{"credit": 50, "debit": 0}],
|
||||||
|
gle_filters={"account": "Stock In Hand - TCP1"},
|
||||||
|
)
|
||||||
|
|||||||
@@ -63,18 +63,16 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
|||||||
item = frappe.get_cached_doc("Item", args.item_code)
|
item = frappe.get_cached_doc("Item", args.item_code)
|
||||||
validate_item_details(args, item)
|
validate_item_details(args, item)
|
||||||
|
|
||||||
out = get_basic_details(args, item, overwrite_warehouse)
|
|
||||||
|
|
||||||
if isinstance(doc, string_types):
|
if isinstance(doc, string_types):
|
||||||
doc = json.loads(doc)
|
doc = json.loads(doc)
|
||||||
|
|
||||||
if doc and doc.get("doctype") == "Purchase Invoice":
|
|
||||||
args["bill_date"] = doc.get("bill_date")
|
|
||||||
|
|
||||||
if doc:
|
if doc:
|
||||||
args["posting_date"] = doc.get("posting_date")
|
args["transaction_date"] = doc.get("transaction_date") or doc.get("posting_date")
|
||||||
args["transaction_date"] = doc.get("transaction_date")
|
|
||||||
|
|
||||||
|
if doc.get("doctype") == "Purchase Invoice":
|
||||||
|
args["bill_date"] = doc.get("bill_date")
|
||||||
|
|
||||||
|
out = get_basic_details(args, item, overwrite_warehouse)
|
||||||
get_item_tax_template(args, item, out)
|
get_item_tax_template(args, item, out)
|
||||||
out["item_tax_rate"] = get_item_tax_map(
|
out["item_tax_rate"] = get_item_tax_map(
|
||||||
args.company,
|
args.company,
|
||||||
@@ -586,9 +584,7 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
|
|||||||
if tax.valid_from or tax.maximum_net_rate:
|
if tax.valid_from or tax.maximum_net_rate:
|
||||||
# In purchase Invoice first preference will be given to supplier invoice date
|
# In purchase Invoice first preference will be given to supplier invoice date
|
||||||
# if supplier date is not present then posting date
|
# if supplier date is not present then posting date
|
||||||
validation_date = (
|
validation_date = args.get("bill_date") or args.get("transaction_date")
|
||||||
args.get("transaction_date") or args.get("bill_date") or args.get("posting_date")
|
|
||||||
)
|
|
||||||
|
|
||||||
if getdate(tax.valid_from) <= getdate(validation_date) and is_within_valid_range(args, tax):
|
if getdate(tax.valid_from) <= getdate(validation_date) and is_within_valid_range(args, tax):
|
||||||
taxes_with_validity.append(tax)
|
taxes_with_validity.append(tax)
|
||||||
@@ -881,10 +877,6 @@ def get_item_price(args, item_code, ignore_party=False):
|
|||||||
conditions += """ and %(transaction_date)s between
|
conditions += """ and %(transaction_date)s between
|
||||||
ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
|
ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
|
||||||
|
|
||||||
if args.get("posting_date"):
|
|
||||||
conditions += """ and %(posting_date)s between
|
|
||||||
ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
|
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
""" select name, price_list_rate, uom
|
""" select name, price_list_rate, uom
|
||||||
from `tabItem Price` {conditions}
|
from `tabItem Price` {conditions}
|
||||||
@@ -911,7 +903,6 @@ def get_price_list_rate_for(args, item_code):
|
|||||||
"supplier": args.get("supplier"),
|
"supplier": args.get("supplier"),
|
||||||
"uom": args.get("uom"),
|
"uom": args.get("uom"),
|
||||||
"transaction_date": args.get("transaction_date"),
|
"transaction_date": args.get("transaction_date"),
|
||||||
"posting_date": args.get("posting_date"),
|
|
||||||
"batch_no": args.get("batch_no"),
|
"batch_no": args.get("batch_no"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1342,12 +1333,22 @@ def get_price_list_currency_and_exchange_rate(args):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_default_bom(item_code=None):
|
def get_default_bom(item_code=None):
|
||||||
if item_code:
|
def _get_bom(item):
|
||||||
bom = frappe.db.get_value(
|
bom = frappe.get_all(
|
||||||
"BOM", {"docstatus": 1, "is_default": 1, "is_active": 1, "item": item_code}
|
"BOM", dict(item=item, is_active=True, is_default=True, docstatus=1), limit=1
|
||||||
)
|
)
|
||||||
if bom:
|
return bom[0].name if bom else None
|
||||||
return bom
|
|
||||||
|
if not item_code:
|
||||||
|
return
|
||||||
|
|
||||||
|
bom_name = _get_bom(item_code)
|
||||||
|
|
||||||
|
template_item = frappe.db.get_value("Item", item_code, "variant_of")
|
||||||
|
if not bom_name and template_item:
|
||||||
|
bom_name = _get_bom(template_item)
|
||||||
|
|
||||||
|
return bom_name
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
Reference in New Issue
Block a user