From 42858707ccdcda834b85079ec4d925f3db4fbce1 Mon Sep 17 00:00:00 2001 From: roshmol Date: Tue, 17 Mar 2026 07:41:09 +0000 Subject: [PATCH] feat:create singlr purchase invoice for purchase receipt --- rmax_custom/api/purchase_invoice.py | 40 ++++++ rmax_custom/hooks.py | 5 +- rmax_custom/public/js/purchase receipt.js | 136 ++++++++++-------- .../public/js/purchase_receipt_list.js | 37 +++++ 4 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 rmax_custom/api/purchase_invoice.py create mode 100644 rmax_custom/public/js/purchase_receipt_list.js diff --git a/rmax_custom/api/purchase_invoice.py b/rmax_custom/api/purchase_invoice.py new file mode 100644 index 0000000..d81041b --- /dev/null +++ b/rmax_custom/api/purchase_invoice.py @@ -0,0 +1,40 @@ +import frappe + +@frappe.whitelist() +def create_single_purchase_invoice(receipt_names): + import json + if isinstance(receipt_names, str): + receipt_names = json.loads(receipt_names) + + # Get first receipt for header info + pr = frappe.get_doc("Purchase Receipt", receipt_names[0]) + + # Create new Purchase Invoice + pi = frappe.new_doc("Purchase Invoice") + pi.supplier = pr.supplier + pi.company = pr.company + pi.currency = pr.currency + pi.buying_price_list = pr.buying_price_list + pi.remarks = "Created from: " + ", ".join(receipt_names) + + # Get all items from all receipts + for receipt_name in receipt_names: + receipt = frappe.get_doc("Purchase Receipt", receipt_name) + for item in receipt.items: + pi.append("items", { + "item_code": item.item_code, + "item_name": item.item_name, + "description": item.description, + "qty": item.qty, + "rate": item.rate, + "uom": item.uom, + "warehouse": item.warehouse, + "purchase_receipt": receipt_name, + "purchase_receipt_item": item.name, + "expense_account": item.expense_account, + "cost_center": item.cost_center + }) + + pi.insert(ignore_permissions=True) + frappe.db.commit() + return pi.name diff --git a/rmax_custom/hooks.py b/rmax_custom/hooks.py index e7ce21d..cf95be7 100644 --- a/rmax_custom/hooks.py +++ b/rmax_custom/hooks.py @@ -57,6 +57,9 @@ "Purchase Receipt": "public/js/purchase receipt.js", "Landed Cost Voucher": "public/js/landed_cost_voucher.js" } +doctype_list_js = { + "Purchase Receipt": "public/js/purchase_receipt_list.js" +} # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} @@ -295,4 +298,4 @@ ] ] } -] \ No newline at end of file +] diff --git a/rmax_custom/public/js/purchase receipt.js b/rmax_custom/public/js/purchase receipt.js index c5ade1e..0eaedb7 100644 --- a/rmax_custom/public/js/purchase receipt.js +++ b/rmax_custom/public/js/purchase receipt.js @@ -1,4 +1,4 @@ - +//Purchase Receipt - Final GRN Button + Auto-cancel original on submit of new receipt frappe.ui.form.on('Purchase Receipt', { refresh: function(frm) { if (frm.doc.docstatus === 1 || frm.doc.docstatus === 0) { @@ -6,8 +6,69 @@ frappe.ui.form.on('Purchase Receipt', { new_purchase_receipt_with_data(frm); }, __('Create')); } + }, + + // Auto-cancel original when new receipt is SUBMITTED + on_submit: function(frm) { + let source_name = frm._source_name; + let source_docstatus = frm._source_docstatus; + + if (!source_name) return; + + if (source_docstatus === 1) { + frappe.call({ + method: 'frappe.client.cancel', + args: { doctype: 'Purchase Receipt', name: source_name }, + callback: function(r) { + if (!r.exc) { + frappe.show_alert({ + message: __(source_name + ' automatically cancelled.'), + indicator: 'green' + }, 6); + frm._source_name = null; + } else { + frappe.msgprint(__('Could not auto-cancel ' + source_name + '. Please cancel manually.')); + } + } + }); + + } else if (source_docstatus === 0) { + frappe.call({ + method: 'frappe.client.delete', + args: { doctype: 'Purchase Receipt', name: source_name }, + callback: function(r) { + if (!r.exc) { + frappe.show_alert({ + message: __(source_name + ' automatically deleted.'), + indicator: 'green' + }, 6); + frm._source_name = null; + } else { + frappe.msgprint(__('Could not delete ' + source_name + '. Please delete manually.')); + } + } + }); + } } }); +function subtract_10_minutes(time_str) { + if (!time_str) return time_str; + let parts = time_str.split(':'); + let hours = parseInt(parts[0]) || 0; + let minutes = parseInt(parts[1]) || 0; + let seconds = parseInt(parts[2]) || 0; + let total_seconds = (hours * 3600) + (minutes * 60) + seconds - (10 * 60); + if (total_seconds < 0) total_seconds += 86400; + + let new_hours = Math.floor(total_seconds / 3600); + let new_minutes = Math.floor((total_seconds % 3600) / 60); + let new_seconds = total_seconds % 60; + + // Pad to HH:MM:SS + return String(new_hours).padStart(2, '0') + ':' + + String(new_minutes).padStart(2, '0') + ':' + + String(new_seconds).padStart(2, '0'); +} function new_purchase_receipt_with_data(frm) { @@ -17,7 +78,7 @@ function new_purchase_receipt_with_data(frm) { } // Store source data BEFORE navigating away - let source = JSON.parse(JSON.stringify(frm.doc)); + let source = JSON.parse(JSON.stringify(frm.doc)); let source_name = frm.doc.name; let source_docstatus = frm.doc.docstatus; @@ -38,7 +99,7 @@ function populate_new_form(source, source_name, source_docstatus) { return; } - //Helper: safe set value + // Helper: safe set value function s(field, value) { try { if (new_frm.fields_dict[field] !== undefined && value) { @@ -47,7 +108,7 @@ function populate_new_form(source, source_name, source_docstatus) { } catch(e) {} } - //Header + // Header s('company', source.company); s('supplier', source.supplier); s('supplier_name', source.supplier_name); @@ -70,19 +131,24 @@ function populate_new_form(source, source_name, source_docstatus) { s('lr_no', source.lr_no); s('lr_date', source.lr_date); s('supplier_delivery_note', source.supplier_delivery_note); + s('posting_date', source.posting_date); + + let adjusted_time = subtract_10_minutes(source.posting_time); + if (new_frm.fields_dict['posting_time'] !== undefined && adjusted_time) { + new_frm.doc['posting_time'] = adjusted_time; + new_frm.doc['set_posting_time'] = 1; + } - //Address & Contact + // Address & Contact s('supplier_address', source.supplier_address); s('contact_person', source.contact_person); s('contact_email', source.contact_email); s('shipping_address', source.shipping_address); s('billing_address', source.billing_address); - - // Store source info for after_save new_frm._source_name = source_name; new_frm._source_docstatus = source_docstatus; - //ITEMS + // ITEMS new_frm.doc.items = []; (source.items || []).forEach(function(item, idx) { @@ -124,7 +190,7 @@ function populate_new_form(source, source_name, source_docstatus) { row.allow_zero_valuation_rate = item.allow_zero_valuation_rate; }); - //TAXES + // TAXES new_frm.doc.taxes = []; (source.taxes || []).forEach(function(tax, idx) { @@ -141,7 +207,7 @@ function populate_new_form(source, source_name, source_docstatus) { row.row_id = tax.row_id; }); - //Refresh all fields + // Refresh all fields new_frm.refresh_fields(); new_frm.refresh_field('items'); new_frm.refresh_field('taxes'); @@ -149,51 +215,7 @@ function populate_new_form(source, source_name, source_docstatus) { try { new_frm.script_manager.trigger('calculate_taxes_and_totals'); } catch(e) {} frappe.show_alert({ - message: __('Data carried forward from ' + source_name + '. Save to auto-cancel original.'), + message: __('Data carried forward from ' + source_name + ' | Posting Time set to ' + adjusted_time + '. Submit to auto-cancel original.'), indicator: 'blue' - }, 5); -} - -//Auto-cancel original when new receipt is SAVED -frappe.ui.form.on('Purchase Receipt', { - after_save: function(frm) { - let source_name = frm._source_name; - let source_docstatus = frm._source_docstatus; - - if (!source_name) return; - - if (source_docstatus === 1) { - frappe.call({ - method: 'frappe.client.cancel', - args: { doctype: 'Purchase Receipt', name: source_name }, - callback: function(r) { - if (!r.exc) { - frappe.show_alert({ - message: __(source_name + ' automatically cancelled.'), - indicator: 'green' - }, 6); - frm._source_name = null; - } else { - frappe.msgprint(__('Could not auto-cancel ' + source_name + '. Please cancel manually.')); - } - } - }); - } else if (source_docstatus === 0) { - frappe.call({ - method: 'frappe.client.delete', - args: { doctype: 'Purchase Receipt', name: source_name }, - callback: function(r) { - if (!r.exc) { - frappe.show_alert({ - message: __('Draft' + source_name + ' automatically deleted.'), - indicator: 'green' - }, 6); - frm._source_name = null; - } else { - frappe.msgprint(__('Could not delete draft ' + source_name + '. Please delete manually.')); - } - } - }); - } - } -}); + }, 6); +} \ No newline at end of file diff --git a/rmax_custom/public/js/purchase_receipt_list.js b/rmax_custom/public/js/purchase_receipt_list.js new file mode 100644 index 0000000..3a340be --- /dev/null +++ b/rmax_custom/public/js/purchase_receipt_list.js @@ -0,0 +1,37 @@ +frappe.listview_settings['Purchase Receipt'] = { + onload: function(listview) { + listview.page.add_action_item(__('Create Single Purchase Invoice'), function() { + let selected = listview.get_checked_items(); + if (selected.length === 0) { + frappe.msgprint(__('Please select at least one Purchase Receipt.')); + return; + } + + let receipt_names = selected.map(r => r.name); + + frappe.confirm( + __('Create 1 Purchase Invoice from ' + receipt_names.length + ' Purchase Receipt(s)?'), + function() { + frappe.call({ + method: 'rmax_custom.api.purchase_invoice.create_single_purchase_invoice', + args: { + receipt_names: receipt_names + }, + freeze: true, + freeze_message: __('Creating Purchase Invoice...'), + callback: function(r) { + if (r.message) { + frappe.msgprint({ + title: __('Success'), + message: __('Purchase Invoice ' + r.message + ' created successfully.'), + indicator: 'green' + }); + listview.refresh(); + } + } + }); + } + ); + }); + } +};