diff --git a/utility_billing/utility_billing/doctype/meter_reading/meter_reading.js b/utility_billing/utility_billing/doctype/meter_reading/meter_reading.js index 794b4e5..62844d1 100644 --- a/utility_billing/utility_billing/doctype/meter_reading/meter_reading.js +++ b/utility_billing/utility_billing/doctype/meter_reading/meter_reading.js @@ -16,6 +16,8 @@ frappe.ui.form.on("Meter Reading", { }, }; }; + + update_meter_number_query(frm); }, customer: function (frm) { @@ -37,6 +39,7 @@ frappe.ui.form.on("Meter Reading", { }, }); } + update_meter_number_query(frm); }, items_add: function (frm) { @@ -109,6 +112,7 @@ frappe.ui.form.on("Meter Reading Item", { }, }); } + update_meter_number_query(frm); }, meter_number: function (frm, cdt, cdn) { const row = locals[cdt][cdn]; @@ -118,6 +122,38 @@ frappe.ui.form.on("Meter Reading Item", { }, }); +function update_meter_number_query(frm) { + frappe.db + .get_list("Warranty Claim", { + filters: { status: "Closed", customer: frm.doc.customer }, + fields: ["serial_no"], + }) + .then((warrantyClaims) => { + const closedWarrantySerials = warrantyClaims.map((claim) => claim.serial_no); + + frm.fields_dict["items"].grid.get_field("meter_number").get_query = function () { + return { + filters: { + status: "Active", + name: ["in", closedWarrantySerials], + }, + }; + }; + + frm.doc.items.forEach((row) => { + if (!row.meter_number && closedWarrantySerials.length === 1) { + frappe.model.set_value( + row.doctype, + row.name, + "meter_number", + closedWarrantySerials[0] + ); + } + }); + frm.refresh_field("items"); + }); +} + function fetch_previous_reading(frm, row) { if (row.item_code && frm.doc.customer) { frappe.call({ diff --git a/utility_billing/utility_billing/doctype/meter_reading/meter_reading.py b/utility_billing/utility_billing/doctype/meter_reading/meter_reading.py index f4c083e..5ad2198 100644 --- a/utility_billing/utility_billing/doctype/meter_reading/meter_reading.py +++ b/utility_billing/utility_billing/doctype/meter_reading/meter_reading.py @@ -13,10 +13,16 @@ class MeterReading(Document): def validate(self): + for item in self.items: + self.validate_item_readings(item) create_meter_reading_rates(self, self.price_list, self.date) def on_submit(self): settings = frappe.get_single("Utility Billing Settings") + if not self.rates or len(self.rates) == 0: + frappe.throw( + frappe._("Cannot submit Meter Reading. No rates available.") + ) existing_sales_order = frappe.db.exists( { "doctype": "Sales Order Meter Reading", @@ -31,6 +37,31 @@ def on_submit(self): sales_order = create_sales_order(self) sales_order.submit() + def validate_item_readings(self, item): + """Validate readings for each item.""" + if item.current_reading is None: + frappe.throw( + frappe._( + f"Current reading is required for item: {item.item_code}" + ) + ) + + previous_reading = get_previous_invoice_reading( + item_code=item.item_code, + customer=self.customer, + meter_number=item.meter_number + ) + item.previous_reading = previous_reading + + item.consumption = item.current_reading - previous_reading + + if item.consumption < 0: + frappe.throw( + frappe._( + f"Current reading cannot be lower than the previous reading for item: {item.item_code}" + ) + ) + def create_sales_order(meter_reading): """Create a Sales Order based on the Meter Reading.""" @@ -116,3 +147,24 @@ def get_customer_details(customer): ) return customer_doc.as_dict() + + +@frappe.whitelist() +def get_serial_numbers_from_warranty_claims(customer): + """ + Fetch serial numbers from closed Warranty Claims for the given customer and item. + """ + serial_numbers = frappe.get_all( + "Warranty Claim", + filters={ + "customer": customer, + "status": "Closed", + }, + fields=["serial_no"], + ) + serial_list = [] + for claim in serial_numbers: + if claim.get("serial_no"): + serial_list.extend(claim["serial_no"].split("\n")) + + return list(set(serial_list)) diff --git a/utility_billing/utility_billing/doctype/utility_billing_settings/utility_billing_settings.json b/utility_billing/utility_billing/doctype/utility_billing_settings/utility_billing_settings.json index ccf215c..6bb8c1b 100644 --- a/utility_billing/utility_billing/doctype/utility_billing_settings/utility_billing_settings.json +++ b/utility_billing/utility_billing/doctype/utility_billing_settings/utility_billing_settings.json @@ -8,6 +8,7 @@ "sales_order_creation_state", "sales_invoice_creation_state", "column_break_moce", + "stock_entry_creation_state", "individual_invoices_for_multiple_sales_orders" ], "fields": [ @@ -34,12 +35,19 @@ "fieldtype": "Select", "label": "Sales Invoice Creation State", "options": "Draft\nSubmitted" + }, + { + "default": "Draft", + "fieldname": "stock_entry_creation_state", + "fieldtype": "Select", + "label": "Stock Entry Creation State", + "options": "Draft\nSubmitted" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-11-16 19:13:25.929144", + "modified": "2024-11-28 16:53:04.866577", "modified_by": "Administrator", "module": "Utility Billing", "name": "Utility Billing Settings", diff --git a/utility_billing/utility_billing/doctype/utility_service_request/utility_service_request.js b/utility_billing/utility_billing/doctype/utility_service_request/utility_service_request.js index 75fab1f..15cc630 100644 --- a/utility_billing/utility_billing/doctype/utility_service_request/utility_service_request.js +++ b/utility_billing/utility_billing/doctype/utility_service_request/utility_service_request.js @@ -36,6 +36,26 @@ frappe.ui.form.on("Utility Service Request", { }; }; + let closedWarrantySerials = []; + + frappe.db + .get_list("Warranty Claim", { + filters: { status: "Closed" }, + fields: ["serial_no"], + }) + .then((warrantyClaims) => { + closedWarrantySerials = warrantyClaims.map((claim) => claim.serial_no); + }); + + frm.fields_dict["items"].grid.get_field("meter_number").get_query = function () { + return { + filters: { + status: "Active", + name: ["not in", closedWarrantySerials], + }, + }; + }; + frm.set_query("customer_group", function () { return { filters: { @@ -113,6 +133,11 @@ frappe.ui.form.on("Utility Service Request", { }); frappe.ui.form.on("Utility Service Request Item", { + form_render: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + handle_item_code(frm, cdt, cdn, row.item_code); + }, + items_add: function (frm, cdt, cdn) { let row = locals[cdt][cdn]; let delivery_date = frm.doc.delivery_date || frappe.datetime.nowdate(); @@ -126,44 +151,7 @@ frappe.ui.form.on("Utility Service Request Item", { item_code: function (frm, cdt, cdn) { let row = locals[cdt][cdn]; - if (row.item_code) { - frappe.call({ - method: "utility_billing.utility_billing.doctype.utility_service_request.utility_service_request.get_item_details", - args: { - item_code: row.item_code, - price_list: frm.doc.price_list, - }, - callback: function (r) { - if (r.message) { - let item = r.message; - frappe.model.set_value(cdt, cdn, { - item_name: item.item_name, - uom: item.uom, - rate: item.rate, - warehouse: item.warehouse, - description: item.description, - qty: 1, - conversion_factor: item.conversion_factor, - brand: item.brand, - item_group: item.item_group, - stock_uom: item.stock_uom, - bom_no: item.bom_no, - weight_per_unit: item.weight_per_unit, - weight_uom: item.weight_uom, - item_tax_template: item.item_tax_template, - warehouse: item.default_warehouse, - }); - - let amount = flt(item.rate) * flt(row.qty || 1); - frappe.model.set_value(cdt, cdn, { - rate: item.rate, - amount: amount, - base_price_list_rate: item.rate, - }); - } - }, - }); - } + handle_item_code(frm, cdt, cdn, row.item_code, true); }, rate: function (frm, cdt, cdn) { @@ -181,6 +169,64 @@ frappe.ui.form.on("Utility Service Request Item", { }, }); +function handle_item_code(frm, cdt, cdn, item_code, update_fields = false) { + if (item_code) { + frappe.call({ + method: "utility_billing.utility_billing.doctype.utility_service_request.utility_service_request.get_item_details", + args: { + item_code: item_code, + price_list: frm.doc.price_list, + }, + callback: function (r) { + if (r.message) { + let item = r.message; + if (update_fields) { + update_item_fields(frm, cdt, cdn, item); + } + toggle_meter_number(frm, cdt, cdn, item.item_group === "Meter"); + } + }, + }); + } else { + toggle_meter_number(frm, cdt, cdn, false); + } +} + +function update_item_fields(frm, cdt, cdn, item) { + frappe.model.set_value(cdt, cdn, { + item_name: item.item_name, + uom: item.uom, + rate: item.rate, + warehouse: item.warehouse, + description: item.description, + qty: 1, + conversion_factor: item.conversion_factor, + brand: item.brand, + item_group: item.item_group, + stock_uom: item.stock_uom, + bom_no: item.bom_no, + weight_per_unit: item.weight_per_unit, + weight_uom: item.weight_uom, + item_tax_template: item.item_tax_template, + warehouse: item.default_warehouse, + }); + + let amount = flt(item.rate) * flt(frm.doc.qty || 1); + frappe.model.set_value(cdt, cdn, { + rate: item.rate, + amount: amount, + base_price_list_rate: item.rate, + }); +} + +function toggle_meter_number(frm, cdt, cdn, show) { + frm.fields_dict["items"].grid.toggle_display("meter_number", show, cdt, cdn); + if (!show) { + frappe.model.set_value(cdt, cdn, "meter_number", null); + } + frm.fields_dict["items"].grid.toggle_reqd("warehouse", show, cdt, cdn); +} + function calculate_amount(frm, cdt, cdn) { let row = locals[cdt][cdn]; let amount = flt(row.rate) * flt(row.qty); diff --git a/utility_billing/utility_billing/doctype/utility_service_request/utility_service_request.py b/utility_billing/utility_billing/doctype/utility_service_request/utility_service_request.py index 3400eaa..0bbb705 100644 --- a/utility_billing/utility_billing/doctype/utility_service_request/utility_service_request.py +++ b/utility_billing/utility_billing/doctype/utility_service_request/utility_service_request.py @@ -20,6 +20,10 @@ def create_customer_and_sales_order(docname): customer_doc = create_customer(doc) link_contact_and_address_to_customer(customer_doc, doc) sales_order_doc = create_sales_order(doc, customer_doc) + create_stock_entry_for_meter_issue(docname) + for item in doc.items: + if item.item_group == "Meter" and item.meter_number: + create_warranty_claim(customer_doc, item.meter_number, item.item_code) return {"sales_order": sales_order_doc.name} @@ -214,4 +218,44 @@ def get_item_details(item_code, price_list=None): @frappe.whitelist() def bom_new_version(bom): bom = frappe.get_doc("BOM", bom) - return frappe.copy_doc(bom) \ No newline at end of file + return frappe.copy_doc(bom) + + +def create_warranty_claim(customer_doc, serial_number, item_code): + warranty_claim = frappe.new_doc("Warranty Claim") + warranty_claim.customer = customer_doc.name + warranty_claim.complaint = customer_doc.name + warranty_claim.serial_no = serial_number + warranty_claim.item_code = item_code + warranty_claim.complaint_date = nowdate() + warranty_claim.status = "Closed" + warranty_claim.save() + return warranty_claim + + +@frappe.whitelist() +def create_stock_entry_for_meter_issue(docname): + doc = frappe.get_doc("Utility Service Request", docname) + + auto_submit_stock_entry = frappe.db.get_single_value("Utility Billing Settings", "stock_entry_creation_state") + + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.stock_entry_type = "Material Issue" + + for item in doc.items: + if item.item_group == "Meter" and item.meter_number: + stock_entry_item = item.as_dict() + stock_entry_item.update({ + "serial_no": item.meter_number, + "use_serial_batch_fields": 1, + "s_warehouse": item.warehouse, + }) + stock_entry.append("items", stock_entry_item) + + if stock_entry.items: + stock_entry.save() + + if auto_submit_stock_entry == "Submitted": + stock_entry.submit() + + return {"stock_entry": stock_entry.name} diff --git a/utility_billing/utility_billing/doctype/utility_service_request_item/utility_service_request_item.json b/utility_billing/utility_billing/doctype/utility_service_request_item/utility_service_request_item.json index eff3f6a..c6b7c1b 100644 --- a/utility_billing/utility_billing/doctype/utility_service_request_item/utility_service_request_item.json +++ b/utility_billing/utility_billing/doctype/utility_service_request_item/utility_service_request_item.json @@ -15,6 +15,7 @@ "col_break1", "delivery_date", "item_name", + "meter_number", "section_break_5", "description", "item_group", @@ -606,8 +607,11 @@ "depends_on": "eval:doc.delivered_by_supplier!=1", "fieldname": "warehouse", "fieldtype": "Link", + "in_filter": 1, "in_list_view": 1, - "label": "Delivery Warehouse", + "in_preview": 1, + "in_standard_filter": 1, + "label": "Warehouse", "oldfieldname": "reserved_warehouse", "oldfieldtype": "Link", "options": "Warehouse", @@ -860,11 +864,23 @@ "label": "Purchase Order Item", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "meter_number", + "fieldtype": "Link", + "hidden": 1, + "in_filter": 1, + "in_list_view": 1, + "in_preview": 1, + "in_standard_filter": 1, + "label": "Meter Number", + "mandatory_depends_on": "eva", + "options": "Serial No" } ], "istable": 1, "links": [], - "modified": "2024-10-24 15:23:19.326401", + "modified": "2024-11-28 16:27:12.639649", "modified_by": "Administrator", "module": "Utility Billing", "name": "Utility Service Request Item", diff --git a/utility_billing/utility_billing/overrides/server/sales_order.py b/utility_billing/utility_billing/overrides/server/sales_order.py index 3224ee1..2da3b31 100755 --- a/utility_billing/utility_billing/overrides/server/sales_order.py +++ b/utility_billing/utility_billing/overrides/server/sales_order.py @@ -223,7 +223,7 @@ def set_missing_values(source, target): def update_item(source, target, source_parent): - """Update the item details for the invoice.""" + """Update the item details for the invoice and copy serial number as meter number if applicable.""" target.amount = flt(source.amount) - flt(source.billed_amt) target.base_amount = target.amount * flt(source_parent.conversion_rate) target.qty = ( @@ -234,6 +234,21 @@ def update_item(source, target, source_parent): set_cost_center(source_parent, target) + sales_order = frappe.get_doc("Sales Order", source_parent.name) + + if sales_order.utility_service_request: + utility_service_request = frappe.get_doc( + "Utility Service Request", sales_order.utility_service_request + ) + + for request_item in utility_service_request.items: + if request_item.item_code == target.item_code: + if request_item.meter_number: + target.serial_no = request_item.meter_number + break + + return target + def set_cost_center(source_parent, target): """Set the cost center for the invoice item."""