mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-28 02:58:30 +00:00
Merge pull request #41854 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -89,6 +89,7 @@
|
||||
"custom_remarks",
|
||||
"remarks",
|
||||
"base_in_words",
|
||||
"is_opening",
|
||||
"column_break_16",
|
||||
"letter_head",
|
||||
"print_heading",
|
||||
@@ -777,6 +778,16 @@
|
||||
"label": "Reconcile on Advance Payment Date",
|
||||
"no_copy": 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,
|
||||
@@ -790,7 +801,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2024-05-17 10:21:11.199445",
|
||||
"modified": "2024-05-31 17:07:06.197249",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -116,11 +116,13 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
self.book_advance_payments_in_separate_party_account = False
|
||||
if self.party_type not in ("Customer", "Supplier"):
|
||||
self.is_opening = "No"
|
||||
return
|
||||
|
||||
if not frappe.db.get_value(
|
||||
"Company", self.company, "book_advance_payments_in_separate_party_account"
|
||||
):
|
||||
self.is_opening = "No"
|
||||
return
|
||||
|
||||
# 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 (
|
||||
account_type == "Receivable" and self.party_type == "Supplier"
|
||||
):
|
||||
self.is_opening = "No"
|
||||
return
|
||||
|
||||
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 reference_types - allowed_types:
|
||||
self.book_advance_payments_in_separate_party_account = False
|
||||
self.is_opening = "No"
|
||||
return
|
||||
|
||||
liability_account = get_party_account(
|
||||
|
||||
@@ -1729,6 +1729,68 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
self.check_gl_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):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
|
||||
@@ -677,7 +677,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
if (frm.doc.supplier) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _, throw
|
||||
from frappe import _, qb, throw
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
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.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)
|
||||
|
||||
def validate_credit_to_acc(self):
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-07 14:24:13.321522",
|
||||
"modified": "2024-06-06 13:56:37.908879",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Accounting Ledger Settings",
|
||||
@@ -30,13 +30,17 @@
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Administrator",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1
|
||||
"select": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
|
||||
@@ -787,6 +787,7 @@
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Time Sheets",
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice Timesheet",
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -2187,7 +2188,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2024-05-08 18:02:28.549041",
|
||||
"modified": "2024-06-07 16:49:32.458402",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -69,48 +69,50 @@ def get_asset_categories_for_grouped_by_category(filters):
|
||||
condition = ""
|
||||
if filters.get("asset_category"):
|
||||
condition += " and asset_category = %(asset_category)s"
|
||||
# nosemgrep
|
||||
return frappe.db.sql(
|
||||
f"""
|
||||
SELECT asset_category,
|
||||
ifnull(sum(case when purchase_date < %(from_date)s then
|
||||
case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then
|
||||
gross_purchase_amount
|
||||
SELECT a.asset_category,
|
||||
ifnull(sum(case when a.purchase_date < %(from_date)s then
|
||||
case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then
|
||||
a.gross_purchase_amount
|
||||
else
|
||||
0
|
||||
end
|
||||
else
|
||||
0
|
||||
end), 0) as cost_as_on_from_date,
|
||||
ifnull(sum(case when purchase_date >= %(from_date)s then
|
||||
gross_purchase_amount
|
||||
ifnull(sum(case when a.purchase_date >= %(from_date)s then
|
||||
a.gross_purchase_amount
|
||||
else
|
||||
0
|
||||
end), 0) as cost_of_new_purchase,
|
||||
ifnull(sum(case when ifnull(disposal_date, 0) != 0
|
||||
and disposal_date >= %(from_date)s
|
||||
and disposal_date <= %(to_date)s then
|
||||
case when status = "Sold" then
|
||||
gross_purchase_amount
|
||||
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
|
||||
and a.disposal_date >= %(from_date)s
|
||||
and a.disposal_date <= %(to_date)s then
|
||||
case when a.status = "Sold" then
|
||||
a.gross_purchase_amount
|
||||
else
|
||||
0
|
||||
end
|
||||
else
|
||||
0
|
||||
end), 0) as cost_of_sold_asset,
|
||||
ifnull(sum(case when ifnull(disposal_date, 0) != 0
|
||||
and disposal_date >= %(from_date)s
|
||||
and disposal_date <= %(to_date)s then
|
||||
case when status = "Scrapped" then
|
||||
gross_purchase_amount
|
||||
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
|
||||
and a.disposal_date >= %(from_date)s
|
||||
and a.disposal_date <= %(to_date)s then
|
||||
case when a.status = "Scrapped" then
|
||||
a.gross_purchase_amount
|
||||
else
|
||||
0
|
||||
end
|
||||
else
|
||||
0
|
||||
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}
|
||||
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,
|
||||
|
||||
@@ -715,6 +715,9 @@ class StockController(AccountsController):
|
||||
|
||||
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):
|
||||
if not table_name:
|
||||
table_name = "items"
|
||||
|
||||
@@ -452,6 +452,10 @@ class ProductionPlan(Document):
|
||||
{"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(
|
||||
"po_items",
|
||||
{
|
||||
@@ -459,7 +463,7 @@ class ProductionPlan(Document):
|
||||
"item_code": data.item_code,
|
||||
"description": data.description or item_details.description,
|
||||
"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,
|
||||
"pending_qty": data.pending_qty,
|
||||
"planned_start_date": now_datetime(),
|
||||
|
||||
@@ -328,6 +328,28 @@ class TestProductionPlan(FrappeTestCase):
|
||||
|
||||
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):
|
||||
"Test combining FG items in Production Plan."
|
||||
item = "Test Production Item 1"
|
||||
|
||||
@@ -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.refresh_field("taxes");
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -1246,8 +1246,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
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 = ''
|
||||
frappe.run_serially([
|
||||
() => this.remove_pricing_rule_for_item(item),
|
||||
|
||||
@@ -794,6 +794,11 @@ def get_requested_item_qty(sales_order):
|
||||
def make_material_request(source_name, target_doc=None):
|
||||
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):
|
||||
return flt(
|
||||
flt(so_item.qty)
|
||||
@@ -849,6 +854,7 @@ def make_material_request(source_name, target_doc=None):
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
postprocess,
|
||||
)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -42,7 +42,7 @@ class HolidayList(Document):
|
||||
def validate(self):
|
||||
self.validate_days()
|
||||
self.total_holidays = len(self.holidays)
|
||||
self.validate_dupliacte_date()
|
||||
self.validate_duplicate_date()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_weekly_off_dates(self):
|
||||
@@ -148,7 +148,7 @@ class HolidayList(Document):
|
||||
def clear_table(self):
|
||||
self.set("holidays", [])
|
||||
|
||||
def validate_dupliacte_date(self):
|
||||
def validate_duplicate_date(self):
|
||||
unique_dates = []
|
||||
for row in self.holidays:
|
||||
if row.holiday_date in unique_dates:
|
||||
|
||||
@@ -26,3 +26,29 @@ class TestVehicle(unittest.TestCase):
|
||||
}
|
||||
)
|
||||
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")
|
||||
)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:license_plate",
|
||||
"beta": 0,
|
||||
"creation": "2016-09-03 03:33:27.680331",
|
||||
@@ -834,7 +835,8 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2022-06-27 14:48:30.813359",
|
||||
"links": [],
|
||||
"modified": "2024-05-31 06:38:15.399283",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Vehicle",
|
||||
|
||||
@@ -164,7 +164,11 @@ class StockReconciliation(StockController):
|
||||
for item in self.items:
|
||||
if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle:
|
||||
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
|
||||
|
||||
if not save and item.use_serial_batch_fields:
|
||||
@@ -282,7 +286,12 @@ class StockReconciliation(StockController):
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
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(
|
||||
{
|
||||
@@ -316,12 +325,11 @@ class StockReconciliation(StockController):
|
||||
total_current_qty += current_qty
|
||||
entry.qty = current_qty * -1
|
||||
|
||||
reco_obj.flags.ignore_validate = True
|
||||
reco_obj.save()
|
||||
|
||||
row.current_qty = total_current_qty
|
||||
|
||||
return reco_obj.name
|
||||
return reco_obj
|
||||
|
||||
def has_change_in_serial_batch(self, row) -> bool:
|
||||
bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []}
|
||||
@@ -721,7 +729,7 @@ class StockReconciliation(StockController):
|
||||
for d in serial_nos:
|
||||
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"""
|
||||
|
||||
if not serial_nos and row.serial_no:
|
||||
@@ -755,7 +763,7 @@ class StockReconciliation(StockController):
|
||||
has_dimensions = True
|
||||
|
||||
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.qty_after_transaction = flt(row.current_qty)
|
||||
data.previous_qty_after_transaction = flt(row.qty)
|
||||
@@ -785,6 +793,8 @@ class StockReconciliation(StockController):
|
||||
has_serial_no = False
|
||||
for row in self.items:
|
||||
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 has_serial_no:
|
||||
|
||||
@@ -1109,6 +1109,8 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
)
|
||||
|
||||
sr.reload()
|
||||
|
||||
self.assertTrue(sr.items[0].current_valuation_rate)
|
||||
current_sabb = sr.items[0].current_serial_and_batch_bundle
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", current_sabb)
|
||||
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)
|
||||
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):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
@@ -36,6 +36,7 @@ from erpnext.stock.utils import (
|
||||
get_incoming_outgoing_rate_for_cancel,
|
||||
get_incoming_rate,
|
||||
get_or_make_bin,
|
||||
get_serial_nos_data,
|
||||
get_stock_balance,
|
||||
get_valuation_method,
|
||||
)
|
||||
@@ -811,9 +812,10 @@ class update_entries_after:
|
||||
self.update_outgoing_rate_on_transaction(sle)
|
||||
|
||||
def get_serialized_values(self, sle):
|
||||
from erpnext.stock.serial_batch_bundle import SerialNoValuation
|
||||
|
||||
incoming_rate = flt(sle.incoming_rate)
|
||||
actual_qty = flt(sle.actual_qty)
|
||||
serial_nos = cstr(sle.serial_no).split("\n")
|
||||
|
||||
if incoming_rate < 0:
|
||||
# wrong incoming rate
|
||||
@@ -826,8 +828,16 @@ class update_entries_after:
|
||||
# In case of delivery/stock issue, get average purchase rate
|
||||
# of serial nos of current entry
|
||||
if not sle.is_cancelled:
|
||||
outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos)
|
||||
stock_value_change = -1 * outgoing_value
|
||||
new_sle = copy.deepcopy(sle)
|
||||
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:
|
||||
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
|
||||
|
||||
def update_batched_values(self, sle):
|
||||
from erpnext.stock.serial_batch_bundle import BatchNoValuation
|
||||
|
||||
incoming_rate = flt(sle.incoming_rate)
|
||||
actual_qty = flt(sle.actual_qty)
|
||||
|
||||
@@ -1282,21 +1294,25 @@ class update_entries_after:
|
||||
if actual_qty > 0:
|
||||
stock_value_difference = incoming_rate * actual_qty
|
||||
else:
|
||||
outgoing_rate = get_batch_incoming_rate(
|
||||
item_code=sle.item_code,
|
||||
warehouse=sle.warehouse,
|
||||
batch_no=sle.batch_no,
|
||||
posting_date=sle.posting_date,
|
||||
posting_time=sle.posting_time,
|
||||
creation=sle.creation,
|
||||
new_sle = copy.deepcopy(sle)
|
||||
|
||||
new_sle.qty = new_sle.actual_qty
|
||||
new_sle.batch_nos = frappe._dict({new_sle.batch_no: new_sle})
|
||||
batch_obj = BatchNoValuation(
|
||||
sle=new_sle,
|
||||
warehouse=new_sle.get("warehouse"),
|
||||
item_code=new_sle.get("item_code"),
|
||||
)
|
||||
|
||||
outgoing_rate = batch_obj.get_incoming_rate()
|
||||
|
||||
if outgoing_rate is None:
|
||||
# This can *only* happen if qty available for the batch is zero.
|
||||
# in such case fall back various other rates.
|
||||
# future entries will correct the overall accounting as each
|
||||
# batch individually uses moving average rates.
|
||||
outgoing_rate = self.get_fallback_rate(sle)
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user