Merge pull request #41854 from frappe/version-15-hotfix

chore: release v15
This commit is contained in:
rohitwaghchaure
2024-06-11 18:47:08 +05:30
committed by GitHub
20 changed files with 252 additions and 49 deletions

View File

@@ -89,6 +89,7 @@
"custom_remarks", "custom_remarks",
"remarks", "remarks",
"base_in_words", "base_in_words",
"is_opening",
"column_break_16", "column_break_16",
"letter_head", "letter_head",
"print_heading", "print_heading",
@@ -777,6 +778,16 @@
"label": "Reconcile on Advance Payment Date", "label": "Reconcile on Advance Payment Date",
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
},
{
"default": "No",
"depends_on": "eval: doc.book_advance_payments_in_separate_party_account == 1",
"fieldname": "is_opening",
"fieldtype": "Select",
"label": "Is Opening",
"options": "No\nYes",
"print_hide": 1,
"search_index": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
@@ -790,7 +801,7 @@
"table_fieldname": "payment_entries" "table_fieldname": "payment_entries"
} }
], ],
"modified": "2024-05-17 10:21:11.199445", "modified": "2024-05-31 17:07:06.197249",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@@ -116,11 +116,13 @@ class PaymentEntry(AccountsController):
self.book_advance_payments_in_separate_party_account = False self.book_advance_payments_in_separate_party_account = False
if self.party_type not in ("Customer", "Supplier"): if self.party_type not in ("Customer", "Supplier"):
self.is_opening = "No"
return return
if not frappe.db.get_value( if not frappe.db.get_value(
"Company", self.company, "book_advance_payments_in_separate_party_account" "Company", self.company, "book_advance_payments_in_separate_party_account"
): ):
self.is_opening = "No"
return return
# Important to set this flag for the gl building logic to work properly # Important to set this flag for the gl building logic to work properly
@@ -132,6 +134,7 @@ class PaymentEntry(AccountsController):
if (account_type == "Payable" and self.party_type == "Customer") or ( if (account_type == "Payable" and self.party_type == "Customer") or (
account_type == "Receivable" and self.party_type == "Supplier" account_type == "Receivable" and self.party_type == "Supplier"
): ):
self.is_opening = "No"
return return
if self.references: if self.references:
@@ -141,6 +144,7 @@ class PaymentEntry(AccountsController):
# If there are referencers other than `allowed_types`, treat this as a normal payment entry # If there are referencers other than `allowed_types`, treat this as a normal payment entry
if reference_types - allowed_types: if reference_types - allowed_types:
self.book_advance_payments_in_separate_party_account = False self.book_advance_payments_in_separate_party_account = False
self.is_opening = "No"
return return
liability_account = get_party_account( liability_account = get_party_account(

View File

@@ -1729,6 +1729,68 @@ class TestPaymentEntry(FrappeTestCase):
self.check_gl_entries() self.check_gl_entries()
self.check_pl_entries() self.check_pl_entries()
def test_opening_flag_for_advance_as_liability(self):
company = "_Test Company"
advance_account = create_account(
parent_account="Current Assets - _TC",
account_name="Advances Received",
company=company,
account_type="Receivable",
)
# Enable Advance in separate party account
frappe.db.set_value(
"Company",
company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_received_account": advance_account,
},
)
# Advance Payment
adv = create_payment_entry(
party_type="Customer",
party="_Test Customer",
payment_type="Receive",
paid_from="Debtors - _TC",
paid_to="_Test Cash - _TC",
)
adv.is_opening = "Yes"
adv.save() # use save() to trigger set_liability_account()
adv.submit()
gl_with_opening_set = frappe.db.get_all(
"GL Entry", filters={"voucher_no": adv.name, "is_opening": "Yes"}
)
# 'Is Opening' can be 'Yes' for Advances in separate party account
self.assertNotEqual(gl_with_opening_set, [])
# Disable Advance in separate party account
frappe.db.set_value(
"Company",
company,
{
"book_advance_payments_in_separate_party_account": 0,
"default_advance_received_account": None,
},
)
payment = create_payment_entry(
party_type="Customer",
party="_Test Customer",
payment_type="Receive",
paid_from="Debtors - _TC",
paid_to="_Test Cash - _TC",
)
payment.is_opening = "Yes"
payment.save()
payment.submit()
gl_with_opening_set = frappe.db.get_all(
"GL Entry", filters={"voucher_no": payment.name, "is_opening": "Yes"}
)
# 'Is Opening' should always be 'No' for normal advance payments
self.assertEqual(gl_with_opening_set, [])
def create_payment_entry(**args): def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry") payment_entry = frappe.new_doc("Payment Entry")

View File

@@ -677,7 +677,7 @@ frappe.ui.form.on("Purchase Invoice", {
if (frm.doc.supplier) { if (frm.doc.supplier) {
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0; frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
} }
if (!frm.doc.__onload.supplier_tds) { if (!frm.doc.__onload.enable_apply_tds) {
frm.set_df_property("apply_tds", "read_only", 1); frm.set_df_property("apply_tds", "read_only", 1);
} }
} }

View File

@@ -3,7 +3,7 @@
import frappe import frappe
from frappe import _, throw from frappe import _, qb, throw
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.query_builder.functions import Sum from frappe.query_builder.functions import Sum
from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
@@ -347,6 +347,22 @@ class PurchaseInvoice(BuyingController):
self.tax_withholding_category = tds_category self.tax_withholding_category = tds_category
self.set_onload("supplier_tds", tds_category) self.set_onload("supplier_tds", tds_category)
# If Linked Purchase Order has TDS applied, enable 'apply_tds' checkbox
if purchase_orders := [x.purchase_order for x in self.items if x.purchase_order]:
po = qb.DocType("Purchase Order")
po_with_tds = (
qb.from_(po)
.select(po.name)
.where(
po.docstatus.eq(1)
& (po.name.isin(purchase_orders))
& (po.apply_tds.eq(1))
& (po.tax_withholding_category.notnull())
)
.run()
)
self.set_onload("enable_apply_tds", True if po_with_tds else False)
super().set_missing_values(for_validate) super().set_missing_values(for_validate)
def validate_credit_to_acc(self): def validate_credit_to_acc(self):

View File

@@ -17,7 +17,7 @@
"in_create": 1, "in_create": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2023-11-07 14:24:13.321522", "modified": "2024-06-06 13:56:37.908879",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Repost Accounting Ledger Settings", "name": "Repost Accounting Ledger Settings",
@@ -30,13 +30,17 @@
"print": 1, "print": 1,
"read": 1, "read": 1,
"role": "Administrator", "role": "Administrator",
"select": 1,
"share": 1, "share": 1,
"write": 1 "write": 1
}, },
{ {
"create": 1,
"delete": 1,
"read": 1, "read": 1,
"role": "System Manager", "role": "System Manager",
"select": 1 "select": 1,
"write": 1
} }
], ],
"sort_field": "modified", "sort_field": "modified",

View File

@@ -787,6 +787,7 @@
"hide_days": 1, "hide_days": 1,
"hide_seconds": 1, "hide_seconds": 1,
"label": "Time Sheets", "label": "Time Sheets",
"no_copy": 1,
"options": "Sales Invoice Timesheet", "options": "Sales Invoice Timesheet",
"print_hide": 1 "print_hide": 1
}, },
@@ -2187,7 +2188,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2024-05-08 18:02:28.549041", "modified": "2024-06-07 16:49:32.458402",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -69,48 +69,50 @@ def get_asset_categories_for_grouped_by_category(filters):
condition = "" condition = ""
if filters.get("asset_category"): if filters.get("asset_category"):
condition += " and asset_category = %(asset_category)s" condition += " and asset_category = %(asset_category)s"
# nosemgrep
return frappe.db.sql( return frappe.db.sql(
f""" f"""
SELECT asset_category, SELECT a.asset_category,
ifnull(sum(case when purchase_date < %(from_date)s then ifnull(sum(case when a.purchase_date < %(from_date)s then
case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then
gross_purchase_amount a.gross_purchase_amount
else else
0 0
end end
else else
0 0
end), 0) as cost_as_on_from_date, end), 0) as cost_as_on_from_date,
ifnull(sum(case when purchase_date >= %(from_date)s then ifnull(sum(case when a.purchase_date >= %(from_date)s then
gross_purchase_amount a.gross_purchase_amount
else else
0 0
end), 0) as cost_of_new_purchase, end), 0) as cost_of_new_purchase,
ifnull(sum(case when ifnull(disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and disposal_date >= %(from_date)s and a.disposal_date >= %(from_date)s
and disposal_date <= %(to_date)s then and a.disposal_date <= %(to_date)s then
case when status = "Sold" then case when a.status = "Sold" then
gross_purchase_amount a.gross_purchase_amount
else else
0 0
end end
else else
0 0
end), 0) as cost_of_sold_asset, end), 0) as cost_of_sold_asset,
ifnull(sum(case when ifnull(disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and disposal_date >= %(from_date)s and a.disposal_date >= %(from_date)s
and disposal_date <= %(to_date)s then and a.disposal_date <= %(to_date)s then
case when status = "Scrapped" then case when a.status = "Scrapped" then
gross_purchase_amount a.gross_purchase_amount
else else
0 0
end end
else else
0 0
end), 0) as cost_of_scrapped_asset end), 0) as cost_of_scrapped_asset
from `tabAsset` from `tabAsset` a
where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition} where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition}
group by asset_category and not exists(select name from `tabAsset Capitalization Asset Item` where asset = a.name)
group by a.asset_category
""", """,
{ {
"to_date": filters.to_date, "to_date": filters.to_date,

View File

@@ -715,6 +715,9 @@ class StockController(AccountsController):
row.db_set("rejected_serial_and_batch_bundle", None) row.db_set("rejected_serial_and_batch_bundle", None)
if row.get("current_serial_and_batch_bundle"):
row.db_set("current_serial_and_batch_bundle", None)
def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False): def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False):
if not table_name: if not table_name:
table_name = "items" table_name = "items"

View File

@@ -452,6 +452,10 @@ class ProductionPlan(Document):
{"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty} {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty}
) )
bom_no = data.bom_no or item_details and item_details.bom_no or ""
if not bom_no:
continue
pi = self.append( pi = self.append(
"po_items", "po_items",
{ {
@@ -459,7 +463,7 @@ class ProductionPlan(Document):
"item_code": data.item_code, "item_code": data.item_code,
"description": data.description or item_details.description, "description": data.description or item_details.description,
"stock_uom": item_details and item_details.stock_uom or "", "stock_uom": item_details and item_details.stock_uom or "",
"bom_no": data.bom_no or item_details and item_details.bom_no or "", "bom_no": bom_no,
"planned_qty": data.pending_qty, "planned_qty": data.pending_qty,
"pending_qty": data.pending_qty, "pending_qty": data.pending_qty,
"planned_start_date": now_datetime(), "planned_start_date": now_datetime(),

View File

@@ -328,6 +328,28 @@ class TestProductionPlan(FrappeTestCase):
self.assertEqual(pln2.po_items[0].bom_no, bom2.name) self.assertEqual(pln2.po_items[0].bom_no, bom2.name)
def test_production_plan_with_non_active_bom_item(self):
item = make_item("Test Production Item 1 for Non Active BOM", {"is_stock_item": 1}).name
so1 = make_sales_order(item_code=item, qty=1)
pln = frappe.new_doc("Production Plan")
pln.company = so1.company
pln.get_items_from = "Sales Order"
pln.append(
"sales_orders",
{
"sales_order": so1.name,
"sales_order_date": so1.transaction_date,
"customer": so1.customer,
"grand_total": so1.grand_total,
},
)
pln.get_items()
self.assertFalse(pln.po_items)
def test_production_plan_combine_items(self): def test_production_plan_combine_items(self):
"Test combining FG items in Production Plan." "Test combining FG items in Production Plan."
item = "Test Production Item 1" item = "Test Production Item 1"

View File

@@ -83,7 +83,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total")); this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total"));
} }
this.frm.refresh_field("taxes"); this.frm.refresh_fields();
} }
calculate_discount_amount() { calculate_discount_amount() {
@@ -841,7 +841,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}); });
} }
this.frm.refresh_field("taxes"); this.frm.refresh_fields();
} }
set_default_payment(total_amount_to_pay, update_paid_amount) { set_default_payment(total_amount_to_pay, update_paid_amount) {

View File

@@ -1246,8 +1246,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
} }
qty(doc, cdt, cdn) { qty(doc, cdt, cdn) {
if (!this.frm.doc.__onload?.load_after_mapping) { let item = frappe.get_doc(cdt, cdn);
let item = frappe.get_doc(cdt, cdn); if (!this.is_a_mapped_document(item)) {
// item.pricing_rules = '' // item.pricing_rules = ''
frappe.run_serially([ frappe.run_serially([
() => this.remove_pricing_rule_for_item(item), () => this.remove_pricing_rule_for_item(item),

View File

@@ -794,6 +794,11 @@ def get_requested_item_qty(sales_order):
def make_material_request(source_name, target_doc=None): def make_material_request(source_name, target_doc=None):
requested_item_qty = get_requested_item_qty(source_name) requested_item_qty = get_requested_item_qty(source_name)
def postprocess(source, target):
if source.tc_name and frappe.db.get_value("Terms and Conditions", source.tc_name, "buying") != 1:
target.tc_name = None
target.terms = None
def get_remaining_qty(so_item): def get_remaining_qty(so_item):
return flt( return flt(
flt(so_item.qty) flt(so_item.qty)
@@ -849,6 +854,7 @@ def make_material_request(source_name, target_doc=None):
}, },
}, },
target_doc, target_doc,
postprocess,
) )
return doc return doc

View File

@@ -42,7 +42,7 @@ class HolidayList(Document):
def validate(self): def validate(self):
self.validate_days() self.validate_days()
self.total_holidays = len(self.holidays) self.total_holidays = len(self.holidays)
self.validate_dupliacte_date() self.validate_duplicate_date()
@frappe.whitelist() @frappe.whitelist()
def get_weekly_off_dates(self): def get_weekly_off_dates(self):
@@ -148,7 +148,7 @@ class HolidayList(Document):
def clear_table(self): def clear_table(self):
self.set("holidays", []) self.set("holidays", [])
def validate_dupliacte_date(self): def validate_duplicate_date(self):
unique_dates = [] unique_dates = []
for row in self.holidays: for row in self.holidays:
if row.holiday_date in unique_dates: if row.holiday_date in unique_dates:

View File

@@ -26,3 +26,29 @@ class TestVehicle(unittest.TestCase):
} }
) )
vehicle.insert() vehicle.insert()
def test_renaming_vehicle(self):
license_plate = random_string(10).upper()
vehicle = frappe.get_doc(
{
"doctype": "Vehicle",
"license_plate": license_plate,
"make": "Skoda",
"model": "Slavia",
"last_odometer": 5000,
"acquisition_date": frappe.utils.nowdate(),
"location": "Mumbai",
"chassis_no": "1234EFGH",
"uom": "Litre",
"vehicle_value": frappe.utils.flt(500000),
}
)
vehicle.insert()
new_license_plate = random_string(10).upper()
frappe.rename_doc("Vehicle", license_plate, new_license_plate)
self.assertEqual(
new_license_plate, frappe.db.get_value("Vehicle", new_license_plate, "license_plate")
)

View File

@@ -2,7 +2,8 @@
"allow_copy": 0, "allow_copy": 0,
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "actions": [],
"allow_rename": 1,
"autoname": "field:license_plate", "autoname": "field:license_plate",
"beta": 0, "beta": 0,
"creation": "2016-09-03 03:33:27.680331", "creation": "2016-09-03 03:33:27.680331",
@@ -834,7 +835,8 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2022-06-27 14:48:30.813359", "links": [],
"modified": "2024-05-31 06:38:15.399283",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Vehicle", "name": "Vehicle",

View File

@@ -164,7 +164,11 @@ class StockReconciliation(StockController):
for item in self.items: for item in self.items:
if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle: if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle:
bundle = self.get_bundle_for_specific_serial_batch(item) bundle = self.get_bundle_for_specific_serial_batch(item)
item.current_serial_and_batch_bundle = bundle item.current_serial_and_batch_bundle = bundle.name
item.current_valuation_rate = abs(bundle.avg_rate)
if not item.valuation_rate:
item.valuation_rate = item.current_valuation_rate
continue continue
if not save and item.use_serial_batch_fields: if not save and item.use_serial_batch_fields:
@@ -282,7 +286,12 @@ class StockReconciliation(StockController):
from erpnext.stock.serial_batch_bundle import SerialBatchCreation from erpnext.stock.serial_batch_bundle import SerialBatchCreation
if row.current_serial_and_batch_bundle and not self.has_change_in_serial_batch(row): if row.current_serial_and_batch_bundle and not self.has_change_in_serial_batch(row):
return row.current_serial_and_batch_bundle return frappe._dict(
{
"name": row.current_serial_and_batch_bundle,
"avg_rate": row.current_valuation_rate,
}
)
cls_obj = SerialBatchCreation( cls_obj = SerialBatchCreation(
{ {
@@ -316,12 +325,11 @@ class StockReconciliation(StockController):
total_current_qty += current_qty total_current_qty += current_qty
entry.qty = current_qty * -1 entry.qty = current_qty * -1
reco_obj.flags.ignore_validate = True
reco_obj.save() reco_obj.save()
row.current_qty = total_current_qty row.current_qty = total_current_qty
return reco_obj.name return reco_obj
def has_change_in_serial_batch(self, row) -> bool: def has_change_in_serial_batch(self, row) -> bool:
bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []} bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []}
@@ -721,7 +729,7 @@ class StockReconciliation(StockController):
for d in serial_nos: for d in serial_nos:
frappe.db.set_value("Serial No", d, "purchase_rate", valuation_rate) frappe.db.set_value("Serial No", d, "purchase_rate", valuation_rate)
def get_sle_for_items(self, row, serial_nos=None): def get_sle_for_items(self, row, serial_nos=None, current_bundle=True):
"""Insert Stock Ledger Entries""" """Insert Stock Ledger Entries"""
if not serial_nos and row.serial_no: if not serial_nos and row.serial_no:
@@ -755,7 +763,7 @@ class StockReconciliation(StockController):
has_dimensions = True has_dimensions = True
if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle): if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle):
if row.current_qty: if row.current_qty and current_bundle:
data.actual_qty = -1 * row.current_qty data.actual_qty = -1 * row.current_qty
data.qty_after_transaction = flt(row.current_qty) data.qty_after_transaction = flt(row.current_qty)
data.previous_qty_after_transaction = flt(row.qty) data.previous_qty_after_transaction = flt(row.qty)
@@ -785,6 +793,8 @@ class StockReconciliation(StockController):
has_serial_no = False has_serial_no = False
for row in self.items: for row in self.items:
sl_entries.append(self.get_sle_for_items(row)) sl_entries.append(self.get_sle_for_items(row))
if row.serial_and_batch_bundle and row.current_serial_and_batch_bundle:
sl_entries.append(self.get_sle_for_items(row, current_bundle=False))
if sl_entries: if sl_entries:
if has_serial_no: if has_serial_no:

View File

@@ -1109,6 +1109,8 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
) )
sr.reload() sr.reload()
self.assertTrue(sr.items[0].current_valuation_rate)
current_sabb = sr.items[0].current_serial_and_batch_bundle current_sabb = sr.items[0].current_serial_and_batch_bundle
doc = frappe.get_doc("Serial and Batch Bundle", current_sabb) doc = frappe.get_doc("Serial and Batch Bundle", current_sabb)
for row in doc.entries: for row in doc.entries:
@@ -1118,6 +1120,18 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
batch_qty = get_batch_qty(batches[0].batch_no, warehouse, item.name) batch_qty = get_batch_qty(batches[0].batch_no, warehouse, item.name)
self.assertEqual(batch_qty, 100) self.assertEqual(batch_qty, 100)
for row in frappe.get_all("Repost Item Valuation", filters={"voucher_no": sr.name}):
rdoc = frappe.get_doc("Repost Item Valuation", row.name)
rdoc.cancel()
rdoc.delete()
sr.cancel()
for row in frappe.get_all(
"Serial and Batch Bundle", fields=["docstatus"], filters={"voucher_no": sr.name}
):
self.assertEqual(row.docstatus, 2)
def test_not_reconcile_all_serial_nos(self): def test_not_reconcile_all_serial_nos(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate

View File

@@ -36,6 +36,7 @@ from erpnext.stock.utils import (
get_incoming_outgoing_rate_for_cancel, get_incoming_outgoing_rate_for_cancel,
get_incoming_rate, get_incoming_rate,
get_or_make_bin, get_or_make_bin,
get_serial_nos_data,
get_stock_balance, get_stock_balance,
get_valuation_method, get_valuation_method,
) )
@@ -811,9 +812,10 @@ class update_entries_after:
self.update_outgoing_rate_on_transaction(sle) self.update_outgoing_rate_on_transaction(sle)
def get_serialized_values(self, sle): def get_serialized_values(self, sle):
from erpnext.stock.serial_batch_bundle import SerialNoValuation
incoming_rate = flt(sle.incoming_rate) incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty) actual_qty = flt(sle.actual_qty)
serial_nos = cstr(sle.serial_no).split("\n")
if incoming_rate < 0: if incoming_rate < 0:
# wrong incoming rate # wrong incoming rate
@@ -826,8 +828,16 @@ class update_entries_after:
# In case of delivery/stock issue, get average purchase rate # In case of delivery/stock issue, get average purchase rate
# of serial nos of current entry # of serial nos of current entry
if not sle.is_cancelled: if not sle.is_cancelled:
outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos) new_sle = copy.deepcopy(sle)
stock_value_change = -1 * outgoing_value new_sle.qty = new_sle.actual_qty
new_sle.serial_nos = get_serial_nos_data(new_sle.get("serial_no"))
sn_obj = SerialNoValuation(
sle=new_sle, warehouse=new_sle.get("warehouse"), item_code=new_sle.get("item_code")
)
outgoing_value = sn_obj.get_incoming_rate()
stock_value_change = actual_qty * outgoing_value
else: else:
stock_value_change = actual_qty * sle.outgoing_rate stock_value_change = actual_qty * sle.outgoing_rate
@@ -1272,6 +1282,8 @@ class update_entries_after:
self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
def update_batched_values(self, sle): def update_batched_values(self, sle):
from erpnext.stock.serial_batch_bundle import BatchNoValuation
incoming_rate = flt(sle.incoming_rate) incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty) actual_qty = flt(sle.actual_qty)
@@ -1282,21 +1294,25 @@ class update_entries_after:
if actual_qty > 0: if actual_qty > 0:
stock_value_difference = incoming_rate * actual_qty stock_value_difference = incoming_rate * actual_qty
else: else:
outgoing_rate = get_batch_incoming_rate( new_sle = copy.deepcopy(sle)
item_code=sle.item_code,
warehouse=sle.warehouse, new_sle.qty = new_sle.actual_qty
batch_no=sle.batch_no, new_sle.batch_nos = frappe._dict({new_sle.batch_no: new_sle})
posting_date=sle.posting_date, batch_obj = BatchNoValuation(
posting_time=sle.posting_time, sle=new_sle,
creation=sle.creation, warehouse=new_sle.get("warehouse"),
item_code=new_sle.get("item_code"),
) )
outgoing_rate = batch_obj.get_incoming_rate()
if outgoing_rate is None: if outgoing_rate is None:
# This can *only* happen if qty available for the batch is zero. # This can *only* happen if qty available for the batch is zero.
# in such case fall back various other rates. # in such case fall back various other rates.
# future entries will correct the overall accounting as each # future entries will correct the overall accounting as each
# batch individually uses moving average rates. # batch individually uses moving average rates.
outgoing_rate = self.get_fallback_rate(sle) outgoing_rate = self.get_fallback_rate(sle)
stock_value_difference = outgoing_rate * actual_qty stock_value_difference = outgoing_rate * actual_qty
self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference) self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference)