Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-29865
This commit is contained in:
@@ -166,7 +166,7 @@ class OpeningInvoiceCreationTool(Document):
|
||||
frappe.scrub(row.party_type): row.party,
|
||||
"is_pos": 0,
|
||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
||||
"update_stock": 0,
|
||||
"update_stock": 0, # important: https://github.com/frappe/erpnext/pull/23559
|
||||
"invoice_number": row.invoice_number,
|
||||
"disable_rounded_total": 1
|
||||
})
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.cache_manager import clear_doctype_cache
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
||||
create_dimension,
|
||||
@@ -14,14 +10,17 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
|
||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
|
||||
get_temporary_opening_account,
|
||||
)
|
||||
from erpnext.tests.utils import ERPNextTestCase
|
||||
|
||||
test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
|
||||
|
||||
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
def setUp(self):
|
||||
class TestOpeningInvoiceCreationTool(ERPNextTestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||
make_company()
|
||||
create_dimension()
|
||||
return super().setUpClass()
|
||||
|
||||
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None):
|
||||
doc = frappe.get_single("Opening Invoice Creation Tool")
|
||||
@@ -31,26 +30,20 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
return doc.make_invoices()
|
||||
|
||||
def test_opening_sales_invoice_creation(self):
|
||||
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
|
||||
try:
|
||||
invoices = self.make_invoices(company="_Test Opening Invoice Company")
|
||||
invoices = self.make_invoices(company="_Test Opening Invoice Company")
|
||||
|
||||
self.assertEqual(len(invoices), 2)
|
||||
expected_value = {
|
||||
"keys": ["customer", "outstanding_amount", "status"],
|
||||
0: ["_Test Customer", 300, "Overdue"],
|
||||
1: ["_Test Customer 1", 250, "Overdue"],
|
||||
}
|
||||
self.check_expected_values(invoices, expected_value)
|
||||
self.assertEqual(len(invoices), 2)
|
||||
expected_value = {
|
||||
"keys": ["customer", "outstanding_amount", "status"],
|
||||
0: ["_Test Customer", 300, "Overdue"],
|
||||
1: ["_Test Customer 1", 250, "Overdue"],
|
||||
}
|
||||
self.check_expected_values(invoices, expected_value)
|
||||
|
||||
si = frappe.get_doc("Sales Invoice", invoices[0])
|
||||
si = frappe.get_doc("Sales Invoice", invoices[0])
|
||||
|
||||
# Check if update stock is not enabled
|
||||
self.assertEqual(si.update_stock, 0)
|
||||
|
||||
finally:
|
||||
property_setter.delete()
|
||||
clear_doctype_cache("Sales Invoice")
|
||||
# Check if update stock is not enabled
|
||||
self.assertEqual(si.update_stock, 0)
|
||||
|
||||
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
|
||||
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
|
||||
|
||||
@@ -1078,7 +1078,7 @@ def get_outstanding_reference_documents(args):
|
||||
if d.voucher_type in ("Purchase Invoice"):
|
||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||
|
||||
# Get all SO / PO which are not fully billed or aginst which full advance not paid
|
||||
# Get all SO / PO which are not fully billed or against which full advance not paid
|
||||
orders_to_be_billed = []
|
||||
if (args.get("party_type") != "Student"):
|
||||
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
|
||||
|
||||
@@ -439,7 +439,6 @@ class POSInvoice(SalesInvoice):
|
||||
self.paid_amount = 0
|
||||
|
||||
def set_account_for_mode_of_payment(self):
|
||||
self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
|
||||
for pay in self.payments:
|
||||
if not pay.account:
|
||||
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
||||
|
||||
@@ -46,7 +46,7 @@ def valdiate_taxes_and_charges_template(doc):
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_account_head(tax, doc)
|
||||
validate_account_head(tax.idx, tax.account_head, doc.company)
|
||||
validate_cost_center(tax, doc)
|
||||
validate_inclusive_tax(tax, doc)
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
|
||||
.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
|
||||
|
||||
def validate_party_accounts(doc):
|
||||
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
companies = []
|
||||
|
||||
for account in doc.get("accounts"):
|
||||
@@ -331,6 +331,9 @@ def validate_party_accounts(doc):
|
||||
if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:
|
||||
frappe.throw(_("Billing currency must be equal to either default company's currency or party account currency"))
|
||||
|
||||
# validate if account is mapped for same company
|
||||
validate_account_head(account.idx, account.account, account.company)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
|
||||
|
||||
@@ -68,6 +68,28 @@ frappe.ui.form.on('Asset Repair', {
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Asset Repair Consumed Item', {
|
||||
item_code: function(frm, cdt, cdn) {
|
||||
var item = locals[cdt][cdn];
|
||||
|
||||
let item_args = {
|
||||
'item_code': item.item_code,
|
||||
'warehouse': frm.doc.warehouse,
|
||||
'qty': item.consumed_quantity,
|
||||
'serial_no': item.serial_no,
|
||||
'company': frm.doc.company
|
||||
};
|
||||
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.utils.get_incoming_rate',
|
||||
args: {
|
||||
args: item_args
|
||||
},
|
||||
callback: function(r) {
|
||||
frappe.model.set_value(cdt, cdn, 'valuation_rate', r.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
consumed_quantity: function(frm, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);
|
||||
|
||||
@@ -13,12 +13,10 @@
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fetch_from": "item.valuation_rate",
|
||||
"fieldname": "valuation_rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Valuation Rate",
|
||||
"read_only": 1
|
||||
"label": "Valuation Rate"
|
||||
},
|
||||
{
|
||||
"fieldname": "consumed_quantity",
|
||||
@@ -49,7 +47,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-11 18:23:00.492483",
|
||||
"modified": "2022-02-08 17:37:20.028290",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair Consumed Item",
|
||||
|
||||
@@ -1567,13 +1567,12 @@ def validate_taxes_and_charges(tax):
|
||||
tax.rate = None
|
||||
|
||||
|
||||
def validate_account_head(tax, doc):
|
||||
company = frappe.get_cached_value('Account',
|
||||
tax.account_head, 'company')
|
||||
def validate_account_head(idx, account, company):
|
||||
account_company = frappe.get_cached_value('Account', account, 'company')
|
||||
|
||||
if company != doc.company:
|
||||
if account_company != company:
|
||||
frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
|
||||
.format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account'))
|
||||
.format(idx, frappe.bold(account), frappe.bold(company)), title=_('Invalid Account'))
|
||||
|
||||
|
||||
def validate_cost_center(tax, doc):
|
||||
|
||||
@@ -9,6 +9,7 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
||||
get_sales_orders,
|
||||
get_warehouse_list,
|
||||
)
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
@@ -466,26 +467,29 @@ class TestProductionPlan(ERPNextTestCase):
|
||||
bom = make_bom(item=item, raw_materials=raw_materials)
|
||||
|
||||
# Create Production Plan
|
||||
pln = create_production_plan(item_code=bom.item, planned_qty=10)
|
||||
pln = create_production_plan(item_code=bom.item, planned_qty=5)
|
||||
|
||||
# All the created Work Orders
|
||||
wo_list = []
|
||||
|
||||
# Create and Submit 1st Work Order for 5 qty
|
||||
create_work_order(item, pln, 5)
|
||||
# Create and Submit 1st Work Order for 3 qty
|
||||
create_work_order(item, pln, 3)
|
||||
pln.reload()
|
||||
self.assertEqual(pln.po_items[0].ordered_qty, 3)
|
||||
|
||||
# Create and Submit 2nd Work Order for 2 qty
|
||||
create_work_order(item, pln, 2)
|
||||
pln.reload()
|
||||
self.assertEqual(pln.po_items[0].ordered_qty, 5)
|
||||
|
||||
# Create and Submit 2nd Work Order for 3 qty
|
||||
create_work_order(item, pln, 3)
|
||||
pln.reload()
|
||||
self.assertEqual(pln.po_items[0].ordered_qty, 8)
|
||||
# Overproduction
|
||||
self.assertRaises(OverProductionError, create_work_order, item=item, pln=pln, qty=2)
|
||||
|
||||
# Cancel 1st Work Order
|
||||
wo1 = frappe.get_doc("Work Order", wo_list[0])
|
||||
wo1.cancel()
|
||||
pln.reload()
|
||||
self.assertEqual(pln.po_items[0].ordered_qty, 3)
|
||||
self.assertEqual(pln.po_items[0].ordered_qty, 2)
|
||||
|
||||
# Cancel 2nd Work Order
|
||||
wo2 = frappe.get_doc("Work Order", wo_list[1])
|
||||
|
||||
@@ -632,6 +632,21 @@ class WorkOrder(Document):
|
||||
if not self.qty > 0:
|
||||
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
|
||||
|
||||
if self.production_plan and self.production_plan_item:
|
||||
qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1)
|
||||
|
||||
allowance_qty =flt(frappe.db.get_single_value("Manufacturing Settings",
|
||||
"overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)
|
||||
|
||||
max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0)
|
||||
|
||||
if max_qty < 1:
|
||||
frappe.throw(_("Cannot produce more item for {0}")
|
||||
.format(self.production_item), OverProductionError)
|
||||
elif self.qty > max_qty:
|
||||
frappe.throw(_("Cannot produce more than {0} items for {1}")
|
||||
.format(max_qty, self.production_item), OverProductionError)
|
||||
|
||||
def validate_transfer_against(self):
|
||||
if not self.docstatus == 1:
|
||||
# let user configure operations until they're ready to submit
|
||||
|
||||
@@ -2265,13 +2265,17 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
},
|
||||
|
||||
coupon_code: function() {
|
||||
var me = this;
|
||||
frappe.run_serially([
|
||||
() => this.frm.doc.ignore_pricing_rule=1,
|
||||
() => me.ignore_pricing_rule(),
|
||||
() => this.frm.doc.ignore_pricing_rule=0,
|
||||
() => me.apply_pricing_rule()
|
||||
]);
|
||||
if (this.frm.doc.coupon_code || this.frm._last_coupon_code) {
|
||||
// reset pricing rules if coupon code is set or is unset
|
||||
const _ignore_pricing_rule = this.frm.doc.ignore_pricing_rule;
|
||||
return frappe.run_serially([
|
||||
() => this.frm.doc.ignore_pricing_rule=1,
|
||||
() => this.frm.trigger('ignore_pricing_rule'),
|
||||
() => this.frm.doc.ignore_pricing_rule=_ignore_pricing_rule,
|
||||
() => this.frm.trigger('apply_pricing_rule'),
|
||||
() => this.frm._last_coupon_code = this.frm.doc.coupon_code
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -304,12 +304,13 @@ erpnext.HierarchyChart = class {
|
||||
}
|
||||
|
||||
get_child_nodes(node_id) {
|
||||
let me = this;
|
||||
return new Promise(resolve => {
|
||||
frappe.call({
|
||||
method: this.method,
|
||||
method: me.method,
|
||||
args: {
|
||||
parent: node_id,
|
||||
company: this.company
|
||||
company: me.company
|
||||
}
|
||||
}).then(r => resolve(r.message));
|
||||
});
|
||||
@@ -350,12 +351,13 @@ erpnext.HierarchyChart = class {
|
||||
}
|
||||
|
||||
get_all_nodes() {
|
||||
let me = this;
|
||||
return new Promise(resolve => {
|
||||
frappe.call({
|
||||
method: 'erpnext.utilities.hierarchy_chart.get_all_nodes',
|
||||
args: {
|
||||
method: this.method,
|
||||
company: this.company
|
||||
method: me.method,
|
||||
company: me.company
|
||||
},
|
||||
callback: (r) => {
|
||||
resolve(r.message);
|
||||
@@ -427,8 +429,8 @@ erpnext.HierarchyChart = class {
|
||||
|
||||
add_connector(parent_id, child_id) {
|
||||
// using pure javascript for better performance
|
||||
const parent_node = document.querySelector(`#${parent_id}`);
|
||||
const child_node = document.querySelector(`#${child_id}`);
|
||||
const parent_node = document.getElementById(`${parent_id}`);
|
||||
const child_node = document.getElementById(`${child_id}`);
|
||||
|
||||
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
let me = this;
|
||||
return new Promise(resolve => {
|
||||
frappe.call({
|
||||
method: this.method,
|
||||
method: me.method,
|
||||
args: {
|
||||
parent: node_id,
|
||||
company: me.company,
|
||||
@@ -286,8 +286,8 @@ erpnext.HierarchyChartMobile = class {
|
||||
}
|
||||
|
||||
add_connector(parent_id, child_id) {
|
||||
const parent_node = document.querySelector(`#${parent_id}`);
|
||||
const child_node = document.querySelector(`#${child_id}`);
|
||||
const parent_node = document.getElementById(`${parent_id}`);
|
||||
const child_node = document.getElementById(`${child_id}`);
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
|
||||
@@ -518,7 +518,8 @@ erpnext.HierarchyChartMobile = class {
|
||||
level.nextAll('li').remove();
|
||||
|
||||
let node_object = this.nodes[node.id];
|
||||
let current_node = level.find(`#${node.id}`).detach();
|
||||
let current_node = level.find(`[id="${node.id}"]`).detach();
|
||||
|
||||
current_node.removeClass('active-child active-path');
|
||||
|
||||
node_object.expanded = 0;
|
||||
|
||||
@@ -170,17 +170,20 @@ erpnext.PointOfSale.Payment = class {
|
||||
});
|
||||
|
||||
frappe.ui.form.on('POS Invoice', 'coupon_code', (frm) => {
|
||||
if (!frm.doc.ignore_pricing_rule) {
|
||||
if (frm.doc.coupon_code) {
|
||||
frappe.run_serially([
|
||||
() => frm.doc.ignore_pricing_rule=1,
|
||||
() => frm.trigger('ignore_pricing_rule'),
|
||||
() => frm.doc.ignore_pricing_rule=0,
|
||||
() => frm.trigger('apply_pricing_rule'),
|
||||
() => frm.save(),
|
||||
() => this.update_totals_section(frm.doc)
|
||||
]);
|
||||
}
|
||||
if (!frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
|
||||
frappe.run_serially([
|
||||
() => frm.doc.ignore_pricing_rule=1,
|
||||
() => frm.trigger('ignore_pricing_rule'),
|
||||
() => frm.doc.ignore_pricing_rule=0,
|
||||
() => frm.trigger('apply_pricing_rule'),
|
||||
() => frm.save(),
|
||||
() => this.update_totals_section(frm.doc)
|
||||
]);
|
||||
} else if (frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
|
||||
frappe.show_alert({
|
||||
message: __("Ignore Pricing Rule is enabled. Cannot apply coupon code."),
|
||||
indicator: "orange"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -399,6 +399,7 @@ class Item(Document):
|
||||
|
||||
if merge:
|
||||
self.validate_properties_before_merge(new_name)
|
||||
self.validate_duplicate_product_bundles_before_merge(old_name, new_name)
|
||||
self.validate_duplicate_website_item_before_merge(old_name, new_name)
|
||||
|
||||
def after_rename(self, old_name, new_name, merge):
|
||||
@@ -463,6 +464,20 @@ class Item(Document):
|
||||
msg += ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list])
|
||||
frappe.throw(msg, title=_("Cannot Merge"), exc=DataValidationError)
|
||||
|
||||
def validate_duplicate_product_bundles_before_merge(self, old_name, new_name):
|
||||
"Block merge if both old and new items have product bundles."
|
||||
old_bundle = frappe.get_value("Product Bundle",filters={"new_item_code": old_name})
|
||||
new_bundle = frappe.get_value("Product Bundle",filters={"new_item_code": new_name})
|
||||
|
||||
if old_bundle and new_bundle:
|
||||
bundle_link = get_link_to_form("Product Bundle", old_bundle)
|
||||
old_name, new_name = frappe.bold(old_name), frappe.bold(new_name)
|
||||
|
||||
msg = _("Please delete Product Bundle {0}, before merging {1} into {2}").format(
|
||||
bundle_link, old_name, new_name
|
||||
)
|
||||
frappe.throw(msg, title=_("Cannot Merge"), exc=DataValidationError)
|
||||
|
||||
def validate_duplicate_website_item_before_merge(self, old_name, new_name):
|
||||
"""
|
||||
Block merge if both old and new items have website items against them.
|
||||
@@ -480,8 +495,9 @@ class Item(Document):
|
||||
|
||||
old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0]
|
||||
web_item_link = get_link_to_form("Website Item", old_web_item)
|
||||
old_name, new_name = frappe.bold(old_name), frappe.bold(new_name)
|
||||
|
||||
msg = f"Please delete linked Website Item {frappe.bold(web_item_link)} before merging {old_name} and {new_name}"
|
||||
msg = f"Please delete linked Website Item {frappe.bold(web_item_link)} before merging {old_name} into {new_name}"
|
||||
frappe.throw(_(msg), title=_("Cannot Merge"), exc=DataValidationError)
|
||||
|
||||
def set_last_purchase_rate(self, new_name):
|
||||
|
||||
@@ -14,6 +14,7 @@ from erpnext.controllers.item_variant import (
|
||||
get_variant,
|
||||
)
|
||||
from erpnext.stock.doctype.item.item import (
|
||||
DataValidationError,
|
||||
InvalidBarcode,
|
||||
StockExistsForTemplate,
|
||||
get_item_attribute,
|
||||
@@ -387,6 +388,26 @@ class TestItem(ERPNextTestCase):
|
||||
self.assertTrue(frappe.db.get_value("Bin",
|
||||
{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"}))
|
||||
|
||||
def test_item_merging_with_product_bundle(self):
|
||||
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||
|
||||
create_item("Test Item Bundle Item 1", is_stock_item=False)
|
||||
create_item("Test Item Bundle Item 2", is_stock_item=False)
|
||||
create_item("Test Item inside Bundle")
|
||||
bundle_items = ["Test Item inside Bundle"]
|
||||
|
||||
# make bundles for both items
|
||||
bundle1 = make_product_bundle("Test Item Bundle Item 1", bundle_items, qty=2)
|
||||
make_product_bundle("Test Item Bundle Item 2", bundle_items, qty=2)
|
||||
|
||||
with self.assertRaises(DataValidationError):
|
||||
frappe.rename_doc("Item", "Test Item Bundle Item 1", "Test Item Bundle Item 2", merge=True)
|
||||
|
||||
bundle1.delete()
|
||||
frappe.rename_doc("Item", "Test Item Bundle Item 1", "Test Item Bundle Item 2", merge=True)
|
||||
|
||||
self.assertFalse(frappe.db.exists("Item", "Test Item Bundle Item 1"))
|
||||
|
||||
def test_uom_conversion_factor(self):
|
||||
if frappe.db.exists('Item', 'Test Item UOM'):
|
||||
frappe.delete_doc('Item', 'Test Item UOM')
|
||||
|
||||
@@ -627,6 +627,12 @@ frappe.ui.form.on('Stock Entry Detail', {
|
||||
frm.events.set_serial_no(frm, cdt, cdn, () => {
|
||||
frm.events.get_warehouse_details(frm, cdt, cdn);
|
||||
});
|
||||
|
||||
// set allow_zero_valuation_rate to 0 if s_warehouse is selected.
|
||||
let item = frappe.get_doc(cdt, cdn);
|
||||
if (item.s_warehouse) {
|
||||
item.allow_zero_valuation_rate = 0;
|
||||
}
|
||||
},
|
||||
|
||||
t_warehouse: function(frm, cdt, cdn) {
|
||||
|
||||
@@ -45,6 +45,7 @@ def get_sle(**args):
|
||||
|
||||
class TestStockEntry(ERPNextTestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "0")
|
||||
|
||||
@@ -566,6 +567,7 @@ class TestStockEntry(ERPNextTestCase):
|
||||
st1.set_stock_entry_type()
|
||||
st1.insert()
|
||||
st1.submit()
|
||||
st1.cancel()
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
|
||||
@@ -690,6 +692,8 @@ class TestStockEntry(ERPNextTestCase):
|
||||
bom_no = frappe.db.get_value("BOM", {"item": "_Test Variant Item",
|
||||
"is_default": 1, "docstatus": 1})
|
||||
|
||||
make_item_variant() # make variant of _Test Variant Item if absent
|
||||
|
||||
work_order = frappe.new_doc("Work Order")
|
||||
work_order.update({
|
||||
"company": "_Test Company",
|
||||
@@ -1101,13 +1105,10 @@ class TestStockEntry(ERPNextTestCase):
|
||||
|
||||
# Check if FG cost is calculated based on RM total cost
|
||||
# RM total cost = 200, FG rate = 200/4(FG qty) = 50
|
||||
self.assertEqual(se.items[1].basic_rate, 50)
|
||||
self.assertEqual(se.items[1].basic_rate, flt(se.items[0].basic_rate/4))
|
||||
self.assertEqual(se.value_difference, 0.0)
|
||||
self.assertEqual(se.total_incoming_value, se.total_outgoing_value)
|
||||
|
||||
# teardown
|
||||
se.delete()
|
||||
|
||||
def make_serialized_item(**args):
|
||||
args = frappe._dict(args)
|
||||
se = frappe.copy_doc(test_records[0])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-03-29 18:22:12",
|
||||
"creation": "2022-02-05 00:17:49.860824",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"editable_grid": 1,
|
||||
@@ -340,13 +340,13 @@
|
||||
"label": "More Information"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"fieldname": "allow_zero_valuation_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Zero Valuation Rate",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only_depends_on": "eval:doc.s_warehouse"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@@ -556,12 +556,14 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-22 16:47:11.268975",
|
||||
"modified": "2022-02-26 00:51:24.963653",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
@@ -60,6 +60,9 @@ def add_invariant_check_fields(sles):
|
||||
fifo_qty += qty
|
||||
fifo_value += qty * rate
|
||||
|
||||
if sle.actual_qty < 0:
|
||||
sle.consumption_rate = sle.stock_value_difference / sle.actual_qty
|
||||
|
||||
balance_qty += sle.actual_qty
|
||||
balance_stock_value += sle.stock_value_difference
|
||||
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
|
||||
@@ -145,9 +148,9 @@ def get_columns():
|
||||
"label": "Incoming Rate",
|
||||
},
|
||||
{
|
||||
"fieldname": "outgoing_rate",
|
||||
"fieldname": "consumption_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Outgoing Rate",
|
||||
"label": "Consumption Rate",
|
||||
},
|
||||
{
|
||||
"fieldname": "qty_after_transaction",
|
||||
|
||||
@@ -23,7 +23,6 @@ class NegativeStockError(frappe.ValidationError): pass
|
||||
class SerialNoExistsInFutureTransaction(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
_exceptions = frappe.local('stockledger_exceptions')
|
||||
|
||||
def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||
from erpnext.controllers.stock_controller import future_sle_exists
|
||||
@@ -459,6 +458,8 @@ class update_entries_after(object):
|
||||
|
||||
# rounding as per precision
|
||||
self.wh_data.stock_value = flt(self.wh_data.stock_value, self.precision)
|
||||
if not self.wh_data.qty_after_transaction:
|
||||
self.wh_data.stock_value = 0.0
|
||||
stock_value_difference = self.wh_data.stock_value - self.wh_data.prev_stock_value
|
||||
self.wh_data.prev_stock_value = self.wh_data.stock_value
|
||||
|
||||
@@ -622,9 +623,7 @@ class update_entries_after(object):
|
||||
if not self.wh_data.valuation_rate and sle.voucher_detail_no:
|
||||
allow_zero_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
|
||||
if not allow_zero_rate:
|
||||
self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
||||
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
|
||||
currency=erpnext.get_company_currency(sle.company), company=sle.company)
|
||||
self.wh_data.valuation_rate = self.get_fallback_rate(sle)
|
||||
|
||||
def get_incoming_value_for_serial_nos(self, sle, serial_nos):
|
||||
# get rate from serial nos within same company
|
||||
@@ -690,9 +689,7 @@ class update_entries_after(object):
|
||||
if not self.wh_data.valuation_rate and sle.voucher_detail_no:
|
||||
allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
|
||||
if not allow_zero_valuation_rate:
|
||||
self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
||||
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
|
||||
currency=erpnext.get_company_currency(sle.company), company=sle.company)
|
||||
self.wh_data.valuation_rate = self.get_fallback_rate(sle)
|
||||
|
||||
def get_fifo_values(self, sle):
|
||||
incoming_rate = flt(sle.incoming_rate)
|
||||
@@ -723,9 +720,7 @@ class update_entries_after(object):
|
||||
# Get valuation rate from last sle if exists or from valuation rate field in item master
|
||||
allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
|
||||
if not allow_zero_valuation_rate:
|
||||
_rate = get_valuation_rate(sle.item_code, sle.warehouse,
|
||||
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
|
||||
currency=erpnext.get_company_currency(sle.company), company=sle.company)
|
||||
_rate = self.get_fallback_rate(sle)
|
||||
else:
|
||||
_rate = 0
|
||||
|
||||
@@ -788,6 +783,13 @@ class update_entries_after(object):
|
||||
else:
|
||||
return 0
|
||||
|
||||
def get_fallback_rate(self, sle) -> float:
|
||||
"""When exact incoming rate isn't available use any of other "average" rates as fallback.
|
||||
This should only get used for negative stock."""
|
||||
return get_valuation_rate(sle.item_code, sle.warehouse,
|
||||
sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
|
||||
currency=erpnext.get_company_currency(sle.company), company=sle.company)
|
||||
|
||||
def get_sle_before_datetime(self, args):
|
||||
"""get previous stock ledger entry before current time-bucket"""
|
||||
sle = get_stock_ledger_entries(args, "<", "desc", "limit 1", for_update=False)
|
||||
|
||||
@@ -3726,7 +3726,7 @@ Earliest Age,Frühestes Alter,
|
||||
Edit Details,Details bearbeiten,
|
||||
Edit Profile,Profil bearbeiten,
|
||||
Either GST Transporter ID or Vehicle No is required if Mode of Transport is Road,Bei Straßentransport ist entweder die GST-Transporter-ID oder die Fahrzeug-Nr. Erforderlich,
|
||||
Email,Email,
|
||||
Email,E-Mail,
|
||||
Email Campaigns,E-Mail-Kampagnen,
|
||||
Employee ID is linked with another instructor,Die Mitarbeiter-ID ist mit einem anderen Ausbilder verknüpft,
|
||||
Employee Tax and Benefits,Mitarbeitersteuern und -leistungen,
|
||||
@@ -6481,7 +6481,7 @@ Select Users,Wählen Sie Benutzer aus,
|
||||
Send Emails At,Die E-Mails senden um,
|
||||
Reminder,Erinnerung,
|
||||
Daily Work Summary Group User,Tägliche Arbeit Zusammenfassung Gruppenbenutzer,
|
||||
email,Email,
|
||||
email,E-Mail,
|
||||
Parent Department,Elternabteilung,
|
||||
Leave Block List,Urlaubssperrenliste,
|
||||
Days for which Holidays are blocked for this department.,"Tage, an denen eine Urlaubssperre für diese Abteilung gilt.",
|
||||
|
||||
|
Can't render this file because it is too large.
|
Reference in New Issue
Block a user