Merge branch 'develop' into persistent-indexing
This commit is contained in:
commit
f37f7ca5c3
@ -94,10 +94,13 @@ class BankTransaction(Document):
|
|||||||
pe.append(reference)
|
pe.append(reference)
|
||||||
|
|
||||||
def update_allocated_amount(self):
|
def update_allocated_amount(self):
|
||||||
self.allocated_amount = (
|
allocated_amount = (
|
||||||
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
|
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
|
||||||
)
|
)
|
||||||
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount
|
unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount
|
||||||
|
|
||||||
|
self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
|
||||||
|
self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
|
||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
self.allocate_payment_entries()
|
self.allocate_payment_entries()
|
||||||
|
|||||||
@ -14,23 +14,8 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
super.setup(doc);
|
super.setup(doc);
|
||||||
}
|
}
|
||||||
company() {
|
company() {
|
||||||
|
super.company();
|
||||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||||
|
|
||||||
let me = this;
|
|
||||||
if (this.frm.doc.company) {
|
|
||||||
frappe.call({
|
|
||||||
method:
|
|
||||||
"erpnext.accounts.party.get_party_account",
|
|
||||||
args: {
|
|
||||||
party_type: 'Customer',
|
|
||||||
party: this.frm.doc.customer,
|
|
||||||
company: this.frm.doc.company
|
|
||||||
},
|
|
||||||
callback: (response) => {
|
|
||||||
if (response) me.frm.set_value("debit_to", response.message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onload() {
|
onload() {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|||||||
@ -83,7 +83,10 @@ class ReceivablePayableReport(object):
|
|||||||
self.skip_total_row = 1
|
self.skip_total_row = 1
|
||||||
|
|
||||||
if self.filters.get("in_party_currency"):
|
if self.filters.get("in_party_currency"):
|
||||||
self.skip_total_row = 1
|
if self.filters.get("party") and len(self.filters.get("party")) == 1:
|
||||||
|
self.skip_total_row = 0
|
||||||
|
else:
|
||||||
|
self.skip_total_row = 1
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
self.get_ple_entries()
|
self.get_ple_entries()
|
||||||
|
|||||||
@ -357,7 +357,13 @@ def get_conditions(filters, additional_conditions=None):
|
|||||||
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
|
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
|
||||||
|
|
||||||
if filters.get("warehouse"):
|
if filters.get("warehouse"):
|
||||||
conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
|
if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
|
||||||
|
lft, rgt = frappe.db.get_all(
|
||||||
|
"Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True
|
||||||
|
)[0]
|
||||||
|
conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) "
|
||||||
|
else:
|
||||||
|
conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
|
||||||
|
|
||||||
if filters.get("brand"):
|
if filters.get("brand"):
|
||||||
conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
|
conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
|
||||||
|
|||||||
@ -216,6 +216,18 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.get("is_return") and self.get("return_against"):
|
||||||
|
document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
|
||||||
|
frappe.msgprint(
|
||||||
|
_(
|
||||||
|
"{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}."
|
||||||
|
).format(
|
||||||
|
document_type,
|
||||||
|
get_link_to_form("Payment Reconciliation"),
|
||||||
|
get_link_to_form(self.doctype, self.get("return_against")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
|
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
|
||||||
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
|
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
|
||||||
self.set_advances()
|
self.set_advances()
|
||||||
@ -2691,7 +2703,7 @@ def get_advance_journal_entries(
|
|||||||
|
|
||||||
if order_list:
|
if order_list:
|
||||||
q = q.where(
|
q = q.where(
|
||||||
(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_type).isin(order_list))
|
(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_name).isin(order_list))
|
||||||
)
|
)
|
||||||
|
|
||||||
q = q.orderby(journal_entry.posting_date)
|
q = q.orderby(journal_entry.posting_date)
|
||||||
|
|||||||
@ -217,8 +217,8 @@ class BuyingController(SubcontractingController):
|
|||||||
lc_voucher_data = frappe.db.sql(
|
lc_voucher_data = frappe.db.sql(
|
||||||
"""select sum(applicable_charges), cost_center
|
"""select sum(applicable_charges), cost_center
|
||||||
from `tabLanded Cost Item`
|
from `tabLanded Cost Item`
|
||||||
where docstatus = 1 and purchase_receipt_item = %s""",
|
where docstatus = 1 and purchase_receipt_item = %s and receipt_document = %s""",
|
||||||
d.name,
|
(d.name, self.name),
|
||||||
)
|
)
|
||||||
d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
|
d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
|
||||||
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
|
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from typing import List, Tuple
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.utils import cint, flt, get_link_to_form, getdate
|
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import (
|
from erpnext.accounts.general_ledger import (
|
||||||
@ -162,6 +162,9 @@ class StockController(AccountsController):
|
|||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||||
|
|
||||||
|
if self.get("_action") == "update_after_submit":
|
||||||
|
return
|
||||||
|
|
||||||
# To handle test cases
|
# To handle test cases
|
||||||
if frappe.flags.in_test and frappe.flags.use_serial_and_batch_fields:
|
if frappe.flags.in_test and frappe.flags.use_serial_and_batch_fields:
|
||||||
return
|
return
|
||||||
@ -171,13 +174,16 @@ class StockController(AccountsController):
|
|||||||
table_name = "stock_items"
|
table_name = "stock_items"
|
||||||
|
|
||||||
for row in self.get(table_name):
|
for row in self.get(table_name):
|
||||||
|
if row.serial_and_batch_bundle and (row.serial_no or row.batch_no):
|
||||||
|
self.validate_serial_nos_and_batches_with_bundle(row)
|
||||||
|
|
||||||
if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"):
|
if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not row.use_serial_batch_fields and (
|
if not row.use_serial_batch_fields and (
|
||||||
row.serial_no or row.batch_no or row.get("rejected_serial_no")
|
row.serial_no or row.batch_no or row.get("rejected_serial_no")
|
||||||
):
|
):
|
||||||
frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle"))
|
row.use_serial_batch_fields = 1
|
||||||
|
|
||||||
if row.use_serial_batch_fields and (
|
if row.use_serial_batch_fields and (
|
||||||
not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
|
not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
|
||||||
@ -219,7 +225,6 @@ class StockController(AccountsController):
|
|||||||
row.db_set(
|
row.db_set(
|
||||||
{
|
{
|
||||||
"rejected_serial_and_batch_bundle": sn_doc.name,
|
"rejected_serial_and_batch_bundle": sn_doc.name,
|
||||||
"rejected_serial_no": "",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -227,11 +232,44 @@ class StockController(AccountsController):
|
|||||||
row.db_set(
|
row.db_set(
|
||||||
{
|
{
|
||||||
"serial_and_batch_bundle": sn_doc.name,
|
"serial_and_batch_bundle": sn_doc.name,
|
||||||
"serial_no": "",
|
|
||||||
"batch_no": "",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_serial_nos_and_batches_with_bundle(self, row):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
throw_error = False
|
||||||
|
if row.serial_no:
|
||||||
|
serial_nos = frappe.get_all(
|
||||||
|
"Serial and Batch Entry", fields=["serial_no"], filters={"parent": row.serial_and_batch_bundle}
|
||||||
|
)
|
||||||
|
serial_nos = sorted([cstr(d.serial_no) for d in serial_nos])
|
||||||
|
parsed_serial_nos = get_serial_nos(row.serial_no)
|
||||||
|
|
||||||
|
if len(serial_nos) != len(parsed_serial_nos):
|
||||||
|
throw_error = True
|
||||||
|
elif serial_nos != parsed_serial_nos:
|
||||||
|
for serial_no in serial_nos:
|
||||||
|
if serial_no not in parsed_serial_nos:
|
||||||
|
throw_error = True
|
||||||
|
break
|
||||||
|
|
||||||
|
elif row.batch_no:
|
||||||
|
batches = frappe.get_all(
|
||||||
|
"Serial and Batch Entry", fields=["batch_no"], filters={"parent": row.serial_and_batch_bundle}
|
||||||
|
)
|
||||||
|
batches = sorted([d.batch_no for d in batches])
|
||||||
|
|
||||||
|
if batches != [row.batch_no]:
|
||||||
|
throw_error = True
|
||||||
|
|
||||||
|
if throw_error:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"At row {0}: Serial and Batch Bundle {1} has already created. Please remove the values from the serial no or batch no fields."
|
||||||
|
).format(row.idx, row.serial_and_batch_bundle)
|
||||||
|
)
|
||||||
|
|
||||||
def set_use_serial_batch_fields(self):
|
def set_use_serial_batch_fields(self):
|
||||||
if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
|
if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
|
||||||
for row in self.items:
|
for row in self.items:
|
||||||
|
|||||||
@ -539,6 +539,10 @@ class SubcontractingController(StockController):
|
|||||||
def __add_supplied_item(self, item_row, bom_item, qty):
|
def __add_supplied_item(self, item_row, bom_item, qty):
|
||||||
bom_item.conversion_factor = item_row.conversion_factor
|
bom_item.conversion_factor = item_row.conversion_factor
|
||||||
rm_obj = self.append(self.raw_material_table, bom_item)
|
rm_obj = self.append(self.raw_material_table, bom_item)
|
||||||
|
if rm_obj.get("qty"):
|
||||||
|
# Qty field not exists
|
||||||
|
rm_obj.qty = 0.0
|
||||||
|
|
||||||
rm_obj.reference_name = item_row.name
|
rm_obj.reference_name = item_row.name
|
||||||
|
|
||||||
if self.doctype == self.subcontract_data.order_doctype:
|
if self.doctype == self.subcontract_data.order_doctype:
|
||||||
|
|||||||
@ -401,7 +401,7 @@ class TestSubcontractingController(FrappeTestCase):
|
|||||||
{
|
{
|
||||||
"main_item_code": "Subcontracted Item SA4",
|
"main_item_code": "Subcontracted Item SA4",
|
||||||
"item_code": "Subcontracted SRM Item 3",
|
"item_code": "Subcontracted SRM Item 3",
|
||||||
"qty": 1.0,
|
"qty": 3.0,
|
||||||
"rate": 100.0,
|
"rate": 100.0,
|
||||||
"stock_uom": "Nos",
|
"stock_uom": "Nos",
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
@ -914,12 +914,6 @@ def update_item_details(child_row, details):
|
|||||||
else child_row.get("consumed_qty")
|
else child_row.get("consumed_qty")
|
||||||
)
|
)
|
||||||
|
|
||||||
if child_row.serial_no:
|
|
||||||
details.serial_no.extend(get_serial_nos(child_row.serial_no))
|
|
||||||
|
|
||||||
if child_row.batch_no:
|
|
||||||
details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
|
|
||||||
|
|
||||||
if child_row.serial_and_batch_bundle:
|
if child_row.serial_and_batch_bundle:
|
||||||
doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
|
doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
|
||||||
for row in doc.get("entries"):
|
for row in doc.get("entries"):
|
||||||
@ -928,6 +922,12 @@ def update_item_details(child_row, details):
|
|||||||
|
|
||||||
if row.batch_no:
|
if row.batch_no:
|
||||||
details.batch_no[row.batch_no] += row.qty * (-1 if doc.type_of_transaction == "Outward" else 1)
|
details.batch_no[row.batch_no] += row.qty * (-1 if doc.type_of_transaction == "Outward" else 1)
|
||||||
|
else:
|
||||||
|
if child_row.serial_no:
|
||||||
|
details.serial_no.extend(get_serial_nos(child_row.serial_no))
|
||||||
|
|
||||||
|
if child_row.batch_no:
|
||||||
|
details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
|
||||||
|
|
||||||
|
|
||||||
def make_stock_transfer_entry(**args):
|
def make_stock_transfer_entry(**args):
|
||||||
|
|||||||
@ -41,11 +41,49 @@ class BlanketOrder(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_duplicate_items()
|
self.validate_duplicate_items()
|
||||||
|
self.set_party_item_code()
|
||||||
|
|
||||||
def validate_dates(self):
|
def validate_dates(self):
|
||||||
if getdate(self.from_date) > getdate(self.to_date):
|
if getdate(self.from_date) > getdate(self.to_date):
|
||||||
frappe.throw(_("From date cannot be greater than To date"))
|
frappe.throw(_("From date cannot be greater than To date"))
|
||||||
|
|
||||||
|
def set_party_item_code(self):
|
||||||
|
item_ref = {}
|
||||||
|
if self.blanket_order_type == "Selling":
|
||||||
|
item_ref = self.get_customer_items_ref()
|
||||||
|
else:
|
||||||
|
item_ref = self.get_supplier_items_ref()
|
||||||
|
|
||||||
|
if not item_ref:
|
||||||
|
return
|
||||||
|
|
||||||
|
for row in self.items:
|
||||||
|
row.party_item_code = item_ref.get(row.item_code)
|
||||||
|
|
||||||
|
def get_customer_items_ref(self):
|
||||||
|
items = [d.item_code for d in self.items]
|
||||||
|
|
||||||
|
return frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
"Item Customer Detail",
|
||||||
|
filters={"parent": ("in", items), "customer_name": self.customer},
|
||||||
|
fields=["parent", "ref_code"],
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_supplier_items_ref(self):
|
||||||
|
items = [d.item_code for d in self.items]
|
||||||
|
|
||||||
|
return frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
"Item Supplier",
|
||||||
|
filters={"parent": ("in", items), "supplier": self.supplier},
|
||||||
|
fields=["parent", "supplier_part_no"],
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_duplicate_items(self):
|
def validate_duplicate_items(self):
|
||||||
item_list = []
|
item_list = []
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from frappe.tests.utils import FrappeTestCase
|
|||||||
from frappe.utils import add_months, today
|
from frappe.utils import add_months, today
|
||||||
|
|
||||||
from erpnext import get_company_currency
|
from erpnext import get_company_currency
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
|
||||||
from .blanket_order import make_order
|
from .blanket_order import make_order
|
||||||
|
|
||||||
@ -90,6 +91,30 @@ class TestBlanketOrder(FrappeTestCase):
|
|||||||
frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
|
frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
|
||||||
po.submit()
|
po.submit()
|
||||||
|
|
||||||
|
def test_party_item_code(self):
|
||||||
|
item_doc = make_item("_Test Item 1 for Blanket Order")
|
||||||
|
item_code = item_doc.name
|
||||||
|
|
||||||
|
customer = "_Test Customer"
|
||||||
|
supplier = "_Test Supplier"
|
||||||
|
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"Item Customer Detail", {"customer_name": customer, "parent": item_code}
|
||||||
|
):
|
||||||
|
item_doc.append("customer_items", {"customer_name": customer, "ref_code": "CUST-REF-1"})
|
||||||
|
item_doc.save()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Item Supplier", {"supplier": supplier, "parent": item_code}):
|
||||||
|
item_doc.append("supplier_items", {"supplier": supplier, "supplier_part_no": "SUPP-PART-1"})
|
||||||
|
item_doc.save()
|
||||||
|
|
||||||
|
# Blanket Order for Selling
|
||||||
|
bo = make_blanket_order(blanket_order_type="Selling", customer=customer, item_code=item_code)
|
||||||
|
self.assertEqual(bo.items[0].party_item_code, "CUST-REF-1")
|
||||||
|
|
||||||
|
bo = make_blanket_order(blanket_order_type="Purchasing", supplier=supplier, item_code=item_code)
|
||||||
|
self.assertEqual(bo.items[0].party_item_code, "SUPP-PART-1")
|
||||||
|
|
||||||
|
|
||||||
def make_blanket_order(**args):
|
def make_blanket_order(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2018-05-24 07:20:04.255236",
|
"creation": "2018-05-24 07:20:04.255236",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@ -6,6 +7,7 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"item_code",
|
"item_code",
|
||||||
"item_name",
|
"item_name",
|
||||||
|
"party_item_code",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"qty",
|
"qty",
|
||||||
"rate",
|
"rate",
|
||||||
@ -62,10 +64,17 @@
|
|||||||
"fieldname": "terms_and_conditions",
|
"fieldname": "terms_and_conditions",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"label": "Terms and Conditions"
|
"label": "Terms and Conditions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party_item_code",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Party Item Code",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-11-18 19:37:46.245878",
|
"links": [],
|
||||||
|
"modified": "2024-02-14 18:25:26.479672",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Blanket Order Item",
|
"name": "Blanket Order Item",
|
||||||
@ -74,5 +83,6 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@ -20,6 +20,7 @@ class BlanketOrderItem(Document):
|
|||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
|
party_item_code: DF.Data | None
|
||||||
qty: DF.Float
|
qty: DF.Float
|
||||||
rate: DF.Currency
|
rate: DF.Currency
|
||||||
terms_and_conditions: DF.Text | None
|
terms_and_conditions: DF.Text | None
|
||||||
|
|||||||
@ -748,7 +748,7 @@ class JobCard(Document):
|
|||||||
fields=["total_time_in_mins", "hour_rate"],
|
fields=["total_time_in_mins", "hour_rate"],
|
||||||
filters={"is_corrective_job_card": 1, "docstatus": 1, "work_order": self.work_order},
|
filters={"is_corrective_job_card": 1, "docstatus": 1, "work_order": self.work_order},
|
||||||
):
|
):
|
||||||
wo.corrective_operation_cost += flt(row.total_time_in_mins) * flt(row.hour_rate)
|
wo.corrective_operation_cost += flt(row.total_time_in_mins / 60) * flt(row.hour_rate)
|
||||||
|
|
||||||
wo.calculate_operating_cost()
|
wo.calculate_operating_cost()
|
||||||
wo.flags.ignore_validate_update_after_submit = True
|
wo.flags.ignore_validate_update_after_submit = True
|
||||||
|
|||||||
@ -652,7 +652,10 @@ class ProductionPlan(Document):
|
|||||||
"project": self.project,
|
"project": self.project,
|
||||||
}
|
}
|
||||||
|
|
||||||
key = (d.item_code, d.sales_order, d.warehouse)
|
key = (d.item_code, d.sales_order, d.sales_order_item, d.warehouse)
|
||||||
|
if self.combine_items:
|
||||||
|
key = (d.item_code, d.sales_order, d.warehouse)
|
||||||
|
|
||||||
if not d.sales_order:
|
if not d.sales_order:
|
||||||
key = (d.name, d.item_code, d.warehouse)
|
key = (d.name, d.item_code, d.warehouse)
|
||||||
|
|
||||||
|
|||||||
@ -368,7 +368,8 @@ erpnext.buying = {
|
|||||||
|
|
||||||
let update_values = {
|
let update_values = {
|
||||||
"serial_and_batch_bundle": r.name,
|
"serial_and_batch_bundle": r.name,
|
||||||
"qty": qty
|
"use_serial_batch_fields": 0,
|
||||||
|
"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.warehouse) {
|
if (r.warehouse) {
|
||||||
@ -408,7 +409,8 @@ erpnext.buying = {
|
|||||||
|
|
||||||
let update_values = {
|
let update_values = {
|
||||||
"serial_and_batch_bundle": r.name,
|
"serial_and_batch_bundle": r.name,
|
||||||
"rejected_qty": qty
|
"use_serial_batch_fields": 0,
|
||||||
|
"rejected_qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.warehouse) {
|
if (r.warehouse) {
|
||||||
|
|||||||
@ -145,6 +145,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.frm.fields_dict['items'].grid.get_field('batch_no')) {
|
||||||
|
this.frm.set_query('batch_no', 'items', function(doc, cdt, cdn) {
|
||||||
|
return me.set_query_for_batch(doc, cdt, cdn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if(
|
if(
|
||||||
this.frm.docstatus < 2
|
this.frm.docstatus < 2
|
||||||
&& this.frm.fields_dict["payment_terms_template"]
|
&& this.frm.fields_dict["payment_terms_template"]
|
||||||
@ -234,7 +240,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_fields_onload_for_line_item() {
|
set_fields_onload_for_line_item() {
|
||||||
if (this.frm.is_new && this.frm.doc?.items) {
|
if (this.frm.is_new() && this.frm.doc?.items) {
|
||||||
this.frm.doc.items.forEach(item => {
|
this.frm.doc.items.forEach(item => {
|
||||||
if (item.docstatus === 0
|
if (item.docstatus === 0
|
||||||
&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
|
&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
|
||||||
@ -1633,18 +1639,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
return item_list;
|
return item_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
items_delete() {
|
|
||||||
this.update_localstorage_scanned_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
update_localstorage_scanned_data() {
|
|
||||||
let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
|
|
||||||
if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
|
|
||||||
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
|
|
||||||
barcode_scanner.update_localstorage_scanned_data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_set_values_for_item_list(children) {
|
_set_values_for_item_list(children) {
|
||||||
const items_rule_dict = {};
|
const items_rule_dict = {};
|
||||||
|
|
||||||
|
|||||||
@ -676,6 +676,7 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date",
|
fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date",
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date"),
|
label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date"),
|
||||||
|
default: frm.doc.doctype == 'Sales Order' ? frm.doc.delivery_date : frm.doc.schedule_date,
|
||||||
reqd: 1
|
reqd: 1
|
||||||
})
|
})
|
||||||
fields.splice(3, 0, {
|
fields.splice(3, 0, {
|
||||||
|
|||||||
@ -339,7 +339,8 @@ erpnext.sales_common = {
|
|||||||
|
|
||||||
frappe.model.set_value(item.doctype, item.name, {
|
frappe.model.set_value(item.doctype, item.name, {
|
||||||
"serial_and_batch_bundle": r.name,
|
"serial_and_batch_bundle": r.name,
|
||||||
"qty": qty
|
"use_serial_batch_fields": 0,
|
||||||
|
"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
let warehouse = this.item?.type_of_transaction === "Outward" ?
|
let warehouse = this.item?.type_of_transaction === "Outward" ?
|
||||||
(this.item.warehouse || this.item.s_warehouse) : "";
|
(this.item.warehouse || this.item.s_warehouse) : "";
|
||||||
|
|
||||||
|
if (this.frm.doc.doctype === 'Stock Entry') {
|
||||||
|
warehouse = this.item.s_warehouse || this.item.t_warehouse;
|
||||||
|
}
|
||||||
|
|
||||||
if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') {
|
if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') {
|
||||||
warehouse = this.get_warehouse();
|
warehouse = this.get_warehouse();
|
||||||
}
|
}
|
||||||
@ -367,19 +371,11 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
label: __('Batch No'),
|
label: __('Batch No'),
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
get_query: () => {
|
get_query: () => {
|
||||||
if (this.item.type_of_transaction !== "Outward") {
|
return {
|
||||||
return {
|
query : "erpnext.controllers.queries.get_batch_no",
|
||||||
filters: {
|
filters: {
|
||||||
'item': this.item.item_code,
|
'item_code': this.item.item_code,
|
||||||
}
|
'warehouse': this.item.s_warehouse || this.item.t_warehouse,
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
query : "erpnext.controllers.queries.get_batch_no",
|
|
||||||
filters: {
|
|
||||||
'item_code': this.item.item_code,
|
|
||||||
'warehouse': this.get_warehouse()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -65,6 +65,7 @@ class LandedCostVoucher(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.check_mandatory()
|
self.check_mandatory()
|
||||||
self.validate_receipt_documents()
|
self.validate_receipt_documents()
|
||||||
|
self.validate_line_items()
|
||||||
init_landed_taxes_and_totals(self)
|
init_landed_taxes_and_totals(self)
|
||||||
self.set_total_taxes_and_charges()
|
self.set_total_taxes_and_charges()
|
||||||
if not self.get("items"):
|
if not self.get("items"):
|
||||||
@ -72,6 +73,26 @@ class LandedCostVoucher(Document):
|
|||||||
|
|
||||||
self.set_applicable_charges_on_item()
|
self.set_applicable_charges_on_item()
|
||||||
|
|
||||||
|
def validate_line_items(self):
|
||||||
|
for d in self.get("items"):
|
||||||
|
if (
|
||||||
|
d.docstatus == 0
|
||||||
|
and d.purchase_receipt_item
|
||||||
|
and not frappe.db.exists(
|
||||||
|
d.receipt_document_type + " Item",
|
||||||
|
{"name": d.purchase_receipt_item, "parent": d.receipt_document},
|
||||||
|
)
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row {0}: {2} Item {1} does not exist in {2} {3}").format(
|
||||||
|
d.idx,
|
||||||
|
frappe.bold(d.purchase_receipt_item),
|
||||||
|
d.receipt_document_type,
|
||||||
|
frappe.bold(d.receipt_document),
|
||||||
|
),
|
||||||
|
title=_("Incorrect Reference Document (Purchase Receipt Item)"),
|
||||||
|
)
|
||||||
|
|
||||||
def check_mandatory(self):
|
def check_mandatory(self):
|
||||||
if not self.get("purchase_receipts"):
|
if not self.get("purchase_receipts"):
|
||||||
frappe.throw(_("Please enter Receipt Document"))
|
frappe.throw(_("Please enter Receipt Document"))
|
||||||
|
|||||||
@ -250,7 +250,7 @@ frappe.ui.form.on('Material Request', {
|
|||||||
fields: [
|
fields: [
|
||||||
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
|
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
|
||||||
options:"BOM", reqd: 1, get_query: function() {
|
options:"BOM", reqd: 1, get_query: function() {
|
||||||
return {filters: { docstatus:1 }};
|
return {filters: { docstatus:1, "is_active": 1 }};
|
||||||
}},
|
}},
|
||||||
{"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"),
|
{"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"),
|
||||||
options:"Warehouse", reqd: 1},
|
options:"Warehouse", reqd: 1},
|
||||||
|
|||||||
@ -330,7 +330,8 @@ frappe.ui.form.on('Pick List Item', {
|
|||||||
let qty = Math.abs(r.total_qty);
|
let qty = Math.abs(r.total_qty);
|
||||||
frappe.model.set_value(item.doctype, item.name, {
|
frappe.model.set_value(item.doctype, item.name, {
|
||||||
"serial_and_batch_bundle": r.name,
|
"serial_and_batch_bundle": r.name,
|
||||||
"qty": qty
|
"use_serial_batch_fields": 0,
|
||||||
|
"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,6 +73,10 @@ class PickList(Document):
|
|||||||
self.update_status()
|
self.update_status()
|
||||||
self.set_item_locations()
|
self.set_item_locations()
|
||||||
|
|
||||||
|
if self.get("locations"):
|
||||||
|
self.validate_sales_order_percentage()
|
||||||
|
|
||||||
|
def validate_sales_order_percentage(self):
|
||||||
# set percentage picked in SO
|
# set percentage picked in SO
|
||||||
for location in self.get("locations"):
|
for location in self.get("locations"):
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -686,9 +686,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
stock_value_diff = (
|
stock_value_diff = (
|
||||||
flt(d.base_net_amount)
|
flt(d.base_net_amount) + flt(d.item_tax_amount) + flt(d.landed_cost_voucher_amount)
|
||||||
+ flt(d.item_tax_amount / self.conversion_rate)
|
|
||||||
+ flt(d.landed_cost_voucher_amount)
|
|
||||||
)
|
)
|
||||||
elif warehouse_account.get(d.warehouse):
|
elif warehouse_account.get(d.warehouse):
|
||||||
stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
|
stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
|
||||||
|
|||||||
@ -2259,7 +2259,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(pr.items[0].use_serial_batch_fields, 1)
|
self.assertEqual(pr.items[0].use_serial_batch_fields, 1)
|
||||||
self.assertFalse(pr.items[0].serial_no)
|
self.assertTrue(pr.items[0].serial_no)
|
||||||
self.assertTrue(pr.items[0].serial_and_batch_bundle)
|
self.assertTrue(pr.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
sbb_doc = frappe.get_doc("Serial and Batch Bundle", pr.items[0].serial_and_batch_bundle)
|
sbb_doc = frappe.get_doc("Serial and Batch Bundle", pr.items[0].serial_and_batch_bundle)
|
||||||
@ -2406,6 +2406,30 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
for index, d in enumerate(data):
|
for index, d in enumerate(data):
|
||||||
self.assertEqual(d.qty_after_transaction, 11 + index)
|
self.assertEqual(d.qty_after_transaction, 11 + index)
|
||||||
|
|
||||||
|
def test_auto_set_batch_based_on_bundle(self):
|
||||||
|
item_code = make_item(
|
||||||
|
"_Test Auto Set Batch Based on Bundle",
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"batch_number_series": "BATCH-BNU-TASBBB-.#####",
|
||||||
|
"create_new_batch": 1,
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=5,
|
||||||
|
rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(pr.items[0].batch_no)
|
||||||
|
batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
|
||||||
|
self.assertEqual(pr.items[0].batch_no, batch_no)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@ -294,9 +294,20 @@ def repost(doc):
|
|||||||
doc.log_error("Unable to repost item valuation")
|
doc.log_error("Unable to repost item valuation")
|
||||||
|
|
||||||
message = frappe.message_log.pop() if frappe.message_log else ""
|
message = frappe.message_log.pop() if frappe.message_log else ""
|
||||||
|
if isinstance(message, dict):
|
||||||
|
message = message.get("message")
|
||||||
|
|
||||||
if traceback:
|
if traceback:
|
||||||
message += "<br>" + "Traceback: <br>" + traceback
|
message += "<br><br>" + "<b>Traceback:</b> <br>" + traceback
|
||||||
frappe.db.set_value(doc.doctype, doc.name, "error_log", message)
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
doc.doctype,
|
||||||
|
doc.name,
|
||||||
|
{
|
||||||
|
"error_log": message,
|
||||||
|
"status": "Failed",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
outgoing_email_account = frappe.get_cached_value(
|
outgoing_email_account = frappe.get_cached_value(
|
||||||
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
|
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
|
||||||
|
|||||||
@ -207,13 +207,24 @@ frappe.ui.form.on('Serial and Batch Bundle', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('batch_no', 'entries', () => {
|
frm.set_query('batch_no', 'entries', (doc) => {
|
||||||
return {
|
|
||||||
filters: {
|
if (doc.type_of_transaction ==="Outward") {
|
||||||
item: frm.doc.item_code,
|
return {
|
||||||
disabled: 0,
|
query : "erpnext.controllers.queries.get_batch_no",
|
||||||
|
filters: {
|
||||||
|
item_code: doc.item_code,
|
||||||
|
warehouse: doc.warehouse,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
} else {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
item: doc.item_code,
|
||||||
|
disabled: 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('warehouse', 'entries', () => {
|
frm.set_query('warehouse', 'entries', () => {
|
||||||
|
|||||||
@ -543,7 +543,9 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
|
|
||||||
let fields = [
|
let fields = [
|
||||||
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
|
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
|
||||||
options:"BOM", reqd: 1, get_query: filters()},
|
options:"BOM", reqd: 1, get_query: () => {
|
||||||
|
return {filters: { docstatus:1, "is_active": 1 }};
|
||||||
|
}},
|
||||||
{"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"),
|
{"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"),
|
||||||
options:"Warehouse"},
|
options:"Warehouse"},
|
||||||
{"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),
|
{"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),
|
||||||
@ -936,6 +938,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
|
|||||||
this.toggle_related_fields(this.frm.doc);
|
this.toggle_related_fields(this.frm.doc);
|
||||||
this.toggle_enable_bom();
|
this.toggle_enable_bom();
|
||||||
this.show_stock_ledger();
|
this.show_stock_ledger();
|
||||||
|
this.set_fields_onload_for_line_item();
|
||||||
erpnext.utils.view_serial_batch_nos(this.frm);
|
erpnext.utils.view_serial_batch_nos(this.frm);
|
||||||
if (this.frm.doc.docstatus===1 && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
|
if (this.frm.doc.docstatus===1 && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
|
||||||
this.show_general_ledger();
|
this.show_general_ledger();
|
||||||
@ -944,6 +947,35 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
|
|||||||
erpnext.utils.add_item(this.frm);
|
erpnext.utils.add_item(this.frm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serial_no(doc, cdt, cdn) {
|
||||||
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
|
|
||||||
|
if (item?.serial_no) {
|
||||||
|
// Replace all occurences of comma with line feed
|
||||||
|
item.serial_no = item.serial_no.replace(/,/g, '\n');
|
||||||
|
item.conversion_factor = item.conversion_factor || 1;
|
||||||
|
|
||||||
|
let valid_serial_nos = [];
|
||||||
|
let serialnos = item.serial_no.split("\n");
|
||||||
|
for (var i = 0; i < serialnos.length; i++) {
|
||||||
|
if (serialnos[i] != "") {
|
||||||
|
valid_serial_nos.push(serialnos[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frappe.model.set_value(item.doctype, item.name,
|
||||||
|
"qty", valid_serial_nos.length / item.conversion_factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_fields_onload_for_line_item() {
|
||||||
|
if (this.frm.is_new() && this.frm.doc?.items
|
||||||
|
&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1) {
|
||||||
|
this.frm.doc.items.forEach(item => {
|
||||||
|
frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scan_barcode() {
|
scan_barcode() {
|
||||||
frappe.flags.dialog_set = false;
|
frappe.flags.dialog_set = false;
|
||||||
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
|
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
|
||||||
@ -1074,6 +1106,10 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
|
|||||||
|
|
||||||
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
|
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
|
||||||
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
|
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
|
||||||
|
|
||||||
|
if (cint(frappe.user_defaults?.use_serial_batch_fields)) {
|
||||||
|
frappe.model.set_value(row.doctype, row.name, "use_serial_batch_fields", 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
from_warehouse(doc) {
|
from_warehouse(doc) {
|
||||||
@ -1144,7 +1180,8 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
|
|||||||
if (r) {
|
if (r) {
|
||||||
frappe.model.set_value(item.doctype, item.name, {
|
frappe.model.set_value(item.doctype, item.name, {
|
||||||
"serial_and_batch_bundle": r.name,
|
"serial_and_batch_bundle": r.name,
|
||||||
"qty": Math.abs(r.total_qty)
|
"use_serial_batch_fields": 0,
|
||||||
|
"qty": Math.abs(r.total_qty) / flt(item.conversion_factor || 1, precision("conversion_factor", item))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -839,6 +839,7 @@ class StockEntry(StockController):
|
|||||||
currency=erpnext.get_company_currency(self.company),
|
currency=erpnext.get_company_currency(self.company),
|
||||||
company=self.company,
|
company=self.company,
|
||||||
raise_error_if_no_rate=raise_error_if_no_rate,
|
raise_error_if_no_rate=raise_error_if_no_rate,
|
||||||
|
batch_no=d.batch_no,
|
||||||
serial_and_batch_bundle=d.serial_and_batch_bundle,
|
serial_and_batch_bundle=d.serial_and_batch_bundle,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -867,7 +868,7 @@ class StockEntry(StockController):
|
|||||||
if reset_outgoing_rate:
|
if reset_outgoing_rate:
|
||||||
args = self.get_args_for_incoming_rate(d)
|
args = self.get_args_for_incoming_rate(d)
|
||||||
rate = get_incoming_rate(args, raise_error_if_no_rate)
|
rate = get_incoming_rate(args, raise_error_if_no_rate)
|
||||||
if rate > 0:
|
if rate >= 0:
|
||||||
d.basic_rate = rate
|
d.basic_rate = rate
|
||||||
|
|
||||||
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
|
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
|
||||||
@ -890,6 +891,8 @@ class StockEntry(StockController):
|
|||||||
"allow_zero_valuation": item.allow_zero_valuation_rate,
|
"allow_zero_valuation": item.allow_zero_valuation_rate,
|
||||||
"serial_and_batch_bundle": item.serial_and_batch_bundle,
|
"serial_and_batch_bundle": item.serial_and_batch_bundle,
|
||||||
"voucher_detail_no": item.name,
|
"voucher_detail_no": item.name,
|
||||||
|
"batch_no": item.batch_no,
|
||||||
|
"serial_no": item.serial_no,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1775,7 +1775,7 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(se.items[0].use_serial_batch_fields)
|
self.assertTrue(se.items[0].use_serial_batch_fields)
|
||||||
self.assertFalse(se.items[0].serial_no)
|
self.assertTrue(se.items[0].serial_no)
|
||||||
self.assertTrue(se.items[0].serial_and_batch_bundle)
|
self.assertTrue(se.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
@ -1793,7 +1793,7 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
se1.reload()
|
se1.reload()
|
||||||
|
|
||||||
self.assertTrue(se1.items[0].use_serial_batch_fields)
|
self.assertTrue(se1.items[0].use_serial_batch_fields)
|
||||||
self.assertFalse(se1.items[0].serial_no)
|
self.assertTrue(se1.items[0].serial_no)
|
||||||
self.assertTrue(se1.items[0].serial_and_batch_bundle)
|
self.assertTrue(se1.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
|
|||||||
@ -834,6 +834,7 @@ class StockReconciliation(StockController):
|
|||||||
if voucher_detail_no != row.name:
|
if voucher_detail_no != row.name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
val_rate = 0.0
|
||||||
current_qty = 0.0
|
current_qty = 0.0
|
||||||
if row.current_serial_and_batch_bundle:
|
if row.current_serial_and_batch_bundle:
|
||||||
current_qty = self.get_current_qty_for_serial_or_batch(row)
|
current_qty = self.get_current_qty_for_serial_or_batch(row)
|
||||||
@ -843,7 +844,6 @@ class StockReconciliation(StockController):
|
|||||||
row.warehouse,
|
row.warehouse,
|
||||||
self.posting_date,
|
self.posting_date,
|
||||||
self.posting_time,
|
self.posting_time,
|
||||||
voucher_no=self.name,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
current_qty = item_dict.get("qty")
|
current_qty = item_dict.get("qty")
|
||||||
@ -885,7 +885,7 @@ class StockReconciliation(StockController):
|
|||||||
{"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
|
{"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
|
||||||
"name",
|
"name",
|
||||||
)
|
)
|
||||||
and (not row.current_serial_and_batch_bundle and not row.batch_no)
|
and (not row.current_serial_and_batch_bundle)
|
||||||
):
|
):
|
||||||
self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True)
|
self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True)
|
||||||
row.reload()
|
row.reload()
|
||||||
|
|||||||
@ -91,6 +91,12 @@ frappe.query_reports["Stock Ledger"] = {
|
|||||||
"options": "Currency\nFloat",
|
"options": "Currency\nFloat",
|
||||||
"default": "Currency"
|
"default": "Currency"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "segregate_serial_batch_bundle",
|
||||||
|
"label": __("Segregate Serial / Batch Bundle"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"formatter": function (value, row, column, data, default_formatter) {
|
"formatter": function (value, row, column, data, default_formatter) {
|
||||||
value = default_formatter(value, row, column, data);
|
value = default_formatter(value, row, column, data);
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.query_builder.functions import CombineDatetime
|
from frappe.query_builder.functions import CombineDatetime
|
||||||
@ -26,6 +28,10 @@ def execute(filters=None):
|
|||||||
item_details = get_item_details(items, sl_entries, include_uom)
|
item_details = get_item_details(items, sl_entries, include_uom)
|
||||||
opening_row = get_opening_balance(filters, columns, sl_entries)
|
opening_row = get_opening_balance(filters, columns, sl_entries)
|
||||||
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
|
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
|
||||||
|
bundle_details = {}
|
||||||
|
|
||||||
|
if filters.get("segregate_serial_batch_bundle"):
|
||||||
|
bundle_details = get_serial_batch_bundle_details(sl_entries)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
conversion_factors = []
|
conversion_factors = []
|
||||||
@ -45,6 +51,9 @@ def execute(filters=None):
|
|||||||
item_detail = item_details[sle.item_code]
|
item_detail = item_details[sle.item_code]
|
||||||
|
|
||||||
sle.update(item_detail)
|
sle.update(item_detail)
|
||||||
|
if bundle_info := bundle_details.get(sle.serial_and_batch_bundle):
|
||||||
|
data.extend(get_segregated_bundle_entries(sle, bundle_info))
|
||||||
|
continue
|
||||||
|
|
||||||
if filters.get("batch_no") or inventory_dimension_filters_applied:
|
if filters.get("batch_no") or inventory_dimension_filters_applied:
|
||||||
actual_qty += flt(sle.actual_qty, precision)
|
actual_qty += flt(sle.actual_qty, precision)
|
||||||
@ -76,6 +85,60 @@ def execute(filters=None):
|
|||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_segregated_bundle_entries(sle, bundle_details):
|
||||||
|
segregated_entries = []
|
||||||
|
qty_before_transaction = sle.qty_after_transaction - sle.actual_qty
|
||||||
|
stock_value_before_transaction = sle.stock_value - sle.stock_value_difference
|
||||||
|
|
||||||
|
for row in bundle_details:
|
||||||
|
new_sle = copy.deepcopy(sle)
|
||||||
|
new_sle.update(row)
|
||||||
|
|
||||||
|
new_sle.update(
|
||||||
|
{
|
||||||
|
"in_out_rate": flt(new_sle.stock_value_difference / row.qty) if row.qty else 0,
|
||||||
|
"in_qty": row.qty if row.qty > 0 else 0,
|
||||||
|
"out_qty": row.qty if row.qty < 0 else 0,
|
||||||
|
"qty_after_transaction": qty_before_transaction + row.qty,
|
||||||
|
"stock_value": stock_value_before_transaction + new_sle.stock_value_difference,
|
||||||
|
"incoming_rate": row.incoming_rate if row.qty > 0 else 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
qty_before_transaction += row.qty
|
||||||
|
stock_value_before_transaction += new_sle.stock_value_difference
|
||||||
|
|
||||||
|
new_sle.valuation_rate = (
|
||||||
|
stock_value_before_transaction / qty_before_transaction if qty_before_transaction else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
segregated_entries.append(new_sle)
|
||||||
|
|
||||||
|
return segregated_entries
|
||||||
|
|
||||||
|
|
||||||
|
def get_serial_batch_bundle_details(sl_entries):
|
||||||
|
bundle_details = []
|
||||||
|
for sle in sl_entries:
|
||||||
|
if sle.serial_and_batch_bundle:
|
||||||
|
bundle_details.append(sle.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
if not bundle_details:
|
||||||
|
return frappe._dict({})
|
||||||
|
|
||||||
|
_bundle_details = frappe._dict({})
|
||||||
|
batch_entries = frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
filters={"parent": ("in", bundle_details)},
|
||||||
|
fields=["parent", "qty", "incoming_rate", "stock_value_difference", "batch_no", "serial_no"],
|
||||||
|
order_by="parent, idx",
|
||||||
|
)
|
||||||
|
for entry in batch_entries:
|
||||||
|
_bundle_details.setdefault(entry.parent, []).append(entry)
|
||||||
|
|
||||||
|
return _bundle_details
|
||||||
|
|
||||||
|
|
||||||
def update_available_serial_nos(available_serial_nos, sle):
|
def update_available_serial_nos(available_serial_nos, sle):
|
||||||
serial_nos = get_serial_nos(sle.serial_no)
|
serial_nos = get_serial_nos(sle.serial_no)
|
||||||
key = (sle.item_code, sle.warehouse)
|
key = (sle.item_code, sle.warehouse)
|
||||||
@ -256,7 +319,6 @@ def get_columns(filters):
|
|||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
{"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
|
|
||||||
{
|
{
|
||||||
"label": _("Project"),
|
"label": _("Project"),
|
||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
@ -320,15 +382,45 @@ def get_stock_ledger_entries(filters, items):
|
|||||||
if items:
|
if items:
|
||||||
query = query.where(sle.item_code.isin(items))
|
query = query.where(sle.item_code.isin(items))
|
||||||
|
|
||||||
for field in ["voucher_no", "batch_no", "project", "company"]:
|
for field in ["voucher_no", "project", "company"]:
|
||||||
if filters.get(field) and field not in inventory_dimension_fields:
|
if filters.get(field) and field not in inventory_dimension_fields:
|
||||||
query = query.where(sle[field] == filters.get(field))
|
query = query.where(sle[field] == filters.get(field))
|
||||||
|
|
||||||
|
if filters.get("batch_no"):
|
||||||
|
bundles = get_serial_and_batch_bundles(filters)
|
||||||
|
|
||||||
|
if bundles:
|
||||||
|
query = query.where(
|
||||||
|
(sle.serial_and_batch_bundle.isin(bundles)) | (sle.batch_no == filters.batch_no)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
query = query.where(sle.batch_no == filters.batch_no)
|
||||||
|
|
||||||
query = apply_warehouse_filter(query, sle, filters)
|
query = apply_warehouse_filter(query, sle, filters)
|
||||||
|
|
||||||
return query.run(as_dict=True)
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_serial_and_batch_bundles(filters):
|
||||||
|
SBB = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
|
SBE = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(SBE)
|
||||||
|
.inner_join(SBB)
|
||||||
|
.on(SBE.parent == SBB.name)
|
||||||
|
.select(SBE.parent)
|
||||||
|
.where(
|
||||||
|
(SBB.docstatus == 1)
|
||||||
|
& (SBB.has_batch_no == 1)
|
||||||
|
& (SBB.voucher_no.notnull())
|
||||||
|
& (SBE.batch_no == filters.batch_no)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return query.run(pluck=SBE.parent)
|
||||||
|
|
||||||
|
|
||||||
def get_inventory_dimension_fields():
|
def get_inventory_dimension_fields():
|
||||||
return [dimension.fieldname for dimension in get_inventory_dimensions()]
|
return [dimension.fieldname for dimension in get_inventory_dimensions()]
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import frappe
|
|||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
from frappe.utils import cint, flt, get_link_to_form, now, nowtime, today
|
from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
|
||||||
|
|
||||||
from erpnext.stock.deprecated_serial_batch import (
|
from erpnext.stock.deprecated_serial_batch import (
|
||||||
DeprecatedBatchNoValuation,
|
DeprecatedBatchNoValuation,
|
||||||
@ -138,9 +138,17 @@ class SerialBatchBundle:
|
|||||||
self.child_doctype, self.sle.voucher_detail_no, "rejected_serial_and_batch_bundle", sn_doc.name
|
self.child_doctype, self.sle.voucher_detail_no, "rejected_serial_and_batch_bundle", sn_doc.name
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
frappe.db.set_value(
|
values_to_update = {
|
||||||
self.child_doctype, self.sle.voucher_detail_no, "serial_and_batch_bundle", sn_doc.name
|
"serial_and_batch_bundle": sn_doc.name,
|
||||||
)
|
}
|
||||||
|
|
||||||
|
if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
|
||||||
|
if sn_doc.has_serial_no:
|
||||||
|
values_to_update["serial_no"] = "\n".join(cstr(d.serial_no) for d in sn_doc.entries)
|
||||||
|
elif sn_doc.has_batch_no and len(sn_doc.entries) == 1:
|
||||||
|
values_to_update["batch_no"] = sn_doc.entries[0].batch_no
|
||||||
|
|
||||||
|
frappe.db.set_value(self.child_doctype, self.sle.voucher_detail_no, values_to_update)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def child_doctype(self):
|
def child_doctype(self):
|
||||||
@ -905,8 +913,6 @@ class SerialBatchCreation:
|
|||||||
self.batches = get_available_batches(kwargs)
|
self.batches = get_available_batches(kwargs)
|
||||||
|
|
||||||
def set_auto_serial_batch_entries_for_inward(self):
|
def set_auto_serial_batch_entries_for_inward(self):
|
||||||
print(self.get("serial_nos"))
|
|
||||||
|
|
||||||
if (self.get("batches") and self.has_batch_no) or (
|
if (self.get("batches") and self.has_batch_no) or (
|
||||||
self.get("serial_nos") and self.has_serial_no
|
self.get("serial_nos") and self.has_serial_no
|
||||||
):
|
):
|
||||||
|
|||||||
@ -903,7 +903,7 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
precision = doc.precision("total_qty")
|
precision = doc.precision("total_qty")
|
||||||
self.wh_data.qty_after_transaction += flt(doc.total_qty, precision)
|
self.wh_data.qty_after_transaction += flt(doc.total_qty, precision)
|
||||||
if self.wh_data.qty_after_transaction:
|
if flt(self.wh_data.qty_after_transaction, precision):
|
||||||
self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt(
|
self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt(
|
||||||
self.wh_data.qty_after_transaction, precision
|
self.wh_data.qty_after_transaction, precision
|
||||||
)
|
)
|
||||||
|
|||||||
@ -262,7 +262,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
|||||||
item_code=args.get("item_code"),
|
item_code=args.get("item_code"),
|
||||||
)
|
)
|
||||||
|
|
||||||
in_rate = sn_obj.get_incoming_rate()
|
return sn_obj.get_incoming_rate()
|
||||||
|
|
||||||
elif item_details and item_details.has_batch_no and args.get("serial_and_batch_bundle"):
|
elif item_details and item_details.has_batch_no and args.get("serial_and_batch_bundle"):
|
||||||
args.actual_qty = args.qty
|
args.actual_qty = args.qty
|
||||||
@ -272,23 +272,33 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
|||||||
item_code=args.get("item_code"),
|
item_code=args.get("item_code"),
|
||||||
)
|
)
|
||||||
|
|
||||||
in_rate = batch_obj.get_incoming_rate()
|
return batch_obj.get_incoming_rate()
|
||||||
|
|
||||||
elif (args.get("serial_no") or "").strip() and not args.get("serial_and_batch_bundle"):
|
elif (args.get("serial_no") or "").strip() and not args.get("serial_and_batch_bundle"):
|
||||||
in_rate = get_avg_purchase_rate(args.get("serial_no"))
|
args.actual_qty = args.qty
|
||||||
|
args.serial_nos = get_serial_nos_data(args.get("serial_no"))
|
||||||
|
|
||||||
|
sn_obj = SerialNoValuation(
|
||||||
|
sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code")
|
||||||
|
)
|
||||||
|
|
||||||
|
return sn_obj.get_incoming_rate()
|
||||||
elif (
|
elif (
|
||||||
args.get("batch_no")
|
args.get("batch_no")
|
||||||
and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True)
|
and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True)
|
||||||
and not args.get("serial_and_batch_bundle")
|
and not args.get("serial_and_batch_bundle")
|
||||||
):
|
):
|
||||||
in_rate = get_batch_incoming_rate(
|
|
||||||
item_code=args.get("item_code"),
|
args.actual_qty = args.qty
|
||||||
|
args.batch_nos = frappe._dict({args.batch_no: args})
|
||||||
|
|
||||||
|
batch_obj = BatchNoValuation(
|
||||||
|
sle=args,
|
||||||
warehouse=args.get("warehouse"),
|
warehouse=args.get("warehouse"),
|
||||||
batch_no=args.get("batch_no"),
|
item_code=args.get("item_code"),
|
||||||
posting_date=args.get("posting_date"),
|
|
||||||
posting_time=args.get("posting_time"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return batch_obj.get_incoming_rate()
|
||||||
else:
|
else:
|
||||||
valuation_method = get_valuation_method(args.get("item_code"))
|
valuation_method = get_valuation_method(args.get("item_code"))
|
||||||
previous_sle = get_previous_sle(args)
|
previous_sle = get_previous_sle(args)
|
||||||
|
|||||||
@ -113,8 +113,8 @@ class Issue(Document):
|
|||||||
"reference_name": self.name,
|
"reference_name": self.name,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
communication.ignore_permissions = True
|
communication.flags.ignore_permissions = True
|
||||||
communication.ignore_mandatory = True
|
communication.flags.ignore_mandatory = True
|
||||||
communication.save()
|
communication.save()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user