diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bf3ee539dc..77dbc8f9b3 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -67,6 +67,12 @@ treeviews = [ "Department", ] +jinja = { + "methods": [ + "erpnext.stock.serial_batch_bundle.get_serial_or_batch_nos", + ], +} + # website update_website_context = [ "erpnext.e_commerce.shopping_cart.utils.update_website_context", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index b607244591..98ad8a7cdb 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -300,20 +300,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) { super.conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate); - if(frappe.meta.get_docfield(cdt, "stock_qty", cdn) && - in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) { - if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return; - this.set_batch_number(cdt, cdn); - } } qty(doc, cdt, cdn) { super.qty(doc, cdt, cdn); - - if(in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) { - if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return; - this.set_batch_number(cdt, cdn); - } } pick_serial_and_batch(doc, cdt, cdn) { diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 4599c56d91..3ca4bad4e4 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -52,6 +52,7 @@ class StockLedgerEntry(Document): def on_submit(self): self.check_stock_frozen_date() + # Added to handle few test cases where serial_and_batch_bundles are not required if frappe.flags.in_test and frappe.flags.ignore_serial_batch_bundle_validation: return diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index 41f928ba3f..dff407f149 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -297,6 +297,7 @@ def create_material_receipt( se.set_stock_entry_type() se.insert() se.submit() + se.reload() return se diff --git a/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/__init__.py b/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/purchase_receipt_serial_and_batch_bundle_print.json b/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/purchase_receipt_serial_and_batch_bundle_print.json new file mode 100644 index 0000000000..21132e070c --- /dev/null +++ b/erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/purchase_receipt_serial_and_batch_bundle_print.json @@ -0,0 +1,30 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2023-06-01 23:07:25.776606", + "custom_format": 0, + "disabled": 0, + "doc_type": "Purchase Receipt", + "docstatus": 0, + "doctype": "Print Format", + "font_size": 14, + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\\t\\t\\t\\t

Purchase Receipt

{{ doc.name }}\\t\\t\\t\\t

\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"supplier_name\", \"print_hide\": 0, \"label\": \"Supplier Name\"}, {\"fieldname\": \"supplier_delivery_note\", \"print_hide\": 0, \"label\": \"Supplier Delivery Note\"}, {\"fieldname\": \"rack\", \"print_hide\": 0, \"label\": \"Rack\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"apply_putaway_rule\", \"print_hide\": 0, \"label\": \"Apply Putaway Rule\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Accounting Dimensions\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"region\", \"print_hide\": 0, \"label\": \"Region\"}, {\"fieldname\": \"function\", \"print_hide\": 0, \"label\": \"Function\"}, {\"fieldname\": \"depot\", \"print_hide\": 0, \"label\": \"Depot\"}, {\"fieldname\": \"cost_center\", \"print_hide\": 0, \"label\": \"Cost Center\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"location\", \"print_hide\": 0, \"label\": \"Location\"}, {\"fieldname\": \"country\", \"print_hide\": 0, \"label\": \"Country\"}, {\"fieldname\": \"project\", \"print_hide\": 0, \"label\": \"Project\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Items\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"scan_barcode\", \"print_hide\": 0, \"label\": \"Scan Barcode\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"set_from_warehouse\", \"print_hide\": 0, \"label\": \"Set From Warehouse\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- for row in doc.items -%}\\n\\t\\t\\n\\t\\t {% set bundle_data = get_serial_or_batch_nos(row.serial_and_batch_bundle) %}\\n\\t\\t {% set serial_nos = [] %}\\n {% set batches = {} %}\\n\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- endfor -%}\\n\\t\\n
SrItem NameDescriptionQtyRateAmount
{{ row.idx }}\\n\\t\\t\\t\\t{{ row.item_name }}\\n\\t\\t\\t\\t{% if row.item_code != row.item_name -%}\\n\\t\\t\\t\\t
Item Code: {{ row.item_code}}\\n\\t\\t\\t\\t{%- endif %}\\n\\t\\t\\t
\\n\\t\\t\\t\\t
{{ row.description }}
{{ row.qty }} {{ row.uom or row.stock_uom }}{{\\n\\t\\t\\t\\trow.get_formatted(\\\"rate\\\", doc) }}{{\\n\\t\\t\\t\\trow.get_formatted(\\\"amount\\\", doc) }}
\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total_qty\", \"print_hide\": 0, \"label\": \"Total Quantity\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total\", \"print_hide\": 0, \"label\": \"Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"taxes\", \"print_hide\": 0, \"label\": \"Purchase Taxes and Charges\", \"visible_columns\": [{\"fieldname\": \"category\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"add_deduct_tax\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"charge_type\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"row_id\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"included_in_print_rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"included_in_paid_amount\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"account_head\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"description\", \"print_width\": \"300px\", \"print_hide\": 0}, {\"fieldname\": \"rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"region\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"function\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"location\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"cost_center\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"depot\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"country\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"account_currency\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"tax_amount\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"total\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"Totals\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"in_words\", \"print_hide\": 0, \"label\": \"In Words\"}, {\"fieldname\": \"disable_rounded_total\", \"print_hide\": 0, \"label\": \"Disable Rounded Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Supplier Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"address_display\", \"print_hide\": 0, \"label\": \"Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"contact_display\", \"print_hide\": 0, \"label\": \"Contact\"}, {\"fieldname\": \"contact_mobile\", \"print_hide\": 0, \"label\": \"Mobile No\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Company Billing Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"billing_address\", \"print_hide\": 0, \"label\": \"Billing Address\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"billing_address_display\", \"print_hide\": 0, \"label\": \"Billing Address\"}, {\"fieldname\": \"terms\", \"print_hide\": 0, \"label\": \"Terms and Conditions\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- for row in doc.items -%}\\n\\t\\t\\n\\t\\t {% set bundle_data = get_serial_or_batch_nos(row.serial_and_batch_bundle) %}\\n\\t\\t {% set serial_nos = [] %}\\n {% set batches = {} %}\\n \\n {% if bundle_data %}\\n\\t\\t\\t {% for data in bundle_data %}\\n\\t\\t\\t {% if data.serial_no %}\\n\\t\\t\\t {{ serial_nos.append(data.serial_no) or \\\"\\\" }}\\n\\t\\t\\t {% endif %}\\n\\t\\t\\t \\n\\t\\t\\t {% if data.batch_no %}\\n\\t\\t\\t {{ batches.update({data.batch_no: data.qty}) or \\\"\\\" }}\\n\\t\\t\\t {% endif %}\\n\\t\\t\\t {% endfor %}\\n\\t\\t\\t{% endif %}\\n\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t{%- endfor -%}\\n\\t\\n
SrItem NameQtySerial NosBatch Nos (Qty)
{{ row.idx }}\\n\\t\\t\\t\\t{{ row.item_name }}\\n\\t\\t\\t\\t{% if row.item_code != row.item_name -%}\\n\\t\\t\\t\\t
Item Code: {{ row.item_code}}\\n\\t\\t\\t\\t{%- endif %}\\n\\t\\t\\t
{{ row.qty }} {{ row.uom or row.stock_uom }}{{ serial_nos|join(',') }}\\n\\t\\t\\t {% if batches %}\\n {% for batch_no, qty in batches.items() %}\\n

{{batch_no}} : {{qty}} {{ row.uom or row.stock_uom }}

\\n {% endfor %}\\n {% endif %}\\n\\t\\t\\t
\\n\"}]", + "idx": 0, + "line_breaks": 0, + "margin_bottom": 15.0, + "margin_left": 15.0, + "margin_right": 15.0, + "margin_top": 15.0, + "modified": "2023-06-02 00:09:37.315002", + "modified_by": "Administrator", + "module": "Stock", + "name": "Purchase Receipt Serial and Batch Bundle Print", + "owner": "Administrator", + "page_number": "Hide", + "print_format_builder": 1, + "print_format_builder_beta": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py index 99f1a9403b..7212b92bb3 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -22,35 +22,41 @@ def get_columns(filters): "fieldtype": "Link", "fieldname": "voucher_type", "options": "DocType", - "width": 220, + "width": 160, }, { "label": _("Voucher No"), "fieldtype": "Dynamic Link", "fieldname": "voucher_no", "options": "voucher_type", - "width": 220, + "width": 180, }, { "label": _("Company"), "fieldtype": "Link", "fieldname": "company", "options": "Company", - "width": 220, + "width": 150, }, { "label": _("Warehouse"), "fieldtype": "Link", "fieldname": "warehouse", "options": "Warehouse", - "width": 220, + "width": 150, }, { "label": _("Serial No"), "fieldtype": "Link", "fieldname": "serial_no", "options": "Serial No", - "width": 220, + "width": 150, + }, + { + "label": _("Valuation Rate"), + "fieldtype": "Float", + "fieldname": "valuation_rate", + "width": 150, }, ] @@ -84,14 +90,16 @@ def get_data(filters): serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []) - for index, serial_no in enumerate(serial_nos): + for index, bundle_data in enumerate(serial_nos): if index == 0: - args.serial_no = serial_no + args.serial_no = bundle_data.get("serial_no") + args.valuation_rate = bundle_data.get("valuation_rate") data.append(args) else: data.append( { - "serial_no": serial_no, + "serial_no": bundle_data.get("serial_no"), + "valuation_rate": bundle_data.get("valuation_rate"), } ) @@ -106,10 +114,15 @@ def get_serial_nos(filters, serial_bundle_ids): for d in frappe.get_all( "Serial and Batch Entry", - fields=["serial_no", "parent"], + fields=["serial_no", "parent", "stock_value_difference as valuation_rate"], filters=bundle_filters, order_by="idx asc", ): - bundle_wise_serial_nos.setdefault(d.parent, []).append(d.serial_no) + bundle_wise_serial_nos.setdefault(d.parent, []).append( + { + "serial_no": d.serial_no, + "valuation_rate": abs(d.valuation_rate), + } + ) return bundle_wise_serial_nos diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index da13354797..9c55358da2 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -296,6 +296,10 @@ def get_serial_nos_from_bundle(serial_and_batch_bundle, serial_nos=None): return get_serial_nos(serial_and_batch_bundle, serial_nos=serial_nos) +def get_serial_or_batch_nos(bundle): + return frappe.get_all("Serial and Batch Entry", fields=["*"], filters={"parent": bundle}) + + class SerialNoValuation(DeprecatedSerialNoValuation): def __init__(self, **kwargs): for key, value in kwargs.items(): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 01ba491ab5..dc481e8281 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -8,10 +8,10 @@ from typing import Optional, Set, Tuple import frappe from frappe import _, scrub from frappe.model.meta import get_field_precision +from frappe.query_builder import Case from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import ( cint, - cstr, flt, get_link_to_form, getdate,